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