Skip to main content

nix_compat/store_path/
borrowed.rs

1use std::fmt;
2
3use crate::{
4    nixbase32,
5    store_path::{
6        DIGEST_SIZE, ENCODED_DIGEST_SIZE, ParseStorePathError, STORE_DIR_WITH_SLASH, StorePath,
7        StorePathName,
8    },
9};
10
11/// Like [StorePath], but with a `&StorePathName` for the name.
12#[derive(Clone, Debug, Eq, PartialEq, Hash)]
13pub struct StorePathRef<'a> {
14    digest: [u8; DIGEST_SIZE],
15    name: &'a StorePathName,
16}
17
18impl<'a> StorePathRef<'a> {
19    pub fn digest(&self) -> &[u8; DIGEST_SIZE] {
20        &self.digest
21    }
22
23    pub fn name(&self) -> &'a StorePathName {
24        self.name
25    }
26
27    pub fn to_owned(&self) -> StorePath {
28        StorePath {
29            digest: self.digest,
30            name: self.name.to_owned(),
31        }
32    }
33
34    /// Construct by passing the `$digest-$name` string that comes after
35    /// [STORE_DIR_WITH_SLASH].
36    pub fn from_bytes(s: &'a [u8]) -> Result<Self, ParseStorePathError> {
37        // the whole string needs to be at least:
38        //
39        // - 32 characters (encoded hash)
40        // - 1 dash
41        // - 1 character for the name
42        if s.len() < ENCODED_DIGEST_SIZE + 2 {
43            Err(ParseStorePathError::Length)?
44        }
45
46        let digest = nixbase32::decode_fixed(&s[..ENCODED_DIGEST_SIZE])?;
47
48        if s[ENCODED_DIGEST_SIZE] != b'-' {
49            return Err(ParseStorePathError::MissingDash);
50        }
51
52        Ok(Self {
53            digest,
54            name: StorePathName::from_slice(&s[ENCODED_DIGEST_SIZE + 1..])?,
55        })
56    }
57
58    /// Construct from a name and digest.
59    /// The name is validated, and the digest checked for size.
60    pub fn from_name_and_digest(
61        name: &'a StorePathName,
62        digest: &[u8],
63    ) -> Result<Self, ParseStorePathError> {
64        let digest_fixed = digest.try_into().map_err(|_| ParseStorePathError::Length)?;
65        Ok(Self::from_name_and_digest_fixed(name, digest_fixed))
66    }
67
68    /// Construct from a name and digest of correct length.
69    /// The name is validated.
70    pub fn from_name_and_digest_fixed(name: &'a StorePathName, digest: [u8; DIGEST_SIZE]) -> Self {
71        Self { name, digest }
72    }
73
74    /// Construct from an absolute store path string.
75    /// This is equivalent to calling [StorePathRef::from_bytes], but stripping
76    /// the [STORE_DIR_WITH_SLASH] prefix before.
77    pub fn from_absolute_path(s: &'a [u8]) -> Result<Self, ParseStorePathError> {
78        match s.strip_prefix(STORE_DIR_WITH_SLASH.as_bytes()) {
79            Some(s_stripped) => Self::from_bytes(s_stripped),
80            None => Err(ParseStorePathError::MissingStoreDir),
81        }
82    }
83
84    /// Decompose a string into a [StorePathRef] and a [std::path::Path]
85    /// containing the rest of the path, or an error.
86    pub fn from_absolute_path_full<'p, P>(
87        path: &'p P,
88    ) -> Result<(Self, &'p std::path::Path), ParseStorePathError>
89    where
90        P: AsRef<std::path::Path> + 'p + ?Sized,
91        'p: 'a,
92    {
93        // strip [STORE_DIR_WITH_SLASH] from path
94        let p = path
95            .as_ref()
96            .strip_prefix(STORE_DIR_WITH_SLASH)
97            .map_err(|_| ParseStorePathError::MissingStoreDir)?;
98
99        let mut components = p.components();
100
101        use bstr::ByteSlice;
102        let first_component = <[u8]>::from_os_str(
103            components
104                .next()
105                .ok_or(ParseStorePathError::Length)?
106                .as_os_str(),
107        )
108        .ok_or(ParseStorePathError::Name)?;
109
110        // The first component must be parse-able as a [StorePath].
111        if first_component.len() < 34 {
112            return Err(ParseStorePathError::Length);
113        }
114
115        let store_path = Self::from_bytes(first_component)?;
116
117        Ok((store_path, components.as_path()))
118    }
119
120    /// Returns as an absolute store path (prefixed with [STORE_DIR_WITH_SLASH]).
121    pub fn to_absolute_path(&self) -> String {
122        self.as_absolute_path_fmt().to_string()
123    }
124
125    /// Returns a formatter writing an absolute store path (prefixed with [STORE_DIR_WITH_SLASH]).
126    pub fn as_absolute_path_fmt(&'a self) -> impl std::fmt::Display + 'a {
127        struct WithAbsolutePath<'a>(&'a StorePathRef<'a>);
128
129        impl std::fmt::Display for WithAbsolutePath<'_> {
130            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131                write!(f, "{STORE_DIR_WITH_SLASH}{}", self.0)
132            }
133        }
134        WithAbsolutePath(self)
135    }
136}
137
138impl fmt::Display for StorePathRef<'_> {
139    /// The string representation of a store path starts with a digest (20
140    /// bytes), [crate::nixbase32]-encoded, followed by a `-`,
141    /// and ends with the name.
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        write!(f, "{}-{}", nixbase32::encode(&self.digest), self.name)
144    }
145}
146
147impl PartialOrd for StorePathRef<'_> {
148    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
149        Some(self.cmp(other))
150    }
151}
152
153impl<'a> From<StorePathRef<'a>> for StorePath {
154    fn from(value: StorePathRef<'a>) -> Self {
155        Self {
156            digest: value.digest,
157            name: value.name.to_owned(),
158        }
159    }
160}
161
162impl StorePath {
163    pub fn as_ref(&self) -> StorePathRef<'_> {
164        StorePathRef {
165            digest: self.digest,
166            name: &self.name,
167        }
168    }
169}
170
171/// `StorePath`s are sorted by their reverse digest to match the sorting order
172/// of the nixbase32-encoded string.
173impl Ord for StorePathRef<'_> {
174    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
175        self.digest.iter().rev().cmp(other.digest.iter().rev())
176    }
177}
178
179// Ensures it's possible to get() from a HashMap using StorePathRef<'_>,
180// even if it itself uses StorePath<String>
181#[cfg(feature = "hashbrown")]
182impl hashbrown::Equivalent<StorePath> for StorePathRef<'_> {
183    fn equivalent(&self, key: &StorePath) -> bool {
184        self.digest() == key.digest() && self.name() == key.name()
185    }
186}
187
188#[cfg(feature = "serde")]
189impl<'a, 'de: 'a> serde::Deserialize<'de> for StorePathRef<'a> {
190    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
191    where
192        D: serde::Deserializer<'de>,
193    {
194        let string: &'de str = serde::Deserialize::deserialize(deserializer)?;
195        let stripped: Option<&str> = string.strip_prefix(STORE_DIR_WITH_SLASH);
196        let stripped: &str = stripped.ok_or_else(|| {
197            serde::de::Error::invalid_value(
198                serde::de::Unexpected::Str(string),
199                &"store path prefix",
200            )
201        })?;
202        StorePathRef::from_bytes(stripped.as_bytes()).map_err(|_| {
203            serde::de::Error::invalid_value(serde::de::Unexpected::Str(string), &"StorePath")
204        })
205    }
206}
207
208#[cfg(feature = "serde")]
209impl serde::Serialize for StorePathRef<'_> {
210    fn serialize<SR>(&self, serializer: SR) -> Result<SR::Ok, SR::Error>
211    where
212        SR: serde::Serializer,
213    {
214        let string: String = self.to_absolute_path();
215        string.serialize(serializer)
216    }
217}