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(Clone, Debug, Default, strum::Display, Eq, IntoStaticStr, PartialEq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize))]
30pub enum Context {
31 #[default]
33 #[strum(to_string = "default")]
34 #[cfg_attr(feature = "serde", serde(rename = "default"))]
35 Default,
36
37 #[strum(to_string = "{0}")]
40 Custom(CustomContext),
41}
42
43impl Context {
44 pub(crate) fn path_segment(&self) -> PathBuf {
46 match self {
47 Self::Default => "default".into(),
48 Self::Custom(custom) => custom.as_ref().into(),
49 }
50 }
51
52 pub fn parser(input: &mut &str) -> ModalResult<Self> {
79 alt((
80 ("default", eof).value(Self::Default),
81 CustomContext::parser.map(Self::Custom),
82 ))
83 .parse_next(input)
84 }
85}
86
87impl FromStr for Context {
88 type Err = crate::Error;
89
90 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 Ok(Self::parser.parse(s)?)
101 }
102}
103
104#[derive(Clone, Debug, Eq, PartialEq)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize))]
107#[cfg_attr(feature = "serde", serde(rename = "kebab-case"))]
108pub struct CustomContext(IdentifierString);
109
110impl CustomContext {
111 pub fn new(value: IdentifierString) -> Self {
113 Self(value)
114 }
115
116 pub fn parser(input: &mut &str) -> ModalResult<Self> {
137 IdentifierString::parser
138 .map(Self)
139 .context(StrContext::Label("custom context for VOA"))
140 .parse_next(input)
141 }
142}
143
144impl Display for CustomContext {
145 fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
146 write!(fmt, "{}", self.0)
147 }
148}
149
150impl AsRef<str> for CustomContext {
151 fn as_ref(&self) -> &str {
152 self.0.as_ref()
153 }
154}
155
156impl FromStr for CustomContext {
157 type Err = crate::Error;
158
159 fn from_str(s: &str) -> Result<Self, Self::Err> {
169 Ok(Self::parser.parse(s)?)
170 }
171}
172
173impl From<CustomContext> for Context {
174 fn from(val: CustomContext) -> Self {
175 Context::Custom(val)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use rstest::rstest;
182 use testresult::TestResult;
183
184 use super::*;
185
186 #[rstest]
187 #[case(Context::Default, "default")]
188 #[case(Context::Custom(CustomContext::new("abc".parse()?)), "abc")]
189 fn context_display(#[case] context: Context, #[case] display: &str) -> TestResult {
190 assert_eq!(format!("{context}"), display);
191 Ok(())
192 }
193
194 #[rstest]
195 #[case::default("default", Context::Default)]
196 #[case::custom("test", Context::Custom(CustomContext::new("test".parse()?)))]
197 fn context_from_str_succeeds(#[case] input: &str, #[case] expected: Context) -> TestResult {
198 assert_eq!(Context::from_str(input)?, expected);
199 Ok(())
200 }
201
202 #[rstest]
203 #[case::invalid_character(
204 "test$",
205 "test$\n ^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`"
206 )]
207 #[case::all_caps(
208 "TEST",
209 "TEST\n^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`"
210 )]
211 #[case::empty_string(
212 "",
213 "\n^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`"
214 )]
215 fn context_from_str_fails(#[case] input: &str, #[case] error_msg: &str) -> TestResult {
216 match Context::from_str(input) {
217 Ok(id_string) => {
218 return Err(format!(
219 "Should have failed to parse {input} but succeeded: {id_string}"
220 )
221 .into());
222 }
223 Err(error) => {
224 assert_eq!(error.to_string(), format!("Parser error:\n{error_msg}"));
225 Ok(())
226 }
227 }
228 }
229}