use okapi::openapi3::{Components, OpenApi, SchemaObject}; use okapi::schemars::schema::{InstanceType, Schema, SingleOrVec}; use serde_json::Value; fn recurse_fix(mut v: &mut Value) { match &mut v { Value::Array(array) => { for item in array { recurse_fix(item); } } Value::Object(obj) => { for item in obj.iter_mut() { recurse_fix(item.1); } if !obj.contains_key("examples") { if let Some(ex) = obj.get("example").cloned() { obj.insert("examples".to_string(), Value::Array(vec![ex])); } } } _ => {} } } /// Parse OpenAPI 3 schema, treating "example" field as "examples" /// /// Otherwise, examples would be lost pub fn parse_schema_fix_example_issue(file_content: &str) -> OpenApi { let mut root = serde_yaml::from_str::(file_content) .expect("Failed to parse OpenAPI document as YAML document!"); if let Value::Object(root_obj) = &mut root { if let Some(components) = root_obj.get_mut("components") { recurse_fix(components); } } parse_schema(&serde_yaml::to_string(&root).unwrap()) } /// Parse OpenAPI 3 schema pub fn parse_schema(file_content: &str) -> OpenApi { let schema = serde_yaml::from_str::(file_content).expect("Failed to parse document"); if schema.components.is_none() { log::error!("components is missing!"); panic!() } schema } fn expect_single(e: &SingleOrVec) -> &E { match e { SingleOrVec::Single(e) => e, SingleOrVec::Vec(v) => &v[0], } } fn expect_schema_object(s: &Schema) -> &SchemaObject { match s { Schema::Bool(_) => { panic!("Got unexpected bool!"); } Schema::Object(o) => o, } } /// Object child information #[derive(Debug, Clone, serde::Serialize)] pub struct ObjectChild { /// The name of the field in the object pub name: String, /// The structure of the child pub node: TreeNode, } /// The type of the schema #[derive(Debug, Clone, serde::Serialize)] #[serde(tag = "type")] pub enum NodeType { /// NULL value Null, /// BOOLEAN value Boolean, /// Array value Array { /// Schema information about the children of the array item: Box, }, /// Object value Object { /// Required filed for the object required: Option>, /// The children of the object children: Vec, }, /// String value String, /// Number value Number, /// Integer value Integer, } impl NodeType { /// Specify if the type of the node allow children pub fn can_have_children(&self) -> bool { matches!(self, NodeType::Object { .. } | NodeType::Array { .. }) } /// Get a short symbol representing the type of value pub fn symbol(&self) -> &'static str { match self { NodeType::Null => "NULL", NodeType::Boolean => "Bool", NodeType::Array { .. } => "[]", NodeType::Object { .. } => "{}", NodeType::String => "str", NodeType::Number => "num", NodeType::Integer => "int", } } } /// Parser schema structure node #[derive(Debug, Clone, serde::Serialize)] pub struct TreeNode { /// The name of the schema pub name: String, /// The type of the schema #[serde(flatten)] pub r#type: NodeType, /// The description provided for the schema, if available #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, /// Examples values for the schema, if available #[serde(skip_serializing_if = "Vec::is_empty")] pub examples: Vec, /// Discrete values of the schema, if specified #[serde(skip_serializing_if = "Option::is_none")] pub r#enum: Option>, } impl TreeNode { /// Get the name of the schema, alongside with a symbol /// indicating its type pub fn print_name(&self) -> String { format!("{} {}", self.name, self.r#type.symbol()) } /// Merge two TreeNode pub fn merge_with(self, other: Self) -> Self { if !matches!(self.r#type, NodeType::String | NodeType::Object { .. }) { panic!("Cannot merge!"); } if !matches!(other.r#type, NodeType::String | NodeType::Object { .. }) { panic!("Cannot merge other!"); } let r#type = match (self.r#type, other.r#type) { (NodeType::String, NodeType::String) => NodeType::String, (NodeType::String, NodeType::Object { children, required }) | (NodeType::Object { children, required }, NodeType::String) => { NodeType::Object { children, required } } ( NodeType::Object { children: c1, required: r1, }, NodeType::Object { children: mut c2, required: r2, }, ) => { let mut children = c1; children.append(&mut c2); let mut required = r1.unwrap_or_default(); required.append(&mut r2.unwrap_or_default()); NodeType::Object { children, required: match required.is_empty() { true => None, false => Some(required), }, } } (_, _) => unreachable!(), }; TreeNode { name: self.name.to_string(), r#type, description: other.description.or(self.description), examples: match other.examples.is_empty() { true => self.examples, false => other.examples, }, r#enum: other.r#enum.or(self.r#enum), } } /// Turn object into JSON schema pub fn json_schema(&self) -> Value { valico::json_schema::builder::schema(|s| self.json_schema_inner(s)).into_json() } fn json_schema_inner(&self, builder: &mut valico::json_schema::builder::Builder) { if let Some(description) = &self.description { builder.desc(description); } match &self.r#type { NodeType::Null => { builder.null(); } NodeType::Boolean => { builder.boolean(); } NodeType::Array { item } => { builder.array(); builder.items_schema(|b| Self::json_schema_inner(item, b)); } NodeType::Object { children, required } => { if let Some(required) = required { builder.required(required.clone()); } builder.properties(|properties| { for child in children { properties.insert(&child.name, |child_prop| { Self::json_schema_inner(&child.node, child_prop); }); } }); } NodeType::String => { builder.string(); } NodeType::Number => { builder.number(); } NodeType::Integer => { builder.integer(); } } } } /// Construct the tree of a given structure name pub fn build_tree(struct_name: &str, components: &Components) -> TreeNode { let schema = components .schemas .get(struct_name) .unwrap_or_else(|| panic!("Missing {struct_name}")); build_tree_schema(schema, struct_name, components) } /// Build a structure tree using a schema fn build_tree_schema( schema: &SchemaObject, struct_name: &str, components: &Components, ) -> TreeNode { if let Some(name) = &schema.reference { return build_tree( name.strip_prefix("#/components/schemas/").unwrap(), components, ); } if let Some(subschemas) = &schema.subschemas { if let Some(all_of) = &subschemas.all_of { assert!(!all_of.is_empty()); let mut tree = build_tree_schema(expect_schema_object(&all_of[0]), struct_name, components); for other in all_of.iter().skip(1) { let other = build_tree_schema(expect_schema_object(other), struct_name, components); tree = tree.merge_with(other); } tree.name = struct_name.to_string(); return tree; } else { panic!("Unsupported case!"); } } let schema_type = schema .instance_type .as_ref() .map(expect_single) .unwrap_or(&InstanceType::String); let r#type = match schema_type { InstanceType::Null => NodeType::Null, InstanceType::Boolean => NodeType::Boolean, InstanceType::Object => { let object = schema.object.as_ref(); let children = object .map(|s| s.properties.clone()) .unwrap_or_default() .iter() .map(|e| { let o = expect_schema_object(e.1); ObjectChild { name: e.0.to_string(), node: build_tree_schema(o, e.0, components), } }) .collect::>(); let required = object .as_ref() .map(|o| &o.required) .map(|r| r.iter().map(|s| s.to_string()).collect()); NodeType::Object { children, required } } InstanceType::Array => { let item = expect_schema_object(expect_single( schema.array.as_ref().unwrap().items.as_ref().unwrap(), )); NodeType::Array { item: Box::new(build_tree_schema( item, &format!("{struct_name}[]"), components, )), } } InstanceType::Number => NodeType::Number, InstanceType::String => NodeType::String, InstanceType::Integer => NodeType::Integer, }; let metadata = schema.metadata.clone().unwrap_or_default(); TreeNode { name: struct_name.to_string(), r#type, description: metadata.description, examples: metadata .examples .iter() .map(|v| v.to_string()) .collect::>(), r#enum: schema .enum_values .as_ref() .map(|v| v.iter().map(|v| v.to_string()).collect()), } }