use clap::Parser; use okapi::openapi3::{Components, SchemaObject}; use okapi::schemars::schema::{InstanceType, Schema, SingleOrVec}; /// Dump the tree structure of a schema element as dot file #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// The name of the file to dump /// /// If this value is unspecified, the default petstore schema /// is used instead #[arg(short, long)] file_name: Option, /// The name of the structure to dump #[arg(short, long, default_value = "Pet")] struct_name: String, } #[derive(Debug, Clone)] enum NodeType { Null, Boolean, Array { item: Box }, Object { children: Vec }, String, Number, Integer, } #[derive(Debug, Clone)] struct TreeNode { name: String, r#type: NodeType, } impl 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 }) | (NodeType::Object { children }, NodeType::String) => NodeType::Object { children: children.clone(), }, (NodeType::Object { children: c1 }, NodeType::Object { children: c2 }) => { let mut children = c1.clone(); children.append(&mut c2.clone()); NodeType::Object { children } } (_, _) => unreachable!(), }; TreeNode { name: other.name.to_string(), r#type, } } } fn main() { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); let args = Args::parse(); let file_content = match args.file_name { None => include_str!("../examples/petstore.yaml").to_string(), Some(path) => std::fs::read_to_string(path).expect("Unable to load schema file!"), }; // Parse schema let schema = serde_yaml::from_str::(&file_content) .expect("Failed to parse document"); if schema.components.is_none() { log::error!("components is missing!"); panic!() } let tree = build_tree(&args.struct_name, schema.components.as_ref().unwrap()); println!("{:#?}", tree); } 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, } } fn build_tree(struct_name: &str, components: &Components) -> TreeNode { let schema = components .schemas .get(struct_name) .expect(&format!("Missing {struct_name}")); build_tree_schema(schema, struct_name, components) } 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 children = schema .object .as_ref() .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::>(); NodeType::Object { children } } 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, }; TreeNode { name: struct_name.to_string(), r#type, } }