feat(tvix/nix-compat-derive): Add deriver for NixDeserialize
This adds a nix-compat-derive derive crate that implements a deriver for NixDeserialize implementations. This is to reduce the amount of code needed to implement deserialization for all the types used by the Nix daemon protocol. Change-Id: I484724b550e8a1d5e9adad9555d9dc1374ae95c2 Reviewed-on: https://cl.tvl.fyi/c/depot/+/12022 Autosubmit: Brian Olsen <me@griff.name> Tested-by: BuildkiteCI Reviewed-by: flokli <flokli@flokli.de>
This commit is contained in:
parent
9af6920478
commit
ced05a2bb6
48 changed files with 2376 additions and 3 deletions
44
tvix/Cargo.lock
generated
44
tvix/Cargo.lock
generated
|
|
@ -2342,6 +2342,7 @@ dependencies = [
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
|
"nix-compat-derive",
|
||||||
"nom",
|
"nom",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
|
@ -2358,6 +2359,35 @@ dependencies = [
|
||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix-compat-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"hex-literal",
|
||||||
|
"nix-compat",
|
||||||
|
"pretty_assertions",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rstest",
|
||||||
|
"syn 2.0.72",
|
||||||
|
"tokio",
|
||||||
|
"tokio-test",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix-compat-derive-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"hex-literal",
|
||||||
|
"nix-compat",
|
||||||
|
"nix-compat-derive",
|
||||||
|
"pretty_assertions",
|
||||||
|
"rstest",
|
||||||
|
"tokio",
|
||||||
|
"tokio-test",
|
||||||
|
"trybuild",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nohash-hasher"
|
name = "nohash-hasher"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -4708,6 +4738,20 @@ version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "trybuild"
|
||||||
|
version = "1.0.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "207aa50d36c4be8d8c6ea829478be44a372c6a77669937bb39c698e52f1491e8"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"termcolor",
|
||||||
|
"toml 0.8.15",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tvix-build"
|
name = "tvix-build"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
172
tvix/Cargo.nix
172
tvix/Cargo.nix
|
|
@ -55,6 +55,26 @@ rec {
|
||||||
# File a bug if you depend on any for non-debug work!
|
# File a bug if you depend on any for non-debug work!
|
||||||
debug = internal.debugCrate { inherit packageId; };
|
debug = internal.debugCrate { inherit packageId; };
|
||||||
};
|
};
|
||||||
|
"nix-compat-derive" = rec {
|
||||||
|
packageId = "nix-compat-derive";
|
||||||
|
build = internal.buildRustCrateWithFeatures {
|
||||||
|
packageId = "nix-compat-derive";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Debug support which might change between releases.
|
||||||
|
# File a bug if you depend on any for non-debug work!
|
||||||
|
debug = internal.debugCrate { inherit packageId; };
|
||||||
|
};
|
||||||
|
"nix-compat-derive-tests" = rec {
|
||||||
|
packageId = "nix-compat-derive-tests";
|
||||||
|
build = internal.buildRustCrateWithFeatures {
|
||||||
|
packageId = "nix-compat-derive-tests";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Debug support which might change between releases.
|
||||||
|
# File a bug if you depend on any for non-debug work!
|
||||||
|
debug = internal.debugCrate { inherit packageId; };
|
||||||
|
};
|
||||||
"tvix-build" = rec {
|
"tvix-build" = rec {
|
||||||
packageId = "tvix-build";
|
packageId = "tvix-build";
|
||||||
build = internal.buildRustCrateWithFeatures {
|
build = internal.buildRustCrateWithFeatures {
|
||||||
|
|
@ -7395,6 +7415,12 @@ rec {
|
||||||
name = "mimalloc";
|
name = "mimalloc";
|
||||||
packageId = "mimalloc";
|
packageId = "mimalloc";
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
name = "nix-compat-derive";
|
||||||
|
packageId = "nix-compat-derive";
|
||||||
|
optional = true;
|
||||||
|
usesDefaultFeatures = false;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
name = "nom";
|
name = "nom";
|
||||||
packageId = "nom";
|
packageId = "nom";
|
||||||
|
|
@ -7488,12 +7514,115 @@ rec {
|
||||||
features = {
|
features = {
|
||||||
"async" = [ "tokio" ];
|
"async" = [ "tokio" ];
|
||||||
"bytes" = [ "dep:bytes" ];
|
"bytes" = [ "dep:bytes" ];
|
||||||
"default" = [ "async" "wire" ];
|
"default" = [ "async" "wire" "nix-compat-derive" ];
|
||||||
|
"nix-compat-derive" = [ "dep:nix-compat-derive" ];
|
||||||
"pin-project-lite" = [ "dep:pin-project-lite" ];
|
"pin-project-lite" = [ "dep:pin-project-lite" ];
|
||||||
"tokio" = [ "dep:tokio" ];
|
"tokio" = [ "dep:tokio" ];
|
||||||
"wire" = [ "tokio" "pin-project-lite" "bytes" ];
|
"wire" = [ "tokio" "pin-project-lite" "bytes" ];
|
||||||
};
|
};
|
||||||
resolvedDefaultFeatures = [ "async" "bytes" "default" "pin-project-lite" "tokio" "wire" ];
|
resolvedDefaultFeatures = [ "async" "bytes" "default" "nix-compat-derive" "pin-project-lite" "test" "tokio" "wire" ];
|
||||||
|
};
|
||||||
|
"nix-compat-derive" = rec {
|
||||||
|
crateName = "nix-compat-derive";
|
||||||
|
version = "0.1.0";
|
||||||
|
edition = "2021";
|
||||||
|
src = lib.cleanSourceWith { filter = sourceFilter; src = ./nix-compat-derive; };
|
||||||
|
procMacro = true;
|
||||||
|
libName = "nix_compat_derive";
|
||||||
|
dependencies = [
|
||||||
|
{
|
||||||
|
name = "proc-macro2";
|
||||||
|
packageId = "proc-macro2";
|
||||||
|
features = [ "proc-macro" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "quote";
|
||||||
|
packageId = "quote";
|
||||||
|
features = [ "proc-macro" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "syn";
|
||||||
|
packageId = "syn 2.0.72";
|
||||||
|
features = [ "full" "extra-traits" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
devDependencies = [
|
||||||
|
{
|
||||||
|
name = "hex-literal";
|
||||||
|
packageId = "hex-literal";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "nix-compat";
|
||||||
|
packageId = "nix-compat";
|
||||||
|
usesDefaultFeatures = false;
|
||||||
|
features = [ "async" "wire" "test" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "pretty_assertions";
|
||||||
|
packageId = "pretty_assertions";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "rstest";
|
||||||
|
packageId = "rstest";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "tokio";
|
||||||
|
packageId = "tokio";
|
||||||
|
features = [ "io-util" "macros" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "tokio-test";
|
||||||
|
packageId = "tokio-test";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
features = {
|
||||||
|
"default" = [ "external" ];
|
||||||
|
};
|
||||||
|
resolvedDefaultFeatures = [ "default" "external" ];
|
||||||
|
};
|
||||||
|
"nix-compat-derive-tests" = rec {
|
||||||
|
crateName = "nix-compat-derive-tests";
|
||||||
|
version = "0.1.0";
|
||||||
|
edition = "2021";
|
||||||
|
src = lib.cleanSourceWith { filter = sourceFilter; src = ./nix-compat-derive-tests; };
|
||||||
|
devDependencies = [
|
||||||
|
{
|
||||||
|
name = "hex-literal";
|
||||||
|
packageId = "hex-literal";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "nix-compat";
|
||||||
|
packageId = "nix-compat";
|
||||||
|
features = [ "test" "wire" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "nix-compat-derive";
|
||||||
|
packageId = "nix-compat-derive";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "pretty_assertions";
|
||||||
|
packageId = "pretty_assertions";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "rstest";
|
||||||
|
packageId = "rstest";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "tokio";
|
||||||
|
packageId = "tokio";
|
||||||
|
features = [ "io-util" "macros" ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "tokio-test";
|
||||||
|
packageId = "tokio-test";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "trybuild";
|
||||||
|
packageId = "trybuild";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
features = { };
|
||||||
|
resolvedDefaultFeatures = [ "compile-tests" ];
|
||||||
};
|
};
|
||||||
"nohash-hasher" = rec {
|
"nohash-hasher" = rec {
|
||||||
crateName = "nohash-hasher";
|
crateName = "nohash-hasher";
|
||||||
|
|
@ -15498,6 +15627,45 @@ rec {
|
||||||
];
|
];
|
||||||
|
|
||||||
};
|
};
|
||||||
|
"trybuild" = rec {
|
||||||
|
crateName = "trybuild";
|
||||||
|
version = "1.0.99";
|
||||||
|
edition = "2021";
|
||||||
|
sha256 = "1s4i2hpyb66676xkg6b6fxm2qdsawj5lfad8ds68vgn46q6sayi0";
|
||||||
|
authors = [
|
||||||
|
"David Tolnay <dtolnay@gmail.com>"
|
||||||
|
];
|
||||||
|
dependencies = [
|
||||||
|
{
|
||||||
|
name = "glob";
|
||||||
|
packageId = "glob";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "serde";
|
||||||
|
packageId = "serde";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "serde_derive";
|
||||||
|
packageId = "serde_derive";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "serde_json";
|
||||||
|
packageId = "serde_json";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "termcolor";
|
||||||
|
packageId = "termcolor";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "toml";
|
||||||
|
packageId = "toml 0.8.15";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
features = {
|
||||||
|
"diff" = [ "dissimilar" ];
|
||||||
|
"dissimilar" = [ "dep:dissimilar" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
"tvix-build" = rec {
|
"tvix-build" = rec {
|
||||||
crateName = "tvix-build";
|
crateName = "tvix-build";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ members = [
|
||||||
"glue",
|
"glue",
|
||||||
"nar-bridge",
|
"nar-bridge",
|
||||||
"nix-compat",
|
"nix-compat",
|
||||||
|
"nix-compat-derive",
|
||||||
|
"nix-compat-derive-tests",
|
||||||
"serde",
|
"serde",
|
||||||
"store",
|
"store",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
||||||
27
tvix/nix-compat-derive-tests/Cargo.toml
Normal file
27
tvix/nix-compat-derive-tests/Cargo.toml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
[package]
|
||||||
|
name = "nix-compat-derive-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
compile-tests = []
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
hex-literal = "0.4.1"
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
|
rstest = "0.19.0"
|
||||||
|
tokio-test = "0.4.3"
|
||||||
|
trybuild = "1.0.96"
|
||||||
|
|
||||||
|
[dev-dependencies.nix-compat]
|
||||||
|
version = "0.1.0"
|
||||||
|
path = "../nix-compat"
|
||||||
|
features = ["test", "wire"]
|
||||||
|
|
||||||
|
[dev-dependencies.nix-compat-derive]
|
||||||
|
version = "0.1.0"
|
||||||
|
path = "../nix-compat-derive"
|
||||||
|
|
||||||
|
[dev-dependencies.tokio]
|
||||||
|
version = "^1.38"
|
||||||
|
features = ["io-util", "macros"]
|
||||||
5
tvix/nix-compat-derive-tests/default.nix
Normal file
5
tvix/nix-compat-derive-tests/default.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{ depot, ... }:
|
||||||
|
|
||||||
|
depot.tvix.crates.workspaceMembers.nix-compat-derive-tests.build.override {
|
||||||
|
runTests = true;
|
||||||
|
}
|
||||||
417
tvix/nix-compat-derive-tests/tests/read_derive.rs
Normal file
417
tvix/nix-compat-derive-tests/tests/read_derive.rs
Normal file
|
|
@ -0,0 +1,417 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use nix_compat::nix_daemon::de::mock::{Builder, Error};
|
||||||
|
use nix_compat::nix_daemon::de::NixRead;
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
pub struct UnitTest;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
pub struct EmptyTupleTest();
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
pub struct StructTest {
|
||||||
|
first: u64,
|
||||||
|
second: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
pub struct TupleTest(u64, String);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
pub struct StructVersionTest {
|
||||||
|
test: u64,
|
||||||
|
#[nix(version = "20..")]
|
||||||
|
hello: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_test() -> StructVersionTest {
|
||||||
|
StructVersionTest {
|
||||||
|
test: 89,
|
||||||
|
hello: String::from("klomp"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
pub struct TupleVersionTest(u64, #[nix(version = "25..")] String);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
pub struct TupleVersionDefaultTest(
|
||||||
|
u64,
|
||||||
|
#[nix(version = "..25", default = "default_test")] StructVersionTest,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_unit() {
|
||||||
|
let mut mock = Builder::new().build();
|
||||||
|
let v: UnitTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(UnitTest, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_empty_tuple() {
|
||||||
|
let mut mock = Builder::new().build();
|
||||||
|
let v: EmptyTupleTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(EmptyTupleTest(), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_struct() {
|
||||||
|
let mut mock = Builder::new().read_number(89).read_slice(b"klomp").build();
|
||||||
|
let v: StructTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
StructTest {
|
||||||
|
first: 89,
|
||||||
|
second: String::from("klomp"),
|
||||||
|
},
|
||||||
|
v
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_tuple() {
|
||||||
|
let mut mock = Builder::new().read_number(89).read_slice(b"klomp").build();
|
||||||
|
let v: TupleTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(TupleTest(89, String::from("klomp")), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_struct_version() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.version((1, 20))
|
||||||
|
.read_number(89)
|
||||||
|
.read_slice(b"klomp")
|
||||||
|
.build();
|
||||||
|
let v: StructVersionTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(default_test(), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_struct_without_version() {
|
||||||
|
let mut mock = Builder::new().version((1, 19)).read_number(89).build();
|
||||||
|
let v: StructVersionTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
StructVersionTest {
|
||||||
|
test: 89,
|
||||||
|
hello: String::new(),
|
||||||
|
},
|
||||||
|
v
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_tuple_version() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.version((1, 26))
|
||||||
|
.read_number(89)
|
||||||
|
.read_slice(b"klomp")
|
||||||
|
.build();
|
||||||
|
let v: TupleVersionTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(TupleVersionTest(89, "klomp".into()), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_tuple_without_version() {
|
||||||
|
let mut mock = Builder::new().version((1, 19)).read_number(89).build();
|
||||||
|
let v: TupleVersionTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(TupleVersionTest(89, String::new()), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_complex_1() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.version((1, 19))
|
||||||
|
.read_number(999)
|
||||||
|
.read_number(666)
|
||||||
|
.build();
|
||||||
|
let v: TupleVersionDefaultTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
TupleVersionDefaultTest(
|
||||||
|
999,
|
||||||
|
StructVersionTest {
|
||||||
|
test: 666,
|
||||||
|
hello: String::new()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
v
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_complex_2() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.version((1, 20))
|
||||||
|
.read_number(999)
|
||||||
|
.read_number(666)
|
||||||
|
.read_slice(b"The quick brown \xF0\x9F\xA6\x8A jumps over 13 lazy \xF0\x9F\x90\xB6.")
|
||||||
|
.build();
|
||||||
|
let v: TupleVersionDefaultTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
TupleVersionDefaultTest(
|
||||||
|
999,
|
||||||
|
StructVersionTest {
|
||||||
|
test: 666,
|
||||||
|
hello: String::from("The quick brown 🦊 jumps over 13 lazy 🐶.")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
v
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_complex_3() {
|
||||||
|
let mut mock = Builder::new().version((1, 25)).read_number(999).build();
|
||||||
|
let v: TupleVersionDefaultTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
TupleVersionDefaultTest(
|
||||||
|
999,
|
||||||
|
StructVersionTest {
|
||||||
|
test: 89,
|
||||||
|
hello: String::from("klomp")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
v
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_complex_4() {
|
||||||
|
let mut mock = Builder::new().version((1, 26)).read_number(999).build();
|
||||||
|
let v: TupleVersionDefaultTest = mock.read_value().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
TupleVersionDefaultTest(
|
||||||
|
999,
|
||||||
|
StructVersionTest {
|
||||||
|
test: 89,
|
||||||
|
hello: String::from("klomp")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
v
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_field_invalid_data() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.read_number(666)
|
||||||
|
.read_slice(b"The quick brown \xED\xA0\x80 jumped.")
|
||||||
|
.build();
|
||||||
|
let err = mock.read_value::<StructTest>().await.unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
Error::InvalidData("invalid utf-8 sequence of 1 bytes from index 16".into()),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_field_missing_data() {
|
||||||
|
let mut mock = Builder::new().read_number(666).build();
|
||||||
|
let err = mock.read_value::<StructTest>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::MissingData("unexpected end-of-file".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_field_no_data() {
|
||||||
|
let mut mock = Builder::new().build();
|
||||||
|
let err = mock.read_value::<StructTest>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::MissingData("unexpected end-of-file".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_field_reader_error_first() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.read_number_error(Error::InvalidData("Bad reader".into()))
|
||||||
|
.build();
|
||||||
|
let err = mock.read_value::<StructTest>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::InvalidData("Bad reader".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_field_reader_error_later() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.read_number(999)
|
||||||
|
.read_bytes_error(Error::InvalidData("Bad reader".into()))
|
||||||
|
.build();
|
||||||
|
let err = mock.read_value::<StructTest>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::InvalidData("Bad reader".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
#[nix(from_str)]
|
||||||
|
struct TestFromStr;
|
||||||
|
|
||||||
|
impl FromStr for TestFromStr {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s == "test" {
|
||||||
|
Ok(TestFromStr)
|
||||||
|
} else {
|
||||||
|
Err(s.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_from_str() {
|
||||||
|
let mut mock = Builder::new().read_slice(b"test").build();
|
||||||
|
let value = mock.read_value::<TestFromStr>().await.unwrap();
|
||||||
|
assert_eq!(TestFromStr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_from_str_invalid_data() {
|
||||||
|
let mut mock = Builder::new().read_slice(b"wrong string").build();
|
||||||
|
let err = mock.read_value::<TestFromStr>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::InvalidData("wrong string".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_from_str_invalid_string() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.read_slice(b"The quick brown \xED\xA0\x80 jumped.")
|
||||||
|
.build();
|
||||||
|
let err = mock.read_value::<TestFromStr>().await.unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
Error::InvalidData("invalid utf-8 sequence of 1 bytes from index 16".into()),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_from_str_reader_error() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.read_bytes_error(Error::InvalidData("Bad reader".into()))
|
||||||
|
.build();
|
||||||
|
let err = mock.read_value::<TestFromStr>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::InvalidData("Bad reader".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
#[nix(try_from = "u64")]
|
||||||
|
struct TestTryFromU64;
|
||||||
|
|
||||||
|
impl TryFrom<u64> for TestTryFromU64 {
|
||||||
|
type Error = u64;
|
||||||
|
|
||||||
|
fn try_from(value: u64) -> Result<TestTryFromU64, Self::Error> {
|
||||||
|
if value == 42 {
|
||||||
|
Ok(TestTryFromU64)
|
||||||
|
} else {
|
||||||
|
Err(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_try_from_u64() {
|
||||||
|
let mut mock = Builder::new().read_number(42).build();
|
||||||
|
let value = mock.read_value::<TestTryFromU64>().await.unwrap();
|
||||||
|
assert_eq!(TestTryFromU64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_try_from_u64_invalid_data() {
|
||||||
|
let mut mock = Builder::new().read_number(666).build();
|
||||||
|
let err = mock.read_value::<TestTryFromU64>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::InvalidData("666".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_try_from_u64_reader_error() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.read_number_error(Error::InvalidData("Bad reader".into()))
|
||||||
|
.build();
|
||||||
|
let err = mock.read_value::<TestTryFromU64>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::InvalidData("Bad reader".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
#[nix(from = "u64")]
|
||||||
|
struct TestFromU64;
|
||||||
|
|
||||||
|
impl From<u64> for TestFromU64 {
|
||||||
|
fn from(_value: u64) -> TestFromU64 {
|
||||||
|
TestFromU64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_from_u64() {
|
||||||
|
let mut mock = Builder::new().read_number(42).build();
|
||||||
|
let value = mock.read_value::<TestFromU64>().await.unwrap();
|
||||||
|
assert_eq!(TestFromU64, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_from_u64_reader_error() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.read_number_error(Error::InvalidData("Bad reader".into()))
|
||||||
|
.build();
|
||||||
|
let err = mock.read_value::<TestFromU64>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::InvalidData("Bad reader".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, NixDeserialize)]
|
||||||
|
enum TestEnum {
|
||||||
|
#[nix(version = "..=19")]
|
||||||
|
Pre20(TestTryFromU64),
|
||||||
|
#[nix(version = "20..")]
|
||||||
|
Post20(StructVersionTest),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_enum_19() {
|
||||||
|
let mut mock = Builder::new().version((1, 19)).read_number(42).build();
|
||||||
|
let value = mock.read_value::<TestEnum>().await.unwrap();
|
||||||
|
assert_eq!(TestEnum::Pre20(TestTryFromU64), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_enum_20() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.version((1, 20))
|
||||||
|
.read_number(42)
|
||||||
|
.read_slice(b"klomp")
|
||||||
|
.build();
|
||||||
|
let value = mock.read_value::<TestEnum>().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
TestEnum::Post20(StructVersionTest {
|
||||||
|
test: 42,
|
||||||
|
hello: "klomp".into(),
|
||||||
|
}),
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_enum_reader_error() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.version((1, 19))
|
||||||
|
.read_number_error(Error::InvalidData("Bad reader".into()))
|
||||||
|
.build();
|
||||||
|
let err = mock.read_value::<TestEnum>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::InvalidData("Bad reader".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_enum_invalid_data_19() {
|
||||||
|
let mut mock = Builder::new().version((1, 19)).read_number(666).build();
|
||||||
|
let err = mock.read_value::<TestEnum>().await.unwrap_err();
|
||||||
|
assert_eq!(Error::InvalidData("666".into()), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn read_enum_invalid_data_20() {
|
||||||
|
let mut mock = Builder::new()
|
||||||
|
.version((1, 20))
|
||||||
|
.read_number(666)
|
||||||
|
.read_slice(b"The quick brown \xED\xA0\x80 jumped.")
|
||||||
|
.build();
|
||||||
|
let err = mock.read_value::<TestEnum>().await.unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
Error::InvalidData("invalid utf-8 sequence of 1 bytes from index 16".into()),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
6
tvix/nix-compat-derive-tests/tests/ui.rs
Normal file
6
tvix/nix-compat-derive-tests/tests/ui.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#[cfg(feature = "compile-tests")]
|
||||||
|
#[test]
|
||||||
|
fn ui() {
|
||||||
|
let t = trybuild::TestCases::new();
|
||||||
|
t.compile_fail("tests/ui/*.rs");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
pub struct BadType;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Test {
|
||||||
|
version: BadType,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
error[E0277]: the trait bound `BadType: NixDeserialize` is not satisfied
|
||||||
|
--> tests/ui/deserialize_bad_type.rs:7:14
|
||||||
|
|
|
||||||
|
7 | version: BadType,
|
||||||
|
| ^^^^^^^ the trait `NixDeserialize` is not implemented for `BadType`
|
||||||
|
|
|
||||||
|
= help: the following other types implement trait `NixDeserialize`:
|
||||||
|
BTreeMap<K, V>
|
||||||
|
String
|
||||||
|
Test
|
||||||
|
Vec<T>
|
||||||
|
bool
|
||||||
|
bytes::bytes::Bytes
|
||||||
|
i64
|
||||||
|
u64
|
||||||
|
usize
|
||||||
|
note: required by a bound in `try_read_value`
|
||||||
|
--> $WORKSPACE/nix-compat/src/nix_daemon/de/mod.rs
|
||||||
|
|
|
||||||
|
| fn try_read_value<V: NixDeserialize>(
|
||||||
|
| ^^^^^^^^^^^^^^ required by this bound in `NixRead::try_read_value`
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub enum Test {
|
||||||
|
#[nix(version = "..=10")]
|
||||||
|
Old,
|
||||||
|
#[nix(version = "15..=17")]
|
||||||
|
Legacy,
|
||||||
|
#[nix(version = "50..")]
|
||||||
|
NewWay,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
error[E0004]: non-exhaustive patterns: `11_u8..=14_u8` and `18_u8..=49_u8` not covered
|
||||||
|
--> tests/ui/deserialize_enum_non_exaustive.rs:3:10
|
||||||
|
|
|
||||||
|
3 | #[derive(NixDeserialize)]
|
||||||
|
| ^^^^^^^^^^^^^^ patterns `11_u8..=14_u8` and `18_u8..=49_u8` not covered
|
||||||
|
|
|
||||||
|
= note: the matched value is of type `u8`
|
||||||
|
= note: this error originates in the derive macro `NixDeserialize` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
#[nix(from = "u64")]
|
||||||
|
pub struct Test;
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
error[E0277]: the trait bound `Test: From<u64>` is not satisfied
|
||||||
|
--> tests/ui/deserialize_from_missing.rs:4:14
|
||||||
|
|
|
||||||
|
4 | #[nix(from = "u64")]
|
||||||
|
| ^^^^^ the trait `From<u64>` is not implemented for `Test`
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
#[nix(from_str)]
|
||||||
|
pub struct Test;
|
||||||
|
|
||||||
|
impl FromStr for Test {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s == "test" {
|
||||||
|
Ok(Test)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||||
|
--> tests/ui/deserialize_from_str_error_not_display.rs:6:7
|
||||||
|
|
|
||||||
|
6 | #[nix(from_str)]
|
||||||
|
| ^^^^^^^^ `()` cannot be formatted with the default formatter
|
||||||
|
|
|
||||||
|
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||||
|
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
|
||||||
|
note: required by a bound in `invalid_data`
|
||||||
|
--> $WORKSPACE/nix-compat/src/nix_daemon/de/mod.rs
|
||||||
|
|
|
||||||
|
| fn invalid_data<T: fmt::Display>(msg: T) -> Self {
|
||||||
|
| ^^^^^^^^^^^^ required by this bound in `Error::invalid_data`
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
#[nix(from_str)]
|
||||||
|
pub struct Test;
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
error[E0277]: the trait bound `Test: FromStr` is not satisfied
|
||||||
|
--> tests/ui/deserialize_from_str_missing.rs:4:7
|
||||||
|
|
|
||||||
|
4 | #[nix(from_str)]
|
||||||
|
| ^^^^^^^^ the trait `FromStr` is not implemented for `Test`
|
||||||
|
|
|
||||||
|
= help: the following other types implement trait `FromStr`:
|
||||||
|
IpAddr
|
||||||
|
Ipv4Addr
|
||||||
|
Ipv6Addr
|
||||||
|
NonZero<i128>
|
||||||
|
NonZero<i16>
|
||||||
|
NonZero<i32>
|
||||||
|
NonZero<i64>
|
||||||
|
NonZero<i8>
|
||||||
|
and $N others
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Value(String);
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Test {
|
||||||
|
#[nix(version = "20..")]
|
||||||
|
version: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
error[E0277]: the trait bound `Value: Default` is not satisfied
|
||||||
|
--> tests/ui/deserialize_missing_default.rs:6:10
|
||||||
|
|
|
||||||
|
6 | #[derive(NixDeserialize)]
|
||||||
|
| ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Value`
|
||||||
|
|
|
||||||
|
= note: this error originates in the derive macro `NixDeserialize` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
help: consider annotating `Value` with `#[derive(Default)]`
|
||||||
|
|
|
||||||
|
4 + #[derive(Default)]
|
||||||
|
5 | pub struct Value(String);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Value(String);
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Test {
|
||||||
|
#[nix(version = "20..", default = "Value::make_default")]
|
||||||
|
version: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
error[E0599]: no function or associated item named `make_default` found for struct `Value` in the current scope
|
||||||
|
--> tests/ui/deserialize_missing_default_path.rs:8:39
|
||||||
|
|
|
||||||
|
4 | pub struct Value(String);
|
||||||
|
| ---------------- function or associated item `make_default` not found for this struct
|
||||||
|
...
|
||||||
|
8 | #[nix(version = "20..", default = "Value::make_default")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^ function or associated item not found in `Value`
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
use nix_compat_derive::nix_deserialize_remote;
|
||||||
|
|
||||||
|
pub struct Value(String);
|
||||||
|
impl From<String> for Value {
|
||||||
|
fn from(s: String) -> Value {
|
||||||
|
Value(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nix_deserialize_remote!(
|
||||||
|
#[nix()]
|
||||||
|
Value
|
||||||
|
);
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: Missing from_str, from or try_from attribute
|
||||||
|
--> tests/ui/deserialize_remote_missing_attr.rs:10:25
|
||||||
|
|
|
||||||
|
10 | nix_deserialize_remote!(#[nix()] Value);
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
#[nix(try_from = "u64")]
|
||||||
|
pub struct Test;
|
||||||
|
|
||||||
|
impl TryFrom<u64> for Test {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: u64) -> Result<Test, Self::Error> {
|
||||||
|
if value == 42 {
|
||||||
|
Ok(Test)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
error[E0277]: `()` doesn't implement `std::fmt::Display`
|
||||||
|
--> tests/ui/deserialize_try_from_error_not_display.rs:4:18
|
||||||
|
|
|
||||||
|
4 | #[nix(try_from = "u64")]
|
||||||
|
| ^^^^^ `()` cannot be formatted with the default formatter
|
||||||
|
|
|
||||||
|
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||||
|
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
|
||||||
|
note: required by a bound in `invalid_data`
|
||||||
|
--> $WORKSPACE/nix-compat/src/nix_daemon/de/mod.rs
|
||||||
|
|
|
||||||
|
| fn invalid_data<T: fmt::Display>(msg: T) -> Self {
|
||||||
|
| ^^^^^^^^^^^^ required by this bound in `Error::invalid_data`
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
#[nix(try_from = "u64")]
|
||||||
|
pub struct Test;
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
error[E0277]: the trait bound `Test: From<u64>` is not satisfied
|
||||||
|
--> tests/ui/deserialize_try_from_missing.rs:4:18
|
||||||
|
|
|
||||||
|
4 | #[nix(try_from = "u64")]
|
||||||
|
| ^^^^^ the trait `From<u64>` is not implemented for `Test`, which is required by `Test: TryFrom<u64>`
|
||||||
|
|
|
||||||
|
= note: required for `u64` to implement `Into<Test>`
|
||||||
|
= note: required for `Test` to implement `TryFrom<u64>`
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Test {
|
||||||
|
#[nix(default = 12)]
|
||||||
|
version: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: expected nix attribute default to be string
|
||||||
|
--> tests/ui/parse_bad_default.rs:5:21
|
||||||
|
|
|
||||||
|
5 | #[nix(default = 12)]
|
||||||
|
| ^^
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Test {
|
||||||
|
#[nix(default = "12")]
|
||||||
|
version: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: expected identifier
|
||||||
|
--> tests/ui/parse_bad_default_path.rs:5:21
|
||||||
|
|
|
||||||
|
5 | #[nix(default = "12")]
|
||||||
|
| ^^^^
|
||||||
9
tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.rs
Normal file
9
tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Test {
|
||||||
|
#[nix]
|
||||||
|
version: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: expected attribute arguments in parentheses: #[nix(...)]
|
||||||
|
--> tests/ui/parse_bad_nix.rs:5:7
|
||||||
|
|
|
||||||
|
5 | #[nix]
|
||||||
|
| ^^^
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Test {
|
||||||
|
#[nix(version = 12)]
|
||||||
|
version: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: expected nix attribute version to be string
|
||||||
|
--> tests/ui/parse_bad_version.rs:5:21
|
||||||
|
|
|
||||||
|
5 | #[nix(version = 12)]
|
||||||
|
| ^^
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
use nix_compat_derive::NixDeserialize;
|
||||||
|
|
||||||
|
#[derive(NixDeserialize)]
|
||||||
|
pub struct Test {
|
||||||
|
#[nix(version)]
|
||||||
|
version: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
error: expected `=`
|
||||||
|
--> tests/ui/parse_mising_version.rs:5:18
|
||||||
|
|
|
||||||
|
5 | #[nix(version)]
|
||||||
|
| ^
|
||||||
32
tvix/nix-compat-derive/Cargo.toml
Normal file
32
tvix/nix-compat-derive/Cargo.toml
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
[package]
|
||||||
|
name = "nix-compat-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
external = []
|
||||||
|
default = ["external"]
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = { version = "1.0.86", features = ["proc-macro"] }
|
||||||
|
quote = { version = "1.0.36", features = ["proc-macro"] }
|
||||||
|
syn = { version = "2.0.72", features = ["full", "extra-traits"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
hex-literal = "0.4.1"
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
|
rstest = "0.19.0"
|
||||||
|
tokio-test = "0.4.3"
|
||||||
|
|
||||||
|
[dev-dependencies.tokio]
|
||||||
|
version = "^1.38"
|
||||||
|
features = ["io-util", "macros"]
|
||||||
|
|
||||||
|
[dev-dependencies.nix-compat]
|
||||||
|
path = "../nix-compat"
|
||||||
|
default-features = false
|
||||||
|
features = ["async", "wire", "test"]
|
||||||
5
tvix/nix-compat-derive/default.nix
Normal file
5
tvix/nix-compat-derive/default.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{ depot, lib, ... }:
|
||||||
|
|
||||||
|
depot.tvix.crates.workspaceMembers.nix-compat-derive.build.override {
|
||||||
|
runTests = true;
|
||||||
|
}
|
||||||
272
tvix/nix-compat-derive/src/de.rs
Normal file
272
tvix/nix-compat-derive/src/de.rs
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{DeriveInput, Generics, Path, Type};
|
||||||
|
|
||||||
|
use crate::internal::attrs::Default;
|
||||||
|
use crate::internal::inputs::RemoteInput;
|
||||||
|
use crate::internal::{attrs, Container, Context, Data, Field, Remote, Style, Variant};
|
||||||
|
|
||||||
|
pub fn expand_nix_deserialize(nnixrs: Path, input: &mut DeriveInput) -> syn::Result<TokenStream> {
|
||||||
|
let cx = Context::new();
|
||||||
|
let cont = Container::from_ast(&cx, nnixrs, input);
|
||||||
|
cx.check()?;
|
||||||
|
let cont = cont.unwrap();
|
||||||
|
|
||||||
|
let ty = cont.ident_type();
|
||||||
|
let body = nix_deserialize_body(&cont);
|
||||||
|
let crate_path = cont.crate_path();
|
||||||
|
|
||||||
|
Ok(nix_deserialize_impl(
|
||||||
|
crate_path,
|
||||||
|
&ty,
|
||||||
|
&cont.original.generics,
|
||||||
|
body,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_nix_deserialize_remote(
|
||||||
|
crate_path: Path,
|
||||||
|
input: &RemoteInput,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
|
let cx = Context::new();
|
||||||
|
let remote = Remote::from_ast(&cx, crate_path, input);
|
||||||
|
cx.check()?;
|
||||||
|
let remote = remote.unwrap();
|
||||||
|
|
||||||
|
let crate_path = remote.crate_path();
|
||||||
|
let body = nix_deserialize_body_from(crate_path, &remote.attrs).expect("From tokenstream");
|
||||||
|
let generics = Generics::default();
|
||||||
|
Ok(nix_deserialize_impl(crate_path, remote.ty, &generics, body))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_deserialize_impl(
|
||||||
|
crate_path: &Path,
|
||||||
|
ty: &Type,
|
||||||
|
generics: &Generics,
|
||||||
|
body: TokenStream,
|
||||||
|
) -> TokenStream {
|
||||||
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[automatically_derived]
|
||||||
|
impl #impl_generics #crate_path::nix_daemon::de::NixDeserialize for #ty #ty_generics
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
#[allow(clippy::manual_async_fn)]
|
||||||
|
fn try_deserialize<R>(reader: &mut R) -> impl ::std::future::Future<Output=Result<Option<Self>, R::Error>> + Send + '_
|
||||||
|
where R: ?Sized + #crate_path::nix_daemon::de::NixRead + Send,
|
||||||
|
{
|
||||||
|
#body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_deserialize_body_from(
|
||||||
|
crate_path: &syn::Path,
|
||||||
|
attrs: &attrs::Container,
|
||||||
|
) -> Option<TokenStream> {
|
||||||
|
if let Some(span) = attrs.from_str.as_ref() {
|
||||||
|
Some(nix_deserialize_from_str(crate_path, span.span()))
|
||||||
|
} else if let Some(type_from) = attrs.type_from.as_ref() {
|
||||||
|
Some(nix_deserialize_from(type_from))
|
||||||
|
} else {
|
||||||
|
attrs
|
||||||
|
.type_try_from
|
||||||
|
.as_ref()
|
||||||
|
.map(|type_try_from| nix_deserialize_try_from(crate_path, type_try_from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_deserialize_body(cont: &Container) -> TokenStream {
|
||||||
|
if let Some(tokens) = nix_deserialize_body_from(cont.crate_path(), &cont.attrs) {
|
||||||
|
tokens
|
||||||
|
} else {
|
||||||
|
match &cont.data {
|
||||||
|
Data::Struct(style, fields) => nix_deserialize_struct(*style, fields),
|
||||||
|
Data::Enum(variants) => nix_deserialize_enum(variants),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_deserialize_struct(style: Style, fields: &[Field<'_>]) -> TokenStream {
|
||||||
|
let read_fields = fields.iter().map(|f| {
|
||||||
|
let field = f.var_ident();
|
||||||
|
let ty = f.ty;
|
||||||
|
let read_value = quote_spanned! {
|
||||||
|
ty.span()=> if first__ {
|
||||||
|
first__ = false;
|
||||||
|
if let Some(v) = reader.try_read_value::<#ty>().await? {
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reader.read_value::<#ty>().await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(version) = f.attrs.version.as_ref() {
|
||||||
|
let default = match &f.attrs.default {
|
||||||
|
Default::Default => quote_spanned!(ty.span()=>::std::default::Default::default),
|
||||||
|
Default::Path(path) => path.to_token_stream(),
|
||||||
|
_ => panic!("No default for versioned field"),
|
||||||
|
};
|
||||||
|
quote! {
|
||||||
|
let #field : #ty = if (#version).contains(&reader.version().minor()) {
|
||||||
|
#read_value
|
||||||
|
} else {
|
||||||
|
#default()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
let #field : #ty = #read_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let field_names = fields.iter().map(|f| f.var_ident());
|
||||||
|
let construct = match style {
|
||||||
|
Style::Struct => {
|
||||||
|
quote! {
|
||||||
|
Self { #(#field_names),* }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Style::Tuple => {
|
||||||
|
quote! {
|
||||||
|
Self(#(#field_names),*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Style::Unit => quote!(Self),
|
||||||
|
};
|
||||||
|
quote! {
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
async move {
|
||||||
|
let mut first__ = true;
|
||||||
|
#(#read_fields)*
|
||||||
|
Ok(Some(#construct))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_deserialize_variant(variant: &Variant<'_>) -> TokenStream {
|
||||||
|
let ident = variant.ident;
|
||||||
|
let read_fields = variant.fields.iter().map(|f| {
|
||||||
|
let field = f.var_ident();
|
||||||
|
let ty = f.ty;
|
||||||
|
let read_value = quote_spanned! {
|
||||||
|
ty.span()=> if first__ {
|
||||||
|
first__ = false;
|
||||||
|
if let Some(v) = reader.try_read_value::<#ty>().await? {
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reader.read_value::<#ty>().await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(version) = f.attrs.version.as_ref() {
|
||||||
|
let default = match &f.attrs.default {
|
||||||
|
Default::Default => quote_spanned!(ty.span()=>::std::default::Default::default),
|
||||||
|
Default::Path(path) => path.to_token_stream(),
|
||||||
|
_ => panic!("No default for versioned field"),
|
||||||
|
};
|
||||||
|
quote! {
|
||||||
|
let #field : #ty = if (#version).contains(&reader.version().minor()) {
|
||||||
|
#read_value
|
||||||
|
} else {
|
||||||
|
#default()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
let #field : #ty = #read_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let field_names = variant.fields.iter().map(|f| f.var_ident());
|
||||||
|
let construct = match variant.style {
|
||||||
|
Style::Struct => {
|
||||||
|
quote! {
|
||||||
|
Self::#ident { #(#field_names),* }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Style::Tuple => {
|
||||||
|
quote! {
|
||||||
|
Self::#ident(#(#field_names),*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Style::Unit => quote!(Self::#ident),
|
||||||
|
};
|
||||||
|
let version = &variant.attrs.version;
|
||||||
|
quote! {
|
||||||
|
#version => {
|
||||||
|
#(#read_fields)*
|
||||||
|
Ok(Some(#construct))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_deserialize_enum(variants: &[Variant<'_>]) -> TokenStream {
|
||||||
|
let match_variant = variants
|
||||||
|
.iter()
|
||||||
|
.map(|variant| nix_deserialize_variant(variant));
|
||||||
|
quote! {
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
async move {
|
||||||
|
let mut first__ = true;
|
||||||
|
match reader.version().minor() {
|
||||||
|
#(#match_variant)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_deserialize_from(ty: &Type) -> TokenStream {
|
||||||
|
quote_spanned! {
|
||||||
|
ty.span() =>
|
||||||
|
async move {
|
||||||
|
if let Some(value) = reader.try_read_value::<#ty>().await? {
|
||||||
|
Ok(Some(<Self as ::std::convert::From<#ty>>::from(value)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_deserialize_try_from(crate_path: &Path, ty: &Type) -> TokenStream {
|
||||||
|
quote_spanned! {
|
||||||
|
ty.span() =>
|
||||||
|
async move {
|
||||||
|
use #crate_path::nix_daemon::de::Error;
|
||||||
|
if let Some(item) = reader.try_read_value::<#ty>().await? {
|
||||||
|
<Self as ::std::convert::TryFrom<#ty>>::try_from(item)
|
||||||
|
.map_err(Error::invalid_data)
|
||||||
|
.map(Some)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_deserialize_from_str(crate_path: &Path, span: Span) -> TokenStream {
|
||||||
|
quote_spanned! {
|
||||||
|
span =>
|
||||||
|
async move {
|
||||||
|
use #crate_path::nix_daemon::de::Error;
|
||||||
|
if let Some(buf) = reader.try_read_bytes().await? {
|
||||||
|
let s = ::std::str::from_utf8(&buf)
|
||||||
|
.map_err(Error::invalid_data)?;
|
||||||
|
<Self as ::std::str::FromStr>::from_str(s)
|
||||||
|
.map_err(Error::invalid_data)
|
||||||
|
.map(Some)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
358
tvix/nix-compat-derive/src/internal/attrs.rs
Normal file
358
tvix/nix-compat-derive/src/internal/attrs.rs
Normal file
|
|
@ -0,0 +1,358 @@
|
||||||
|
use quote::ToTokens;
|
||||||
|
use syn::meta::ParseNestedMeta;
|
||||||
|
use syn::parse::Parse;
|
||||||
|
use syn::{parse_quote, Attribute, Expr, ExprLit, ExprPath, Lit, Token};
|
||||||
|
|
||||||
|
use super::symbol::{Symbol, CRATE, DEFAULT, FROM, FROM_STR, NIX, TRY_FROM, VERSION};
|
||||||
|
use super::Context;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum Default {
|
||||||
|
None,
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
Default,
|
||||||
|
Path(ExprPath),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default {
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
matches!(self, Default::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Field {
|
||||||
|
pub default: Default,
|
||||||
|
pub version: Option<syn::ExprRange>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Field {
|
||||||
|
pub fn from_ast(ctx: &Context, attrs: &Vec<Attribute>) -> Field {
|
||||||
|
let mut version = None;
|
||||||
|
let mut default = Default::None;
|
||||||
|
for attr in attrs {
|
||||||
|
if attr.path() != NIX {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Err(err) = attr.parse_nested_meta(|meta| {
|
||||||
|
if meta.path == VERSION {
|
||||||
|
version = parse_lit(ctx, &meta, VERSION)?;
|
||||||
|
} else if meta.path == DEFAULT {
|
||||||
|
if meta.input.peek(Token![=]) {
|
||||||
|
if let Some(path) = parse_lit(ctx, &meta, DEFAULT)? {
|
||||||
|
default = Default::Path(path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
default = Default::Default;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let path = meta.path.to_token_stream().to_string();
|
||||||
|
return Err(meta.error(format_args!("unknown nix field attribute '{}'", path)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}) {
|
||||||
|
eprintln!("{:?}", err.span().source_text());
|
||||||
|
ctx.syn_error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if version.is_some() && default.is_none() {
|
||||||
|
default = Default::Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
Field { default, version }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Variant {
|
||||||
|
pub version: syn::ExprRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Variant {
|
||||||
|
pub fn from_ast(ctx: &Context, attrs: &Vec<Attribute>) -> Variant {
|
||||||
|
let mut version = parse_quote!(..);
|
||||||
|
for attr in attrs {
|
||||||
|
if attr.path() != NIX {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Err(err) = attr.parse_nested_meta(|meta| {
|
||||||
|
if meta.path == VERSION {
|
||||||
|
if let Some(v) = parse_lit(ctx, &meta, VERSION)? {
|
||||||
|
version = v;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let path = meta.path.to_token_stream().to_string();
|
||||||
|
return Err(
|
||||||
|
meta.error(format_args!("unknown nix variant attribute '{}'", path))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}) {
|
||||||
|
eprintln!("{:?}", err.span().source_text());
|
||||||
|
ctx.syn_error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant { version }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Container {
|
||||||
|
pub from_str: Option<syn::Path>,
|
||||||
|
pub type_from: Option<syn::Type>,
|
||||||
|
pub type_try_from: Option<syn::Type>,
|
||||||
|
pub crate_path: Option<syn::Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Container {
|
||||||
|
pub fn from_ast(ctx: &Context, attrs: &Vec<Attribute>) -> Container {
|
||||||
|
let mut type_from = None;
|
||||||
|
let mut type_try_from = None;
|
||||||
|
let mut crate_path = None;
|
||||||
|
let mut from_str = None;
|
||||||
|
|
||||||
|
for attr in attrs {
|
||||||
|
if attr.path() != NIX {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Err(err) = attr.parse_nested_meta(|meta| {
|
||||||
|
if meta.path == FROM {
|
||||||
|
type_from = parse_lit(ctx, &meta, FROM)?;
|
||||||
|
} else if meta.path == TRY_FROM {
|
||||||
|
type_try_from = parse_lit(ctx, &meta, TRY_FROM)?;
|
||||||
|
} else if meta.path == FROM_STR {
|
||||||
|
from_str = Some(meta.path);
|
||||||
|
} else if meta.path == CRATE {
|
||||||
|
crate_path = parse_lit(ctx, &meta, CRATE)?;
|
||||||
|
} else {
|
||||||
|
let path = meta.path.to_token_stream().to_string();
|
||||||
|
return Err(
|
||||||
|
meta.error(format_args!("unknown nix variant attribute '{}'", path))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}) {
|
||||||
|
eprintln!("{:?}", err.span().source_text());
|
||||||
|
ctx.syn_error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Container {
|
||||||
|
from_str,
|
||||||
|
type_from,
|
||||||
|
type_try_from,
|
||||||
|
crate_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_lit_str(
|
||||||
|
ctx: &Context,
|
||||||
|
meta: &ParseNestedMeta,
|
||||||
|
attr: Symbol,
|
||||||
|
) -> syn::Result<Option<syn::LitStr>> {
|
||||||
|
let expr: Expr = meta.value()?.parse()?;
|
||||||
|
let mut value = &expr;
|
||||||
|
while let Expr::Group(e) = value {
|
||||||
|
value = &e.expr;
|
||||||
|
}
|
||||||
|
if let Expr::Lit(ExprLit {
|
||||||
|
lit: Lit::Str(s), ..
|
||||||
|
}) = value
|
||||||
|
{
|
||||||
|
Ok(Some(s.clone()))
|
||||||
|
} else {
|
||||||
|
ctx.error_spanned(
|
||||||
|
expr,
|
||||||
|
format_args!("expected nix attribute {} to be string", attr),
|
||||||
|
);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_lit<T: Parse>(
|
||||||
|
ctx: &Context,
|
||||||
|
meta: &ParseNestedMeta,
|
||||||
|
attr: Symbol,
|
||||||
|
) -> syn::Result<Option<T>> {
|
||||||
|
match get_lit_str(ctx, meta, attr)? {
|
||||||
|
Some(lit) => Ok(Some(lit.parse()?)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use syn::{parse_quote, Attribute};
|
||||||
|
|
||||||
|
use crate::internal::Context;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_field_version() {
|
||||||
|
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(version="..34")])];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let field = Field::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
field,
|
||||||
|
Field {
|
||||||
|
default: Default::Default,
|
||||||
|
version: Some(parse_quote!(..34)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_field_default() {
|
||||||
|
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(default)])];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let field = Field::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
field,
|
||||||
|
Field {
|
||||||
|
default: Default::Default,
|
||||||
|
version: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_field_default_path() {
|
||||||
|
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(default="Default::default")])];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let field = Field::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
field,
|
||||||
|
Field {
|
||||||
|
default: Default::Path(parse_quote!(Default::default)),
|
||||||
|
version: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_field_both() {
|
||||||
|
let attrs: Vec<Attribute> =
|
||||||
|
vec![parse_quote!(#[nix(version="..", default="Default::default")])];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let field = Field::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
field,
|
||||||
|
Field {
|
||||||
|
default: Default::Path(parse_quote!(Default::default)),
|
||||||
|
version: Some(parse_quote!(..)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_field_both_rev() {
|
||||||
|
let attrs: Vec<Attribute> =
|
||||||
|
vec![parse_quote!(#[nix(default="Default::default", version="..")])];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let field = Field::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
field,
|
||||||
|
Field {
|
||||||
|
default: Default::Path(parse_quote!(Default::default)),
|
||||||
|
version: Some(parse_quote!(..)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_field_no_attr() {
|
||||||
|
let attrs: Vec<Attribute> = vec![];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let field = Field::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
field,
|
||||||
|
Field {
|
||||||
|
default: Default::None,
|
||||||
|
version: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_field_no_subattrs() {
|
||||||
|
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix()])];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let field = Field::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
field,
|
||||||
|
Field {
|
||||||
|
default: Default::None,
|
||||||
|
version: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_variant_version() {
|
||||||
|
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(version="..34")])];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let variant = Variant::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
variant,
|
||||||
|
Variant {
|
||||||
|
version: parse_quote!(..34),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_variant_no_attr() {
|
||||||
|
let attrs: Vec<Attribute> = vec![];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let variant = Variant::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
variant,
|
||||||
|
Variant {
|
||||||
|
version: parse_quote!(..),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_variant_no_subattrs() {
|
||||||
|
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix()])];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let variant = Variant::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
variant,
|
||||||
|
Variant {
|
||||||
|
version: parse_quote!(..),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_container_try_from() {
|
||||||
|
let attrs: Vec<Attribute> = vec![parse_quote!(#[nix(try_from="u64")])];
|
||||||
|
let ctx = Context::new();
|
||||||
|
let container = Container::from_ast(&ctx, &attrs);
|
||||||
|
ctx.check().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
container,
|
||||||
|
Container {
|
||||||
|
from_str: None,
|
||||||
|
type_from: None,
|
||||||
|
type_try_from: Some(parse_quote!(u64)),
|
||||||
|
crate_path: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
tvix/nix-compat-derive/src/internal/ctx.rs
Normal file
50
tvix/nix-compat-derive/src/internal/ctx.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fmt;
|
||||||
|
use std::thread::panicking;
|
||||||
|
|
||||||
|
use quote::ToTokens;
|
||||||
|
|
||||||
|
pub struct Context {
|
||||||
|
errors: RefCell<Option<Vec<syn::Error>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub fn new() -> Context {
|
||||||
|
Context {
|
||||||
|
errors: RefCell::new(Some(Vec::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn syn_error(&self, error: syn::Error) {
|
||||||
|
self.errors
|
||||||
|
.borrow_mut()
|
||||||
|
.as_mut()
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.push(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_spanned<T: ToTokens, D: fmt::Display>(&self, tokens: T, message: D) {
|
||||||
|
self.syn_error(syn::Error::new_spanned(tokens, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(&self) -> syn::Result<()> {
|
||||||
|
let mut iter = self.errors.borrow_mut().take().unwrap().into_iter();
|
||||||
|
let mut err = match iter.next() {
|
||||||
|
None => return Ok(()),
|
||||||
|
Some(err) => err,
|
||||||
|
};
|
||||||
|
for next_err in iter {
|
||||||
|
err.combine(next_err);
|
||||||
|
}
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Context {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.errors.borrow().is_some() && !panicking() {
|
||||||
|
panic!("Context dropped without checking errors");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
tvix/nix-compat-derive/src/internal/inputs.rs
Normal file
110
tvix/nix-compat-derive/src/internal/inputs.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct RemoteInput {
|
||||||
|
pub attrs: Vec<syn::Attribute>,
|
||||||
|
pub ident: syn::Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl syn::parse::Parse for RemoteInput {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let attrs = input.call(syn::Attribute::parse_outer)?;
|
||||||
|
|
||||||
|
let ident = input.parse::<syn::Type>()?;
|
||||||
|
Ok(RemoteInput { attrs, ident })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl quote::ToTokens for RemoteInput {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
fn is_outer(attr: &&syn::Attribute) -> bool {
|
||||||
|
match attr.style {
|
||||||
|
syn::AttrStyle::Outer => true,
|
||||||
|
syn::AttrStyle::Inner(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for attr in self.attrs.iter().filter(is_outer) {
|
||||||
|
attr.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
self.ident.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use syn::parse_quote;
|
||||||
|
//use syn::parse::Parse;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input() {
|
||||||
|
let p: RemoteInput = parse_quote!(u64);
|
||||||
|
assert_eq!(
|
||||||
|
p,
|
||||||
|
RemoteInput {
|
||||||
|
attrs: vec![],
|
||||||
|
ident: parse_quote!(u64),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_attr() {
|
||||||
|
let p: RemoteInput = parse_quote!(
|
||||||
|
#[nix]
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
p,
|
||||||
|
RemoteInput {
|
||||||
|
attrs: vec![parse_quote!(#[nix])],
|
||||||
|
ident: parse_quote!(u64),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_attr_multiple() {
|
||||||
|
let p: RemoteInput = parse_quote!(
|
||||||
|
#[nix]
|
||||||
|
#[hello]
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
p,
|
||||||
|
RemoteInput {
|
||||||
|
attrs: vec![parse_quote!(#[nix]), parse_quote!(#[hello])],
|
||||||
|
ident: parse_quote!(u64),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_attr_full() {
|
||||||
|
let p: RemoteInput = parse_quote!(
|
||||||
|
#[nix(try_from = "u64")]
|
||||||
|
usize
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
p,
|
||||||
|
RemoteInput {
|
||||||
|
attrs: vec![parse_quote!(#[nix(try_from="u64")])],
|
||||||
|
ident: parse_quote!(usize),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_attr_other() {
|
||||||
|
let p: RemoteInput = parse_quote!(
|
||||||
|
#[muh]
|
||||||
|
u64
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
p,
|
||||||
|
RemoteInput {
|
||||||
|
attrs: vec![parse_quote!(#[muh])],
|
||||||
|
ident: parse_quote!(u64),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
183
tvix/nix-compat-derive/src/internal/mod.rs
Normal file
183
tvix/nix-compat-derive/src/internal/mod.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::Token;
|
||||||
|
|
||||||
|
pub mod attrs;
|
||||||
|
mod ctx;
|
||||||
|
pub mod inputs;
|
||||||
|
mod symbol;
|
||||||
|
|
||||||
|
pub use ctx::Context;
|
||||||
|
|
||||||
|
pub struct Field<'a> {
|
||||||
|
pub member: syn::Member,
|
||||||
|
pub ty: &'a syn::Type,
|
||||||
|
pub attrs: attrs::Field,
|
||||||
|
pub original: &'a syn::Field,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Field<'a> {
|
||||||
|
pub fn from_ast(ctx: &Context, idx: usize, field: &'a syn::Field) -> Field<'a> {
|
||||||
|
let attrs = attrs::Field::from_ast(ctx, &field.attrs);
|
||||||
|
let member = match &field.ident {
|
||||||
|
Some(id) => syn::Member::Named(id.clone()),
|
||||||
|
None => syn::Member::Unnamed(idx.into()),
|
||||||
|
};
|
||||||
|
Field {
|
||||||
|
member,
|
||||||
|
attrs,
|
||||||
|
ty: &field.ty,
|
||||||
|
original: field,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn var_ident(&self) -> syn::Ident {
|
||||||
|
match &self.member {
|
||||||
|
syn::Member::Named(name) => name.clone(),
|
||||||
|
syn::Member::Unnamed(idx) => {
|
||||||
|
syn::Ident::new(&format!("field{}", idx.index), self.original.span())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Variant<'a> {
|
||||||
|
pub ident: &'a syn::Ident,
|
||||||
|
pub attrs: attrs::Variant,
|
||||||
|
pub style: Style,
|
||||||
|
pub fields: Vec<Field<'a>>,
|
||||||
|
//pub original: &'a syn::Variant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Variant<'a> {
|
||||||
|
pub fn from_ast(ctx: &Context, variant: &'a syn::Variant) -> Self {
|
||||||
|
let attrs = attrs::Variant::from_ast(ctx, &variant.attrs);
|
||||||
|
let (style, fields) = match &variant.fields {
|
||||||
|
syn::Fields::Named(fields) => (Style::Struct, fields_ast(ctx, &fields.named)),
|
||||||
|
syn::Fields::Unnamed(fields) => (Style::Tuple, fields_ast(ctx, &fields.unnamed)),
|
||||||
|
syn::Fields::Unit => (Style::Unit, Vec::new()),
|
||||||
|
};
|
||||||
|
Variant {
|
||||||
|
ident: &variant.ident,
|
||||||
|
attrs,
|
||||||
|
style,
|
||||||
|
fields,
|
||||||
|
//original: variant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||||
|
pub enum Style {
|
||||||
|
Struct,
|
||||||
|
Tuple,
|
||||||
|
Unit,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Data<'a> {
|
||||||
|
Enum(Vec<Variant<'a>>),
|
||||||
|
Struct(Style, Vec<Field<'a>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Container<'a> {
|
||||||
|
pub ident: &'a syn::Ident,
|
||||||
|
pub attrs: attrs::Container,
|
||||||
|
pub data: Data<'a>,
|
||||||
|
pub crate_path: syn::Path,
|
||||||
|
pub original: &'a syn::DeriveInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Container<'a> {
|
||||||
|
pub fn from_ast(
|
||||||
|
ctx: &Context,
|
||||||
|
crate_path: syn::Path,
|
||||||
|
input: &'a mut syn::DeriveInput,
|
||||||
|
) -> Option<Container<'a>> {
|
||||||
|
let attrs = attrs::Container::from_ast(ctx, &input.attrs);
|
||||||
|
let data = match &input.data {
|
||||||
|
syn::Data::Struct(s) => match &s.fields {
|
||||||
|
syn::Fields::Named(fields) => {
|
||||||
|
Data::Struct(Style::Struct, fields_ast(ctx, &fields.named))
|
||||||
|
}
|
||||||
|
syn::Fields::Unnamed(fields) => {
|
||||||
|
Data::Struct(Style::Tuple, fields_ast(ctx, &fields.unnamed))
|
||||||
|
}
|
||||||
|
syn::Fields::Unit => Data::Struct(Style::Unit, Vec::new()),
|
||||||
|
},
|
||||||
|
syn::Data::Enum(e) => {
|
||||||
|
let variants = e
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.map(|variant| Variant::from_ast(ctx, variant))
|
||||||
|
.collect();
|
||||||
|
Data::Enum(variants)
|
||||||
|
}
|
||||||
|
syn::Data::Union(u) => {
|
||||||
|
ctx.error_spanned(u.union_token, "Union not supported by nixrs");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(Container {
|
||||||
|
ident: &input.ident,
|
||||||
|
attrs,
|
||||||
|
data,
|
||||||
|
crate_path,
|
||||||
|
original: input,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crate_path(&self) -> &syn::Path {
|
||||||
|
if let Some(crate_path) = self.attrs.crate_path.as_ref() {
|
||||||
|
crate_path
|
||||||
|
} else {
|
||||||
|
&self.crate_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ident_type(&self) -> syn::Type {
|
||||||
|
let path: syn::Path = self.ident.clone().into();
|
||||||
|
let tp = syn::TypePath { qself: None, path };
|
||||||
|
tp.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Remote<'a> {
|
||||||
|
pub attrs: attrs::Container,
|
||||||
|
pub ty: &'a syn::Type,
|
||||||
|
pub crate_path: syn::Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Remote<'a> {
|
||||||
|
pub fn from_ast(
|
||||||
|
ctx: &Context,
|
||||||
|
crate_path: syn::Path,
|
||||||
|
input: &'a inputs::RemoteInput,
|
||||||
|
) -> Option<Remote<'a>> {
|
||||||
|
let attrs = attrs::Container::from_ast(ctx, &input.attrs);
|
||||||
|
if attrs.from_str.is_none() && attrs.type_from.is_none() && attrs.type_try_from.is_none() {
|
||||||
|
ctx.error_spanned(input, "Missing from_str, from or try_from attribute");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Remote {
|
||||||
|
ty: &input.ident,
|
||||||
|
attrs,
|
||||||
|
crate_path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crate_path(&self) -> &syn::Path {
|
||||||
|
if let Some(crate_path) = self.attrs.crate_path.as_ref() {
|
||||||
|
crate_path
|
||||||
|
} else {
|
||||||
|
&self.crate_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fields_ast<'a>(ctx: &Context, fields: &'a Punctuated<syn::Field, Token![,]>) -> Vec<Field<'a>> {
|
||||||
|
fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, field)| Field::from_ast(ctx, idx, field))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
32
tvix/nix-compat-derive/src/internal/symbol.rs
Normal file
32
tvix/nix-compat-derive/src/internal/symbol.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use syn::Path;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Symbol(&'static str);
|
||||||
|
|
||||||
|
pub const NIX: Symbol = Symbol("nix");
|
||||||
|
pub const VERSION: Symbol = Symbol("version");
|
||||||
|
pub const DEFAULT: Symbol = Symbol("default");
|
||||||
|
pub const FROM: Symbol = Symbol("from");
|
||||||
|
pub const TRY_FROM: Symbol = Symbol("try_from");
|
||||||
|
pub const FROM_STR: Symbol = Symbol("from_str");
|
||||||
|
pub const CRATE: Symbol = Symbol("crate");
|
||||||
|
|
||||||
|
impl PartialEq<Symbol> for Path {
|
||||||
|
fn eq(&self, word: &Symbol) -> bool {
|
||||||
|
self.is_ident(word.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PartialEq<Symbol> for &'a Path {
|
||||||
|
fn eq(&self, word: &Symbol) -> bool {
|
||||||
|
self.is_ident(word.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Symbol {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
348
tvix/nix-compat-derive/src/lib.rs
Normal file
348
tvix/nix-compat-derive/src/lib.rs
Normal file
|
|
@ -0,0 +1,348 @@
|
||||||
|
//! # Using derive
|
||||||
|
//!
|
||||||
|
//! 1. [Overview](#overview)
|
||||||
|
//! 3. [Attributes](#attributes)
|
||||||
|
//! 1. [Container attributes](#container-attributes)
|
||||||
|
//! 1. [`#[nix(from_str)]`](#nixfrom_str)
|
||||||
|
//! 2. [`#[nix(from = "FromType")]`](#nixfrom--fromtype)
|
||||||
|
//! 3. [`#[nix(try_from = "FromType")]`](#nixtry_from--fromtype)
|
||||||
|
//! 4. [`#[nix(crate = "...")]`](#nixcrate--)
|
||||||
|
//! 2. [Variant attributes](#variant-attributes)
|
||||||
|
//! 1. [`#[nix(version = "range")]`](#nixversion--range)
|
||||||
|
//! 3. [Field attributes](#field-attributes)
|
||||||
|
//! 1. [`#[nix(version = "range")]`](#nixversion--range-1)
|
||||||
|
//! 2. [`#[nix(default)]`](#nixdefault)
|
||||||
|
//! 3. [`#[nix(default = "path")]`](#nixdefault--path)
|
||||||
|
//!
|
||||||
|
//! ## Overview
|
||||||
|
//!
|
||||||
|
//! This crate contains derive macros and function-like macros for implementing
|
||||||
|
//! `NixDeserialize` with less boilerplate.
|
||||||
|
//!
|
||||||
|
//! ### Examples
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! struct Unnamed(u64, String);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! struct Fields {
|
||||||
|
//! number: u64,
|
||||||
|
//! message: String,
|
||||||
|
//! };
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! struct Ignored;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Attributes
|
||||||
|
//!
|
||||||
|
//! To customize the derived trait implementations you can add
|
||||||
|
//! [attributes](https://doc.rust-lang.org/reference/attributes.html)
|
||||||
|
//! to containers, fields and variants.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! #[nix(crate="nix_compat")] // <-- This is a container attribute
|
||||||
|
//! struct Fields {
|
||||||
|
//! number: u64,
|
||||||
|
//! #[nix(version="..20")] // <-- This is a field attribute
|
||||||
|
//! message: String,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! #[nix(crate="nix_compat")] // <-- This is also a container attribute
|
||||||
|
//! enum E {
|
||||||
|
//! #[nix(version="..10")] // <-- This is a variant attribute
|
||||||
|
//! A(u64),
|
||||||
|
//! #[nix(version="10..")] // <-- This is also a variant attribute
|
||||||
|
//! B(String),
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Container attributes
|
||||||
|
//!
|
||||||
|
//! ##### `#[nix(from_str)]`
|
||||||
|
//!
|
||||||
|
//! When `from_str` is specified the fields are all ignored and instead a
|
||||||
|
//! `String` is first deserialized and then `FromStr::from_str` is used
|
||||||
|
//! to convert this `String` to the container type.
|
||||||
|
//!
|
||||||
|
//! This means that the container must implement `FromStr` and the error
|
||||||
|
//! returned from the `from_str` must implement `Display`.
|
||||||
|
//!
|
||||||
|
//! ###### Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! #[nix(from_str)]
|
||||||
|
//! struct MyString(String);
|
||||||
|
//! impl std::str::FromStr for MyString {
|
||||||
|
//! type Err = String;
|
||||||
|
//! fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
//! if s != "bad string" {
|
||||||
|
//! Ok(MyString(s.to_string()))
|
||||||
|
//! } else {
|
||||||
|
//! Err("Got a bad string".to_string())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ##### `#[nix(from = "FromType")]`
|
||||||
|
//!
|
||||||
|
//! When `from` is specified the fields are all ignored and instead a
|
||||||
|
//! value of `FromType` is first deserialized and then `From::from` is
|
||||||
|
//! used to convert from this value to the container type.
|
||||||
|
//!
|
||||||
|
//! This means that the container must implement `From<FromType>` and
|
||||||
|
//! `FromType` must implement `NixDeserialize`.
|
||||||
|
//!
|
||||||
|
//! ###### Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! #[nix(from="usize")]
|
||||||
|
//! struct MyValue(usize);
|
||||||
|
//! impl From<usize> for MyValue {
|
||||||
|
//! fn from(val: usize) -> Self {
|
||||||
|
//! MyValue(val)
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ##### `#[nix(try_from = "FromType")]`
|
||||||
|
//!
|
||||||
|
//! With `try_from` a value of `FromType` is first deserialized and then
|
||||||
|
//! `TryFrom::try_from` is used to convert from this value to the container
|
||||||
|
//! type.
|
||||||
|
//!
|
||||||
|
//! This means that the container must implement `TryFrom<FromType>` and
|
||||||
|
//! `FromType` must implement `NixDeserialize`.
|
||||||
|
//! The error returned from `try_from` also needs to implement `Display`.
|
||||||
|
//!
|
||||||
|
//! ###### Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! #[nix(try_from="usize")]
|
||||||
|
//! struct WrongAnswer(usize);
|
||||||
|
//! impl TryFrom<usize> for WrongAnswer {
|
||||||
|
//! type Error = String;
|
||||||
|
//! fn try_from(val: usize) -> Result<Self, Self::Error> {
|
||||||
|
//! if val != 42 {
|
||||||
|
//! Ok(WrongAnswer(val))
|
||||||
|
//! } else {
|
||||||
|
//! Err("Got the answer to life the universe and everything".to_string())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ##### `#[nix(crate = "...")]`
|
||||||
|
//!
|
||||||
|
//! Specify the path to the `nix-compat` crate instance to use when referring
|
||||||
|
//! to the API in the generated code. This is usually not needed.
|
||||||
|
//!
|
||||||
|
//! ### Variant attributes
|
||||||
|
//!
|
||||||
|
//! ##### `#[nix(version = "range")]`
|
||||||
|
//!
|
||||||
|
//! Specifies the protocol version range where this variant is used.
|
||||||
|
//! When deriving an enum the `version` attribute is used to select which
|
||||||
|
//! variant of the enum to deserialize. The range is for minor version and
|
||||||
|
//! the version ranges of all variants combined must cover all versions
|
||||||
|
//! without any overlap or the first variant that matches is selected.
|
||||||
|
//!
|
||||||
|
//! ###### Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! enum Testing {
|
||||||
|
//! #[nix(version="..=18")]
|
||||||
|
//! OldVersion(u64),
|
||||||
|
//! #[nix(version="19..")]
|
||||||
|
//! NewVersion(String),
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Field attributes
|
||||||
|
//!
|
||||||
|
//! ##### `#[nix(version = "range")]`
|
||||||
|
//!
|
||||||
|
//! Specifies the protocol version range where this field is included.
|
||||||
|
//! The range is for minor version. For example `version = "..20"`
|
||||||
|
//! includes the field in protocol versions `1.0` to `1.19` and skips
|
||||||
|
//! it in version `1.20` and above.
|
||||||
|
//!
|
||||||
|
//! ###### Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! struct Field {
|
||||||
|
//! number: u64,
|
||||||
|
//! #[nix(version="..20")]
|
||||||
|
//! messsage: String,
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ##### `#[nix(default)]`
|
||||||
|
//!
|
||||||
|
//! When a field is skipped because the active protocol version falls
|
||||||
|
//! outside the range specified in [`#[nix(version = "range")]`](#nixversion--range-1)
|
||||||
|
//! this attribute indicates that `Default::default()` should be used
|
||||||
|
//! to get a value for the field. This is also the default
|
||||||
|
//! when you only specify [`#[nix(version = "range")]`](#nixversion--range-1).
|
||||||
|
//!
|
||||||
|
//! ###### Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! struct Field {
|
||||||
|
//! number: u64,
|
||||||
|
//! #[nix(version="..20", default)]
|
||||||
|
//! messsage: String,
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ##### `#[nix(default = "path")]`
|
||||||
|
//!
|
||||||
|
//! When a field is skipped because the active protocol version falls
|
||||||
|
//! outside the range specified in [`#[nix(version = "range")]`](#nixversion--range-1)
|
||||||
|
//! this attribute indicates that the function in `path` should be called to
|
||||||
|
//! get a default value for the field. The given function must be callable
|
||||||
|
//! as `fn() -> T`.
|
||||||
|
//! For example `default = "my_value"` would call `my_value()` and `default =
|
||||||
|
//! "AType::empty"` would call `AType::empty()`.
|
||||||
|
//!
|
||||||
|
//! ###### Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use nix_compat_derive::NixDeserialize;
|
||||||
|
//! #
|
||||||
|
//! #[derive(NixDeserialize)]
|
||||||
|
//! struct Field {
|
||||||
|
//! number: u64,
|
||||||
|
//! #[nix(version="..20", default="missing_string")]
|
||||||
|
//! messsage: String,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn missing_string() -> String {
|
||||||
|
//! "missing string".to_string()
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use internal::inputs::RemoteInput;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use syn::{parse_quote, DeriveInput};
|
||||||
|
|
||||||
|
mod de;
|
||||||
|
mod internal;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "external"))]
|
||||||
|
#[proc_macro_derive(NixDeserialize, attributes(nix))]
|
||||||
|
pub fn derive_nix_deserialize(item: TokenStream) -> TokenStream {
|
||||||
|
let mut input = syn::parse_macro_input!(item as DeriveInput);
|
||||||
|
let nnixrs: syn::Path = parse_quote!(crate);
|
||||||
|
de::expand_nix_deserialize(nnixrs, &mut input)
|
||||||
|
.unwrap_or_else(syn::Error::into_compile_error)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "external")]
|
||||||
|
#[proc_macro_derive(NixDeserialize, attributes(nix))]
|
||||||
|
pub fn derive_nix_deserialize(item: TokenStream) -> TokenStream {
|
||||||
|
let mut input = syn::parse_macro_input!(item as DeriveInput);
|
||||||
|
let nnixrs: syn::Path = parse_quote!(::nix_compat);
|
||||||
|
de::expand_nix_deserialize(nnixrs, &mut input)
|
||||||
|
.unwrap_or_else(syn::Error::into_compile_error)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro to implement `NixDeserialize` on a type.
|
||||||
|
/// Sometimes you can't use the deriver to implement `NixDeserialize`
|
||||||
|
/// (like when dealing with types in Rust standard library) but don't want
|
||||||
|
/// to implement it yourself. So this macro can be used for those situations
|
||||||
|
/// where you would derive using `#[nix(from_str)]`,
|
||||||
|
/// `#[nix(from = "FromType")]` or `#[nix(try_from = "FromType")]` if you
|
||||||
|
/// could.
|
||||||
|
///
|
||||||
|
/// #### Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use nix_compat_derive::nix_deserialize_remote;
|
||||||
|
/// #
|
||||||
|
/// struct MyU64(u64);
|
||||||
|
///
|
||||||
|
/// impl From<u64> for MyU64 {
|
||||||
|
/// fn from(value: u64) -> Self {
|
||||||
|
/// Self(value)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// nix_deserialize_remote!(#[nix(from="u64")] MyU64);
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "external"))]
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn nix_deserialize_remote(item: TokenStream) -> TokenStream {
|
||||||
|
let input = syn::parse_macro_input!(item as RemoteInput);
|
||||||
|
let crate_path = parse_quote!(crate);
|
||||||
|
de::expand_nix_deserialize_remote(crate_path, &input)
|
||||||
|
.unwrap_or_else(syn::Error::into_compile_error)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro to implement `NixDeserialize` on a type.
|
||||||
|
/// Sometimes you can't use the deriver to implement `NixDeserialize`
|
||||||
|
/// (like when dealing with types in Rust standard library) but don't want
|
||||||
|
/// to implement it yourself. So this macro can be used for those situations
|
||||||
|
/// where you would derive using `#[nix(from_str)]`,
|
||||||
|
/// `#[nix(from = "FromType")]` or `#[nix(try_from = "FromType")]` if you
|
||||||
|
/// could.
|
||||||
|
///
|
||||||
|
/// #### Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use nix_compat_derive::nix_deserialize_remote;
|
||||||
|
/// #
|
||||||
|
/// struct MyU64(u64);
|
||||||
|
///
|
||||||
|
/// impl From<u64> for MyU64 {
|
||||||
|
/// fn from(value: u64) -> Self {
|
||||||
|
/// Self(value)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// nix_deserialize_remote!(#[nix(from="u64")] MyU64);
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "external")]
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn nix_deserialize_remote(item: TokenStream) -> TokenStream {
|
||||||
|
let input = syn::parse_macro_input!(item as RemoteInput);
|
||||||
|
let crate_path = parse_quote!(::nix_compat);
|
||||||
|
de::expand_nix_deserialize_remote(crate_path, &input)
|
||||||
|
.unwrap_or_else(syn::Error::into_compile_error)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
@ -8,9 +8,10 @@ edition = "2021"
|
||||||
async = ["tokio"]
|
async = ["tokio"]
|
||||||
# code emitting low-level packets used in the daemon protocol.
|
# code emitting low-level packets used in the daemon protocol.
|
||||||
wire = ["tokio", "pin-project-lite", "bytes"]
|
wire = ["tokio", "pin-project-lite", "bytes"]
|
||||||
|
test = []
|
||||||
|
|
||||||
# Enable all features by default.
|
# Enable all features by default.
|
||||||
default = ["async", "wire"]
|
default = ["async", "wire", "nix-compat-derive"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "2.4.1"
|
bitflags = "2.4.1"
|
||||||
|
|
@ -33,6 +34,11 @@ tracing = "0.1.37"
|
||||||
optional = true
|
optional = true
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
|
||||||
|
[dependencies.nix-compat-derive]
|
||||||
|
path = "../nix-compat-derive"
|
||||||
|
optional = true
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
optional = true
|
optional = true
|
||||||
version = "1.32.0"
|
version = "1.32.0"
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,13 @@ impl From<u16> for ProtocolVersion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test"))]
|
||||||
|
impl From<(u8, u8)> for ProtocolVersion {
|
||||||
|
fn from((major, minor): (u8, u8)) -> Self {
|
||||||
|
Self::from_parts(major, minor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<u64> for ProtocolVersion {
|
impl TryFrom<u64> for ProtocolVersion {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue