1use crate::{narinfo::SignatureRef, nixbase32, nixhash::NixHash, store_path::StorePathRef};
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeSet;
4
5#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
11pub struct ExportedPathInfo<'a> {
12 #[serde(rename = "closureSize")]
13 pub closure_size: u64,
14
15 #[serde(
16 rename = "narHash",
17 serialize_with = "to_nix_nixbase32_string",
18 deserialize_with = "from_nix_hash_string"
19 )]
20 pub nar_sha256: [u8; 32],
21
22 #[serde(rename = "narSize")]
23 pub nar_size: u64,
24
25 #[serde(borrow)]
26 pub path: StorePathRef<'a>,
27
28 #[serde(borrow)]
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub deriver: Option<StorePathRef<'a>>,
31
32 pub references: BTreeSet<StorePathRef<'a>>,
35 #[serde(default, skip_serializing_if = "Vec::is_empty")]
38 pub signatures: Vec<SignatureRef<'a>>,
39}
40
41impl Ord for ExportedPathInfo<'_> {
43 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
44 self.path.cmp(&other.path)
45 }
46}
47
48impl PartialOrd for ExportedPathInfo<'_> {
49 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
50 Some(self.cmp(other))
51 }
52}
53
54fn to_nix_nixbase32_string<S>(v: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
55where
56 S: serde::Serializer,
57{
58 let string = NixHash::Sha256(*v).to_nix_nixbase32_string();
59 string.serialize(serializer)
60}
61
62const NIXBASE32_SHA256_ENCODE_LEN: usize = nixbase32::encode_len(32);
64
65fn from_nix_hash_string<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
66where
67 D: serde::Deserializer<'de>,
68{
69 let str: &'de str = Deserialize::deserialize(deserializer)?;
70 if let Some(digest_str) = str.strip_prefix("sha256:") {
71 return from_nix_nixbase32_string::<D>(digest_str);
72 }
73 if let Some(digest_str) = str.strip_prefix("sha256-") {
74 return from_sri_string::<D>(digest_str);
75 }
76 Err(serde::de::Error::invalid_value(
77 serde::de::Unexpected::Str(str),
78 &"extected a valid nixbase32 or sri narHash",
79 ))
80}
81
82fn from_sri_string<'de, D>(str: &str) -> Result<[u8; 32], D::Error>
83where
84 D: serde::Deserializer<'de>,
85{
86 let digest: [u8; 32] = data_encoding::BASE64
87 .decode(str.as_bytes())
88 .map_err(|_| {
89 serde::de::Error::invalid_value(
90 serde::de::Unexpected::Str(str),
91 &"valid base64 encoded string",
92 )
93 })?
94 .try_into()
95 .map_err(|_| {
96 serde::de::Error::invalid_value(serde::de::Unexpected::Str(str), &"valid digest len")
97 })?;
98
99 Ok(digest)
100}
101
102fn from_nix_nixbase32_string<'de, D>(str: &str) -> Result<[u8; 32], D::Error>
103where
104 D: serde::Deserializer<'de>,
105{
106 let digest_str: [u8; NIXBASE32_SHA256_ENCODE_LEN] =
107 str.as_bytes().try_into().map_err(|_| {
108 serde::de::Error::invalid_value(serde::de::Unexpected::Str(str), &"valid digest len")
109 })?;
110
111 let digest: [u8; 32] = nixbase32::decode_fixed(digest_str).map_err(|_| {
112 serde::de::Error::invalid_value(serde::de::Unexpected::Str(str), &"valid nixbase32")
113 })?;
114
115 Ok(digest)
116}
117
118#[cfg(test)]
119mod tests {
120 use hex_literal::hex;
121
122 use super::*;
123
124 #[test]
126 fn serialize_deserialize() {
127 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"]}]"#;
130
131 let deserialized: BTreeSet<ExportedPathInfo> =
133 serde_json::from_str(pathinfos_str_json).expect("must serialize");
134
135 let serialized_again = serde_json::to_string(&deserialized).expect("must deserialize");
136 assert_eq!(pathinfos_str_json, serialized_again);
137
138 assert_eq!(
140 &ExportedPathInfo {
141 closure_size: 1828984,
142 nar_sha256: hex!(
143 "8a46c4eee4911eb842b1b2f144a9376be45a43d1da6b7af8cffa43a942177587"
144 ),
145 nar_size: 1828984,
146 path: StorePathRef::from_bytes(
147 b"7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1"
148 )
149 .expect("must parse"),
150 deriver: None,
151 references: BTreeSet::from_iter([StorePathRef::from_bytes(
152 b"7n0mbqydcipkpbxm24fab066lxk68aqk-libunistring-1.1"
153 )
154 .unwrap()]),
155 signatures: vec![],
156 },
157 deserialized.first().unwrap()
158 );
159 }
160
161 #[test]
163 fn serialize_deserialize_from_path_info() {
164 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}]"#;
167
168 let deserialized: BTreeSet<ExportedPathInfo> =
169 serde_json::from_str(pathinfos_str_json).expect("must serialize");
170
171 assert_eq!(
172 &ExportedPathInfo {
173 closure_size: 10756176,
174 nar_sha256: hex!(
175 "13bdcdb74340286c429ecc8114351a0806c0fb0885e6a8ead4ef49ed6ae74f41"
176 ),
177 nar_size: 7020664,
178 path: StorePathRef::from_bytes(
179 b"z6r3bn5l51679pwkvh9nalp6c317z34m-libcxx-16.0.6-dev"
180 )
181 .expect("must parse"),
182 deriver: Some(
183 StorePathRef::from_bytes(
184 b"vs9976cyyxpykvdnlv7x85fpp3shn6ij-libcxx-16.0.6.drv"
185 )
186 .expect("must parse")
187 ),
188 references: BTreeSet::from_iter([StorePathRef::from_bytes(
189 b"lzzd5jgybnpfj86xkcpnd54xgwc4m457-libcxx-16.0.6"
190 )
191 .unwrap()]),
192 signatures: vec![SignatureRef::parse("cache.nixos.org-1:cTdhK6hnpPwtMXFX43CYb7v+CbpAusVI/MORZ3v5aHvpBYNg1MfBHVVeoexMBpNtHA8uFAn0aEsJaLXYIDhJDg==").expect("must parse")],
193 },
194 deserialized.first().unwrap()
195 );
196 }
197}