diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5c24fc7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,167 @@ +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)] +pub enum NodeType { + Null, + Boolean, + Array { item: Box }, + Object { children: Vec }, + String, + Number, + Integer, +} + +#[derive(Debug, Clone)] +pub struct TreeNode { + pub name: String, + pub r#type: NodeType, +} + +impl TreeNode { + /// 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 }) + | (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, + } + } +} + +/// 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) + .expect(&format!("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 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, + } +} diff --git a/src/main.rs b/src/main.rs index 2b0a444..feddb61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use clap::Parser; -use okapi::openapi3::{Components, SchemaObject}; -use okapi::schemars::schema::{InstanceType, Schema, SingleOrVec}; +use openapi_parser::{build_tree, parse_schema}; /// Dump the tree structure of a schema element as dot file #[derive(Parser, Debug)] @@ -18,56 +17,6 @@ struct Args { 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")); @@ -78,116 +27,8 @@ fn main() { 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 schema = parse_schema(&file_content); 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, - } -}