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