Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion doc/manual/rl-next/json-format-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ 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

Version 1 format is still accepted when reading for backward compatibility.
- **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`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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.
Expand Down
12 changes: 10 additions & 2 deletions src/libstore-tests/data/nar-info/impure.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 5 additions & 1 deletion src/libstore-tests/data/nar-info/pure.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
},
"method": "nar"
},
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narSize": 34878,
"references": [
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
Expand Down
6 changes: 5 additions & 1 deletion src/libstore-tests/data/path-info/empty_impure.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"ca": null,
"deriver": null,
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narSize": 0,
"references": [],
"registrationTime": null,
Expand Down
6 changes: 5 additions & 1 deletion src/libstore-tests/data/path-info/empty_pure.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"ca": null,
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narSize": 0,
"references": [],
"version": 2
Expand Down
6 changes: 5 additions & 1 deletion src/libstore-tests/data/path-info/impure.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 5 additions & 1 deletion src/libstore-tests/data/path-info/pure.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
},
"method": "nar"
},
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narSize": 34878,
"references": [
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
Expand Down
36 changes: 18 additions & 18 deletions src/libstore-tests/nar-info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/libstore-tests/path-info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"); }); \
}
Expand Down
9 changes: 0 additions & 9 deletions src/libstore/derivation-options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -423,15 +423,6 @@ void adl_serializer<DerivationOptions>::to_json(json & json, const DerivationOpt
json["allowSubstitutes"] = o.allowSubstitutes;
}

template<typename T>
static inline std::optional<T> ptrToOwned(const json * ptr)
{
if (ptr)
return std::optional{*ptr};
else
return std::nullopt;
}

DerivationOptions::OutputChecks adl_serializer<DerivationOptions::OutputChecks>::from_json(const json & json_)
{
auto & json = getObject(json_);
Expand Down
2 changes: 1 addition & 1 deletion src/libstore/include/nix/store/nar-info.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down
2 changes: 1 addition & 1 deletion src/libstore/include/nix/store/path-info.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down
22 changes: 11 additions & 11 deletions src/libstore/nar-info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -130,19 +130,19 @@ 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())
jsonObject["url"] = url;
if (!compression.empty())
jsonObject["compression"] = compression;
if (fileHash)
jsonObject["downloadHash"] = fileHash->to_string(hashFormat, true);
jsonObject["downloadHash"] = *fileHash;
if (fileSize)
jsonObject["downloadSize"] = fileSize;
}
Expand All @@ -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;
}
Expand Down
37 changes: 13 additions & 24 deletions src/libstore/path-info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,15 @@ 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;

auto jsonObject = json::object();

jsonObject["version"] = 2;

jsonObject["narHash"] = narHash.to_string(hashFormat, true);
jsonObject["narHash"] = narHash;
jsonObject["narSize"] = narSize;

{
Expand Down Expand Up @@ -192,16 +191,13 @@ UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(const StoreDirConfig & store

auto & json = getObject(_json);

// Check version (optional for backward compatibility)
nlohmann::json::number_unsigned_t version = 1;
if (json.contains("version")) {
version = getUnsigned(valueAt(json, "version"));
if (version != 1 && version != 2) {
throw Error("Unsupported path info JSON format version %d, expected 1 through 2", version);
}
{
auto version = getUnsigned(valueAt(json, "version"));
if (version != 2)
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 {
Expand All @@ -213,19 +209,12 @@ UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(const StoreDirConfig & store
throw;
}

// New format as this as nullable but mandatory field; handling
// missing is for back-compat.
if (auto * rawCa0 = optionalValueAt(json, "ca"))
if (auto * rawCa = getNullable(*rawCa0))
switch (version) {
case 1:
// old string format also used in SQLite DB and .narinfo
res.ca = ContentAddress::parse(getString(*rawCa));
break;
case 2 ... std::numeric_limits<decltype(version)>::max():
res.ca = *rawCa;
break;
}
try {
res.ca = ptrToOwned<ContentAddress>(getNullable(valueAt(json, "ca")));
} catch (Error & e) {
e.addTrace({}, "while reading key 'ca'");
throw;
}

if (auto * rawDeriver0 = optionalValueAt(json, "deriver"))
if (auto * rawDeriver = getNullable(*rawDeriver0))
Expand Down
9 changes: 9 additions & 0 deletions src/libutil/include/nix/util/json-utils.hh
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,13 @@ struct adl_serializer<std::optional<T>>
}
};

template<typename T>
static inline std::optional<T> ptrToOwned(const json * ptr)
{
if (ptr)
return std::optional{*ptr};
else
return std::nullopt;
}

} // namespace nlohmann
2 changes: 1 addition & 1 deletion src/nix/path-info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 10 additions & 2 deletions tests/functional/path-info.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading