voa_core/identifiers/
context.rs1use std::{
2 fmt::{Display, Formatter},
3 path::PathBuf,
4 str::FromStr,
5};
6
7use strum::IntoStaticStr;
8use winnow::{
9 ModalResult,
10 Parser,
11 combinator::{alt, eof},
12 error::StrContext,
13};
14
15use crate::identifiers::IdentifierString;
16#[cfg(doc)]
17use crate::identifiers::{Os, Purpose};
18
19#[derive(
29 Clone, Debug, Default, strum::Display, Eq, Hash, IntoStaticStr, Ord, PartialEq, PartialOrd,
30)]
31#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
32pub enum Context {
33 #[default]
35 #[strum(to_string = "default")]
36 #[cfg_attr(feature = "serde", serde(rename = "default"))]
37 Default,
38
39 #[strum(to_string = "{0}")]
42 #[cfg_attr(feature = "serde", serde(rename = "custom"))]
43 Custom(CustomContext),
44}
45
46impl Context {
47 pub(crate) fn path_segment(&self) -> PathBuf {
49 match self {
50 Self::Default => "default".into(),
51 Self::Custom(custom) => custom.as_ref().into(),
52 }
53 }
54
55 pub fn parser(input: &mut &str) -> ModalResult<Self> {
82 alt((
83 ("default", eof).value(Self::Default),
84 CustomContext::parser.map(Self::Custom),
85 ))
86 .parse_next(input)
87 }
88}
89
90impl FromStr for Context {
91 type Err = crate::Error;
92
93 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 Ok(Self::parser.parse(s)?)
104 }
105}
106
107#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
109#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
110#[cfg_attr(feature = "serde", serde(rename = "kebab-case"))]
111pub struct CustomContext(IdentifierString);
112
113impl CustomContext {
114 pub fn new(value: IdentifierString) -> Self {
116 Self(value)
117 }
118
119 pub fn parser(input: &mut &str) -> ModalResult<Self> {
140 IdentifierString::parser
141 .map(Self)
142 .context(StrContext::Label("custom context for VOA"))
143 .parse_next(input)
144 }
145}
146
147impl Display for CustomContext {
148 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
149 write!(fmt, "{}", self.0)
150 }
151}
152
153impl AsRef<str> for CustomContext {
154 fn as_ref(&self) -> &str {
155 self.0.as_ref()
156 }
157}
158
159impl FromStr for CustomContext {
160 type Err = crate::Error;
161
162 fn from_str(s: &str) -> Result<Self, Self::Err> {
172 Ok(Self::parser.parse(s)?)
173 }
174}
175
176impl From<CustomContext> for Context {
177 fn from(val: CustomContext) -> Self {
178 Context::Custom(val)
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use rstest::rstest;
185 use testresult::TestResult;
186
187 use super::*;
188
189 #[rstest]
190 #[case(Context::Default, "default")]
191 #[case(Context::Custom(CustomContext::new("abc".parse()?)), "abc")]
192 fn context_display(#[case] context: Context, #[case] display: &str) -> TestResult {
193 assert_eq!(format!("{context}"), display);
194 Ok(())
195 }
196
197 #[rstest]
198 #[case::default("default", Context::Default)]
199 #[case::custom("test", Context::Custom(CustomContext::new("test".parse()?)))]
200 fn context_from_str_succeeds(#[case] input: &str, #[case] expected: Context) -> TestResult {
201 assert_eq!(Context::from_str(input)?, expected);
202 Ok(())
203 }
204
205 #[rstest]
206 #[case::invalid_character(
207 "test$",
208 "test$\n ^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`"
209 )]
210 #[case::all_caps(
211 "TEST",
212 "TEST\n^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`"
213 )]
214 #[case::empty_string(
215 "",
216 "\n^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`"
217 )]
218 fn context_from_str_fails(#[case] input: &str, #[case] error_msg: &str) -> TestResult {
219 match Context::from_str(input) {
220 Ok(id_string) => {
221 panic!("Should have failed to parse {input} but succeeded: {id_string}");
222 }
223 Err(error) => {
224 assert_eq!(error.to_string(), format!("Parser error:\n{error_msg}"));
225 Ok(())
226 }
227 }
228 }
229}