nix_compat/derivation/
output_name.rs1use std::{borrow::Cow, fmt, str::FromStr};
2
3use smol_str::SmolStr;
4
5use crate::store_path;
6
7#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[cfg_attr(
15 feature = "serde",
16 derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
17)]
18pub struct OutputName(SmolStr);
19
20impl OutputName {
21 pub fn as_str(&self) -> &str {
23 self.0.as_str()
24 }
25
26 pub fn from_static(s: &'static str) -> Result<Self, ParseOutputNameError> {
28 validate(s)?;
29
30 Ok(Self(SmolStr::new_static(s)))
31 }
32
33 pub const fn out() -> Self {
35 Self(SmolStr::new_static("out"))
36 }
37
38 pub fn build_store_path_name<'n>(
41 &self,
42 name: &'n store_path::StorePathName,
43 ) -> Result<Cow<'n, store_path::StorePathName>, store_path::ParseStorePathNameError> {
44 if self == &Self::out() {
45 Ok(Cow::Borrowed(name))
46 } else {
47 let s = smol_str::format_smolstr!("{name}-{self}");
48 Ok(Cow::Owned(s.try_into()?))
49 }
50 }
51}
52
53fn validate<S: AsRef<str>>(s: S) -> Result<(), ParseOutputNameError> {
54 let name = s.as_ref();
55 store_path::StorePathName::from_slice(name.as_bytes())?;
56
57 if name == "drv" {
60 return Err(ParseOutputNameError::ReservedNameDrv);
61 }
62
63 Ok(())
64}
65
66impl PartialEq<&str> for OutputName {
67 fn eq(&self, other: &&str) -> bool {
68 self.as_str() == *other
69 }
70}
71impl PartialEq<str> for OutputName {
72 fn eq(&self, other: &str) -> bool {
73 self.as_str() == other
74 }
75}
76impl PartialEq<OutputName> for &str {
77 fn eq(&self, other: &OutputName) -> bool {
78 *self == other.as_str()
79 }
80}
81impl PartialEq<OutputName> for str {
82 fn eq(&self, other: &OutputName) -> bool {
83 self == other.as_str()
84 }
85}
86impl PartialEq<String> for OutputName {
87 fn eq(&self, other: &String) -> bool {
88 self.as_str() == other.as_str()
89 }
90}
91
92impl PartialEq<OutputName> for String {
93 fn eq(&self, other: &OutputName) -> bool {
94 self.as_str() == other.as_str()
95 }
96}
97
98impl fmt::Display for OutputName {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 f.write_str(self.as_str())
101 }
102}
103
104impl AsRef<str> for OutputName {
105 fn as_ref(&self) -> &str {
106 self.as_str()
107 }
108}
109
110impl std::borrow::Borrow<str> for OutputName {
111 fn borrow(&self) -> &str {
112 self.as_str()
113 }
114}
115
116impl Default for OutputName {
117 fn default() -> Self {
118 Self::out()
119 }
120}
121
122impl FromStr for OutputName {
123 type Err = ParseOutputNameError;
124
125 fn from_str(s: &str) -> Result<Self, Self::Err> {
126 validate(s)?;
127
128 Ok(Self(SmolStr::new(s)))
129 }
130}
131
132impl TryFrom<String> for OutputName {
133 type Error = ParseOutputNameError;
134
135 fn try_from(value: String) -> Result<Self, Self::Error> {
136 validate(&value)?;
137
138 Ok(Self(SmolStr::new(value)))
139 }
140}
141
142impl From<OutputName> for String {
143 fn from(value: OutputName) -> Self {
144 value.0.into()
145 }
146}
147
148impl From<&OutputName> for String {
149 fn from(value: &OutputName) -> Self {
150 value.as_str().into()
151 }
152}
153
154#[derive(thiserror::Error, Debug, PartialEq)]
156#[allow(missing_docs)]
157pub enum ParseOutputNameError {
158 #[error("Invalid length")]
159 InvalidLength,
160 #[error("Invalid name")]
161 InvalidName,
162 #[error("Invalid reserved name 'drv'")]
163 ReservedNameDrv,
164}
165
166impl From<store_path::ParseStorePathNameError> for ParseOutputNameError {
167 fn from(value: store_path::ParseStorePathNameError) -> Self {
168 match value {
169 store_path::ParseStorePathNameError::Length => ParseOutputNameError::InvalidLength,
170 store_path::ParseStorePathNameError::Name => ParseOutputNameError::InvalidName,
171 }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use rstest::rstest;
178
179 use super::OutputName;
180
181 #[rstest]
182 #[should_panic(expected = "InvalidName")]
183 #[case("bin{n")]
184 #[should_panic(expected = "InvalidName")]
185 #[case("bin{n")]
186 #[should_panic(expected = "InvalidName")]
187 #[case(" bin{n")]
188 #[should_panic(expected = "InvalidName")]
189 #[case("invalid name")]
190 #[should_panic(expected = "InvalidName")]
191 #[case("invalid/name")]
192 #[should_panic(expected = "ReservedNameDrv")]
193 #[case("drv")]
194 #[should_panic(expected = "InvalidLength")]
195 #[case("")]
196 fn parse_fail(#[case] value: &str) {
197 value.parse::<OutputName>().unwrap();
198 }
199
200 #[rstest]
201 #[case("out")]
202 #[case("dev")]
203 #[case("lib")]
204 #[case("bin")]
205 #[case("debug")]
206 fn parse(#[case] value: &str) {
207 value.parse::<OutputName>().unwrap();
208 }
209
210 #[test]
211 fn size() {
212 assert_eq!(size_of::<OutputName>(), size_of::<String>());
213 }
214}