nix_compat/
path_info.rs

1#[cfg(feature = "serde")]
2use crate::nixhash;
3use crate::{narinfo::SignatureRef, store_path::StorePathRef};
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeSet;
7
8/// Represents information about a Store Path that Nix provides inside the build
9/// if the exportReferencesGraph feature is used.
10/// This is not to be confused with the format Nix uses in its `nix path-info` command.
11/// It includes some more fields, like `registrationTime`, `signatures` and `ultimate`,
12/// does not include the `closureSize` and encodes `narHash` as SRI.
13#[derive(Clone, Debug, Eq, PartialEq, Hash)]
14#[cfg_attr(
15    feature = "serde",
16    derive(Serialize, Deserialize),
17    serde(rename_all = "camelCase")
18)]
19pub struct ExportedPathInfo<'a> {
20    pub closure_size: u64,
21
22    #[cfg_attr(
23        feature = "serde",
24        serde(
25            rename = "narHash",
26            serialize_with = "nixhash::serde::to_nix_nixbase32",
27            deserialize_with = "nixhash::serde::from_nix_nixbase32_or_sri"
28        )
29    )]
30    pub nar_sha256: [u8; 32],
31
32    pub nar_size: u64,
33
34    #[cfg_attr(feature = "serde", serde(borrow))]
35    pub path: StorePathRef<'a>,
36
37    #[cfg_attr(
38        feature = "serde",
39        serde(borrow, skip_serializing_if = "Option::is_none")
40    )]
41    pub deriver: Option<StorePathRef<'a>>,
42
43    /// The list of other Store Paths this Store Path refers to.
44    /// StorePathRef does Ord by the nixbase32-encoded string repr, so this is correct.
45    pub references: BTreeSet<StorePathRef<'a>>,
46    // more recent versions of Nix also have a `valid: true` field here, Nix 2.3 doesn't,
47    // and nothing seems to use it.
48    #[cfg_attr(
49        feature = "serde",
50        serde(default, skip_serializing_if = "Vec::is_empty")
51    )]
52    pub signatures: Vec<SignatureRef<'a>>,
53}
54
55/// ExportedPathInfo are ordered by their `path` field.
56impl Ord for ExportedPathInfo<'_> {
57    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
58        self.path.cmp(&other.path)
59    }
60}
61
62impl PartialOrd for ExportedPathInfo<'_> {
63    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
64        Some(self.cmp(other))
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    #[cfg(feature = "serde")]
71    use hex_literal::hex;
72
73    #[cfg(feature = "serde")]
74    use super::*;
75
76    /// Ensure we can create the same JSON as the exportReferencesGraph feature
77    #[cfg(feature = "serde")]
78    #[test]
79    fn serialize_deserialize() {
80        // JSON extracted from a build of
81        // stdenv.mkDerivation { name = "hello"; __structuredAttrs = true; exportReferencesGraph.blub = [ pkgs.hello ]; nativeBuildInputs = [pkgs.jq]; buildCommand = "jq -rc .blub $NIX_ATTRS_JSON_FILE > $out"; }
82        let pathinfos_str_json = r#"[{"closureSize":1828984,"narHash":"sha256:11vm2x1ajhzsrzw7lsyss51mmr3b6yll9wdjn51bh7liwkpc8ila","narSize":1828984,"path":"/nix/store/7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1","references":["/nix/store/7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1"]},{"closureSize":32696176,"narHash":"sha256:0alzbhjxdcsmr1pk7z0bdh46r2xpq3xs3k9y82bi4bx5pklcvw5x","narSize":226560,"path":"/nix/store/dbghhbq1x39yxgkv3vkgfwbxrmw9nfzi-hello-2.12.1","references":["/nix/store/dbghhbq1x39yxgkv3vkgfwbxrmw9nfzi-hello-2.12.1","/nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5"]},{"closureSize":32469616,"narHash":"sha256:1zw5p05fh0k836ybfxkskv8apcv2m3pm2wa6y90wqn5w5kjyj13c","narSize":30119936,"path":"/nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5","references":["/nix/store/ddwyrxif62r8n6xclvskjyy6szdhvj60-glibc-2.39-5","/nix/store/rxganm4ibf31qngal3j3psp20mak37yy-xgcc-13.2.0-libgcc","/nix/store/s32cldbh9pfzd9z82izi12mdlrw0yf8q-libidn2-2.3.7"]},{"closureSize":159560,"narHash":"sha256:10q8iyvfmpfck3yiisnj1j8vp6lq3km17r26sr95zpdf9mgmk69s","narSize":159560,"path":"/nix/store/rxganm4ibf31qngal3j3psp20mak37yy-xgcc-13.2.0-libgcc","references":[]},{"closureSize":2190120,"narHash":"sha256:1cv997nzxbd91jhmzwnhxa1ahlzp5ffli8m4a5npcq8zg0vb1kwg","narSize":361136,"path":"/nix/store/s32cldbh9pfzd9z82izi12mdlrw0yf8q-libidn2-2.3.7","references":["/nix/store/7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1","/nix/store/s32cldbh9pfzd9z82izi12mdlrw0yf8q-libidn2-2.3.7"]}]"#;
83
84        // We ensure it roundtrips (to check the sorting is correct)
85        let deserialized: BTreeSet<ExportedPathInfo> =
86            serde_json::from_str(pathinfos_str_json).expect("must serialize");
87
88        let serialized_again = serde_json::to_string(&deserialized).expect("must deserialize");
89        assert_eq!(pathinfos_str_json, serialized_again);
90
91        // Also compare one specific item to be populated as expected.
92        assert_eq!(
93            &ExportedPathInfo {
94                closure_size: 1828984,
95                nar_sha256: hex!(
96                    "8a46c4eee4911eb842b1b2f144a9376be45a43d1da6b7af8cffa43a942177587"
97                ),
98                nar_size: 1828984,
99                path: StorePathRef::from_bytes(
100                    b"7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1"
101                )
102                .expect("must parse"),
103                deriver: None,
104                references: BTreeSet::from_iter([StorePathRef::from_bytes(
105                    b"7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1"
106                )
107                .unwrap()]),
108                signatures: vec![],
109            },
110            deserialized.first().unwrap()
111        );
112    }
113
114    /// Ensure we can parse output from `nix path-info --json``
115    #[cfg(feature = "serde")]
116    #[test]
117    fn serialize_deserialize_from_path_info() {
118        // JSON extracted from
119        // nix path-info /nix/store/z6r3bn5l51679pwkvh9nalp6c317z34m-libcxx-16.0.6-dev --json --closure-size
120        let pathinfos_str_json = r#"[{"closureSize":10756176,"deriver":"/nix/store/vs9976cyyxpykvdnlv7x85fpp3shn6ij-libcxx-16.0.6.drv","narHash":"sha256-E73Nt0NAKGxCnsyBFDUaCAbA+wiF5qjq1O9J7WrnT0E=","narSize":7020664,"path":"/nix/store/z6r3bn5l51679pwkvh9nalp6c317z34m-libcxx-16.0.6-dev","references":["/nix/store/lzzd5jgybnpfj86xkcpnd54xgwc4m457-libcxx-16.0.6"],"registrationTime":1730048276,"signatures":["cache.nixos.org-1:cTdhK6hnpPwtMXFX43CYb7v+CbpAusVI/MORZ3v5aHvpBYNg1MfBHVVeoexMBpNtHA8uFAn0aEsJaLXYIDhJDg=="],"valid":true}]"#;
121
122        let deserialized: BTreeSet<ExportedPathInfo> =
123            serde_json::from_str(pathinfos_str_json).expect("must serialize");
124
125        assert_eq!(
126            &ExportedPathInfo {
127                closure_size: 10756176,
128                nar_sha256: hex!(
129                    "13bdcdb74340286c429ecc8114351a0806c0fb0885e6a8ead4ef49ed6ae74f41"
130                ),
131                nar_size: 7020664,
132                path: StorePathRef::from_bytes(
133                    b"z6r3bn5l51679pwkvh9nalp6c317z34m-libcxx-16.0.6-dev"
134                )
135                .expect("must parse"),
136                deriver: Some(
137                    StorePathRef::from_bytes(
138                        b"vs9976cyyxpykvdnlv7x85fpp3shn6ij-libcxx-16.0.6.drv"
139                    )
140                    .expect("must parse")
141                ),
142                references: BTreeSet::from_iter([StorePathRef::from_bytes(
143                    b"lzzd5jgybnpfj86xkcpnd54xgwc4m457-libcxx-16.0.6"
144                )
145                .unwrap()]),
146                signatures: vec![SignatureRef::parse("cache.nixos.org-1:cTdhK6hnpPwtMXFX43CYb7v+CbpAusVI/MORZ3v5aHvpBYNg1MfBHVVeoexMBpNtHA8uFAn0aEsJaLXYIDhJDg==").expect("must parse")],
147            },
148            deserialized.first().unwrap()
149        );
150    }
151}