mirror of
https://github.com/NixOS/nix.git
synced 2025-11-22 18:29:36 +01:00
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.
This commit is contained in:
parent
c4906741a1
commit
7357a654de
26 changed files with 605 additions and 101 deletions
|
|
@ -37,6 +37,7 @@ mkMesonDerivation (finalAttrs: {
|
||||||
(fileset.unions [
|
(fileset.unions [
|
||||||
../../.version
|
../../.version
|
||||||
# For example JSON
|
# For example JSON
|
||||||
|
../../src/libutil-tests/data/memory-source-accessor
|
||||||
../../src/libutil-tests/data/hash
|
../../src/libutil-tests/data/hash
|
||||||
../../src/libstore-tests/data/content-address
|
../../src/libstore-tests/data/content-address
|
||||||
../../src/libstore-tests/data/store-path
|
../../src/libstore-tests/data/store-path
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,7 @@
|
||||||
- [Architecture and Design](architecture/architecture.md)
|
- [Architecture and Design](architecture/architecture.md)
|
||||||
- [Formats and Protocols](protocols/index.md)
|
- [Formats and Protocols](protocols/index.md)
|
||||||
- [JSON Formats](protocols/json/index.md)
|
- [JSON Formats](protocols/json/index.md)
|
||||||
|
- [File System Object](protocols/json/file-system-object.md)
|
||||||
- [Hash](protocols/json/hash.md)
|
- [Hash](protocols/json/hash.md)
|
||||||
- [Content Address](protocols/json/content-address.md)
|
- [Content Address](protocols/json/content-address.md)
|
||||||
- [Store Path](protocols/json/store-path.md)
|
- [Store Path](protocols/json/store-path.md)
|
||||||
|
|
|
||||||
21
doc/manual/source/protocols/json/file-system-object.md
Normal file
21
doc/manual/source/protocols/json/file-system-object.md
Normal file
|
|
@ -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}}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- need to convert YAML to JSON first
|
||||||
|
## Raw Schema
|
||||||
|
|
||||||
|
[JSON Schema for File System Object v1](schema/file-system-object-v1.json)
|
||||||
|
-->
|
||||||
|
|
@ -11,6 +11,7 @@ s/\\`/`/g
|
||||||
#
|
#
|
||||||
# As we have more such relative links, more replacements of this nature
|
# As we have more such relative links, more replacements of this nature
|
||||||
# should appear below.
|
# 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\)\?#/$defs/algorithm^[JSON format for `Hash`](./hash.html#algorithm)^g
|
||||||
s^\(./hash-v1.yaml\)^[JSON format for `Hash`](./hash.html)^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
|
s^\(./content-address-v1.yaml\)\?#/$defs/method^[JSON format for `ContentAddress`](./content-address.html#method)^g
|
||||||
|
|
|
||||||
|
|
@ -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')
|
json_schema_config = files('json-schema-for-humans-config.yaml')
|
||||||
|
|
||||||
schemas = [
|
schemas = [
|
||||||
|
'file-system-object-v1',
|
||||||
'hash-v1',
|
'hash-v1',
|
||||||
'content-address-v1',
|
'content-address-v1',
|
||||||
'store-path-v1',
|
'store-path-v1',
|
||||||
|
|
|
||||||
1
doc/manual/source/protocols/json/schema/file-system-object-v1
Symbolic link
1
doc/manual/source/protocols/json/schema/file-system-object-v1
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../src/libutil-tests/data/memory-source-accessor
|
||||||
|
|
@ -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
|
||||||
|
|
@ -3,19 +3,23 @@
|
||||||
Nix uses a simplified model of the file system, which consists of file system objects.
|
Nix uses a simplified model of the file system, which consists of file system objects.
|
||||||
Every file system object is one of the following:
|
Every file system object is one of the following:
|
||||||
|
|
||||||
- File
|
- [**Regular File**]{#regular}
|
||||||
|
|
||||||
- A possibly empty sequence of bytes for contents
|
- 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
|
- 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
|
Mapping of names to child file system objects
|
||||||
|
|
||||||
- [Symbolic link](https://en.m.wikipedia.org/wiki/Symbolic_link)
|
- [**Symbolic link**]{#symlink}
|
||||||
|
|
||||||
An arbitrary string.
|
An arbitrary string, known as the *target* of the symlink.
|
||||||
Nix does not assign any semantics to symbolic links.
|
|
||||||
|
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.
|
File system objects and their children form a tree.
|
||||||
A bare file or symlink can be a root file system object.
|
A bare file or symlink can be a root file system object.
|
||||||
|
|
|
||||||
1
src/json-schema-checks/file-system-object
Symbolic link
1
src/json-schema-checks/file-system-object
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../src/libutil-tests/data/memory-source-accessor
|
||||||
|
|
@ -20,6 +20,14 @@ schema_dir = meson.current_source_dir() / 'schema'
|
||||||
|
|
||||||
# Get all example files
|
# Get all example files
|
||||||
schemas = [
|
schemas = [
|
||||||
|
{
|
||||||
|
'stem' : 'file-system-object',
|
||||||
|
'schema' : schema_dir / 'file-system-object-v1.yaml',
|
||||||
|
'files' : [
|
||||||
|
'simple.json',
|
||||||
|
'complex.json',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'stem' : 'hash',
|
'stem' : 'hash',
|
||||||
'schema' : schema_dir / 'hash-v1.yaml',
|
'schema' : schema_dir / 'hash-v1.yaml',
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ mkMesonDerivation (finalAttrs: {
|
||||||
fileset = lib.fileset.unions [
|
fileset = lib.fileset.unions [
|
||||||
../../.version
|
../../.version
|
||||||
../../doc/manual/source/protocols/json/schema
|
../../doc/manual/source/protocols/json/schema
|
||||||
|
../../src/libutil-tests/data/memory-source-accessor
|
||||||
../../src/libutil-tests/data/hash
|
../../src/libutil-tests/data/hash
|
||||||
../../src/libstore-tests/data/content-address
|
../../src/libstore-tests/data/content-address
|
||||||
../../src/libstore-tests/data/store-path
|
../../src/libstore-tests/data/store-path
|
||||||
|
|
|
||||||
24
src/libutil-tests/data/memory-source-accessor/complex.json
Normal file
24
src/libutil-tests/data/memory-source-accessor/complex.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"contents": "asdf",
|
||||||
|
"executable": false,
|
||||||
|
"type": "regular"
|
||||||
|
}
|
||||||
23
src/libutil-tests/data/nar-listing/deep.json
Normal file
23
src/libutil-tests/data/nar-listing/deep.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
7
src/libutil-tests/data/nar-listing/shallow.json
Normal file
7
src/libutil-tests/data/nar-listing/shallow.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"entries": {
|
||||||
|
"bar": {},
|
||||||
|
"foo": {}
|
||||||
|
},
|
||||||
|
"type": "directory"
|
||||||
|
}
|
||||||
|
|
@ -224,42 +224,15 @@ TEST_F(GitTest, tree_sha256_write)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace memory_source_accessor {
|
||||||
|
|
||||||
|
extern ref<MemorySourceAccessor> exampleComplex();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(GitTest, both_roundrip)
|
TEST_F(GitTest, both_roundrip)
|
||||||
{
|
{
|
||||||
using File = MemorySourceAccessor::File;
|
auto files = memory_source_accessor::exampleComplex();
|
||||||
|
|
||||||
auto files = make_ref<MemorySourceAccessor>();
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto hashAlgo : {HashAlgorithm::SHA1, HashAlgorithm::SHA256}) {
|
for (const auto hashAlgo : {HashAlgorithm::SHA1, HashAlgorithm::SHA256}) {
|
||||||
std::map<Hash, std::string> cas;
|
std::map<Hash, std::string> cas;
|
||||||
|
|
|
||||||
116
src/libutil-tests/memory-source-accessor.cc
Normal file
116
src/libutil-tests/memory-source-accessor.cc
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#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<MemorySourceAccessor> exampleSimple()
|
||||||
|
{
|
||||||
|
auto sc = make_ref<MemorySourceAccessor>();
|
||||||
|
sc->root = File{File::Regular{
|
||||||
|
.executable = false,
|
||||||
|
.contents = "asdf",
|
||||||
|
}};
|
||||||
|
return sc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref<MemorySourceAccessor> exampleComplex()
|
||||||
|
{
|
||||||
|
auto files = make_ref<MemorySourceAccessor>();
|
||||||
|
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<MemorySourceAccessor>,
|
||||||
|
::testing::WithParamInterface<std::pair<std::string_view, MemorySourceAccessor>>
|
||||||
|
{};
|
||||||
|
|
||||||
|
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<MemorySourceAccessor>(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
|
||||||
|
|
@ -63,7 +63,9 @@ sources = files(
|
||||||
'json-utils.cc',
|
'json-utils.cc',
|
||||||
'logging.cc',
|
'logging.cc',
|
||||||
'lru-cache.cc',
|
'lru-cache.cc',
|
||||||
|
'memory-source-accessor.cc',
|
||||||
'monitorfdhup.cc',
|
'monitorfdhup.cc',
|
||||||
|
'nar-listing.cc',
|
||||||
'nix_api_util.cc',
|
'nix_api_util.cc',
|
||||||
'nix_api_util_internal.cc',
|
'nix_api_util_internal.cc',
|
||||||
'pool.cc',
|
'pool.cc',
|
||||||
|
|
|
||||||
83
src/libutil-tests/nar-listing.cc
Normal file
83
src/libutil-tests/nar-listing.cc
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#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<MemorySourceAccessor> 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<NarListing>,
|
||||||
|
::testing::WithParamInterface<std::pair<std::string_view, NarListing>>
|
||||||
|
{};
|
||||||
|
|
||||||
|
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<ShallowNarListing>,
|
||||||
|
::testing::WithParamInterface<std::pair<std::string_view, ShallowNarListing>>
|
||||||
|
{};
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -6,15 +6,18 @@
|
||||||
#include "nix/util/experimental-features.hh"
|
#include "nix/util/experimental-features.hh"
|
||||||
|
|
||||||
// Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
|
// Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
|
||||||
#define JSON_IMPL(TYPE) \
|
#define JSON_IMPL_INNER(TYPE) \
|
||||||
namespace nlohmann { \
|
|
||||||
using namespace nix; \
|
|
||||||
template<> \
|
|
||||||
struct adl_serializer<TYPE> \
|
struct adl_serializer<TYPE> \
|
||||||
{ \
|
{ \
|
||||||
static TYPE from_json(const json & json); \
|
static TYPE from_json(const json & json); \
|
||||||
static void to_json(json & json, const TYPE & t); \
|
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) \
|
#define JSON_IMPL_WITH_XP_FEATURES(TYPE) \
|
||||||
|
|
|
||||||
|
|
@ -160,4 +160,53 @@ struct MemorySink : FileSystemObjectSink
|
||||||
void createSymlink(const CanonPath & path, const std::string & target) override;
|
void createSymlink(const CanonPath & path, const std::string & target) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct json_avoids_null<MemorySourceAccessor::File::Regular> : std::true_type
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct json_avoids_null<MemorySourceAccessor::File::Directory> : std::true_type
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct json_avoids_null<MemorySourceAccessor::File::Symlink> : std::true_type
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct json_avoids_null<MemorySourceAccessor::File> : std::true_type
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct json_avoids_null<MemorySourceAccessor> : std::true_type
|
||||||
|
{};
|
||||||
|
|
||||||
} // namespace nix
|
} // namespace nix
|
||||||
|
|
||||||
|
namespace nlohmann {
|
||||||
|
|
||||||
|
using namespace nix;
|
||||||
|
|
||||||
|
#define ARG fso::Regular<RegularContents>
|
||||||
|
template<typename RegularContents>
|
||||||
|
JSON_IMPL_INNER(ARG)
|
||||||
|
#undef ARG
|
||||||
|
|
||||||
|
#define ARG fso::DirectoryT<Child>
|
||||||
|
template<typename Child>
|
||||||
|
JSON_IMPL_INNER(ARG)
|
||||||
|
#undef ARG
|
||||||
|
|
||||||
|
template<>
|
||||||
|
JSON_IMPL_INNER(fso::Symlink)
|
||||||
|
|
||||||
|
template<>
|
||||||
|
JSON_IMPL_INNER(fso::Opaque)
|
||||||
|
|
||||||
|
#define ARG fso::VariantT<RegularContents, recur>
|
||||||
|
template<typename RegularContents, bool recur>
|
||||||
|
JSON_IMPL_INNER(ARG)
|
||||||
|
#undef ARG
|
||||||
|
|
||||||
|
} // namespace nlohmann
|
||||||
|
|
||||||
|
JSON_IMPL(MemorySourceAccessor)
|
||||||
|
|
|
||||||
|
|
@ -75,14 +75,6 @@ NarListing listNarDeep(SourceAccessor & accessor, const CanonPath & path);
|
||||||
*/
|
*/
|
||||||
ShallowNarListing listNarShallow(SourceAccessor & accessor, const CanonPath & path);
|
ShallowNarListing listNarShallow(SourceAccessor & accessor, const CanonPath & path);
|
||||||
|
|
||||||
/**
|
// All json_avoids_null and JSON_IMPL covered by generic templates in memory-source-accessor.hh
|
||||||
* 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);
|
|
||||||
|
|
||||||
} // namespace nix
|
} // namespace nix
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "nix/util/memory-source-accessor.hh"
|
#include "nix/util/memory-source-accessor.hh"
|
||||||
|
#include "nix/util/json-utils.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
||||||
162
src/libutil/memory-source-accessor/json.cc
Normal file
162
src/libutil/memory-source-accessor/json.cc
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
#include "nix/util/memory-source-accessor.hh"
|
||||||
|
#include "nix/util/nar-accessor.hh"
|
||||||
|
#include "nix/util/json-utils.hh"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace nlohmann {
|
||||||
|
|
||||||
|
using namespace nix;
|
||||||
|
|
||||||
|
// fso::Regular<RegularContents>
|
||||||
|
template<>
|
||||||
|
MemorySourceAccessor::File::Regular adl_serializer<MemorySourceAccessor::File::Regular>::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<MemorySourceAccessor::File::Regular>::to_json(
|
||||||
|
json & json, const MemorySourceAccessor::File::Regular & r)
|
||||||
|
{
|
||||||
|
json = {
|
||||||
|
{"executable", r.executable},
|
||||||
|
{"contents", r.contents},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
NarListing::Regular adl_serializer<NarListing::Regular>::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<uint64_t>(sizePtr),
|
||||||
|
.narOffset = ptrToOwned<uint64_t>(offsetPtr).and_then(
|
||||||
|
[](auto v) { return v != 0 ? std::optional{v} : std::nullopt; }),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void adl_serializer<NarListing::Regular>::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<typename Child>
|
||||||
|
void adl_serializer<fso::DirectoryT<Child>>::to_json(json & j, const fso::DirectoryT<Child> & d)
|
||||||
|
{
|
||||||
|
j["entries"] = d.entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Child>
|
||||||
|
fso::DirectoryT<Child> adl_serializer<fso::DirectoryT<Child>>::from_json(const json & json)
|
||||||
|
{
|
||||||
|
auto & obj = getObject(json);
|
||||||
|
return fso::DirectoryT<Child>{
|
||||||
|
.entries = valueAt(obj, "entries"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// fso::Symlink
|
||||||
|
fso::Symlink adl_serializer<fso::Symlink>::from_json(const json & json)
|
||||||
|
{
|
||||||
|
auto & obj = getObject(json);
|
||||||
|
return fso::Symlink{
|
||||||
|
.target = getString(valueAt(obj, "target")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void adl_serializer<fso::Symlink>::to_json(json & json, const fso::Symlink & s)
|
||||||
|
{
|
||||||
|
json = {
|
||||||
|
{"target", s.target},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// fso::Opaque
|
||||||
|
fso::Opaque adl_serializer<fso::Opaque>::from_json(const json &)
|
||||||
|
{
|
||||||
|
return fso::Opaque{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void adl_serializer<fso::Opaque>::to_json(json & j, const fso::Opaque &)
|
||||||
|
{
|
||||||
|
j = nlohmann::json::object();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fso::VariantT<RegularContents, recur> - generic implementation
|
||||||
|
template<typename RegularContents, bool recur>
|
||||||
|
void adl_serializer<fso::VariantT<RegularContents, recur>>::to_json(
|
||||||
|
json & j, const fso::VariantT<RegularContents, recur> & val)
|
||||||
|
{
|
||||||
|
using Variant = fso::VariantT<RegularContents, recur>;
|
||||||
|
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<typename RegularContents, bool recur>
|
||||||
|
fso::VariantT<RegularContents, recur>
|
||||||
|
adl_serializer<fso::VariantT<RegularContents, recur>>::from_json(const json & json)
|
||||||
|
{
|
||||||
|
using Variant = fso::VariantT<RegularContents, recur>;
|
||||||
|
auto & obj = getObject(json);
|
||||||
|
auto type = getString(valueAt(obj, "type"));
|
||||||
|
if (type == "regular")
|
||||||
|
return static_cast<typename Variant::Regular>(json);
|
||||||
|
if (type == "directory")
|
||||||
|
return static_cast<typename Variant::Directory>(json);
|
||||||
|
if (type == "symlink")
|
||||||
|
return static_cast<typename Variant::Symlink>(json);
|
||||||
|
else
|
||||||
|
throw Error("unknown type of file '%s'", type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit instantiations for VariantT types we use
|
||||||
|
template struct adl_serializer<MemorySourceAccessor::File>;
|
||||||
|
template struct adl_serializer<NarListing>;
|
||||||
|
template struct adl_serializer<ShallowNarListing>;
|
||||||
|
|
||||||
|
// MemorySourceAccessor
|
||||||
|
MemorySourceAccessor adl_serializer<MemorySourceAccessor>::from_json(const json & json)
|
||||||
|
{
|
||||||
|
MemorySourceAccessor res;
|
||||||
|
res.root = json;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void adl_serializer<MemorySourceAccessor>::to_json(json & json, const MemorySourceAccessor & val)
|
||||||
|
{
|
||||||
|
json = val.root;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nlohmann
|
||||||
|
|
@ -146,6 +146,7 @@ sources = [ config_priv_h ] + files(
|
||||||
'json-utils.cc',
|
'json-utils.cc',
|
||||||
'logging.cc',
|
'logging.cc',
|
||||||
'memory-source-accessor.cc',
|
'memory-source-accessor.cc',
|
||||||
|
'memory-source-accessor/json.cc',
|
||||||
'mounted-source-accessor.cc',
|
'mounted-source-accessor.cc',
|
||||||
'nar-accessor.cc',
|
'nar-accessor.cc',
|
||||||
'pos-table.cc',
|
'pos-table.cc',
|
||||||
|
|
|
||||||
|
|
@ -324,52 +324,4 @@ ShallowNarListing listNarShallow(SourceAccessor & accessor, const CanonPath & pa
|
||||||
return listNarImpl<false>(accessor, path);
|
return listNarImpl<false>(accessor, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Listing>
|
|
||||||
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<Listing, NarListing>) {
|
|
||||||
to_json(j["entries"][name], child);
|
|
||||||
} else if constexpr (std::is_same_v<Listing, ShallowNarListing>) {
|
|
||||||
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
|
} // namespace nix
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue