diff --git a/doc/manual/rl-next/json-format-changes.md b/doc/manual/rl-next/json-format-changes.md index ef442c27e..bd7e11243 100644 --- a/doc/manual/rl-next/json-format-changes.md +++ b/doc/manual/rl-next/json-format-changes.md @@ -22,6 +22,14 @@ The store path info JSON format has been updated from version 1 to version 2: - New: `"ca": {"method": "nar", "hash": {"algorithm": "sha256", "format": "base64", "hash": "EMIJ+giQ..."}}` - Still `null` values for input-addressed store objects +- **Structured hash fields**: + + Hash values (`narHash` and `downloadHash`) are now structured JSON objects instead of strings: + + - Old: `"narHash": "sha256:FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="` + - New: `"narHash": {"algorithm": "sha256", "format": "base64", "hash": "FePFYIlM..."}` + - Same structure applies to `downloadHash` in NAR info contexts + Nix currently only produces, and doesn't consume this format. **Affected command**: `nix path-info --json` diff --git a/doc/manual/source/protocols/json/schema/store-object-info-v2.yaml b/doc/manual/source/protocols/json/schema/store-object-info-v2.yaml index 4f442e0c3..44d9e5eae 100644 --- a/doc/manual/source/protocols/json/schema/store-object-info-v2.yaml +++ b/doc/manual/source/protocols/json/schema/store-object-info-v2.yaml @@ -71,7 +71,7 @@ $defs: Note: This field may not be present in all contexts, such as when the path is used as the key and the the store object info the value in map. narHash: - type: string + "$ref": "./hash-v1.yaml" title: NAR Hash description: | Hash of the [file system object](@docroot@/store/file-system-object.md) part of the store object when serialized as a [Nix Archive](@docroot@/store/file-system-object/content-address.md#serial-nix-archive). @@ -229,7 +229,7 @@ $defs: > This is an impure "`.narinfo`" field that may not be included in certain contexts. downloadHash: - type: string + "$ref": "./hash-v1.yaml" title: Download Hash description: | A digest for the compressed archive itself, as opposed to the data contained within. diff --git a/src/libstore-tests/data/nar-info/impure.json b/src/libstore-tests/data/nar-info/impure.json index f35ff990b..13cfa8639 100644 --- a/src/libstore-tests/data/nar-info/impure.json +++ b/src/libstore-tests/data/nar-info/impure.json @@ -9,9 +9,17 @@ }, "compression": "xz", "deriver": "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv", - "downloadHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=", + "downloadHash": { + "algorithm": "sha256", + "format": "base64", + "hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=" + }, "downloadSize": 4029176, - "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=", + "narHash": { + "algorithm": "sha256", + "format": "base64", + "hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=" + }, "narSize": 34878, "references": [ "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar", diff --git a/src/libstore-tests/data/nar-info/pure.json b/src/libstore-tests/data/nar-info/pure.json index 2c5cb3bde..470f92da9 100644 --- a/src/libstore-tests/data/nar-info/pure.json +++ b/src/libstore-tests/data/nar-info/pure.json @@ -7,7 +7,11 @@ }, "method": "nar" }, - "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=", + "narHash": { + "algorithm": "sha256", + "format": "base64", + "hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=" + }, "narSize": 34878, "references": [ "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar", diff --git a/src/libstore-tests/data/path-info/empty_impure.json b/src/libstore-tests/data/path-info/empty_impure.json index 381acaa03..2fcd2078c 100644 --- a/src/libstore-tests/data/path-info/empty_impure.json +++ b/src/libstore-tests/data/path-info/empty_impure.json @@ -1,7 +1,11 @@ { "ca": null, "deriver": null, - "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=", + "narHash": { + "algorithm": "sha256", + "format": "base64", + "hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=" + }, "narSize": 0, "references": [], "registrationTime": null, diff --git a/src/libstore-tests/data/path-info/empty_pure.json b/src/libstore-tests/data/path-info/empty_pure.json index 6d3fa646b..365e2f646 100644 --- a/src/libstore-tests/data/path-info/empty_pure.json +++ b/src/libstore-tests/data/path-info/empty_pure.json @@ -1,6 +1,10 @@ { "ca": null, - "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=", + "narHash": { + "algorithm": "sha256", + "format": "base64", + "hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=" + }, "narSize": 0, "references": [], "version": 2 diff --git a/src/libstore-tests/data/path-info/impure.json b/src/libstore-tests/data/path-info/impure.json index 141b38a16..5e9944e5a 100644 --- a/src/libstore-tests/data/path-info/impure.json +++ b/src/libstore-tests/data/path-info/impure.json @@ -8,7 +8,11 @@ "method": "nar" }, "deriver": "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv", - "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=", + "narHash": { + "algorithm": "sha256", + "format": "base64", + "hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=" + }, "narSize": 34878, "references": [ "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar", diff --git a/src/libstore-tests/data/path-info/pure.json b/src/libstore-tests/data/path-info/pure.json index 2c5cb3bde..470f92da9 100644 --- a/src/libstore-tests/data/path-info/pure.json +++ b/src/libstore-tests/data/path-info/pure.json @@ -7,7 +7,11 @@ }, "method": "nar" }, - "narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=", + "narHash": { + "algorithm": "sha256", + "format": "base64", + "hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=" + }, "narSize": 34878, "references": [ "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar", diff --git a/src/libstore-tests/nar-info.cc b/src/libstore-tests/nar-info.cc index 751c5e305..41faa9274 100644 --- a/src/libstore-tests/nar-info.cc +++ b/src/libstore-tests/nar-info.cc @@ -59,24 +59,24 @@ static NarInfo makeNarInfo(const Store & store, bool includeImpureInfo) return info; } -#define JSON_TEST(STEM, PURE) \ - TEST_F(NarInfoTest, NarInfo_##STEM##_from_json) \ - { \ - readTest(#STEM, [&](const auto & encoded_) { \ - auto encoded = json::parse(encoded_); \ - auto expected = makeNarInfo(*store, PURE); \ - NarInfo got = NarInfo::fromJSON(*store, expected.path, encoded); \ - ASSERT_EQ(got, expected); \ - }); \ - } \ - \ - TEST_F(NarInfoTest, NarInfo_##STEM##_to_json) \ - { \ - writeTest( \ - #STEM, \ - [&]() -> json { return makeNarInfo(*store, PURE).toJSON(*store, PURE, HashFormat::SRI); }, \ - [](const auto & file) { return json::parse(readFile(file)); }, \ - [](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \ +#define JSON_TEST(STEM, PURE) \ + TEST_F(NarInfoTest, NarInfo_##STEM##_from_json) \ + { \ + readTest(#STEM, [&](const auto & encoded_) { \ + auto encoded = json::parse(encoded_); \ + auto expected = makeNarInfo(*store, PURE); \ + NarInfo got = NarInfo::fromJSON(*store, expected.path, encoded); \ + ASSERT_EQ(got, expected); \ + }); \ + } \ + \ + TEST_F(NarInfoTest, NarInfo_##STEM##_to_json) \ + { \ + writeTest( \ + #STEM, \ + [&]() -> json { return makeNarInfo(*store, PURE).toJSON(*store, PURE); }, \ + [](const auto & file) { return json::parse(readFile(file)); }, \ + [](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \ } JSON_TEST(pure, false) diff --git a/src/libstore-tests/path-info.cc b/src/libstore-tests/path-info.cc index 63310c1c3..a40b26149 100644 --- a/src/libstore-tests/path-info.cc +++ b/src/libstore-tests/path-info.cc @@ -80,7 +80,7 @@ static UnkeyedValidPathInfo makeFull(const Store & store, bool includeImpureInfo { \ writeTest( \ #STEM, \ - [&]() -> json { return OBJ.toJSON(*store, PURE, HashFormat::SRI); }, \ + [&]() -> json { return OBJ.toJSON(*store, PURE); }, \ [](const auto & file) { return json::parse(readFile(file)); }, \ [](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \ } diff --git a/src/libstore/include/nix/store/nar-info.hh b/src/libstore/include/nix/store/nar-info.hh index 1684837c6..34606a89b 100644 --- a/src/libstore/include/nix/store/nar-info.hh +++ b/src/libstore/include/nix/store/nar-info.hh @@ -42,7 +42,7 @@ struct NarInfo : ValidPathInfo std::string to_string(const StoreDirConfig & store) const; - nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const override; + nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo) const override; static NarInfo fromJSON(const StoreDirConfig & store, const StorePath & path, const nlohmann::json & json); }; diff --git a/src/libstore/include/nix/store/path-info.hh b/src/libstore/include/nix/store/path-info.hh index cbc5abdb4..0f00a14b7 100644 --- a/src/libstore/include/nix/store/path-info.hh +++ b/src/libstore/include/nix/store/path-info.hh @@ -117,7 +117,7 @@ struct UnkeyedValidPathInfo * @param includeImpureInfo If true, variable elements such as the * registration time are included. */ - virtual nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const; + virtual nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo) const; static UnkeyedValidPathInfo fromJSON(const StoreDirConfig & store, const nlohmann::json & json); }; diff --git a/src/libstore/nar-info.cc b/src/libstore/nar-info.cc index 6f1abb273..4d4fb7de2 100644 --- a/src/libstore/nar-info.cc +++ b/src/libstore/nar-info.cc @@ -130,11 +130,11 @@ std::string NarInfo::to_string(const StoreDirConfig & store) const return res; } -nlohmann::json NarInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const +nlohmann::json NarInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo) const { using nlohmann::json; - auto jsonObject = ValidPathInfo::toJSON(store, includeImpureInfo, hashFormat); + auto jsonObject = ValidPathInfo::toJSON(store, includeImpureInfo); if (includeImpureInfo) { if (!url.empty()) @@ -142,7 +142,7 @@ nlohmann::json NarInfo::toJSON(const StoreDirConfig & store, bool includeImpureI if (!compression.empty()) jsonObject["compression"] = compression; if (fileHash) - jsonObject["downloadHash"] = fileHash->to_string(hashFormat, true); + jsonObject["downloadHash"] = *fileHash; if (fileSize) jsonObject["downloadSize"] = fileSize; } @@ -161,17 +161,17 @@ NarInfo NarInfo::fromJSON(const StoreDirConfig & store, const StorePath & path, auto & obj = getObject(json); - if (json.contains("url")) - res.url = getString(valueAt(obj, "url")); + if (auto * url = get(obj, "url")) + res.url = getString(*url); - if (json.contains("compression")) - res.compression = getString(valueAt(obj, "compression")); + if (auto * compression = get(obj, "compression")) + res.compression = getString(*compression); - if (json.contains("downloadHash")) - res.fileHash = Hash::parseAny(getString(valueAt(obj, "downloadHash")), std::nullopt); + if (auto * downloadHash = get(obj, "downloadHash")) + res.fileHash = *downloadHash; - if (json.contains("downloadSize")) - res.fileSize = getUnsigned(valueAt(obj, "downloadSize")); + if (auto * downloadSize = get(obj, "downloadSize")) + res.fileSize = getUnsigned(*downloadSize); return res; } diff --git a/src/libstore/path-info.cc b/src/libstore/path-info.cc index 7d8bf4911..811c397a4 100644 --- a/src/libstore/path-info.cc +++ b/src/libstore/path-info.cc @@ -149,8 +149,7 @@ ValidPathInfo ValidPathInfo::makeFromCA( return res; } -nlohmann::json -UnkeyedValidPathInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const +nlohmann::json UnkeyedValidPathInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo) const { using nlohmann::json; @@ -158,7 +157,7 @@ UnkeyedValidPathInfo::toJSON(const StoreDirConfig & store, bool includeImpureInf jsonObject["version"] = 2; - jsonObject["narHash"] = narHash.to_string(hashFormat, true); + jsonObject["narHash"] = narHash; jsonObject["narSize"] = narSize; { @@ -198,7 +197,7 @@ UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(const StoreDirConfig & store throw Error("Unsupported path info JSON format version %d, only version 2 is currently supported", version); } - res.narHash = Hash::parseAny(getString(valueAt(json, "narHash")), std::nullopt); + res.narHash = valueAt(json, "narHash"); res.narSize = getUnsigned(valueAt(json, "narSize")); try { diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc index 146b775e5..697b73e5c 100644 --- a/src/nix/path-info.cc +++ b/src/nix/path-info.cc @@ -51,7 +51,7 @@ static json pathInfoToJSON(Store & store, const StorePathSet & storePaths, bool // know the name yet until we've read the NAR info. printedStorePath = store.printStorePath(info->path); - jsonObject = info->toJSON(store, true, HashFormat::SRI); + jsonObject = info->toJSON(store, true); if (showClosureSize) { StorePathSet closure; diff --git a/tests/functional/path-info.sh b/tests/functional/path-info.sh index 463ac6214..70ad1a7aa 100755 --- a/tests/functional/path-info.sh +++ b/tests/functional/path-info.sh @@ -17,8 +17,16 @@ diff --unified --color=always \ jq --sort-keys 'map_values(.narHash)') \ <(jq --sort-keys <<-EOF { - "$foo": "sha256-QvtAMbUl/uvi+LCObmqOhvNOapHdA2raiI4xG5zI5pA=", - "$bar": "sha256-9fhYGu9fqxcQC2Kc81qh2RMo1QcLBUBo8U+pPn+jthQ=", + "$foo": { + "algorithm": "sha256", + "format": "base64", + "hash": "QvtAMbUl/uvi+LCObmqOhvNOapHdA2raiI4xG5zI5pA=" + }, + "$bar": { + "algorithm": "sha256", + "format": "base64", + "hash": "9fhYGu9fqxcQC2Kc81qh2RMo1QcLBUBo8U+pPn+jthQ=" + }, "$baz": null } EOF