use okapi::openapi3::{Components, OpenApi, SchemaObject}; use okapi::schemars::schema::{InstanceType, Schema, SingleOrVec}; /// 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, } } #[derive(Debug, Clone, serde::Serialize)] #[serde(tag = "type")] pub enum NodeType { Null, Boolean, Array { item: Box, }, Object { required: Option>, children: Vec, }, String, Number, Integer, } impl NodeType { pub fn can_have_children(&self) -> bool { matches!(self, NodeType::Object { .. } | NodeType::Array { .. }) } 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", } } } #[derive(Debug, Clone, serde::Serialize)] pub struct TreeNode { pub name: String, #[serde(flatten)] pub r#type: NodeType, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(skip_serializing_if = "Vec::is_empty")] pub examples: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub r#enum: Option>, } impl TreeNode { 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), } } } /// 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); } 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); 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()), } }