From a1c39edd4c8a6af90d741b99e579729d42925ef5 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` 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 | 65 +++++++++ src/json-schema-checks/file-system-object | 1 + src/json-schema-checks/meson.build | 8 ++ src/json-schema-checks/package.nix | 1 + src/libstore-tests/references.cc | 11 +- .../data/memory-source-accessor/complex.json | 24 ++++ .../data/memory-source-accessor/simple.json | 5 + src/libutil-tests/git.cc | 41 +----- src/libutil-tests/memory-source-accessor.cc | 116 ++++++++++++++++ src/libutil-tests/meson.build | 1 + src/libutil/include/nix/util/bytes.hh | 41 ++++++ .../nix/util/memory-source-accessor.hh | 39 +++++- src/libutil/include/nix/util/meson.build | 1 + src/libutil/memory-source-accessor.cc | 124 +++++++++++++++++- 20 files changed, 456 insertions(+), 48 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/memory-source-accessor.cc create mode 100644 src/libutil/include/nix/util/bytes.hh diff --git a/doc/manual/package.nix b/doc/manual/package.nix index 81061b7a1..3f4b00049 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 5be3d6a90..62d59aff6 100644 --- a/doc/manual/source/SUMMARY.md.in +++ b/doc/manual/source/SUMMARY.md.in @@ -120,6 +120,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..c7154b18d --- /dev/null +++ b/doc/manual/source/protocols/json/schema/file-system-object-v1.yaml @@ -0,0 +1,65 @@ +"$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/symlink" + required: ["type", "target"] + + - $ref: "#/$defs/directory" + required: ["type", "contents"] + +"$defs": + regular: + title: Regular File + required: ["contents"] + properties: + type: + const: "regular" + contents: + type: string + description: Base64-encoded file contents + executable: + type: boolean + description: Whether the file is executable. + default: false + additionalProperties: false + + symlink: + title: Symbolic Link + required: ["target"] + properties: + type: + const: "symlink" + target: + type: string + description: Target path of the symlink. + additionalProperties: false + + directory: + title: Directory + required: ["contents"] + properties: + type: + const: "directory" + contents: + type: object + description: | + Map of names to nested file system objects (for type=directory) + additionalProperties: + $ref: "#" + additionalProperties: false 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 5365fe75e..2ca43439f 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/libstore-tests/references.cc b/src/libstore-tests/references.cc index 9cecd573e..e23b443fb 100644 --- a/src/libstore-tests/references.cc +++ b/src/libstore-tests/references.cc @@ -1,5 +1,6 @@ #include "nix/store/references.hh" #include "nix/store/path-references.hh" +#include "nix/util/bytes.hh" #include "nix/util/memory-source-accessor.hh" #include @@ -104,21 +105,21 @@ TEST(references, scanForReferencesDeep) // file1.txt: contains hash1 "file1.txt", File::Regular{ - .contents = "This file references " + hash1 + " in its content", + .contents = to_owned(as_bytes("This file references " + hash1 + " in its content")), }, }, { // file2.txt: contains hash2 and hash3 "file2.txt", File::Regular{ - .contents = "Multiple refs: " + hash2 + " and also " + hash3, + .contents = to_owned(as_bytes("Multiple refs: " + hash2 + " and also " + hash3)), }, }, { // file3.txt: contains no references "file3.txt", File::Regular{ - .contents = "This file has no store path references at all", + .contents = to_owned(as_bytes("This file has no store path references at all")), }, }, { @@ -130,7 +131,7 @@ TEST(references, scanForReferencesDeep) // subdir/file4.txt: contains hash1 again "file4.txt", File::Regular{ - .contents = "Subdirectory file with " + hash1, + .contents = to_owned(as_bytes("Subdirectory file with " + hash1)), }, }, }, @@ -140,7 +141,7 @@ TEST(references, scanForReferencesDeep) // link1: a symlink that contains a reference in its target "link1", File::Symlink{ - .target = hash2 + "-target", + .target = to_owned(as_bytes(hash2 + "-target")), }, }, }, 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..23c2e5b42 --- /dev/null +++ b/src/libutil-tests/data/memory-source-accessor/complex.json @@ -0,0 +1,24 @@ +{ + "contents": { + "bar": { + "contents": { + "baz": { + "contents": "Z29vZCBkYXksCg==", + "executable": true, + "type": "regular" + }, + "quux": { + "target": "L292ZXIvdGhlcmU=", + "type": "symlink" + } + }, + "type": "directory" + }, + "foo": { + "contents": "aGVsbG8K", + "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..8c61fa730 --- /dev/null +++ b/src/libutil-tests/data/memory-source-accessor/simple.json @@ -0,0 +1,5 @@ +{ + "contents": "YXNkZg==", + "executable": false, + "type": "regular" +} diff --git a/src/libutil-tests/git.cc b/src/libutil-tests/git.cc index 6180a4cfc..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{ - .contents{ - { - "foo", - File::Regular{ - .contents = "hello\n\0\n\tworld!", - }, - }, - { - "bar", - File::Directory{ - .contents = - { - { - "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..7227c9fa4 --- /dev/null +++ b/src/libutil-tests/memory-source-accessor.cc @@ -0,0 +1,116 @@ +#include + +#include "nix/util/bytes.hh" +#include "nix/util/memory-source-accessor.hh" +#include "nix/util/tests/json-characterization.hh" + +namespace nix { + +namespace memory_source_accessor { + +using File = MemorySourceAccessor::File; + +ref exampleSimple() +{ + auto sc = make_ref(); + sc->root = File{File::Regular{ + .executable = false, + .contents = to_owned(as_bytes("asdf")), + }}; + return sc; +} + +ref exampleComplex() +{ + auto files = make_ref(); + files->root = File::Directory{ + .contents{ + { + "foo", + File::Regular{ + .contents = to_owned(as_bytes("hello\n\0\n\tworld!")), + }, + }, + { + "bar", + File::Directory{ + .contents = + { + { + "baz", + File::Regular{ + .executable = true, + .contents = to_owned(as_bytes("good day,\n\0\n\tworld!")), + }, + }, + { + "quux", + File::Symlink{ + .target = to_owned(as_bytes("/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 c75f4d90a..6c593eef8 100644 --- a/src/libutil-tests/meson.build +++ b/src/libutil-tests/meson.build @@ -63,6 +63,7 @@ sources = files( 'json-utils.cc', 'logging.cc', 'lru-cache.cc', + 'memory-source-accessor.cc', 'monitorfdhup.cc', 'nix_api_util.cc', 'nix_api_util_internal.cc', diff --git a/src/libutil/include/nix/util/bytes.hh b/src/libutil/include/nix/util/bytes.hh new file mode 100644 index 000000000..7fd6cf8c3 --- /dev/null +++ b/src/libutil/include/nix/util/bytes.hh @@ -0,0 +1,41 @@ +#pragma once +///@file + +#include +#include +#include + +namespace nix { + +static inline std::span as_bytes(std::string_view sv) noexcept +{ + return std::span{ + reinterpret_cast(sv.data()), + sv.size(), + }; +} + +static inline std::vector to_owned(std::span bytes) +{ + return std::vector{ + bytes.begin(), + bytes.end(), + }; +} + +/** + * @note this should be avoided, as arbitrary binary data in strings + * views, while allowed, is not really proper. Generally this should + * only be used as a stop-gap with other definitions that themselves + * should be converted to accept `std::span` or + * similar, directly. + */ +static inline std::string_view as_str(std::span sp) +{ + return std::string_view{ + reinterpret_cast(sp.data()), + sp.size(), + }; +} + +} // namespace nix diff --git a/src/libutil/include/nix/util/memory-source-accessor.hh b/src/libutil/include/nix/util/memory-source-accessor.hh index eba282fe1..cd8b3c717 100644 --- a/src/libutil/include/nix/util/memory-source-accessor.hh +++ b/src/libutil/include/nix/util/memory-source-accessor.hh @@ -4,6 +4,7 @@ #include "nix/util/source-path.hh" #include "nix/util/fs-sink.hh" #include "nix/util/variant-wrapper.hh" +#include "nix/util/json-impls.hh" namespace nix { @@ -25,7 +26,7 @@ struct MemorySourceAccessor : virtual SourceAccessor struct Regular { bool executable = false; - std::string contents; + std::vector contents; bool operator==(const Regular &) const = default; auto operator<=>(const Regular &) const = default; @@ -44,7 +45,7 @@ struct MemorySourceAccessor : virtual SourceAccessor struct Symlink { - std::string target; + std::vector target; bool operator==(const Symlink &) const = default; auto operator<=>(const Symlink &) const = default; @@ -86,7 +87,13 @@ struct MemorySourceAccessor : virtual SourceAccessor */ File * open(const CanonPath & path, std::optional create); - SourcePath addFile(CanonPath path, std::string && contents); + SourcePath addFile(CanonPath path, std::vector && contents); + + /** + * Small wrapper of the other `addFile`, purely for convenience when + * the file in question to be added is a string. + */ + SourcePath addFile(CanonPath path, std::string_view contents); }; inline bool MemorySourceAccessor::File::Directory::operator==( @@ -121,4 +128,30 @@ 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 + +JSON_IMPL(MemorySourceAccessor::File::Regular) +JSON_IMPL(MemorySourceAccessor::File::Directory) +JSON_IMPL(MemorySourceAccessor::File::Symlink) +JSON_IMPL(MemorySourceAccessor::File) +JSON_IMPL(MemorySourceAccessor) diff --git a/src/libutil/include/nix/util/meson.build b/src/libutil/include/nix/util/meson.build index 9a606e15d..89790f908 100644 --- a/src/libutil/include/nix/util/meson.build +++ b/src/libutil/include/nix/util/meson.build @@ -12,6 +12,7 @@ headers = files( 'array-from-string-literal.hh', 'base-n.hh', 'base-nix-32.hh', + 'bytes.hh', 'callback.hh', 'canon-path.hh', 'checked-arithmetic.hh', diff --git a/src/libutil/memory-source-accessor.cc b/src/libutil/memory-source-accessor.cc index a9ffb7746..5089470e9 100644 --- a/src/libutil/memory-source-accessor.cc +++ b/src/libutil/memory-source-accessor.cc @@ -1,4 +1,7 @@ #include "nix/util/memory-source-accessor.hh" +#include "nix/util/base-n.hh" +#include "nix/util/bytes.hh" +#include "nix/util/json-utils.hh" namespace nix { @@ -58,7 +61,7 @@ std::string MemorySourceAccessor::readFile(const CanonPath & path) if (!f) throw Error("file '%s' does not exist", path); if (auto * r = std::get_if(&f->raw)) - return r->contents; + return std::string{as_str(r->contents)}; else throw Error("file '%s' is not a regular file", path); } @@ -120,12 +123,12 @@ std::string MemorySourceAccessor::readLink(const CanonPath & path) if (!f) throw Error("file '%s' does not exist", path); if (auto * s = std::get_if(&f->raw)) - return s->target; + return std::string{as_str(s->target)}; else throw Error("file '%s' is not a symbolic link", path); } -SourcePath MemorySourceAccessor::addFile(CanonPath path, std::string && contents) +SourcePath MemorySourceAccessor::addFile(CanonPath path, std::vector && contents) { // Create root directory automatically if necessary as a convenience. if (!root && !path.isRoot()) @@ -142,6 +145,11 @@ SourcePath MemorySourceAccessor::addFile(CanonPath path, std::string && contents return SourcePath{ref(shared_from_this()), path}; } +SourcePath MemorySourceAccessor::addFile(CanonPath path, std::string_view contents) +{ + return addFile(path, to_owned(as_bytes(contents))); +} + using File = MemorySourceAccessor::File; void MemorySink::createDirectory(const CanonPath & path) @@ -190,9 +198,10 @@ void CreateMemoryRegularFile::preallocateContents(uint64_t len) regularFile.contents.reserve(len); } -void CreateMemoryRegularFile::operator()(std::string_view data) +void CreateMemoryRegularFile::operator()(std::string_view data_) { - regularFile.contents += data; + auto data = as_bytes(data_); + regularFile.contents.insert(regularFile.contents.end(), data.begin(), data.end()); } void MemorySink::createSymlink(const CanonPath & path, const std::string & target) @@ -201,7 +210,7 @@ void MemorySink::createSymlink(const CanonPath & path, const std::string & targe if (!f) throw Error("file '%s' cannot be made because some parent file is not a directory", path); if (auto * s = std::get_if(&f->raw)) - s->target = target; + s->target = to_owned(as_bytes(target)); else throw Error("file '%s' is not a symbolic link", path); } @@ -222,3 +231,106 @@ ref makeEmptySourceAccessor() } } // namespace nix + +namespace nlohmann { + +using namespace nix; + +MemorySourceAccessor::File::Regular adl_serializer::from_json(const json & json) +{ + auto & obj = getObject(json); + return MemorySourceAccessor::File::Regular{ + .executable = getBoolean(valueAt(obj, "executable")), + .contents = to_owned(as_bytes(base64::decode(getString(valueAt(obj, "contents"))))), + }; +} + +void adl_serializer::to_json( + json & json, const MemorySourceAccessor::File::Regular & val) +{ + json = { + {"executable", val.executable}, + {"contents", base64::encode(val.contents)}, + }; +} + +MemorySourceAccessor::File::Directory +adl_serializer::from_json(const json & json) +{ + auto & obj = getObject(json); + return MemorySourceAccessor::File::Directory{ + .contents = valueAt(obj, "contents"), + }; +} + +void adl_serializer::to_json( + json & json, const MemorySourceAccessor::File::Directory & val) +{ + json = { + {"contents", val.contents}, + }; +} + +MemorySourceAccessor::File::Symlink adl_serializer::from_json(const json & json) +{ + auto & obj = getObject(json); + return MemorySourceAccessor::File::Symlink{ + .target = to_owned(as_bytes(base64::decode(getString(valueAt(obj, "target"))))), + }; +} + +void adl_serializer::to_json( + json & json, const MemorySourceAccessor::File::Symlink & val) +{ + json = { + {"target", base64::encode(val.target)}, + }; +} + +MemorySourceAccessor::File adl_serializer::from_json(const json & json) +{ + 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); +} + +void adl_serializer::to_json(json & json, const MemorySourceAccessor::File & val) +{ + std::visit( + overloaded{ + [&](const MemorySourceAccessor::File::Regular & r) { + json = r; + json["type"] = "regular"; + }, + [&](const MemorySourceAccessor::File::Directory & d) { + json = d; + json["type"] = "directory"; + }, + [&](const MemorySourceAccessor::File::Symlink & s) { + json = s; + json["type"] = "symlink"; + }, + }, + val.raw); +} + +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