nix_compat/derivation/
output_name.rs1use std::{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
39fn validate<S: AsRef<str>>(s: S) -> Result<(), ParseOutputNameError> {
40 let name = s.as_ref();
41 store_path::validate_name(name.as_bytes())?;
42
43 if name == "drv" {
46 return Err(ParseOutputNameError::ReservedNameDrv);
47 }
48
49 Ok(())
50}
51
52impl PartialEq<&str> for OutputName {
53 fn eq(&self, other: &&str) -> bool {
54 self.as_str() == *other
55 }
56}
57impl PartialEq<str> for OutputName {
58 fn eq(&self, other: &str) -> bool {
59 self.as_str() == other
60 }
61}
62impl PartialEq<OutputName> for &str {
63 fn eq(&self, other: &OutputName) -> bool {
64 *self == other.as_str()
65 }
66}
67impl PartialEq<OutputName> for str {
68 fn eq(&self, other: &OutputName) -> bool {
69 self == other.as_str()
70 }
71}
72impl PartialEq<String> for OutputName {
73 fn eq(&self, other: &String) -> bool {
74 self.as_str() == other.as_str()
75 }
76}
77
78impl PartialEq<OutputName> for String {
79 fn eq(&self, other: &OutputName) -> bool {
80 self.as_str() == other.as_str()
81 }
82}
83
84impl fmt::Display for OutputName {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 f.write_str(self.as_str())
87 }
88}
89
90impl AsRef<str> for OutputName {
91 fn as_ref(&self) -> &str {
92 self.as_str()
93 }
94}
95
96impl std::borrow::Borrow<str> for OutputName {
97 fn borrow(&self) -> &str {
98 self.as_str()
99 }
100}
101
102impl Default for OutputName {
103 fn default() -> Self {
104 Self::out()
105 }
106}
107
108impl FromStr for OutputName {
109 type Err = ParseOutputNameError;
110
111 fn from_str(s: &str) -> Result<Self, Self::Err> {
112 validate(s)?;
113
114 Ok(Self(SmolStr::new(s)))
115 }
116}
117
118impl TryFrom<String> for OutputName {
119 type Error = ParseOutputNameError;
120
121 fn try_from(value: String) -> Result<Self, Self::Error> {
122 validate(&value)?;
123
124 Ok(Self(SmolStr::new(value)))
125 }
126}
127
128impl From<OutputName> for String {
129 fn from(value: OutputName) -> Self {
130 value.0.into()
131 }
132}
133
134impl From<&OutputName> for String {
135 fn from(value: &OutputName) -> Self {
136 value.as_str().into()
137 }
138}
139
140#[derive(thiserror::Error, Debug, PartialEq)]
142#[allow(missing_docs)]
143pub enum ParseOutputNameError {
144 #[error("Invalid length")]
145 InvalidLength,
146 #[error("Invalid name")]
147 InvalidName,
148 #[error("Invalid reserved name 'drv'")]
149 ReservedNameDrv,
150}
151
152impl From<store_path::ParseStorePathNameError> for ParseOutputNameError {
153 fn from(value: store_path::ParseStorePathNameError) -> Self {
154 match value {
155 store_path::ParseStorePathNameError::Length => ParseOutputNameError::InvalidLength,
156 store_path::ParseStorePathNameError::Name => ParseOutputNameError::InvalidName,
157 }
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use rstest::rstest;
164
165 use super::OutputName;
166
167 #[rstest]
168 #[should_panic(expected = "InvalidName")]
169 #[case("bin{n")]
170 #[should_panic(expected = "InvalidName")]
171 #[case("bin{n")]
172 #[should_panic(expected = "InvalidName")]
173 #[case(" bin{n")]
174 #[should_panic(expected = "InvalidName")]
175 #[case("invalid name")]
176 #[should_panic(expected = "InvalidName")]
177 #[case("invalid/name")]
178 #[should_panic(expected = "ReservedNameDrv")]
179 #[case("drv")]
180 #[should_panic(expected = "InvalidLength")]
181 #[case("")]
182 fn parse_fail(#[case] value: &str) {
183 value.parse::<OutputName>().unwrap();
184 }
185
186 #[rstest]
187 #[case("out")]
188 #[case("dev")]
189 #[case("lib")]
190 #[case("bin")]
191 #[case("debug")]
192 fn parse(#[case] value: &str) {
193 value.parse::<OutputName>().unwrap();
194 }
195
196 #[test]
197 fn size() {
198 assert_eq!(size_of::<OutputName>(), size_of::<String>());
199 }
200}