about summary refs log tree commit diff
path: root/tvix/nix-compat-derive-tests
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/nix-compat-derive-tests')
-rw-r--r--tvix/nix-compat-derive-tests/Cargo.toml24
-rw-r--r--tvix/nix-compat-derive-tests/default.nix5
-rw-r--r--tvix/nix-compat-derive-tests/tests/read_derive.rs417
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui.rs6
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_bad_type.rs10
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_bad_type.stderr21
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_enum_non_exaustive.rs13
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_enum_non_exaustive.stderr8
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_from_missing.rs7
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_from_missing.stderr5
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_error_not_display.rs20
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_error_not_display.stderr13
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_missing.rs7
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_missing.stderr16
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default.rs12
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default.stderr12
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default_path.rs12
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default_path.stderr8
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_remote_missing_attr.rs15
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_remote_missing_attr.stderr5
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_error_not_display.rs19
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_error_not_display.stderr13
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_missing.rs7
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_missing.stderr8
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_bad_default.rs9
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_bad_default.stderr5
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_bad_default_path.rs9
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_bad_default_path.stderr5
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.rs9
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.stderr5
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_bad_version.rs9
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_bad_version.stderr5
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_mising_version.rs9
-rw-r--r--tvix/nix-compat-derive-tests/tests/ui/parse_mising_version.stderr5
34 files changed, 753 insertions, 0 deletions
diff --git a/tvix/nix-compat-derive-tests/Cargo.toml b/tvix/nix-compat-derive-tests/Cargo.toml
new file mode 100644
index 000000000000..e69cb10e4fea
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "nix-compat-derive-tests"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+compile-tests = []
+
+[dev-dependencies]
+hex-literal = { workspace = true }
+pretty_assertions = { workspace = true }
+rstest = { workspace = true }
+tokio-test = { workspace = true }
+trybuild = { workspace = true }
+tokio = { workspace = true, features = ["io-util", "macros"] }
+
+[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"
diff --git a/tvix/nix-compat-derive-tests/default.nix b/tvix/nix-compat-derive-tests/default.nix
new file mode 100644
index 000000000000..cabe9ad13780
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/default.nix
@@ -0,0 +1,5 @@
+{ depot, ... }:
+
+depot.tvix.crates.workspaceMembers.nix-compat-derive-tests.build.override {
+  runTests = true;
+}
diff --git a/tvix/nix-compat-derive-tests/tests/read_derive.rs b/tvix/nix-compat-derive-tests/tests/read_derive.rs
new file mode 100644
index 000000000000..055d70cf046e
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/read_derive.rs
@@ -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
+    );
+}
diff --git a/tvix/nix-compat-derive-tests/tests/ui.rs b/tvix/nix-compat-derive-tests/tests/ui.rs
new file mode 100644
index 000000000000..6a7bffeaf832
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui.rs
@@ -0,0 +1,6 @@
+#[cfg(feature = "compile-tests")]
+#[test]
+fn ui() {
+    let t = trybuild::TestCases::new();
+    t.compile_fail("tests/ui/*.rs");
+}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_bad_type.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_bad_type.rs
new file mode 100644
index 000000000000..f77469679999
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_bad_type.rs
@@ -0,0 +1,10 @@
+use nix_compat_derive::NixDeserialize;
+
+pub struct BadType;
+
+#[derive(NixDeserialize)]
+pub struct Test {
+    version: BadType,
+}
+
+fn main() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_bad_type.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_bad_type.stderr
new file mode 100644
index 000000000000..12ffdc83c726
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_bad_type.stderr
@@ -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`
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_enum_non_exaustive.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_enum_non_exaustive.rs
new file mode 100644
index 000000000000..ab559f2b81c8
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_enum_non_exaustive.rs
@@ -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() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_enum_non_exaustive.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_enum_non_exaustive.stderr
new file mode 100644
index 000000000000..8a46d9439e35
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_enum_non_exaustive.stderr
@@ -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)
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_missing.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_missing.rs
new file mode 100644
index 000000000000..913b7c4f7e59
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_missing.rs
@@ -0,0 +1,7 @@
+use nix_compat_derive::NixDeserialize;
+
+#[derive(NixDeserialize)]
+#[nix(from = "u64")]
+pub struct Test;
+
+fn main() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_missing.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_missing.stderr
new file mode 100644
index 000000000000..0124010cf10c
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_missing.stderr
@@ -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`
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_error_not_display.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_error_not_display.rs
new file mode 100644
index 000000000000..36cd4b153740
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_error_not_display.rs
@@ -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() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_error_not_display.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_error_not_display.stderr
new file mode 100644
index 000000000000..8283ed5340f3
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_error_not_display.stderr
@@ -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`
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_missing.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_missing.rs
new file mode 100644
index 000000000000..a959db57e640
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_missing.rs
@@ -0,0 +1,7 @@
+use nix_compat_derive::NixDeserialize;
+
+#[derive(NixDeserialize)]
+#[nix(from_str)]
+pub struct Test;
+
+fn main() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_missing.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_missing.stderr
new file mode 100644
index 000000000000..f68f588011fc
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_from_str_missing.stderr
@@ -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
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default.rs
new file mode 100644
index 000000000000..e9df62845518
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default.rs
@@ -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() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default.stderr
new file mode 100644
index 000000000000..5cc2f5974e4c
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default.stderr
@@ -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);
+  |
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default_path.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default_path.rs
new file mode 100644
index 000000000000..4f319c069dca
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default_path.rs
@@ -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() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default_path.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default_path.stderr
new file mode 100644
index 000000000000..bb9af749128d
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_missing_default_path.stderr
@@ -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`
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_remote_missing_attr.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_remote_missing_attr.rs
new file mode 100644
index 000000000000..cc2ab5bfbc11
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_remote_missing_attr.rs
@@ -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() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_remote_missing_attr.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_remote_missing_attr.stderr
new file mode 100644
index 000000000000..a1c18adc6e48
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_remote_missing_attr.stderr
@@ -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);
+   |                         ^^^^^^^^^^^^^^
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_error_not_display.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_error_not_display.rs
new file mode 100644
index 000000000000..7f8ad6bbfc4e
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_error_not_display.rs
@@ -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() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_error_not_display.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_error_not_display.stderr
new file mode 100644
index 000000000000..8e55a3c56189
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_error_not_display.stderr
@@ -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`
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_missing.rs b/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_missing.rs
new file mode 100644
index 000000000000..899095ae3542
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_missing.rs
@@ -0,0 +1,7 @@
+use nix_compat_derive::NixDeserialize;
+
+#[derive(NixDeserialize)]
+#[nix(try_from = "u64")]
+pub struct Test;
+
+fn main() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_missing.stderr b/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_missing.stderr
new file mode 100644
index 000000000000..9605d1f3378f
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/deserialize_try_from_missing.stderr
@@ -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>`
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default.rs b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default.rs
new file mode 100644
index 000000000000..d87831cecf51
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default.rs
@@ -0,0 +1,9 @@
+use nix_compat_derive::NixDeserialize;
+
+#[derive(NixDeserialize)]
+pub struct Test {
+    #[nix(default = 12)]
+    version: u8,
+}
+
+fn main() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default.stderr b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default.stderr
new file mode 100644
index 000000000000..acb1bc2a47bc
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default.stderr
@@ -0,0 +1,5 @@
+error: expected nix attribute default to be string
+ --> tests/ui/parse_bad_default.rs:5:21
+  |
+5 |     #[nix(default = 12)]
+  |                     ^^
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default_path.rs b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default_path.rs
new file mode 100644
index 000000000000..fbde8ffbc2b0
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default_path.rs
@@ -0,0 +1,9 @@
+use nix_compat_derive::NixDeserialize;
+
+#[derive(NixDeserialize)]
+pub struct Test {
+    #[nix(default = "12")]
+    version: u8,
+}
+
+fn main() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default_path.stderr b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default_path.stderr
new file mode 100644
index 000000000000..7628d4c83bea
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_default_path.stderr
@@ -0,0 +1,5 @@
+error: expected identifier
+ --> tests/ui/parse_bad_default_path.rs:5:21
+  |
+5 |     #[nix(default = "12")]
+  |                     ^^^^
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.rs b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.rs
new file mode 100644
index 000000000000..690e76a20fe6
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.rs
@@ -0,0 +1,9 @@
+use nix_compat_derive::NixDeserialize;
+
+#[derive(NixDeserialize)]
+pub struct Test {
+    #[nix]
+    version: u8,
+}
+
+fn main() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.stderr b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.stderr
new file mode 100644
index 000000000000..da3d2d9aab47
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_nix.stderr
@@ -0,0 +1,5 @@
+error: expected attribute arguments in parentheses: #[nix(...)]
+ --> tests/ui/parse_bad_nix.rs:5:7
+  |
+5 |     #[nix]
+  |       ^^^
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_bad_version.rs b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_version.rs
new file mode 100644
index 000000000000..35b3b05c23e1
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_version.rs
@@ -0,0 +1,9 @@
+use nix_compat_derive::NixDeserialize;
+
+#[derive(NixDeserialize)]
+pub struct Test {
+    #[nix(version = 12)]
+    version: u8,
+}
+
+fn main() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_bad_version.stderr b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_version.stderr
new file mode 100644
index 000000000000..48cc817fac9d
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_bad_version.stderr
@@ -0,0 +1,5 @@
+error: expected nix attribute version to be string
+ --> tests/ui/parse_bad_version.rs:5:21
+  |
+5 |     #[nix(version = 12)]
+  |                     ^^
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_mising_version.rs b/tvix/nix-compat-derive-tests/tests/ui/parse_mising_version.rs
new file mode 100644
index 000000000000..9eaa743ed2b6
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_mising_version.rs
@@ -0,0 +1,9 @@
+use nix_compat_derive::NixDeserialize;
+
+#[derive(NixDeserialize)]
+pub struct Test {
+    #[nix(version)]
+    version: u8,
+}
+
+fn main() {}
diff --git a/tvix/nix-compat-derive-tests/tests/ui/parse_mising_version.stderr b/tvix/nix-compat-derive-tests/tests/ui/parse_mising_version.stderr
new file mode 100644
index 000000000000..79f048e11198
--- /dev/null
+++ b/tvix/nix-compat-derive-tests/tests/ui/parse_mising_version.stderr
@@ -0,0 +1,5 @@
+error: expected `=`
+ --> tests/ui/parse_mising_version.rs:5:18
+  |
+5 |     #[nix(version)]
+  |                  ^