voa_core/identifiers/
technology.rs1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4};
5
6use strum::IntoStaticStr;
7use winnow::{
8 ModalResult,
9 Parser,
10 combinator::{alt, cut_err, eof},
11 error::{StrContext, StrContextValue},
12};
13
14use crate::{
15 Error,
16 identifiers::{IdentifierString, SegmentPath},
17};
18
19#[derive(Clone, Debug, strum::Display, Eq, Hash, IntoStaticStr, PartialEq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27pub enum Technology {
28 #[strum(to_string = "openpgp")]
32 #[cfg_attr(feature = "serde", serde(rename = "openpgp"))]
33 Openpgp,
34
35 #[strum(to_string = "ssh")]
39 #[cfg_attr(feature = "serde", serde(rename = "ssh"))]
40 SSH,
41
42 #[strum(to_string = "{0}")]
44 Custom(CustomTechnology),
45}
46
47impl Technology {
48 pub(crate) fn path_segment(&self) -> Result<SegmentPath, Error> {
50 format!("{self}").try_into()
51 }
52
53 pub fn parser(input: &mut &str) -> ModalResult<Self> {
78 cut_err(alt((
79 ("openpgp", eof).value(Self::Openpgp),
80 ("ssh", eof).value(Self::SSH),
81 CustomTechnology::parser.map(Self::Custom),
82 )))
83 .context(StrContext::Label("a valid VOA technology"))
84 .context(StrContext::Expected(StrContextValue::Description(
85 "'opengpg', 'ssh', or a custom value",
86 )))
87 .parse_next(input)
88 }
89}
90
91impl FromStr for Technology {
92 type Err = crate::Error;
93
94 fn from_str(s: &str) -> Result<Self, Self::Err> {
104 Ok(Self::parser.parse(s)?)
105 }
106}
107
108#[derive(Clone, Debug, Eq, Hash, PartialEq)]
111#[cfg_attr(feature = "serde", derive(serde::Serialize))]
112#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
113pub struct CustomTechnology(IdentifierString);
114
115impl CustomTechnology {
116 pub fn new(value: IdentifierString) -> Self {
118 Self(value)
119 }
120
121 pub fn parser(input: &mut &str) -> ModalResult<Self> {
142 IdentifierString::parser
143 .map(Self)
144 .context(StrContext::Label("custom technology for VOA"))
145 .parse_next(input)
146 }
147}
148
149impl Display for CustomTechnology {
150 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
151 write!(f, "{}", self.0)
152 }
153}
154
155impl AsRef<str> for CustomTechnology {
156 fn as_ref(&self) -> &str {
157 self.0.as_ref()
158 }
159}
160
161impl FromStr for CustomTechnology {
162 type Err = crate::Error;
163
164 fn from_str(s: &str) -> Result<Self, Self::Err> {
174 Ok(Self::parser.parse(s)?)
175 }
176}
177impl From<CustomTechnology> for Technology {
178 fn from(val: CustomTechnology) -> Self {
179 Technology::Custom(val)
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use rstest::rstest;
186 use testresult::TestResult;
187
188 use super::*;
189
190 #[rstest]
191 #[case(Technology::Openpgp, "openpgp")]
192 #[case(Technology::Custom(CustomTechnology::new("foo".parse()?)), "foo")]
193 fn technology_display(
194 #[case] technology: Technology,
195 #[case] display: &str,
196 ) -> testresult::TestResult {
197 assert_eq!(format!("{technology}",), display);
198
199 Ok(())
200 }
201
202 #[test]
203 fn custom_as_ref() -> TestResult {
204 let custom = CustomTechnology::new("foo".parse()?);
205 assert_eq!(custom.as_ref(), "foo");
206
207 Ok(())
208 }
209
210 #[rstest]
211 #[case::default("openpgp", Technology::Openpgp)]
212 #[case::default("ssh", Technology::SSH)]
213 #[case::custom("test", Technology::Custom(CustomTechnology::new("test".parse()?)))]
214 fn technology_from_str_succeeds(
215 #[case] input: &str,
216 #[case] expected: Technology,
217 ) -> TestResult {
218 assert_eq!(Technology::from_str(input)?, expected);
219 Ok(())
220 }
221
222 #[rstest]
223 #[case::invalid_character(
224 "test$",
225 "test$\n ^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`, 'opengpg', 'ssh', or a custom value"
226 )]
227 #[case::all_caps(
228 "TEST",
229 "TEST\n^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`, 'opengpg', 'ssh', or a custom value"
230 )]
231 #[case::empty_string(
232 "",
233 "\n^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`, 'opengpg', 'ssh', or a custom value"
234 )]
235 fn technology_from_str_fails(#[case] input: &str, #[case] error_msg: &str) -> TestResult {
236 match Technology::from_str(input) {
237 Ok(id_string) => {
238 panic!("Should have failed to parse {input} but succeeded: {id_string}");
239 }
240 Err(error) => {
241 assert_eq!(error.to_string(), format!("Parser error:\n{error_msg}"));
242 Ok(())
243 }
244 }
245 }
246}