nix_compat/store_path/
borrowed.rs1use smol_str::SmolStr;
2use std::fmt;
3
4use crate::{
5 nixbase32,
6 store_path::{
7 DIGEST_SIZE, ENCODED_DIGEST_SIZE, ParseStorePathError, STORE_DIR_WITH_SLASH, StorePath,
8 validate_name,
9 },
10};
11
12#[derive(Clone, Debug, Eq, PartialEq, Hash)]
14pub struct StorePathRef<'a> {
15 digest: [u8; DIGEST_SIZE],
16 name: &'a str,
17}
18
19impl<'a> StorePathRef<'a> {
20 pub fn digest(&self) -> &[u8; DIGEST_SIZE] {
21 &self.digest
22 }
23
24 pub fn name(&self) -> &'a str {
25 self.name
26 }
27
28 pub fn to_owned(&self) -> StorePath {
29 StorePath {
30 digest: self.digest,
31 name: SmolStr::new(self.name),
32 }
33 }
34
35 pub fn from_bytes(s: &'a [u8]) -> Result<Self, ParseStorePathError> {
38 if s.len() < ENCODED_DIGEST_SIZE + 2 {
44 Err(ParseStorePathError::Length)?
45 }
46
47 let digest = nixbase32::decode_fixed(&s[..ENCODED_DIGEST_SIZE])?;
48
49 if s[ENCODED_DIGEST_SIZE] != b'-' {
50 return Err(ParseStorePathError::MissingDash);
51 }
52
53 Ok(Self {
54 digest,
55 name: validate_name(&s[ENCODED_DIGEST_SIZE + 1..])?,
56 })
57 }
58
59 pub fn from_name_and_digest(name: &'a str, digest: &[u8]) -> Result<Self, ParseStorePathError> {
62 let digest_fixed = digest.try_into().map_err(|_| ParseStorePathError::Length)?;
63 Self::from_name_and_digest_fixed(name, digest_fixed)
64 }
65
66 pub fn from_name_and_digest_fixed(
69 name: &'a str,
70 digest: [u8; DIGEST_SIZE],
71 ) -> Result<Self, ParseStorePathError> {
72 Ok(Self {
73 name: validate_name(name)?,
74 digest,
75 })
76 }
77
78 pub fn from_absolute_path(s: &'a [u8]) -> Result<Self, ParseStorePathError> {
82 match s.strip_prefix(STORE_DIR_WITH_SLASH.as_bytes()) {
83 Some(s_stripped) => Self::from_bytes(s_stripped),
84 None => Err(ParseStorePathError::MissingStoreDir),
85 }
86 }
87
88 pub fn from_absolute_path_full<'p, P>(
91 path: &'p P,
92 ) -> Result<(Self, &'p std::path::Path), ParseStorePathError>
93 where
94 P: AsRef<std::path::Path> + 'p + ?Sized,
95 'p: 'a,
96 {
97 let p = path
99 .as_ref()
100 .strip_prefix(STORE_DIR_WITH_SLASH)
101 .map_err(|_| ParseStorePathError::MissingStoreDir)?;
102
103 let mut components = p.components();
104
105 use bstr::ByteSlice;
106 let first_component = <[u8]>::from_os_str(
107 components
108 .next()
109 .ok_or(ParseStorePathError::Length)?
110 .as_os_str(),
111 )
112 .ok_or(ParseStorePathError::Name)?;
113
114 if first_component.len() < 34 {
116 return Err(ParseStorePathError::Length);
117 }
118
119 let store_path = Self::from_bytes(first_component)?;
120
121 Ok((store_path, components.as_path()))
122 }
123
124 pub fn to_absolute_path(&self) -> String {
126 self.as_absolute_path_fmt().to_string()
127 }
128
129 pub fn as_absolute_path_fmt(&'a self) -> impl std::fmt::Display + 'a {
131 struct WithAbsolutePath<'a>(&'a StorePathRef<'a>);
132
133 impl std::fmt::Display for WithAbsolutePath<'_> {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(f, "{STORE_DIR_WITH_SLASH}{}", self.0)
136 }
137 }
138 WithAbsolutePath(self)
139 }
140}
141
142impl fmt::Display for StorePathRef<'_> {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{}-{}", nixbase32::encode(&self.digest), self.name)
148 }
149}
150
151impl PartialOrd for StorePathRef<'_> {
152 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
153 Some(self.cmp(other))
154 }
155}
156
157impl<'a> From<StorePathRef<'a>> for StorePath {
158 fn from(value: StorePathRef<'a>) -> Self {
159 Self {
160 digest: value.digest,
161 name: SmolStr::new(value.name),
162 }
163 }
164}
165
166impl StorePath {
167 pub fn as_ref(&self) -> StorePathRef<'_> {
168 StorePathRef {
169 digest: self.digest,
170 name: &self.name,
171 }
172 }
173}
174
175impl Ord for StorePathRef<'_> {
178 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
179 self.digest.iter().rev().cmp(other.digest.iter().rev())
180 }
181}
182
183#[cfg(feature = "hashbrown")]
186impl hashbrown::Equivalent<StorePath> for StorePathRef<'_> {
187 fn equivalent(&self, key: &StorePath) -> bool {
188 self.digest() == key.digest() && self.name() == key.name()
189 }
190}
191
192#[cfg(feature = "serde")]
193impl<'a, 'de: 'a> serde::Deserialize<'de> for StorePathRef<'a> {
194 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
195 where
196 D: serde::Deserializer<'de>,
197 {
198 let string: &'de str = serde::Deserialize::deserialize(deserializer)?;
199 let stripped: Option<&str> = string.strip_prefix(STORE_DIR_WITH_SLASH);
200 let stripped: &str = stripped.ok_or_else(|| {
201 serde::de::Error::invalid_value(
202 serde::de::Unexpected::Str(string),
203 &"store path prefix",
204 )
205 })?;
206 StorePathRef::from_bytes(stripped.as_bytes()).map_err(|_| {
207 serde::de::Error::invalid_value(serde::de::Unexpected::Str(string), &"StorePath")
208 })
209 }
210}
211
212#[cfg(feature = "serde")]
213impl serde::Serialize for StorePathRef<'_> {
214 fn serialize<SR>(&self, serializer: SR) -> Result<SR::Ok, SR::Error>
215 where
216 SR: serde::Serializer,
217 {
218 let string: String = self.to_absolute_path();
219 string.serialize(serializer)
220 }
221}