sw4rm_rs/
reference.rs

1use serde::{Deserialize, Serialize};
2use lazy_static::lazy_static;
3use regex::Regex;
4use crate::Spec;
5
6lazy_static! {
7    static ref PATH_EXPRESSION: Regex = Regex::new(r"^(?<source>[^#]*)#\/(?<location>[^/]+)\/(?<kind>[^/]+)\/*(?<name>\S*)$").unwrap();
8}
9
10pub trait Resolvable: Clone + Sized {
11    fn resolve(spec: &Spec, path: &String) -> Result<Self, ResolveError>;
12}
13
14#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
15#[serde(untagged)]
16pub enum RefOr<T> where T: Resolvable {
17    Reference {
18        #[serde(rename = "$ref")]
19        reference_path: String,
20    },
21    Item(T),
22}
23impl<T> RefOr<T> where T: Resolvable {
24    pub fn resolve(&self, spec: &Spec) -> Result<T, ResolveError> {
25        match self {
26            Self::Item(item) => Ok(item.clone()),
27            Self::Reference { reference_path } => T::resolve(spec, reference_path),
28        }
29    }
30}
31impl<T> Resolvable for Box<T> where T: Resolvable {
32    fn resolve(spec: &Spec, path: &String) -> Result<Self, ResolveError> {
33        Ok(Box::new(T::resolve(spec, path)?))
34    }
35}
36
37#[derive(Debug, Clone, Eq, PartialEq)]
38#[non_exhaustive]
39pub enum ReferenceType {
40    Callbacks,
41    Definitions,
42    Example,
43    Link,
44    Parameter,
45    PathItem,
46    RequestBody,
47    Response,
48    Schema,
49    SecurityScheme,
50}
51impl TryFrom<String> for ReferenceType {
52    type Error = ResolveError;
53
54    fn try_from(value: String) -> Result<Self, Self::Error> {
55        match value.as_str() {
56            "callback" => Ok(Self::Callbacks),
57            "definitions" => Ok(Self::Definitions),
58            "example" => Ok(Self::Example),
59            "link" => Ok(Self::Link),
60            "parameter" | "parameters" => Ok(Self::Parameter),
61            "path_item" => Ok(Self::PathItem),
62            "request_body" => Ok(Self::RequestBody),
63            "response" => Ok(Self::Response),
64            "schemas" => Ok(Self::Schema),
65            "security_scheme" => Ok(Self::SecurityScheme),
66
67            #[allow(unreachable_patterns)]
68            _ => Err(Self::Error::UnknownType)
69        }
70    }
71}
72
73#[derive(Debug)]
74pub enum ResolveError {
75    UnknownType,
76    MissingPathError,
77    MissingLocationError,
78    MissingKindError,
79    MissingMatchesError,
80    UnknownPathError(String),
81}
82
83#[derive(Clone, Debug)]
84pub struct Reference {
85    pub source: Option<String>,
86    pub location: String,
87    pub kind: ReferenceType,
88    pub name: String,
89}
90
91impl TryFrom<String> for Reference {
92    type Error = ResolveError;
93
94    fn try_from(value: String) -> Result<Self, Self::Error> {
95        let matches = PATH_EXPRESSION.captures(value.as_str())
96            .ok_or(Self::Error::MissingMatchesError)?;
97
98        let source = matches.name("source")
99            .map(|v| v.as_str().to_string().clone());
100
101        let location = matches.name("location")
102            .ok_or(Self::Error::MissingLocationError)?
103            .as_str().to_string().clone();
104
105        let kind_item = matches.name("kind")
106            .ok_or(Self::Error::MissingKindError)?
107            .as_str().to_string().clone();
108        let kind: ReferenceType = match kind_item.clone().try_into() {
109            Ok(v) => v,
110            Err(_) => location.clone().try_into()?,
111        };
112
113        let name_item = matches.name("name")
114            .map(|v| v.as_str().to_string().clone());
115        let name = match name_item {
116            Some(v) if !v.is_empty() => v,
117            _ => kind_item.clone()
118        };
119
120        Ok(Self {
121            source, location, kind, name
122        })
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::Reference;
129
130    #[test]
131    fn test_regex_captures_swagger_yaml() {
132        let hay = "#/parameters/nested_response";
133        let path = Reference::try_from(hay.to_string()).unwrap();
134        println!("{path:?}")
135    }
136    #[test]
137    fn test_regex_captures_swagger_json() {
138        let hay = "#/definitions/account-v1.AccountDto";
139        let path = Reference::try_from(hay.to_string()).unwrap();
140        println!("{path:?}")
141    }
142    #[test]
143    fn test_regex_captures_openapi_yaml() {
144        let hay = "#/components/schemas/Pets";
145        let path = Reference::try_from(hay.to_string()).unwrap();
146        println!("{path:?}")
147    }
148    #[test]
149    fn test_regex_captures_openapi_json() {
150        let hay = "#/components/schemas/account-v1.AccountDto";
151        let path = Reference::try_from(hay.to_string()).unwrap();
152        println!("{path:?}")
153    }
154}