openapi_parser/
lib.rs

1use okapi::openapi3::{Components, OpenApi, SchemaObject};
2use okapi::schemars::schema::{InstanceType, Schema, SingleOrVec};
3use serde_json::{json, Value};
4use std::collections::HashMap;
5
6pub const REF_OBJECT: &str = "\u{0}\u{0}\u{0}\u{0}";
7
8fn recurse_fix(mut v: &mut Value) {
9    match &mut v {
10        Value::Array(array) => {
11            for item in array {
12                recurse_fix(item);
13            }
14        }
15        Value::Object(obj) => {
16            for item in obj.iter_mut() {
17                recurse_fix(item.1);
18            }
19
20            if !obj.contains_key("examples") {
21                if let Some(ex) = obj.get("example").cloned() {
22                    obj.insert("examples".to_string(), Value::Array(vec![ex]));
23                }
24            }
25        }
26
27        _ => {}
28    }
29}
30
31/// Parse OpenAPI 3 schema, treating "example" field as "examples"
32///
33/// Otherwise, examples would be lost
34pub fn parse_schema_fix_example_issue(file_content: &str) -> OpenApi {
35    let mut root = serde_yaml::from_str::<Value>(file_content)
36        .expect("Failed to parse OpenAPI document as YAML document!");
37
38    if let Value::Object(root_obj) = &mut root {
39        if let Some(components) = root_obj.get_mut("components") {
40            recurse_fix(components);
41        }
42    }
43
44    parse_schema(&serde_yaml::to_string(&root).unwrap())
45}
46
47/// Parse OpenAPI 3 schema
48pub fn parse_schema(file_content: &str) -> OpenApi {
49    let schema = serde_yaml::from_str::<OpenApi>(file_content).expect("Failed to parse document");
50
51    if schema.components.is_none() {
52        log::error!("components is missing!");
53        panic!()
54    }
55
56    schema
57}
58
59fn expect_single<E>(e: &SingleOrVec<E>) -> &E {
60    match e {
61        SingleOrVec::Single(e) => e,
62        SingleOrVec::Vec(v) => &v[0],
63    }
64}
65
66fn expect_schema_object(s: &Schema) -> &SchemaObject {
67    match s {
68        Schema::Bool(_) => {
69            panic!("Got unexpected bool!");
70        }
71        Schema::Object(o) => o,
72    }
73}
74
75/// Object child information
76#[derive(Debug, Clone, serde::Serialize)]
77pub struct ObjectChild {
78    /// The name of the field in the object
79    pub name: String,
80    /// The structure of the child
81    pub node: TreeNode,
82}
83
84/// The type of the schema
85#[derive(Debug, Clone, serde::Serialize)]
86#[serde(tag = "type")]
87pub enum NodeType {
88    /// NULL value
89    Null,
90    /// BOOLEAN value
91    Boolean,
92    /// Array value
93    Array {
94        /// Schema information about the children of the array
95        item: Box<TreeNode>,
96    },
97    /// Object value
98    Object {
99        /// Required filed for the object
100        required: Option<Vec<String>>,
101        /// The children of the object
102        children: Vec<ObjectChild>,
103    },
104    /// String value
105    String,
106    /// Number value
107    Number,
108    /// Integer value
109    Integer,
110}
111
112impl NodeType {
113    /// Specify if the type of the node allow children
114    pub fn can_have_children(&self) -> bool {
115        matches!(self, NodeType::Object { .. } | NodeType::Array { .. })
116    }
117
118    /// Get a short symbol representing the type of value
119    pub fn symbol(&self) -> &'static str {
120        match self {
121            NodeType::Null => "NULL",
122            NodeType::Boolean => "Bool",
123            NodeType::Array { .. } => "[]",
124            NodeType::Object { .. } => "{}",
125            NodeType::String => "str",
126            NodeType::Number => "num",
127            NodeType::Integer => "int",
128        }
129    }
130}
131
132/// Parser schema structure node
133#[derive(Debug, Clone, serde::Serialize)]
134pub struct TreeNode {
135    /// The name of the schema
136    pub name: String,
137    /// The type of the schema
138    #[serde(flatten)]
139    pub r#type: NodeType,
140    /// The description provided for the schema, if available
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub description: Option<String>,
143    /// Examples values for the schema, if available
144    #[serde(skip_serializing_if = "Vec::is_empty")]
145    pub examples: Vec<String>,
146    /// Discrete values of the schema, if specified
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub r#enum: Option<Vec<String>>,
149}
150
151impl TreeNode {
152    /// Get the name of the schema, alongside with a symbol
153    /// indicating its type
154    pub fn print_name(&self) -> String {
155        format!("{} {}", self.name, self.r#type.symbol())
156    }
157
158    /// Merge two TreeNode
159    pub fn merge_with(self, other: Self) -> Self {
160        if !matches!(self.r#type, NodeType::String | NodeType::Object { .. }) {
161            panic!("Cannot merge!");
162        }
163
164        if !matches!(other.r#type, NodeType::String | NodeType::Object { .. }) {
165            panic!("Cannot merge other!");
166        }
167
168        let r#type = match (self.r#type, other.r#type) {
169            (NodeType::String, NodeType::String) => NodeType::String,
170            (NodeType::String, NodeType::Object { children, required })
171            | (NodeType::Object { children, required }, NodeType::String) => {
172                NodeType::Object { children, required }
173            }
174
175            (
176                NodeType::Object {
177                    children: c1,
178                    required: r1,
179                },
180                NodeType::Object {
181                    children: mut c2,
182                    required: r2,
183                },
184            ) => {
185                let mut children = c1;
186                children.append(&mut c2);
187
188                let mut required = r1.unwrap_or_default();
189                required.append(&mut r2.unwrap_or_default());
190
191                NodeType::Object {
192                    children,
193                    required: match required.is_empty() {
194                        true => None,
195                        false => Some(required),
196                    },
197                }
198            }
199
200            (_, _) => unreachable!(),
201        };
202
203        TreeNode {
204            name: self.name.to_string(),
205            r#type,
206            description: other.description.or(self.description),
207            examples: match other.examples.is_empty() {
208                true => self.examples,
209                false => other.examples,
210            },
211            r#enum: other.r#enum.or(self.r#enum),
212        }
213    }
214
215    /// Get JSON example value
216    pub fn example_value(&self, max_recursion: usize) -> Value {
217        match &self.r#type {
218            NodeType::Null => Value::Null,
219            NodeType::Boolean => {
220                Value::Bool(self.examples.first().map(|e| e == "true").unwrap_or(false))
221            }
222
223            NodeType::Array { item } => Value::Array(vec![item.example_value(max_recursion)]),
224
225            NodeType::Object { children, .. } => {
226                if max_recursion == 0 {
227                    return json!(HashMap::from([(REF_OBJECT.to_string(), &self.name)]));
228                }
229
230                json!(children
231                    .iter()
232                    .map(|c| (c.name.clone(), c.node.example_value(max_recursion - 1)))
233                    .collect::<HashMap<String, serde_json::Value>>())
234            }
235
236            NodeType::String => Value::String(
237                self.examples
238                    .first()
239                    .map(|s| s.as_str().trim_matches('"'))
240                    .unwrap_or("string")
241                    .to_string(),
242            ),
243            NodeType::Number => serde_json::json!(self
244                .examples
245                .first()
246                .map(|s| s.parse::<f64>().expect("Failed to parse f64"))
247                .unwrap_or(1.)),
248            NodeType::Integer => serde_json::json!(self
249                .examples
250                .first()
251                .map(|s| s.parse::<u64>().expect("Failed to parse f64"))
252                .unwrap_or(0)),
253        }
254    }
255
256    /// Turn object into JSON schema
257    pub fn json_schema(&self) -> Value {
258        valico::json_schema::builder::schema(|s| self.json_schema_inner(s)).into_json()
259    }
260
261    fn json_schema_inner(&self, builder: &mut valico::json_schema::builder::Builder) {
262        if let Some(description) = &self.description {
263            builder.desc(description);
264        }
265
266        match &self.r#type {
267            NodeType::Null => {
268                builder.null();
269            }
270            NodeType::Boolean => {
271                builder.boolean();
272            }
273
274            NodeType::Array { item } => {
275                builder.array();
276                builder.items_schema(|b| Self::json_schema_inner(item, b));
277            }
278
279            NodeType::Object { children, required } => {
280                if let Some(required) = required {
281                    builder.required(required.clone());
282                }
283
284                builder.properties(|properties| {
285                    for child in children {
286                        properties.insert(&child.name, |child_prop| {
287                            Self::json_schema_inner(&child.node, child_prop);
288                        });
289                    }
290                });
291            }
292
293            NodeType::String => {
294                builder.string();
295            }
296            NodeType::Number => {
297                builder.number();
298            }
299            NodeType::Integer => {
300                builder.integer();
301            }
302        }
303    }
304}
305
306/// Construct the tree of a given structure name
307pub fn build_tree(struct_name: &str, components: &Components) -> TreeNode {
308    let schema = components
309        .schemas
310        .get(struct_name)
311        .unwrap_or_else(|| panic!("Missing {struct_name}"));
312
313    build_tree_schema(schema, struct_name, components)
314}
315
316/// Build a structure tree using a schema
317fn build_tree_schema(
318    schema: &SchemaObject,
319    struct_name: &str,
320    components: &Components,
321) -> TreeNode {
322    if let Some(name) = &schema.reference {
323        return build_tree(
324            name.strip_prefix("#/components/schemas/").unwrap(),
325            components,
326        );
327    }
328
329    if let Some(subschemas) = &schema.subschemas {
330        if let Some(all_of) = &subschemas.all_of {
331            assert!(!all_of.is_empty());
332            let mut tree =
333                build_tree_schema(expect_schema_object(&all_of[0]), struct_name, components);
334
335            for other in all_of.iter().skip(1) {
336                let other = build_tree_schema(expect_schema_object(other), struct_name, components);
337                tree = tree.merge_with(other);
338            }
339
340            tree.name = struct_name.to_string();
341            return tree;
342        } else {
343            panic!("Unsupported case!");
344        }
345    }
346
347    let schema_type = schema
348        .instance_type
349        .as_ref()
350        .map(expect_single)
351        .unwrap_or(&InstanceType::String);
352
353    let r#type = match schema_type {
354        InstanceType::Null => NodeType::Null,
355        InstanceType::Boolean => NodeType::Boolean,
356        InstanceType::Object => {
357            let object = schema.object.as_ref();
358            let children = object
359                .map(|s| s.properties.clone())
360                .unwrap_or_default()
361                .iter()
362                .map(|e| {
363                    let o = expect_schema_object(e.1);
364                    ObjectChild {
365                        name: e.0.to_string(),
366                        node: build_tree_schema(o, e.0, components),
367                    }
368                })
369                .collect::<Vec<_>>();
370
371            let required = object
372                .as_ref()
373                .map(|o| &o.required)
374                .map(|r| r.iter().map(|s| s.to_string()).collect());
375
376            NodeType::Object { children, required }
377        }
378        InstanceType::Array => {
379            let item = expect_schema_object(expect_single(
380                schema.array.as_ref().unwrap().items.as_ref().unwrap(),
381            ));
382            NodeType::Array {
383                item: Box::new(build_tree_schema(
384                    item,
385                    &format!("{struct_name}[]"),
386                    components,
387                )),
388            }
389        }
390        InstanceType::Number => NodeType::Number,
391        InstanceType::String => NodeType::String,
392        InstanceType::Integer => NodeType::Integer,
393    };
394
395    let metadata = schema.metadata.clone().unwrap_or_default();
396
397    TreeNode {
398        name: struct_name.to_string(),
399        r#type,
400        description: metadata.description,
401        examples: metadata
402            .examples
403            .iter()
404            .map(|v| v.to_string())
405            .collect::<Vec<_>>(),
406        r#enum: schema
407            .enum_values
408            .as_ref()
409            .map(|v| v.iter().map(|v| v.to_string()).collect()),
410    }
411}