feat(nix-compat): Add serde feature flag

This makes serde features optional behind a flag that is not enabled
by default. So Deserialize and Serialize implementations and anything
that deals with JSON.

Change-Id: I04830aa3883da13ea99a4a51b28981e8a5ecd426
Reviewed-on: https://cl.snix.dev/c/snix/+/30660
Autosubmit: Brian Olsen <brian@maven-group.org>
Reviewed-by: Florian Klink <flokli@flokli.de>
Tested-by: besadii
This commit is contained in:
Brian Olsen 2025-08-05 21:18:01 +02:00 committed by clbot
parent 2a29b90c7f
commit 6187029077
22 changed files with 176 additions and 121 deletions

View file

@ -1427,8 +1427,6 @@ dependencies = [
"num-traits",
"num_enum",
"pin-project-lite",
"serde",
"serde_json",
"sha2 0.10.8",
"thiserror 2.0.11",
"tokio",

View file

@ -4140,15 +4140,6 @@ rec {
packageId = "pin-project-lite";
optional = true;
}
{
name = "serde";
packageId = "serde";
features = [ "derive" ];
}
{
name = "serde_json";
packageId = "serde_json";
}
{
name = "sha2";
packageId = "sha2 0.10.8";
@ -4187,6 +4178,7 @@ rec {
"futures" = [ "dep:futures" ];
"nix-compat-derive" = [ "dep:nix-compat-derive" ];
"pin-project-lite" = [ "dep:pin-project-lite" ];
"serde" = [ "dep:serde" "dep:serde_json" ];
"tokio" = [ "dep:tokio" ];
"url" = [ "dep:url" ];
"wire" = [ "tokio" "pin-project-lite" "bytes" ];

View file

@ -1708,8 +1708,6 @@ dependencies = [
"num-traits",
"num_enum",
"pin-project-lite",
"serde",
"serde_json",
"sha2",
"thiserror 2.0.11",
"tokio",

View file

@ -5573,15 +5573,6 @@ rec {
packageId = "pin-project-lite";
optional = true;
}
{
name = "serde";
packageId = "serde";
features = [ "derive" ];
}
{
name = "serde_json";
packageId = "serde_json";
}
{
name = "sha2";
packageId = "sha2";
@ -5620,6 +5611,7 @@ rec {
"futures" = [ "dep:futures" ];
"nix-compat-derive" = [ "dep:nix-compat-derive" ];
"pin-project-lite" = [ "dep:pin-project-lite" ];
"serde" = [ "dep:serde" "dep:serde_json" ];
"tokio" = [ "dep:tokio" ];
"url" = [ "dep:url" ];
"wire" = [ "tokio" "pin-project-lite" "bytes" ];

View file

@ -971,8 +971,6 @@ dependencies = [
"num-traits",
"num_enum",
"pin-project-lite",
"serde",
"serde_json",
"sha2",
"thiserror 2.0.11",
"tokio",

View file

@ -2867,15 +2867,6 @@ rec {
packageId = "pin-project-lite";
optional = true;
}
{
name = "serde";
packageId = "serde";
features = [ "derive" ];
}
{
name = "serde_json";
packageId = "serde_json";
}
{
name = "sha2";
packageId = "sha2";
@ -2914,6 +2905,7 @@ rec {
"futures" = [ "dep:futures" ];
"nix-compat-derive" = [ "dep:nix-compat-derive" ];
"pin-project-lite" = [ "dep:pin-project-lite" ];
"serde" = [ "dep:serde" "dep:serde_json" ];
"tokio" = [ "dep:tokio" ];
"url" = [ "dep:url" ];
"wire" = [ "tokio" "pin-project-lite" "bytes" ];

View file

@ -1010,8 +1010,6 @@ dependencies = [
"num-traits",
"num_enum",
"pin-project-lite",
"serde",
"serde_json",
"sha2",
"thiserror 2.0.11",
"tokio",

View file

@ -2949,15 +2949,6 @@ rec {
packageId = "pin-project-lite";
optional = true;
}
{
name = "serde";
packageId = "serde";
features = [ "derive" ];
}
{
name = "serde_json";
packageId = "serde_json";
}
{
name = "sha2";
packageId = "sha2";
@ -2996,6 +2987,7 @@ rec {
"futures" = [ "dep:futures" ];
"nix-compat-derive" = [ "dep:nix-compat-derive" ];
"pin-project-lite" = [ "dep:pin-project-lite" ];
"serde" = [ "dep:serde" "dep:serde_json" ];
"tokio" = [ "dep:tokio" ];
"url" = [ "dep:url" ];
"wire" = [ "tokio" "pin-project-lite" "bytes" ];

View file

@ -8218,7 +8218,7 @@ rec {
{
name = "drvfmt";
path = "src/bin/drvfmt.rs";
requiredFeatures = [ ];
requiredFeatures = [ "serde" ];
}
];
src = lib.cleanSourceWith { filter = sourceFilter; src = ./nix-compat; };
@ -8292,11 +8292,13 @@ rec {
{
name = "serde";
packageId = "serde";
optional = true;
features = [ "derive" ];
}
{
name = "serde_json";
packageId = "serde_json";
optional = true;
}
{
name = "sha2";
@ -8381,11 +8383,12 @@ rec {
"futures" = [ "dep:futures" ];
"nix-compat-derive" = [ "dep:nix-compat-derive" ];
"pin-project-lite" = [ "dep:pin-project-lite" ];
"serde" = [ "dep:serde" "dep:serde_json" ];
"tokio" = [ "dep:tokio" ];
"url" = [ "dep:url" ];
"wire" = [ "tokio" "pin-project-lite" "bytes" ];
};
resolvedDefaultFeatures = [ "async" "bytes" "daemon" "default" "flakeref" "futures" "nix-compat-derive" "pin-project-lite" "test" "tokio" "url" "wire" ];
resolvedDefaultFeatures = [ "async" "bytes" "daemon" "default" "flakeref" "futures" "nix-compat-derive" "pin-project-lite" "serde" "test" "tokio" "url" "wire" ];
};
"nix-compat-derive" = rec {
crateName = "nix-compat-derive";
@ -14636,7 +14639,7 @@ rec {
{
name = "nix-compat";
packageId = "nix-compat";
features = [ "async" ];
features = [ "async" "serde" ];
}
{
name = "parking_lot";

View file

@ -3,6 +3,10 @@ name = "nix-compat"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "drvfmt"
required-features = ["serde"]
[features]
# async NAR writer. Also needs the `wire` feature.
async = ["tokio"]
@ -11,6 +15,8 @@ wire = ["tokio", "pin-project-lite", "bytes"]
flakeref = ["url"]
# nix-daemon protocol handling
daemon = ["tokio", "nix-compat-derive", "futures"]
# serde support on types
serde = ["dep:serde", "dep:serde_json"]
test = []
# Enable all features by default.
@ -28,8 +34,8 @@ glob.workspace = true
mimalloc.workspace = true
nom.workspace = true
num-traits.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
serde = { workspace = true, features = ["derive"], optional = true }
serde_json = { workspace = true, optional = true }
sha2.workspace = true
thiserror.workspace = true
tracing.workspace = true

View file

@ -2,6 +2,7 @@ use crate::store_path::{
self, StorePath, StorePathRef, build_ca_path, build_output_path, build_text_path,
};
use bstr::BString;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::{BTreeMap, BTreeSet};
@ -26,22 +27,23 @@ pub use validate::validate_output_name;
use self::write::AtermWriteable;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Derivation {
#[serde(rename = "args")]
#[cfg_attr(feature = "serde", serde(rename = "args"))]
pub arguments: Vec<String>,
pub builder: String,
#[serde(rename = "env")]
#[cfg_attr(feature = "serde", serde(rename = "env"))]
pub environment: BTreeMap<String, BString>,
/// Map from drv path to output names used from this derivation.
#[serde(rename = "inputDrvs")]
#[cfg_attr(feature = "serde", serde(rename = "inputDrvs"))]
pub input_derivations: BTreeMap<StorePath<String>, BTreeSet<String>>,
/// Plain store paths of additional inputs.
#[serde(rename = "inputSrcs")]
#[cfg_attr(feature = "serde", serde(rename = "inputSrcs"))]
pub input_sources: BTreeSet<StorePath<String>>,
/// Maps output names to Output.

View file

@ -1,20 +1,25 @@
use crate::nixhash::CAHash;
use crate::{derivation::OutputError, store_path::StorePath};
#[cfg(feature = "serde")]
use serde::de::Unexpected;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use serde_json::Map;
use std::borrow::Cow;
/// References the derivation output.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Output {
/// Store path of build result.
pub path: Option<StorePath<String>>,
#[serde(flatten)]
#[cfg_attr(feature = "serde", serde(flatten))]
pub ca_hash: Option<CAHash>, // we can only represent a subset here.
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Output {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@ -73,6 +78,7 @@ impl Output {
/// This ensures that a potentially valid input addressed
/// output is deserialized as a non-fixed output.
#[cfg(feature = "serde")]
#[test]
fn deserialize_valid_input_addressed_output() {
let json_bytes = r#"
@ -86,6 +92,7 @@ fn deserialize_valid_input_addressed_output() {
/// This ensures that a potentially valid fixed output
/// output deserializes fine as a fixed output.
#[cfg(feature = "serde")]
#[test]
fn deserialize_valid_fixed_output() {
let json_bytes = r#"
@ -101,6 +108,7 @@ fn deserialize_valid_fixed_output() {
/// This ensures that parsing an input with the invalid hash encoding
/// will result in a parsing failure.
#[cfg(feature = "serde")]
#[test]
fn deserialize_with_error_invalid_hash_encoding_fixed_output() {
let json_bytes = r#"
@ -116,6 +124,7 @@ fn deserialize_with_error_invalid_hash_encoding_fixed_output() {
/// This ensures that parsing an input with the wrong hash algo
/// will result in a parsing failure.
#[cfg(feature = "serde")]
#[test]
fn deserialize_with_error_invalid_hash_algo_fixed_output() {
let json_bytes = r#"
@ -131,6 +140,7 @@ fn deserialize_with_error_invalid_hash_algo_fixed_output() {
/// This ensures that parsing an input with the missing hash algo but present hash will result in a
/// parsing failure.
#[cfg(feature = "serde")]
#[test]
fn deserialize_with_error_missing_hash_algo_fixed_output() {
let json_bytes = r#"
@ -145,6 +155,7 @@ fn deserialize_with_error_missing_hash_algo_fixed_output() {
/// This ensures that parsing an input with the missing hash but present hash algo will result in a
/// parsing failure.
#[cfg(feature = "serde")]
#[test]
fn deserialize_with_error_missing_hash_fixed_output() {
let json_bytes = r#"
@ -157,6 +168,7 @@ fn deserialize_with_error_missing_hash_fixed_output() {
assert!(output.is_err());
}
#[cfg(feature = "serde")]
#[test]
fn serialize_deserialize() {
let json_bytes = r#"
@ -171,6 +183,7 @@ fn serialize_deserialize() {
assert_eq!(output, output2);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_deserialize_fixed() {
let json_bytes = r#"

View file

@ -1,19 +1,28 @@
use super::parse_error::ErrorKind;
use crate::derivation::Derivation;
#[cfg(feature = "serde")]
use crate::derivation::output::Output;
use crate::derivation::parse_error::NomError;
use crate::derivation::parser::Error;
#[cfg(feature = "serde")]
use crate::store_path::StorePath;
#[cfg(feature = "serde")]
use bstr::{BStr, BString};
#[cfg(feature = "serde")]
use hex_literal::hex;
#[cfg(feature = "serde")]
use rstest::rstest;
#[cfg(feature = "serde")]
use std::collections::BTreeSet;
use std::fs;
#[cfg(feature = "serde")]
use std::path::{Path, PathBuf};
#[cfg(feature = "serde")]
use std::str::FromStr;
const RESOURCES_PATHS: &str = "src/derivation/tests/derivation_tests";
#[cfg(feature = "serde")]
#[rstest]
fn check_serialization(
#[files("src/derivation/tests/derivation_tests/ok/*.drv")]
@ -33,6 +42,7 @@ fn check_serialization(
assert_eq!(expected, BStr::new(&serialized_derivation));
}
#[cfg(feature = "serde")]
#[rstest]
fn validate(
#[files("src/derivation/tests/derivation_tests/ok/*.drv")]
@ -49,6 +59,7 @@ fn validate(
.expect("derivation failed to validate")
}
#[cfg(feature = "serde")]
#[rstest]
fn check_to_aterm_bytes(
#[files("src/derivation/tests/derivation_tests/ok/*.drv")]
@ -68,6 +79,7 @@ fn check_to_aterm_bytes(
/// Reads in derivations in ATerm representation, parses with that parser,
/// then compares the structs with the ones obtained by parsing the JSON
/// representations.
#[cfg(feature = "serde")]
#[rstest]
fn from_aterm_bytes(
#[files("src/derivation/tests/derivation_tests/ok/*.drv")] path_to_drv_file: PathBuf,
@ -139,6 +151,7 @@ fn from_aterm_bytes_trailer() {
Derivation::from_aterm_bytes(&buf).expect_err("must fail");
}
#[cfg(feature = "serde")]
#[rstest]
#[case::fixed_sha256("bar", "0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv")]
#[case::simple_sha256("foo", "4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv")]
@ -164,6 +177,7 @@ fn derivation_path(#[case] name: &str, #[case] expected_path: &str) {
/// This trims all output paths from a Derivation struct,
/// by setting outputs[$outputName].path and environment[$outputName] to the empty string.
#[cfg(feature = "serde")]
fn derivation_without_output_paths(derivation: &Derivation) -> Derivation {
let mut trimmed_env = derivation.environment.clone();
let mut trimmed_outputs = derivation.outputs.clone();
@ -188,6 +202,7 @@ fn derivation_without_output_paths(derivation: &Derivation) -> Derivation {
}
}
#[cfg(feature = "serde")]
#[rstest]
#[case::fixed_sha256("0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv", hex!("724f3e3634fce4cbbbd3483287b8798588e80280660b9a63fd13a1bc90485b33"))]
#[case::fixed_sha1("ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv", hex!("c79aebd0ce3269393d4a1fde2cbd1d975d879b40f0bf40a48f550edc107fd5df"))]
@ -204,6 +219,7 @@ fn hash_derivation_modulo_fixed(#[case] drv_path: &str, #[case] expected_digest:
/// This reads a Derivation (in A-Term), trims out all fields containing
/// calculated output paths, then triggers the output path calculation and
/// compares the struct to match what was originally read in.
#[cfg(feature = "serde")]
#[rstest]
#[case::fixed_sha256("bar", "0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv")]
#[case::simple_sha256("foo", "4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv")]
@ -298,6 +314,7 @@ fn output_paths(#[case] name: &str, #[case] drv_path_str: &str) {
/// it, then continues with the foo derivation.
///
/// The code ensures the resulting Derivations match our fixtures.
#[cfg(feature = "serde")]
#[test]
fn output_path_construction() {
// create the bar derivation

View file

@ -1,7 +1,9 @@
//! Contains types Nix uses for its logging, visible in the "internal-json" log
//! messages as well as in nix-daemon communication.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use tracing::warn;
/// Every "internal-json" log line emitted by Nix has this prefix.
@ -9,17 +11,13 @@ pub const AT_NIX_PREFIX: &str = "@nix ";
/// The different verbosity levels Nix distinguishes.
#[derive(
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
num_enum::TryFromPrimitive,
num_enum::IntoPrimitive,
Default,
Clone, Debug, Eq, PartialEq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive, Default,
)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(try_from = "u64", into = "u64")
)]
#[serde(try_from = "u64", into = "u64")]
#[cfg_attr(
feature = "daemon",
derive(nix_compat_derive::NixDeserialize, nix_compat_derive::NixSerialize),
@ -59,13 +57,14 @@ impl std::fmt::Display for VerbosityLevel {
/// The different types of log messages Nix' `internal-json` format can
/// represent.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "action" /*, deny_unknown_fields */)]
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde",
derive(Serialize, Deserialize),
serde(tag = "action", rename_all = "camelCase" /*, deny_unknown_fields */))]
// TODO: deny_unknown_fields doesn't seem to work in the testcases below
pub enum LogMessage<'a> {
#[serde(rename = "start")]
Start {
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
fields: Option<Vec<Field<'a>>>,
id: u64,
level: VerbosityLevel,
@ -74,10 +73,10 @@ pub enum LogMessage<'a> {
r#type: ActivityType,
},
#[serde(rename = "stop")]
Stop { id: u64 },
Stop {
id: u64,
},
#[serde(rename = "result")]
Result {
fields: Vec<Field<'a>>,
id: u64,
@ -86,7 +85,6 @@ pub enum LogMessage<'a> {
// FUTUREWORK: there sometimes seems to be column/file/line fields set to null, and a raw_msg field,
// see msg_with_raw_msg testcase. These should be represented.
#[serde(rename = "msg")]
Msg {
level: VerbosityLevel,
msg: std::borrow::Cow<'a, str>,
@ -94,10 +92,12 @@ pub enum LogMessage<'a> {
// Log lines like these are sent by nixpkgs stdenv, present in `nix log` outputs of individual builds.
// They are also interpreted by Nix to re-emit [Self::Result]-style messages.
#[serde(rename = "setPhase")]
SetPhase { phase: &'a str },
SetPhase {
phase: &'a str,
},
}
#[cfg(feature = "serde")]
fn serialize_bytes_as_string<S>(b: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
@ -113,24 +113,22 @@ where
/// Fields in a log message can be either ints or strings.
/// Sometimes, Nix also uses invalid UTF-8 in here, so we use BStr.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
pub enum Field<'a> {
Int(u64),
String(#[serde(serialize_with = "serialize_bytes_as_string")] std::borrow::Cow<'a, [u8]>),
String(
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_bytes_as_string"))]
std::borrow::Cow<'a, [u8]>,
),
}
#[derive(
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
num_enum::TryFromPrimitive,
num_enum::IntoPrimitive,
#[derive(Clone, Debug, Eq, PartialEq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(try_from = "u8", into = "u8")
)]
#[serde(try_from = "u8", into = "u8")]
#[repr(u8)]
pub enum ActivityType {
Unknown = 0,
@ -174,17 +172,12 @@ impl std::fmt::Display for ActivityType {
}
}
#[derive(
Clone,
Debug,
Eq,
PartialEq,
Serialize,
Deserialize,
num_enum::TryFromPrimitive,
num_enum::IntoPrimitive,
#[derive(Clone, Debug, Eq, PartialEq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(try_from = "u8", into = "u8")
)]
#[serde(try_from = "u8", into = "u8")]
#[repr(u8)]
pub enum ResultType {
FileLinked = 100,
@ -200,6 +193,7 @@ pub enum ResultType {
impl<'a> LogMessage<'a> {
/// Parses a given log message string into a [LogMessage].
#[cfg(feature = "serde")]
pub fn from_json_str(s: &'a str) -> Result<Self, Error> {
let s = s.strip_prefix(AT_NIX_PREFIX).ok_or(Error::MissingPrefix)?;
@ -207,6 +201,7 @@ impl<'a> LogMessage<'a> {
}
}
#[cfg(feature = "serde")]
impl std::fmt::Display for LogMessage<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
@ -217,6 +212,7 @@ impl std::fmt::Display for LogMessage<'_> {
}
}
#[cfg(feature = "serde")]
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Missing @nix prefix")]
@ -231,7 +227,10 @@ pub enum Error {
// while it *is* compared.
#[allow(unused_variables)]
mod test {
use super::{ActivityType, Field, LogMessage, ResultType, VerbosityLevel};
use super::VerbosityLevel;
#[cfg(feature = "serde")]
use super::{ActivityType, Field, LogMessage, ResultType};
#[cfg(feature = "serde")]
use rstest::rstest;
#[test]
@ -247,6 +246,7 @@ mod test {
VerbosityLevel::try_from(42).expect_err("must fail parsing");
}
#[cfg(feature = "serde")]
#[rstest]
#[case::start(
r#"@nix {"action":"start","id":1264799149195466,"level":5,"parent":0,"text":"copying '/nix/store/rfqxfljma55x8ybmyg07crnarvqx62sr-nixpkgs-src/pkgs/development/compilers/llvm/18/llvm/lit-shell-script-runner-set-dyld-library-path.patch' to the store","type":0}"#,

View file

@ -1,6 +1,7 @@
pub(crate) mod wire;
mod copy;
#[cfg(feature = "serde")]
pub mod listing;
pub mod reader;
pub mod writer;

View file

@ -4,6 +4,7 @@ use std::{
};
use data_encoding::BASE64;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
const SIGNATURE_LENGTH: usize = std::mem::size_of::<ed25519::SignatureBytes>();
@ -100,6 +101,7 @@ where
}
}
#[cfg(feature = "serde")]
impl<'a, 'de, S> Deserialize<'de> for Signature<S>
where
S: Deref<Target = str> + From<&'a str>,
@ -116,6 +118,7 @@ where
}
}
#[cfg(feature = "serde")]
impl<S: Display> Serialize for Signature<S>
where
S: Deref<Target = str>,
@ -165,6 +168,7 @@ pub enum Error {
mod test {
use data_encoding::BASE64;
use ed25519_dalek::VerifyingKey;
#[cfg(feature = "serde")]
use hex_literal::hex;
use std::sync::LazyLock;
@ -221,6 +225,7 @@ mod test {
Signature::<&str>::parse(input).expect_err("must fail");
}
#[cfg(feature = "serde")]
#[test]
fn serialize_deserialize() {
let signature_actual = Signature {

View file

@ -1,5 +1,6 @@
use std::fmt::Display;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::nixhash::Error;
@ -36,6 +37,7 @@ impl Display for HashAlgo {
}
}
#[cfg(feature = "serde")]
impl Serialize for HashAlgo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -45,6 +47,7 @@ impl Serialize for HashAlgo {
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for HashAlgo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@ -58,6 +61,7 @@ impl<'de> Deserialize<'de> for HashAlgo {
/// TODO(Raito): this could be automated via macros, I suppose.
/// But this may be more expensive than just doing it by hand
/// and ensuring that is kept in sync.
#[cfg(feature = "serde")]
pub const SUPPORTED_ALGOS: [&str; 4] = ["md5", "sha1", "sha256", "sha512"];
impl TryFrom<&str> for HashAlgo {

View file

@ -1,14 +1,11 @@
use crate::nixbase32;
use crate::nixhash::{HashAlgo, NixHash};
use serde::de::Unexpected;
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::nixhash::NixHash;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Unexpected, ser::SerializeMap};
#[cfg(feature = "serde")]
use serde_json::{Map, Value};
use std::borrow::Cow;
use super::algos::SUPPORTED_ALGOS;
use super::decode_digest;
/// A Nix CAHash describes a content-addressed hash of a path.
///
/// The way Nix prints it as a string is a bit confusing, but there's essentially
@ -119,10 +116,15 @@ impl CAHash {
///
/// This is to match how `nix show-derivation` command shows them in JSON
/// representation.
#[cfg(feature = "serde")]
pub(crate) fn from_map<'de, D>(map: &Map<String, Value>) -> Result<Option<Self>, D::Error>
where
D: Deserializer<'de>,
{
use super::algos::SUPPORTED_ALGOS;
use super::decode_digest;
use crate::nixhash::HashAlgo;
// If we don't have hash neither hashAlgo, let's just return None.
if !map.contains_key("hash") && !map.contains_key("hashAlgo") {
return Ok(None);
@ -166,6 +168,7 @@ impl CAHash {
}
}
#[cfg(feature = "serde")]
impl Serialize for CAHash {
/// map a CAHash into the serde data model.
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@ -194,6 +197,7 @@ impl Serialize for CAHash {
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for CAHash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@ -210,13 +214,16 @@ impl<'de> Deserialize<'de> for CAHash {
#[cfg(test)]
mod tests {
#[cfg(feature = "serde")]
use hex_literal::hex;
#[cfg(feature = "serde")]
use crate::{
derivation::CAHash,
nixhash::{HashAlgo, NixHash},
};
#[cfg(feature = "serde")]
#[test]
fn serialize_flat() {
let json_bytes = r#"{
@ -234,6 +241,7 @@ mod tests {
assert_eq!(serialized, json_bytes);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_nar() {
let json_bytes = r#"{
@ -251,6 +259,7 @@ mod tests {
assert_eq!(serialized, json_bytes);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_flat() {
let json_bytes = r#"
@ -272,6 +281,7 @@ mod tests {
);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_hex() {
let json_bytes = r#"
@ -293,6 +303,7 @@ mod tests {
);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_nixbase32() {
let json_bytes = r#"
@ -314,6 +325,7 @@ mod tests {
);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_base64() {
let json_bytes = r#"
@ -335,6 +347,7 @@ mod tests {
);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_deserialize_nar() {
let json_bytes = r#"
@ -350,6 +363,7 @@ mod tests {
assert_eq!(hash, hash2);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_deserialize_flat() {
let json_bytes = r#"

View file

@ -7,6 +7,7 @@ use thiserror;
mod algos;
mod ca_hash;
#[cfg(feature = "serde")]
pub mod serde;
pub use algos::HashAlgo;
@ -537,6 +538,7 @@ mod tests {
NixHash::from_str(weird_base64, Some(HashAlgo::Sha256)).expect_err("must fail");
}
#[cfg(feature = "serde")]
#[test]
fn serialize_deserialize() {
let nixhash_actual = NixHash::Sha256(hex!(

View file

@ -1,4 +1,7 @@
use crate::{narinfo::SignatureRef, nixhash, store_path::StorePathRef};
#[cfg(feature = "serde")]
use crate::nixhash;
use crate::{narinfo::SignatureRef, store_path::StorePathRef};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
@ -7,26 +10,34 @@ use std::collections::BTreeSet;
/// This is not to be confused with the format Nix uses in its `nix path-info` command.
/// It includes some more fields, like `registrationTime`, `signatures` and `ultimate`,
/// does not include the `closureSize` and encodes `narHash` as SRI.
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "camelCase")
)]
pub struct ExportedPathInfo<'a> {
#[serde(rename = "closureSize")]
pub closure_size: u64,
#[serde(
#[cfg_attr(
feature = "serde",
serde(
rename = "narHash",
serialize_with = "nixhash::serde::to_nix_nixbase32",
deserialize_with = "nixhash::serde::from_nix_nixbase32_or_sri"
)
)]
pub nar_sha256: [u8; 32],
#[serde(rename = "narSize")]
pub nar_size: u64,
#[serde(borrow)]
#[cfg_attr(feature = "serde", serde(borrow))]
pub path: StorePathRef<'a>,
#[serde(borrow)]
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(
feature = "serde",
serde(borrow, skip_serializing_if = "Option::is_none")
)]
pub deriver: Option<StorePathRef<'a>>,
/// The list of other Store Paths this Store Path refers to.
@ -34,7 +45,10 @@ pub struct ExportedPathInfo<'a> {
pub references: BTreeSet<StorePathRef<'a>>,
// more recent versions of Nix also have a `valid: true` field here, Nix 2.3 doesn't,
// and nothing seems to use it.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub signatures: Vec<SignatureRef<'a>>,
}
@ -53,11 +67,14 @@ impl PartialOrd for ExportedPathInfo<'_> {
#[cfg(test)]
mod tests {
#[cfg(feature = "serde")]
use hex_literal::hex;
#[cfg(feature = "serde")]
use super::*;
/// Ensure we can create the same JSON as the exportReferencesGraph feature
#[cfg(feature = "serde")]
#[test]
fn serialize_deserialize() {
// JSON extracted from a build of
@ -95,6 +112,7 @@ mod tests {
}
/// Ensure we can parse output from `nix path-info --json``
#[cfg(feature = "serde")]
#[test]
fn serialize_deserialize_from_path_info() {
// JSON extracted from

View file

@ -1,5 +1,6 @@
use crate::nixbase32;
use data_encoding::{BASE64, DecodeError};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::{
fmt,
@ -236,6 +237,7 @@ impl FromStr for StorePath<String> {
}
}
#[cfg(feature = "serde")]
impl<'a, 'de: 'a, S> Deserialize<'de> for StorePath<S>
where
S: AsRef<str> + From<&'a str>,
@ -258,6 +260,7 @@ where
}
}
#[cfg(feature = "serde")]
impl<S> Serialize for StorePath<S>
where
S: AsRef<str>,
@ -345,11 +348,13 @@ mod tests {
use hex_literal::hex;
use pretty_assertions::assert_eq;
use rstest::rstest;
#[cfg(feature = "serde")]
use serde::Deserialize;
#[derive(Deserialize)]
/// An example struct, holding a StorePathRef.
/// Used to test deserializing StorePathRef.
#[cfg(feature = "serde")]
#[derive(Deserialize)]
struct Container<'a> {
#[serde(borrow)]
store_path: StorePathRef<'a>,
@ -492,6 +497,7 @@ mod tests {
);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_ref() {
let nixpath_actual = StorePathRef::from_bytes(
@ -507,6 +513,7 @@ mod tests {
);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_owned() {
let nixpath_actual = StorePathRef::from_bytes(
@ -522,6 +529,7 @@ mod tests {
);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_ref() {
let store_path_str_json =
@ -536,6 +544,7 @@ mod tests {
);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_ref_container() {
let str_json = "{\"store_path\":\"/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432\"}";
@ -548,6 +557,7 @@ mod tests {
);
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_owned() {
let store_path_str_json =

View file

@ -22,7 +22,7 @@ data-encoding.workspace = true
ed25519.workspace = true
ed25519-dalek.workspace = true
futures.workspace = true
nix-compat = { path = "../nix-compat", features = ["async"] }
nix-compat = { path = "../nix-compat", features = ["async", "serde"] }
pin-project-lite.workspace = true
prost.workspace = true
serde = { workspace = true, features = ["derive"] }