From 7357a654de765573a135a3bf18560d52fb561db0 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Sun, 28 Sep 2025 00:29:21 -0400 Subject: [PATCH] `nlohmann::json` instance and JSON Schema for `MemorySourceAccessor` Also do a better JSON and testing for deep and shallow NAR listings. As documented, this for file system objects themselves, since `MemorySourceAccessor` is an implementation detail. --- doc/manual/package.nix | 1 + doc/manual/source/SUMMARY.md.in | 1 + .../protocols/json/file-system-object.md | 21 +++ .../json/fixup-json-schema-generated-doc.sed | 1 + doc/manual/source/protocols/json/meson.build | 1 + .../json/schema/file-system-object-v1 | 1 + .../json/schema/file-system-object-v1.yaml | 71 ++++++++ doc/manual/source/store/file-system-object.md | 14 +- src/json-schema-checks/file-system-object | 1 + src/json-schema-checks/meson.build | 8 + src/json-schema-checks/package.nix | 1 + .../data/memory-source-accessor/complex.json | 24 +++ .../data/memory-source-accessor/simple.json | 5 + src/libutil-tests/data/nar-listing/deep.json | 23 +++ .../data/nar-listing/shallow.json | 7 + src/libutil-tests/git.cc | 41 +---- src/libutil-tests/memory-source-accessor.cc | 116 +++++++++++++ src/libutil-tests/meson.build | 2 + src/libutil-tests/nar-listing.cc | 83 +++++++++ src/libutil/include/nix/util/json-impls.hh | 13 +- .../nix/util/memory-source-accessor.hh | 49 ++++++ src/libutil/include/nix/util/nar-accessor.hh | 10 +- src/libutil/memory-source-accessor.cc | 1 + src/libutil/memory-source-accessor/json.cc | 162 ++++++++++++++++++ src/libutil/meson.build | 1 + src/libutil/nar-accessor.cc | 48 ------ 26 files changed, 605 insertions(+), 101 deletions(-) create mode 100644 doc/manual/source/protocols/json/file-system-object.md create mode 120000 doc/manual/source/protocols/json/schema/file-system-object-v1 create mode 100644 doc/manual/source/protocols/json/schema/file-system-object-v1.yaml create mode 120000 src/json-schema-checks/file-system-object create mode 100644 src/libutil-tests/data/memory-source-accessor/complex.json create mode 100644 src/libutil-tests/data/memory-source-accessor/simple.json create mode 100644 src/libutil-tests/data/nar-listing/deep.json create mode 100644 src/libutil-tests/data/nar-listing/shallow.json create mode 100644 src/libutil-tests/memory-source-accessor.cc create mode 100644 src/libutil-tests/nar-listing.cc create mode 100644 src/libutil/memory-source-accessor/json.cc diff --git a/doc/manual/package.nix b/doc/manual/package.nix index 05368e34e..4a7eceacf 100644 --- a/doc/manual/package.nix +++ b/doc/manual/package.nix @@ -37,6 +37,7 @@ mkMesonDerivation (finalAttrs: { (fileset.unions [ ../../.version # For example JSON + ../../src/libutil-tests/data/memory-source-accessor ../../src/libutil-tests/data/hash ../../src/libstore-tests/data/content-address ../../src/libstore-tests/data/store-path diff --git a/doc/manual/source/SUMMARY.md.in b/doc/manual/source/SUMMARY.md.in index bf2dd9481..806489bb3 100644 --- a/doc/manual/source/SUMMARY.md.in +++ b/doc/manual/source/SUMMARY.md.in @@ -121,6 +121,7 @@ - [Architecture and Design](architecture/architecture.md) - [Formats and Protocols](protocols/index.md) - [JSON Formats](protocols/json/index.md) + - [File System Object](protocols/json/file-system-object.md) - [Hash](protocols/json/hash.md) - [Content Address](protocols/json/content-address.md) - [Store Path](protocols/json/store-path.md) diff --git a/doc/manual/source/protocols/json/file-system-object.md b/doc/manual/source/protocols/json/file-system-object.md new file mode 100644 index 000000000..7a93b65ab --- /dev/null +++ b/doc/manual/source/protocols/json/file-system-object.md @@ -0,0 +1,21 @@ +{{#include file-system-object-v1-fixed.md}} + +## Examples + +### Simple + +```json +{{#include schema/file-system-object-v1/simple.json}} +``` + +### Complex + +```json +{{#include schema/file-system-object-v1/complex.json}} +``` + + diff --git a/doc/manual/source/protocols/json/fixup-json-schema-generated-doc.sed b/doc/manual/source/protocols/json/fixup-json-schema-generated-doc.sed index 27895d42a..9f7b750da 100644 --- a/doc/manual/source/protocols/json/fixup-json-schema-generated-doc.sed +++ b/doc/manual/source/protocols/json/fixup-json-schema-generated-doc.sed @@ -11,6 +11,7 @@ s/\\`/`/g # # As we have more such relative links, more replacements of this nature # should appear below. +s^#/\$defs/\(regular\|symlink\|directory\)^In this schema^g s^\(./hash-v1.yaml\)\?#/$defs/algorithm^[JSON format for `Hash`](./hash.html#algorithm)^g s^\(./hash-v1.yaml\)^[JSON format for `Hash`](./hash.html)^g s^\(./content-address-v1.yaml\)\?#/$defs/method^[JSON format for `ContentAddress`](./content-address.html#method)^g diff --git a/doc/manual/source/protocols/json/meson.build b/doc/manual/source/protocols/json/meson.build index 4ab94c63b..72856a47a 100644 --- a/doc/manual/source/protocols/json/meson.build +++ b/doc/manual/source/protocols/json/meson.build @@ -9,6 +9,7 @@ json_schema_for_humans = find_program('generate-schema-doc', required : false) json_schema_config = files('json-schema-for-humans-config.yaml') schemas = [ + 'file-system-object-v1', 'hash-v1', 'content-address-v1', 'store-path-v1', diff --git a/doc/manual/source/protocols/json/schema/file-system-object-v1 b/doc/manual/source/protocols/json/schema/file-system-object-v1 new file mode 120000 index 000000000..cbb21a10d --- /dev/null +++ b/doc/manual/source/protocols/json/schema/file-system-object-v1 @@ -0,0 +1 @@ +../../../../../../src/libutil-tests/data/memory-source-accessor \ No newline at end of file diff --git a/doc/manual/source/protocols/json/schema/file-system-object-v1.yaml b/doc/manual/source/protocols/json/schema/file-system-object-v1.yaml new file mode 100644 index 000000000..116c7ce0d --- /dev/null +++ b/doc/manual/source/protocols/json/schema/file-system-object-v1.yaml @@ -0,0 +1,71 @@ +"$schema": http://json-schema.org/draft-04/schema# +"$id": https://nix.dev/manual/nix/latest/protocols/json/schema/file-system-object-v1.json +title: File System Object +description: | + This schema describes the JSON representation of Nix's [File System Object](@docroot@/store/file-system-object.md). + + The schema is recursive because file system objects contain other file system objects. +type: object +required: ["type"] +properties: + type: + type: string + enum: ["regular", "symlink", "directory"] + +# Enforce conditional structure based on `type` +anyOf: + - $ref: "#/$defs/regular" + required: ["type", "contents"] + + - $ref: "#/$defs/directory" + required: ["type", "entries"] + + - $ref: "#/$defs/symlink" + required: ["type", "target"] + +"$defs": + regular: + title: Regular File + description: | + See [Regular File](@docroot@/store/file-system-object.md#regular) in the manual for details. + required: ["contents"] + properties: + type: + const: "regular" + contents: + type: string + description: File contents + executable: + type: boolean + description: Whether the file is executable. + default: false + additionalProperties: false + + directory: + title: Directory + description: | + See [Directory](@docroot@/store/file-system-object.md#directory) in the manual for details. + required: ["entries"] + properties: + type: + const: "directory" + entries: + type: object + description: | + Map of names to nested file system objects (for type=directory) + additionalProperties: + $ref: "#" + additionalProperties: false + + symlink: + title: Symbolic Link + description: | + See [Symbolic Link](@docroot@/store/file-system-object.md#symlink) in the manual for details. + required: ["target"] + properties: + type: + const: "symlink" + target: + type: string + description: Target path of the symlink. + additionalProperties: false diff --git a/doc/manual/source/store/file-system-object.md b/doc/manual/source/store/file-system-object.md index 42f047260..60cb3e572 100644 --- a/doc/manual/source/store/file-system-object.md +++ b/doc/manual/source/store/file-system-object.md @@ -3,19 +3,23 @@ Nix uses a simplified model of the file system, which consists of file system objects. Every file system object is one of the following: - - File + - [**Regular File**]{#regular} - A possibly empty sequence of bytes for contents - A single boolean representing the [executable](https://en.m.wikipedia.org/wiki/File-system_permissions#Permissions) permission - - Directory + - [**Directory**]{#directory} Mapping of names to child file system objects - - [Symbolic link](https://en.m.wikipedia.org/wiki/Symbolic_link) + - [**Symbolic link**]{#symlink} - An arbitrary string. - Nix does not assign any semantics to symbolic links. + An arbitrary string, known as the *target* of the symlink. + + In general, Nix does not assign any semantics to symbolic links. + Certain operations however, may make additional assumptions and attempt to use the target to find another file system object. + + > See [the Wikpedia article on symbolic links](https://en.m.wikipedia.org/wiki/Symbolic_link) for background information if you are unfamiliar with this Unix concept. File system objects and their children form a tree. A bare file or symlink can be a root file system object. diff --git a/src/json-schema-checks/file-system-object b/src/json-schema-checks/file-system-object new file mode 120000 index 000000000..b26e030c9 --- /dev/null +++ b/src/json-schema-checks/file-system-object @@ -0,0 +1 @@ +../../src/libutil-tests/data/memory-source-accessor \ No newline at end of file diff --git a/src/json-schema-checks/meson.build b/src/json-schema-checks/meson.build index f72affb0b..b1a829d38 100644 --- a/src/json-schema-checks/meson.build +++ b/src/json-schema-checks/meson.build @@ -20,6 +20,14 @@ schema_dir = meson.current_source_dir() / 'schema' # Get all example files schemas = [ + { + 'stem' : 'file-system-object', + 'schema' : schema_dir / 'file-system-object-v1.yaml', + 'files' : [ + 'simple.json', + 'complex.json', + ], + }, { 'stem' : 'hash', 'schema' : schema_dir / 'hash-v1.yaml', diff --git a/src/json-schema-checks/package.nix b/src/json-schema-checks/package.nix index 609d396be..d9ca880e5 100644 --- a/src/json-schema-checks/package.nix +++ b/src/json-schema-checks/package.nix @@ -20,6 +20,7 @@ mkMesonDerivation (finalAttrs: { fileset = lib.fileset.unions [ ../../.version ../../doc/manual/source/protocols/json/schema + ../../src/libutil-tests/data/memory-source-accessor ../../src/libutil-tests/data/hash ../../src/libstore-tests/data/content-address ../../src/libstore-tests/data/store-path diff --git a/src/libutil-tests/data/memory-source-accessor/complex.json b/src/libutil-tests/data/memory-source-accessor/complex.json new file mode 100644 index 000000000..924a39a54 --- /dev/null +++ b/src/libutil-tests/data/memory-source-accessor/complex.json @@ -0,0 +1,24 @@ +{ + "entries": { + "bar": { + "entries": { + "baz": { + "contents": "good day,\n\u0000\n\tworld!", + "executable": true, + "type": "regular" + }, + "quux": { + "target": "/over/there", + "type": "symlink" + } + }, + "type": "directory" + }, + "foo": { + "contents": "hello\n\u0000\n\tworld!", + "executable": false, + "type": "regular" + } + }, + "type": "directory" +} diff --git a/src/libutil-tests/data/memory-source-accessor/simple.json b/src/libutil-tests/data/memory-source-accessor/simple.json new file mode 100644 index 000000000..1ae328cc4 --- /dev/null +++ b/src/libutil-tests/data/memory-source-accessor/simple.json @@ -0,0 +1,5 @@ +{ + "contents": "asdf", + "executable": false, + "type": "regular" +} diff --git a/src/libutil-tests/data/nar-listing/deep.json b/src/libutil-tests/data/nar-listing/deep.json new file mode 100644 index 000000000..a7ed47c4c --- /dev/null +++ b/src/libutil-tests/data/nar-listing/deep.json @@ -0,0 +1,23 @@ +{ + "entries": { + "bar": { + "entries": { + "baz": { + "executable": true, + "size": 19, + "type": "regular" + }, + "quux": { + "target": "/over/there", + "type": "symlink" + } + }, + "type": "directory" + }, + "foo": { + "size": 15, + "type": "regular" + } + }, + "type": "directory" +} diff --git a/src/libutil-tests/data/nar-listing/shallow.json b/src/libutil-tests/data/nar-listing/shallow.json new file mode 100644 index 000000000..9826cd1a0 --- /dev/null +++ b/src/libutil-tests/data/nar-listing/shallow.json @@ -0,0 +1,7 @@ +{ + "entries": { + "bar": {}, + "foo": {} + }, + "type": "directory" +} diff --git a/src/libutil-tests/git.cc b/src/libutil-tests/git.cc index 9d749b492..f761c4433 100644 --- a/src/libutil-tests/git.cc +++ b/src/libutil-tests/git.cc @@ -224,42 +224,15 @@ TEST_F(GitTest, tree_sha256_write) }); } +namespace memory_source_accessor { + +extern ref exampleComplex(); + +} + TEST_F(GitTest, both_roundrip) { - using File = MemorySourceAccessor::File; - - auto files = make_ref(); - files->root = File::Directory{ - .entries{ - { - "foo", - File::Regular{ - .contents = "hello\n\0\n\tworld!", - }, - }, - { - "bar", - File::Directory{ - .entries = - { - { - "baz", - File::Regular{ - .executable = true, - .contents = "good day,\n\0\n\tworld!", - }, - }, - { - "quux", - File::Symlink{ - .target = "/over/there", - }, - }, - }, - }, - }, - }, - }; + auto files = memory_source_accessor::exampleComplex(); for (const auto hashAlgo : {HashAlgorithm::SHA1, HashAlgorithm::SHA256}) { std::map cas; diff --git a/src/libutil-tests/memory-source-accessor.cc b/src/libutil-tests/memory-source-accessor.cc new file mode 100644 index 000000000..6c7c9ce9e --- /dev/null +++ b/src/libutil-tests/memory-source-accessor.cc @@ -0,0 +1,116 @@ +#include + +#include "nix/util/memory-source-accessor.hh" +#include "nix/util/tests/json-characterization.hh" + +namespace nix { + +namespace memory_source_accessor { + +using namespace std::literals; +using File = MemorySourceAccessor::File; + +ref exampleSimple() +{ + auto sc = make_ref(); + sc->root = File{File::Regular{ + .executable = false, + .contents = "asdf", + }}; + return sc; +} + +ref exampleComplex() +{ + auto files = make_ref(); + files->root = File::Directory{ + .entries{ + { + "foo", + File::Regular{ + .contents = "hello\n\0\n\tworld!"s, + }, + }, + { + "bar", + File::Directory{ + .entries = + { + { + "baz", + File::Regular{ + .executable = true, + .contents = "good day,\n\0\n\tworld!"s, + }, + }, + { + "quux", + File::Symlink{ + .target = "/over/there", + }, + }, + }, + }, + }, + }, + }; + return files; +} + +} // namespace memory_source_accessor + +/* ---------------------------------------------------------------------------- + * JSON + * --------------------------------------------------------------------------*/ + +class MemorySourceAccessorTest : public virtual CharacterizationTest +{ + std::filesystem::path unitTestData = getUnitTestData() / "memory-source-accessor"; + +public: + + std::filesystem::path goldenMaster(std::string_view testStem) const override + { + return unitTestData / testStem; + } +}; + +using nlohmann::json; + +struct MemorySourceAccessorJsonTest : MemorySourceAccessorTest, + JsonCharacterizationTest, + ::testing::WithParamInterface> +{}; + +TEST_P(MemorySourceAccessorJsonTest, from_json) +{ + auto & [name, expected] = GetParam(); + /* Cannot use `readJsonTest` because need to compare `root` field of + the source accessors for equality. */ + readTest(Path{name} + ".json", [&](const auto & encodedRaw) { + auto encoded = json::parse(encodedRaw); + auto decoded = static_cast(encoded); + ASSERT_EQ(decoded.root, expected.root); + }); +} + +TEST_P(MemorySourceAccessorJsonTest, to_json) +{ + auto & [name, value] = GetParam(); + writeJsonTest(name, value); +} + +INSTANTIATE_TEST_SUITE_P( + MemorySourceAccessorJSON, + MemorySourceAccessorJsonTest, + ::testing::Values( + std::pair{ + "simple", + *memory_source_accessor::exampleSimple(), + }, + std::pair{ + "complex", + *memory_source_accessor::exampleComplex(), + })); + +} // namespace nix diff --git a/src/libutil-tests/meson.build b/src/libutil-tests/meson.build index 2772ab060..019bdb6d2 100644 --- a/src/libutil-tests/meson.build +++ b/src/libutil-tests/meson.build @@ -63,7 +63,9 @@ sources = files( 'json-utils.cc', 'logging.cc', 'lru-cache.cc', + 'memory-source-accessor.cc', 'monitorfdhup.cc', + 'nar-listing.cc', 'nix_api_util.cc', 'nix_api_util_internal.cc', 'pool.cc', diff --git a/src/libutil-tests/nar-listing.cc b/src/libutil-tests/nar-listing.cc new file mode 100644 index 000000000..a2b865048 --- /dev/null +++ b/src/libutil-tests/nar-listing.cc @@ -0,0 +1,83 @@ +#include + +#include "nix/util/nar-accessor.hh" +#include "nix/util/tests/json-characterization.hh" + +namespace nix { + +// Forward declaration from memory-source-accessor.cc +namespace memory_source_accessor { +ref exampleComplex(); +} + +/* ---------------------------------------------------------------------------- + * JSON + * --------------------------------------------------------------------------*/ + +class NarListingTest : public virtual CharacterizationTest +{ + std::filesystem::path unitTestData = getUnitTestData() / "nar-listing"; + +public: + + std::filesystem::path goldenMaster(std::string_view testStem) const override + { + return unitTestData / testStem; + } +}; + +using nlohmann::json; + +struct NarListingJsonTest : NarListingTest, + JsonCharacterizationTest, + ::testing::WithParamInterface> +{}; + +TEST_P(NarListingJsonTest, from_json) +{ + auto & [name, expected] = GetParam(); + readJsonTest(name, expected); +} + +TEST_P(NarListingJsonTest, to_json) +{ + auto & [name, value] = GetParam(); + writeJsonTest(name, value); +} + +INSTANTIATE_TEST_SUITE_P( + NarListingJSON, + NarListingJsonTest, + ::testing::Values( + std::pair{ + "deep", + listNarDeep(*memory_source_accessor::exampleComplex(), CanonPath::root), + })); + +struct ShallowNarListingJsonTest : NarListingTest, + JsonCharacterizationTest, + ::testing::WithParamInterface> +{}; + +TEST_P(ShallowNarListingJsonTest, from_json) +{ + auto & [name, expected] = GetParam(); + readJsonTest(name, expected); +} + +TEST_P(ShallowNarListingJsonTest, to_json) +{ + auto & [name, value] = GetParam(); + writeJsonTest(name, value); +} + +INSTANTIATE_TEST_SUITE_P( + ShallowNarListingJSON, + ShallowNarListingJsonTest, + ::testing::Values( + std::pair{ + "shallow", + listNarShallow(*memory_source_accessor::exampleComplex(), CanonPath::root), + })); + +} // namespace nix diff --git a/src/libutil/include/nix/util/json-impls.hh b/src/libutil/include/nix/util/json-impls.hh index 802c212e1..b55716a6d 100644 --- a/src/libutil/include/nix/util/json-impls.hh +++ b/src/libutil/include/nix/util/json-impls.hh @@ -6,15 +6,18 @@ #include "nix/util/experimental-features.hh" // Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types -#define JSON_IMPL(TYPE) \ - namespace nlohmann { \ - using namespace nix; \ - template<> \ +#define JSON_IMPL_INNER(TYPE) \ struct adl_serializer \ { \ static TYPE from_json(const json & json); \ static void to_json(json & json, const TYPE & t); \ - }; \ + }; + +#define JSON_IMPL(TYPE) \ + namespace nlohmann { \ + using namespace nix; \ + template<> \ + JSON_IMPL_INNER(TYPE) \ } #define JSON_IMPL_WITH_XP_FEATURES(TYPE) \ diff --git a/src/libutil/include/nix/util/memory-source-accessor.hh b/src/libutil/include/nix/util/memory-source-accessor.hh index 268f6b06f..933af600a 100644 --- a/src/libutil/include/nix/util/memory-source-accessor.hh +++ b/src/libutil/include/nix/util/memory-source-accessor.hh @@ -160,4 +160,53 @@ struct MemorySink : FileSystemObjectSink void createSymlink(const CanonPath & path, const std::string & target) override; }; +template<> +struct json_avoids_null : std::true_type +{}; + +template<> +struct json_avoids_null : std::true_type +{}; + +template<> +struct json_avoids_null : std::true_type +{}; + +template<> +struct json_avoids_null : std::true_type +{}; + +template<> +struct json_avoids_null : std::true_type +{}; + } // namespace nix + +namespace nlohmann { + +using namespace nix; + +#define ARG fso::Regular +template +JSON_IMPL_INNER(ARG) +#undef ARG + +#define ARG fso::DirectoryT +template +JSON_IMPL_INNER(ARG) +#undef ARG + +template<> +JSON_IMPL_INNER(fso::Symlink) + +template<> +JSON_IMPL_INNER(fso::Opaque) + +#define ARG fso::VariantT +template +JSON_IMPL_INNER(ARG) +#undef ARG + +} // namespace nlohmann + +JSON_IMPL(MemorySourceAccessor) diff --git a/src/libutil/include/nix/util/nar-accessor.hh b/src/libutil/include/nix/util/nar-accessor.hh index 9665af9bc..5488f21b5 100644 --- a/src/libutil/include/nix/util/nar-accessor.hh +++ b/src/libutil/include/nix/util/nar-accessor.hh @@ -75,14 +75,6 @@ NarListing listNarDeep(SourceAccessor & accessor, const CanonPath & path); */ ShallowNarListing listNarShallow(SourceAccessor & accessor, const CanonPath & path); -/** - * Serialize a NarListing to JSON. - */ -void to_json(nlohmann::json & j, const NarListing & listing); - -/** - * Serialize a ShallowNarListing to JSON. - */ -void to_json(nlohmann::json & j, const ShallowNarListing & listing); +// All json_avoids_null and JSON_IMPL covered by generic templates in memory-source-accessor.hh } // namespace nix diff --git a/src/libutil/memory-source-accessor.cc b/src/libutil/memory-source-accessor.cc index 6a9a0772b..ec21c846d 100644 --- a/src/libutil/memory-source-accessor.cc +++ b/src/libutil/memory-source-accessor.cc @@ -1,4 +1,5 @@ #include "nix/util/memory-source-accessor.hh" +#include "nix/util/json-utils.hh" namespace nix { diff --git a/src/libutil/memory-source-accessor/json.cc b/src/libutil/memory-source-accessor/json.cc new file mode 100644 index 000000000..84fbb71bb --- /dev/null +++ b/src/libutil/memory-source-accessor/json.cc @@ -0,0 +1,162 @@ +#include "nix/util/memory-source-accessor.hh" +#include "nix/util/nar-accessor.hh" +#include "nix/util/json-utils.hh" + +#include + +namespace nlohmann { + +using namespace nix; + +// fso::Regular +template<> +MemorySourceAccessor::File::Regular adl_serializer::from_json(const json & json) +{ + auto & obj = getObject(json); + return MemorySourceAccessor::File::Regular{ + .executable = getBoolean(valueAt(obj, "executable")), + .contents = getString(valueAt(obj, "contents")), + }; +} + +template<> +void adl_serializer::to_json( + json & json, const MemorySourceAccessor::File::Regular & r) +{ + json = { + {"executable", r.executable}, + {"contents", r.contents}, + }; +} + +template<> +NarListing::Regular adl_serializer::from_json(const json & json) +{ + auto & obj = getObject(json); + auto * execPtr = optionalValueAt(obj, "executable"); + auto * sizePtr = optionalValueAt(obj, "size"); + auto * offsetPtr = optionalValueAt(obj, "narOffset"); + return NarListing::Regular{ + .executable = execPtr ? getBoolean(*execPtr) : false, + .contents{ + .fileSize = ptrToOwned(sizePtr), + .narOffset = ptrToOwned(offsetPtr).and_then( + [](auto v) { return v != 0 ? std::optional{v} : std::nullopt; }), + }, + }; +} + +template<> +void adl_serializer::to_json(json & j, const NarListing::Regular & r) +{ + if (r.contents.fileSize) + j["size"] = *r.contents.fileSize; + if (r.executable) + j["executable"] = true; + if (r.contents.narOffset) + j["narOffset"] = *r.contents.narOffset; +} + +template +void adl_serializer>::to_json(json & j, const fso::DirectoryT & d) +{ + j["entries"] = d.entries; +} + +template +fso::DirectoryT adl_serializer>::from_json(const json & json) +{ + auto & obj = getObject(json); + return fso::DirectoryT{ + .entries = valueAt(obj, "entries"), + }; +} + +// fso::Symlink +fso::Symlink adl_serializer::from_json(const json & json) +{ + auto & obj = getObject(json); + return fso::Symlink{ + .target = getString(valueAt(obj, "target")), + }; +} + +void adl_serializer::to_json(json & json, const fso::Symlink & s) +{ + json = { + {"target", s.target}, + }; +} + +// fso::Opaque +fso::Opaque adl_serializer::from_json(const json &) +{ + return fso::Opaque{}; +} + +void adl_serializer::to_json(json & j, const fso::Opaque &) +{ + j = nlohmann::json::object(); +} + +// fso::VariantT - generic implementation +template +void adl_serializer>::to_json( + json & j, const fso::VariantT & val) +{ + using Variant = fso::VariantT; + j = nlohmann::json::object(); + std::visit( + overloaded{ + [&](const typename Variant::Regular & r) { + j = r; + j["type"] = "regular"; + }, + [&](const typename Variant::Directory & d) { + j = d; + j["type"] = "directory"; + }, + [&](const typename Variant::Symlink & s) { + j = s; + j["type"] = "symlink"; + }, + }, + val.raw); +} + +template +fso::VariantT +adl_serializer>::from_json(const json & json) +{ + using Variant = fso::VariantT; + auto & obj = getObject(json); + auto type = getString(valueAt(obj, "type")); + if (type == "regular") + return static_cast(json); + if (type == "directory") + return static_cast(json); + if (type == "symlink") + return static_cast(json); + else + throw Error("unknown type of file '%s'", type); +} + +// Explicit instantiations for VariantT types we use +template struct adl_serializer; +template struct adl_serializer; +template struct adl_serializer; + +// MemorySourceAccessor +MemorySourceAccessor adl_serializer::from_json(const json & json) +{ + MemorySourceAccessor res; + res.root = json; + return res; +} + +void adl_serializer::to_json(json & json, const MemorySourceAccessor & val) +{ + json = val.root; +} + +} // namespace nlohmann diff --git a/src/libutil/meson.build b/src/libutil/meson.build index 5290ff2d0..53a346afb 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -146,6 +146,7 @@ sources = [ config_priv_h ] + files( 'json-utils.cc', 'logging.cc', 'memory-source-accessor.cc', + 'memory-source-accessor/json.cc', 'mounted-source-accessor.cc', 'nar-accessor.cc', 'pos-table.cc', diff --git a/src/libutil/nar-accessor.cc b/src/libutil/nar-accessor.cc index 35ee4e536..22b6abdd5 100644 --- a/src/libutil/nar-accessor.cc +++ b/src/libutil/nar-accessor.cc @@ -324,52 +324,4 @@ ShallowNarListing listNarShallow(SourceAccessor & accessor, const CanonPath & pa return listNarImpl(accessor, path); } -template -static void to_json_impl(nlohmann::json & j, const Listing & listing) -{ - std::visit( - overloaded{ - [&](const typename Listing::Regular & r) { - j = nlohmann::json::object(); - j["type"] = "regular"; - if (r.contents.fileSize) - j["size"] = *r.contents.fileSize; - if (r.executable) - j["executable"] = true; - if (r.contents.narOffset) - j["narOffset"] = *r.contents.narOffset; - }, - [&](const typename Listing::Directory & d) { - j = nlohmann::json::object(); - j["type"] = "directory"; - j["entries"] = nlohmann::json::object(); - for (const auto & [name, child] : d.entries) { - if constexpr (std::is_same_v) { - to_json(j["entries"][name], child); - } else if constexpr (std::is_same_v) { - j["entries"][name] = nlohmann::json::object(); - } else { - static_assert(false); - } - } - }, - [&](const typename Listing::Symlink & s) { - j = nlohmann::json::object(); - j["type"] = "symlink"; - j["target"] = s.target; - }, - }, - listing.raw); -} - -void to_json(nlohmann::json & j, const NarListing & listing) -{ - to_json_impl(j, listing); -} - -void to_json(nlohmann::json & j, const ShallowNarListing & listing) -{ - to_json_impl(j, listing); -} - } // namespace nix