jsona_openapi/
lib.rs

1mod openapi;
2
3use std::{cell::RefCell, collections::HashSet, convert::TryFrom, fmt::Display, rc::Rc};
4
5use indexmap::IndexMap;
6use jsona::{
7    dom::{Key, KeyOrIndex, Keys, Node, Object},
8    error::ErrorObject,
9    util::mapper::Mapper,
10};
11pub use jsona_schema::Schema;
12use jsona_schema::SchemaParser;
13pub use openapi::*;
14use serde_json::Value;
15
16const ERROR_SOURCE: &str = "openapi";
17
18#[derive(Clone, Debug)]
19pub struct OpenapiError {
20    pub keys: Keys,
21    pub message: String,
22}
23
24impl OpenapiError {
25    pub fn new<T: ToString>(keys: Keys, message: T) -> Self {
26        Self {
27            keys,
28            message: message.to_string(),
29        }
30    }
31    pub fn to_error_object(&self, node: &Node, mapper: &Mapper) -> ErrorObject {
32        let message = self.message.clone();
33        ErrorObject::new(
34            ERROR_SOURCE,
35            "InvalidOpenapi",
36            message,
37            self.keys.mapper_range(node, mapper),
38        )
39    }
40}
41
42impl Display for OpenapiError {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        if self.keys.is_empty() {
45            write!(f, "{}", self.message)
46        } else {
47            write!(f, "{} at {}", self.message, self.keys)
48        }
49    }
50}
51
52type OpenapiResult<T> = std::result::Result<T, OpenapiError>;
53
54impl TryFrom<&Node> for Openapi {
55    type Error = Vec<OpenapiError>;
56
57    fn try_from(value: &Node) -> Result<Self, Self::Error> {
58        OpenapiParser::parse(value)
59    }
60}
61
62struct OpenapiParser {
63    openapi: Openapi,
64    routes: HashSet<String>,
65    errors: Vec<OpenapiError>,
66    defs: Rc<RefCell<IndexMap<String, Schema>>>,
67}
68
69impl OpenapiParser {
70    fn parse(node: &Node) -> Result<Openapi, Vec<OpenapiError>> {
71        let mut errors: Vec<OpenapiError> = vec![];
72        let routes: HashSet<String> = HashSet::default();
73        let mut openapi = Self::parse_openapi(&mut errors, node);
74        let schemas = get_components_mut(&mut openapi)
75            .schemas
76            .take()
77            .unwrap_or_default();
78        let mut parser = OpenapiParser {
79            openapi,
80            routes,
81            errors,
82            defs: Rc::new(RefCell::new(schemas)),
83        };
84        parser.parse_paths(node);
85        let OpenapiParser {
86            mut openapi,
87            errors,
88            defs,
89            ..
90        } = parser;
91        if errors.is_empty() {
92            if !defs.borrow().is_empty() {
93                get_components_mut(&mut openapi).schemas = Some(defs.take());
94            }
95            Ok(openapi)
96        } else {
97            Err(errors)
98        }
99    }
100
101    fn parse_openapi(errors: &mut Vec<OpenapiError>, value: &Node) -> Openapi {
102        let mut spec = Openapi {
103            openapi: "3.0.0".into(),
104            info: Info {
105                title: "openapi".into(),
106                version: "0.1.0".into(),
107                ..Default::default()
108            },
109            ..Default::default()
110        };
111        match value.get_as_object("@openapi") {
112            Some((key, Some(value))) => {
113                let keys = Keys::single(key);
114                let mut value = Node::from(value).to_plain_json();
115                if let Value::Object(ref mut obj) = value {
116                    if obj.get("info").is_none() {
117                        obj.insert(
118                            "info".into(),
119                            serde_json::to_value(spec.info.clone()).unwrap(),
120                        );
121                    }
122                    if obj.get("openapi").is_none() {
123                        obj.insert(
124                            "openapi".into(),
125                            serde_json::to_value(spec.openapi.clone()).unwrap(),
126                        );
127                    }
128                }
129                if let Value::Object(ref mut v) = value {
130                    v.insert("paths".into(), Value::Object(serde_json::Map::new()));
131                }
132                match serde_json::from_value(value) {
133                    Ok(v) => spec = v,
134                    Err(error) => errors.push(OpenapiError::new(
135                        keys,
136                        format!("invalid spec value, {error}"),
137                    )),
138                }
139            }
140            Some((key, None)) => {
141                errors.push(OpenapiError::new(Keys::single(key), "must be object"))
142            }
143            None => {}
144        }
145        spec
146    }
147
148    fn parse_paths(&mut self, node: &Node) {
149        if let Some(object) = node.as_object() {
150            for (key, value) in object.value().read().iter() {
151                if let Err(error) = self.parse_endpoint(key, value) {
152                    self.errors.push(error);
153                }
154            }
155        } else {
156            self.errors
157                .push(OpenapiError::new(Keys::default(), "must be object"))
158        }
159    }
160
161    fn parse_endpoint(&mut self, key: &Key, value: &Node) -> OpenapiResult<()> {
162        let operation_id = key.value();
163        let keys = Keys::single(key.clone());
164        if !value.is_object() {
165            return Err(OpenapiError::new(keys, "must be object"));
166        }
167        let mut operation = self.parse_endpoint_annotation(&keys, value)?;
168        operation.operation_id = Some(operation_id.into());
169        let (method, path_parts) = self.parse_route(&keys, value)?;
170        let pathname = self.parse_req(&mut operation, &keys, value, &path_parts)?;
171        self.parse_res(&mut operation, &keys, value)?;
172        let path_item = self
173            .openapi
174            .paths
175            .entry(pathname)
176            .or_insert(Default::default());
177        method.add_operation(path_item, operation);
178        Ok(())
179    }
180
181    fn parse_endpoint_annotation(&mut self, keys: &Keys, value: &Node) -> OpenapiResult<Operation> {
182        match value.get_as_object("@endpoint") {
183            Some((key, Some(value))) => {
184                let mut value = Node::from(value).to_plain_json();
185                value
186                    .as_object_mut()
187                    .unwrap()
188                    .insert("responses".into(), Value::Object(Default::default()));
189                serde_json::from_value(value).map_err(|error| {
190                    OpenapiError::new(keys.join(key), format!("invalid endpoint value, {error}"))
191                })
192            }
193            Some((key, None)) => Err(OpenapiError::new(keys.join(key), "must be object")),
194            None => Ok(Operation::default()),
195        }
196    }
197
198    fn parse_route(
199        &mut self,
200        keys: &Keys,
201        value: &Node,
202    ) -> OpenapiResult<(MethodKind, Vec<String>)> {
203        match value.get_as_string("route") {
204            Some((key, Some(value))) => {
205                let keys = keys.join(key);
206                let splitted_route: Vec<&str> = value.value().split(' ').collect();
207                let err = || OpenapiError::new(keys.clone(), "is invalid");
208                if splitted_route.len() != 2 {
209                    return Err(err());
210                }
211                let method = MethodKind::from_str(splitted_route[0]).ok_or_else(err)?;
212                let path = splitted_route[1].trim();
213                let path_parts: Vec<String> = path.split('/').map(|v| v.to_string()).collect();
214                let canonical_route = format!("{} {}", method, path);
215                if !self.routes.insert(canonical_route) {
216                    return Err(OpenapiError::new(keys, "is conflict"));
217                }
218                Ok((method, path_parts))
219            }
220            Some((key, None)) => Err(OpenapiError::new(keys.join(key), "must be string")),
221            None => Err(OpenapiError::new(keys.clone(), "miss route")),
222        }
223    }
224
225    fn parse_req(
226        &mut self,
227        operation: &mut Operation,
228        keys: &Keys,
229        value: &Node,
230        path_parts: &[String],
231    ) -> OpenapiResult<String> {
232        match value.get_as_object("req") {
233            Some((key, Some(value))) => {
234                let keys = keys.join(key);
235                let pathname = self.parse_req_params(operation, &keys, &value, path_parts)?;
236                for (key, value) in value.value().read().iter() {
237                    match key.value() {
238                        "query" => {
239                            self.parse_req_parameters(
240                                operation,
241                                "query",
242                                &keys.join(key.clone()),
243                                value,
244                            )?;
245                        }
246                        "headers" => {
247                            self.parse_req_parameters(
248                                operation,
249                                "header",
250                                &keys.join(key.clone()),
251                                value,
252                            )?;
253                        }
254                        "body" => self.parse_req_body(operation, &keys.join(key.clone()), value)?,
255                        _ => {}
256                    }
257                }
258                Ok(pathname)
259            }
260            Some((key, None)) => Err(OpenapiError::new(keys.join(key), "must be object")),
261            None => {
262                if path_parts.iter().any(|v| v.as_str() == "{}") {
263                    return Err(OpenapiError::new(keys.clone(), "req.params is required"));
264                }
265                Ok(path_parts.join("/"))
266            }
267        }
268    }
269
270    fn parse_req_params(
271        &mut self,
272        operation: &mut Operation,
273        keys: &Keys,
274        value: &Object,
275        path_parts: &[String],
276    ) -> OpenapiResult<String> {
277        match Node::from(value.clone()).get_as_object("params") {
278            Some((key, Some(value))) => {
279                let keys = keys.join(key);
280                let num_params = path_parts.iter().filter(|v| v.as_str() == "{}").count();
281                let map = value.value().read();
282                if num_params != map.len() {
283                    return Err(OpenapiError::new(keys, "does not match route"));
284                }
285                let mut new_path_parts: Vec<String> = vec![];
286                let mut idx = 0;
287                let names: Vec<&str> = map.iter().map(|(k, _)| k.value()).collect();
288                for part in path_parts {
289                    if *part == "{}" {
290                        new_path_parts.push(format!("{{{}}}", names[idx]));
291                        idx += 1;
292                    } else {
293                        new_path_parts.push(part.to_string())
294                    }
295                }
296                self.parse_req_parameters(operation, "path", &keys, &value.into())?;
297                Ok(new_path_parts.join("/"))
298            }
299            Some((key, None)) => Err(OpenapiError::new(keys.join(key), "must be object")),
300            None => {
301                if path_parts.iter().any(|v| v.as_str() == "{}") {
302                    return Err(OpenapiError::new(keys.clone(), "params is required"));
303                }
304                Ok(path_parts.join("/"))
305            }
306        }
307    }
308
309    fn parse_req_parameters(
310        &mut self,
311        operation: &mut Operation,
312        location: &str,
313        keys: &Keys,
314        value: &Node,
315    ) -> OpenapiResult<()> {
316        match value.as_object() {
317            Some(object) => {
318                let mut parameters = vec![];
319                for (key, value) in object.value().read().iter() {
320                    let parameter = Parameter {
321                        name: key.value().to_string(),
322                        location: location.into(),
323                        ..Default::default()
324                    };
325                    parameters.push(self.parse_parameter(
326                        parameter,
327                        &keys.join(key.clone()),
328                        value,
329                    )?);
330                }
331                if let Some(v) = operation.parameters.as_mut() {
332                    v.extend(parameters)
333                } else {
334                    operation.parameters = Some(parameters);
335                }
336                Ok(())
337            }
338            None => Err(OpenapiError::new(keys.clone(), "must be object")),
339        }
340    }
341
342    fn parse_req_body(
343        &mut self,
344        operation: &mut Operation,
345        keys: &Keys,
346        value: &Node,
347    ) -> OpenapiResult<()> {
348        let content_type = parse_string_annotation(keys, value, "@contentType")?
349            .unwrap_or_else(|| "application/json".into());
350        let schema = self.parse_schema(keys, value)?;
351        let media_type = MediaType {
352            schema: Some(schema),
353            examples: if exist_annotation(value, "@example") {
354                Some(OneOrMultiExample::Example {
355                    example: value.to_plain_json(),
356                })
357            } else {
358                None
359            },
360            ..Default::default()
361        };
362        let mut content = IndexMap::default();
363        content.insert(content_type, media_type);
364        let request_body = RequestBody {
365            description: parse_string_annotation(keys, value, "@describe")?,
366            required: Some(true),
367            content,
368        };
369        operation.request_body = Some(ObjectOrReference::Object(request_body));
370        Ok(())
371    }
372
373    fn parse_res(
374        &mut self,
375        operation: &mut Operation,
376        keys: &Keys,
377        value: &Node,
378    ) -> OpenapiResult<()> {
379        match value.get_as_object("res") {
380            Some((key, Some(value))) => {
381                let keys = keys.join(key);
382                for (key, value) in value.value().read().iter() {
383                    let keys = keys.join(key.clone());
384                    let status = key
385                        .value()
386                        .parse::<u32>()
387                        .map_err(|_| OpenapiError::new(keys.clone(), "should be status code"))?;
388                    if !(100..=599).contains(&status) {
389                        return Err(OpenapiError::new(keys, "must be integer in [100, 600)"));
390                    }
391                    let description =
392                        parse_string_annotation(&keys, value, "@describe")?.unwrap_or_default();
393                    let mut response = Response {
394                        description,
395                        ..Default::default()
396                    };
397
398                    let with_header = exist_annotation(value, "@withHeader");
399
400                    if with_header {
401                        match value.as_object() {
402                            Some(object) => {
403                                for (key, value) in object.value().read().iter() {
404                                    match key.value() {
405                                        "headers" => self.parse_res_header(
406                                            &mut response,
407                                            &keys.join(key.clone()),
408                                            value,
409                                        )?,
410                                        "body" => self.parse_res_body(
411                                            &mut response,
412                                            &keys.join(key.clone()),
413                                            value,
414                                        )?,
415                                        _ => {}
416                                    }
417                                }
418                            }
419                            None => {
420                                return Err(OpenapiError::new(keys, "must be object"));
421                            }
422                        }
423                    } else {
424                        self.parse_res_body(&mut response, &keys, value)?;
425                    }
426
427                    operation.responses.insert(status.to_string(), response);
428                }
429                Ok(())
430            }
431            Some((key, None)) => Err(OpenapiError::new(keys.join(key), "must be object")),
432            None => {
433                let default_response = Response {
434                    description: Default::default(),
435                    ..Default::default()
436                };
437                operation.responses.insert("200".into(), default_response);
438                Ok(())
439            }
440        }
441    }
442
443    fn parse_parameter(
444        &mut self,
445        mut parameter: Parameter,
446        keys: &Keys,
447        value: &Node,
448    ) -> OpenapiResult<ObjectOrReference<Parameter>> {
449        if let Some(ref_val) = parse_ref_annotation(keys, value, "#/components/parameters/")? {
450            return Ok(ref_val);
451        }
452        parameter.description = parse_string_annotation(keys, value, "@describe")?;
453        parameter.required = Some(!exist_annotation(value, "@optional"));
454        parameter.schema = Some(self.parse_schema(keys, value)?);
455        parameter.examples = if exist_annotation(value, "@example") {
456            Some(OneOrMultiExample::Example {
457                example: value.to_plain_json(),
458            })
459        } else {
460            None
461        };
462
463        let parameter_object = ObjectOrReference::Object(parameter);
464
465        if let Some(name) = parse_string_annotation(keys, value, "@def")? {
466            return self.def_parameters(name, parameter_object);
467        }
468        Ok(parameter_object)
469    }
470
471    fn parse_res_header(
472        &mut self,
473        response: &mut Response,
474        keys: &Keys,
475        value: &Node,
476    ) -> OpenapiResult<()> {
477        match value.as_object() {
478            Some(value) => {
479                for (key, value) in value.value().read().iter() {
480                    let keys = keys.join(key.clone());
481                    let header = Header {
482                        description: parse_string_annotation(&keys, value, "@describe")?,
483                        required: Some(!exist_annotation(value, "@optional")),
484                        schema: Some(self.parse_schema(&keys, value)?),
485                        ..Default::default()
486                    };
487                    let header_object = ObjectOrReference::Object(header);
488                    response
489                        .headers
490                        .get_or_insert(Default::default())
491                        .insert(key.value().to_string(), header_object);
492                }
493                Ok(())
494            }
495            None => Err(OpenapiError::new(keys.clone(), "must be object")),
496        }
497    }
498
499    fn parse_res_body(
500        &mut self,
501        response: &mut Response,
502        keys: &Keys,
503        value: &Node,
504    ) -> OpenapiResult<()> {
505        let content_type = parse_string_annotation(keys, value, "@contentType")?
506            .unwrap_or_else(|| "application/json".into());
507        let schema = self.parse_schema(keys, value)?;
508        let media_type = MediaType {
509            schema: Some(schema),
510            examples: if exist_annotation(value, "@example") {
511                Some(OneOrMultiExample::Example {
512                    example: value.to_plain_json(),
513                })
514            } else {
515                None
516            },
517            ..Default::default()
518        };
519        response
520            .content
521            .get_or_insert(Default::default())
522            .insert(content_type, media_type);
523        Ok(())
524    }
525
526    fn parse_schema(&mut self, keys: &Keys, value: &Node) -> OpenapiResult<Schema> {
527        let scope = SchemaParser {
528            keys: keys.clone(),
529            node: value.clone(),
530            defs: self.defs.clone(),
531            ref_prefix: Rc::new("#/components/schemas/".to_string()),
532            prefer_optional: false,
533        };
534        let mut schema = scope
535            .parse()
536            .map_err(|_| OpenapiError::new(keys.clone(), "invalid schema"))?;
537        schema.description = None;
538        Ok(schema)
539    }
540
541    fn def_parameters(
542        &mut self,
543        name: String,
544        value: ObjectOrReference<Parameter>,
545    ) -> OpenapiResult<ObjectOrReference<Parameter>> {
546        let components = get_components_mut(&mut self.openapi);
547        if components.parameters.is_none() {
548            components.parameters = Some(Default::default());
549        }
550        components
551            .parameters
552            .as_mut()
553            .unwrap()
554            .insert(name.clone(), value);
555        return Ok(ObjectOrReference::Ref {
556            ref_path: format!("#/components/parameters/{}", name),
557        });
558    }
559}
560
561fn get_components_mut(spec: &mut Openapi) -> &mut Components {
562    if spec.components.is_none() {
563        spec.components = Some(Default::default());
564    }
565    spec.components.as_mut().unwrap()
566}
567
568fn exist_annotation(value: &Node, name: &str) -> bool {
569    value.get(&KeyOrIndex::annotation(name)).is_some()
570}
571
572fn parse_string_annotation(keys: &Keys, value: &Node, name: &str) -> OpenapiResult<Option<String>> {
573    match value.get_as_string(name) {
574        Some((_, Some(value))) => Ok(Some(value.value().to_string())),
575        Some((key, None)) => Err(OpenapiError::new(keys.join(key), "must be string")),
576        None => Ok(None),
577    }
578}
579
580fn parse_ref_annotation<T>(
581    keys: &Keys,
582    value: &Node,
583    ref_prefix: &str,
584) -> OpenapiResult<Option<ObjectOrReference<T>>> {
585    match parse_string_annotation(keys, value, "@ref")? {
586        Some(ref_value) => Ok(Some(ObjectOrReference::Ref {
587            ref_path: format!("{}{}", ref_prefix, ref_value),
588        })),
589        None => Ok(None),
590    }
591}
592
593enum MethodKind {
594    Get,
595    Post,
596    Put,
597    Delete,
598    Patch,
599}
600
601impl MethodKind {
602    pub fn from_str(value: &str) -> Option<Self> {
603        match value.trim().to_lowercase().as_str() {
604            "get" => Some(MethodKind::Get),
605            "post" => Some(MethodKind::Post),
606            "put" => Some(MethodKind::Put),
607            "delete" => Some(MethodKind::Delete),
608            "patch" => Some(MethodKind::Patch),
609            _ => None,
610        }
611    }
612    pub fn add_operation(&self, path_item: &mut PathItem, operation: Operation) {
613        match self {
614            MethodKind::Get => path_item.get = Some(operation),
615            MethodKind::Post => path_item.post = Some(operation),
616            MethodKind::Put => path_item.put = Some(operation),
617            MethodKind::Delete => path_item.delete = Some(operation),
618            MethodKind::Patch => path_item.patch = Some(operation),
619        };
620    }
621}
622impl Display for MethodKind {
623    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
624        match self {
625            MethodKind::Get => write!(f, "get"),
626            MethodKind::Post => write!(f, "post"),
627            MethodKind::Put => write!(f, "put"),
628            MethodKind::Delete => write!(f, "delete"),
629            MethodKind::Patch => write!(f, "patch"),
630        }
631    }
632}