nix_compat/store_path/
name.rs1use std::{borrow::Borrow, fmt, ops::Deref, str::FromStr};
2
3use smol_str::SmolStr;
4
5pub const DRV_EXT: &str = ".drv";
7
8pub const MAX_NAME_LEN: usize = 211;
10
11#[derive(Clone, Debug, Eq, PartialEq, Hash)]
16pub struct StorePathNameBuf(SmolStr);
17
18impl StorePathNameBuf {
19 pub const fn max_len() -> usize {
21 MAX_NAME_LEN
22 }
23
24 pub fn source() -> StorePathNameBuf {
33 StorePathName::source().to_owned()
34 }
35
36 pub fn from_string(name: String) -> Result<Self, ParseStorePathNameError> {
39 validate_name(name.as_bytes())?;
40 Ok(Self(SmolStr::new(name)))
41 }
42
43 pub fn from_smol_str(name: SmolStr) -> Result<Self, ParseStorePathNameError> {
46 validate_name(name.as_bytes())?;
47 Ok(Self(name))
48 }
49
50 pub fn from_slice(name: &[u8]) -> Result<Self, ParseStorePathNameError> {
53 let name = validate_name(name)?;
54 Ok(name.into())
55 }
56
57 #[must_use]
67 #[inline(always)]
68 pub fn as_name(&self) -> &StorePathName {
69 unsafe { StorePathName::from_str_unchecked(&self.0) }
71 }
72}
73
74impl Default for StorePathNameBuf {
75 fn default() -> Self {
76 Self::source()
77 }
78}
79
80impl TryFrom<String> for StorePathNameBuf {
81 type Error = ParseStorePathNameError;
82
83 fn try_from(name: String) -> Result<Self, Self::Error> {
84 StorePathNameBuf::from_string(name)
85 }
86}
87
88impl TryFrom<SmolStr> for StorePathNameBuf {
89 type Error = ParseStorePathNameError;
90
91 fn try_from(name: SmolStr) -> Result<Self, Self::Error> {
92 StorePathNameBuf::from_smol_str(name)
93 }
94}
95
96impl TryFrom<&[u8]> for StorePathNameBuf {
97 type Error = ParseStorePathNameError;
98
99 fn try_from(name: &[u8]) -> Result<Self, Self::Error> {
100 StorePathNameBuf::from_slice(name)
101 }
102}
103
104impl TryFrom<&str> for StorePathNameBuf {
105 type Error = ParseStorePathNameError;
106
107 fn try_from(name: &str) -> Result<Self, Self::Error> {
108 StorePathNameBuf::from_slice(name.as_bytes())
109 }
110}
111
112impl FromStr for StorePathNameBuf {
113 type Err = ParseStorePathNameError;
114
115 fn from_str(name: &str) -> Result<Self, Self::Err> {
116 StorePathNameBuf::from_slice(name.as_bytes())
117 }
118}
119
120impl AsRef<str> for StorePathNameBuf {
121 fn as_ref(&self) -> &str {
122 &self.0
123 }
124}
125
126impl AsRef<StorePathName> for StorePathNameBuf {
127 fn as_ref(&self) -> &StorePathName {
128 self.as_name()
129 }
130}
131
132impl Deref for StorePathNameBuf {
133 type Target = StorePathName;
134
135 fn deref(&self) -> &Self::Target {
136 self.as_name()
137 }
138}
139
140impl Borrow<StorePathName> for StorePathNameBuf {
141 fn borrow(&self) -> &StorePathName {
142 self.as_name()
143 }
144}
145
146impl<'a> From<&'a StorePathName> for StorePathNameBuf {
147 fn from(value: &'a StorePathName) -> Self {
148 value.to_owned()
149 }
150}
151
152impl PartialEq<&StorePathName> for StorePathNameBuf {
153 fn eq(&self, other: &&StorePathName) -> bool {
154 self.as_name() == *other
155 }
156}
157impl PartialEq<StorePathNameBuf> for &StorePathName {
158 fn eq(&self, other: &StorePathNameBuf) -> bool {
159 *self == other.as_name()
160 }
161}
162impl PartialEq<&str> for StorePathNameBuf {
163 fn eq(&self, other: &&str) -> bool {
164 self.as_str() == *other
165 }
166}
167impl PartialEq<str> for StorePathNameBuf {
168 fn eq(&self, other: &str) -> bool {
169 self.as_str() == other
170 }
171}
172impl PartialEq<StorePathNameBuf> for &str {
173 fn eq(&self, other: &StorePathNameBuf) -> bool {
174 *self == other.as_str()
175 }
176}
177impl PartialEq<StorePathNameBuf> for str {
178 fn eq(&self, other: &StorePathNameBuf) -> bool {
179 self == other.as_str()
180 }
181}
182impl PartialEq<String> for StorePathNameBuf {
183 fn eq(&self, other: &String) -> bool {
184 self.as_str() == other.as_str()
185 }
186}
187impl PartialEq<StorePathNameBuf> for String {
188 fn eq(&self, other: &StorePathNameBuf) -> bool {
189 self.as_str() == other.as_str()
190 }
191}
192
193impl fmt::Display for StorePathNameBuf {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 f.write_str(&self.0)
196 }
197}
198
199#[derive(Debug, Eq, PartialEq, Hash)]
204#[repr(transparent)]
205pub struct StorePathName(str);
206
207impl StorePathName {
208 pub const fn max_len() -> usize {
210 MAX_NAME_LEN
211 }
212
213 pub const fn source() -> &'static StorePathName {
222 unsafe { StorePathName::from_str_unchecked("source") }
224 }
225
226 pub const unsafe fn from_str_unchecked(name: &str) -> &StorePathName {
233 unsafe { &*(name as *const str as *const StorePathName) }
235 }
236
237 #[expect(clippy::should_implement_trait)]
240 pub fn from_str(name: &str) -> Result<&StorePathName, ParseStorePathNameError> {
241 validate_name(name.as_bytes())
242 }
243
244 pub fn from_slice(name: &[u8]) -> Result<&StorePathName, ParseStorePathNameError> {
247 validate_name(name)
248 }
249
250 pub fn from_os_str(name: &std::ffi::OsStr) -> Result<&StorePathName, ParseStorePathNameError> {
253 validate_name(name.as_encoded_bytes())
254 }
255
256 pub fn build_drv_name(&self) -> Result<StorePathNameBuf, ParseStorePathNameError> {
267 if self.is_drv() {
268 return Err(ParseStorePathNameError::Name);
269 }
270 let s = smol_str::format_smolstr!("{self}{DRV_EXT}");
271 StorePathNameBuf::from_smol_str(s)
272 }
273
274 #[must_use]
291 pub fn is_drv(&self) -> bool {
292 self.0.len() > DRV_EXT.len() && self.0.ends_with(DRV_EXT)
293 }
294
295 pub fn strip_drv_ext(&self) -> Option<&StorePathName> {
297 self.strip_suffix(DRV_EXT)
298 }
299
300 pub fn strip_suffix(&self, suffix: &str) -> Option<&StorePathName> {
302 if let Some(new_name) = self.0.strip_suffix(suffix)
303 && !new_name.is_empty()
304 {
305 unsafe { Some(StorePathName::from_str_unchecked(new_name)) }
308 } else {
309 None
310 }
311 }
312
313 #[must_use]
322 #[inline]
323 #[expect(clippy::len_without_is_empty)]
324 pub const fn len(&self) -> usize {
325 self.0.len()
326 }
327
328 #[must_use]
339 #[inline(always)]
340 pub const fn as_str(&self) -> &str {
341 &self.0
342 }
343
344 #[must_use]
355 #[inline(always)]
356 pub const fn as_bytes(&self) -> &[u8] {
357 self.0.as_bytes()
358 }
359}
360
361impl AsRef<str> for StorePathName {
362 fn as_ref(&self) -> &str {
363 self.as_str()
364 }
365}
366
367impl AsRef<[u8]> for StorePathName {
368 fn as_ref(&self) -> &[u8] {
369 self.as_bytes()
370 }
371}
372
373impl AsRef<StorePathName> for StorePathName {
374 fn as_ref(&self) -> &StorePathName {
375 self
376 }
377}
378
379impl<'a> TryFrom<&'a [u8]> for &'a StorePathName {
380 type Error = ParseStorePathNameError;
381
382 fn try_from(name: &'a [u8]) -> Result<Self, Self::Error> {
383 StorePathName::from_slice(name)
384 }
385}
386
387impl<'a> TryFrom<&'a str> for &'a StorePathName {
388 type Error = ParseStorePathNameError;
389
390 fn try_from(name: &'a str) -> Result<Self, Self::Error> {
391 StorePathName::from_str(name)
392 }
393}
394
395impl PartialEq<&str> for StorePathName {
396 fn eq(&self, other: &&str) -> bool {
397 self.as_str() == *other
398 }
399}
400impl PartialEq<str> for StorePathName {
401 fn eq(&self, other: &str) -> bool {
402 self.as_str() == other
403 }
404}
405impl PartialEq<StorePathName> for &str {
406 fn eq(&self, other: &StorePathName) -> bool {
407 *self == other.as_str()
408 }
409}
410impl PartialEq<StorePathName> for str {
411 fn eq(&self, other: &StorePathName) -> bool {
412 self == other.as_str()
413 }
414}
415impl PartialEq<String> for StorePathName {
416 fn eq(&self, other: &String) -> bool {
417 self.as_str() == other.as_str()
418 }
419}
420impl PartialEq<StorePathName> for String {
421 fn eq(&self, other: &StorePathName) -> bool {
422 self.as_str() == other.as_str()
423 }
424}
425
426impl ToOwned for StorePathName {
427 type Owned = StorePathNameBuf;
428
429 fn to_owned(&self) -> Self::Owned {
430 StorePathNameBuf(SmolStr::new(&self.0))
431 }
432}
433
434impl fmt::Display for StorePathName {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 f.write_str(&self.0)
437 }
438}
439
440#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)]
442pub enum ParseStorePathNameError {
443 #[error("Invalid length")]
445 Length,
446 #[error("Invalid name")]
448 Name,
449}
450
451static NAME_CHARS: [bool; 256] = {
453 let mut tbl = [false; 256];
454 let mut c = 0;
455
456 loop {
457 tbl[c as usize] = matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'+' | b'-' | b'_' | b'?' | b'=' | b'.');
458
459 if c == u8::MAX {
460 break;
461 }
462
463 c += 1;
464 }
465
466 tbl
467};
468
469fn validate_name(s: &[u8]) -> Result<&StorePathName, ParseStorePathNameError> {
472 if s.is_empty() || s.len() > MAX_NAME_LEN {
474 return Err(ParseStorePathNameError::Length);
475 }
476
477 let mut valid = true;
478 for &c in s {
479 valid = valid && NAME_CHARS[c as usize];
480 }
481
482 if !valid {
483 for &c in s.iter() {
484 if !NAME_CHARS[c as usize] {
485 return Err(ParseStorePathNameError::Name);
486 }
487 }
488
489 unreachable!();
490 }
491
492 let name = unsafe { std::str::from_utf8_unchecked(s) };
494 let ret = unsafe { StorePathName::from_str_unchecked(name) };
496 Ok(ret)
497}