248 lines
6.8 KiB
Rust
248 lines
6.8 KiB
Rust
use clap::{Parser, Subcommand};
|
|
use openapi_parser::{
|
|
build_tree, parse_schema, parse_schema_fix_example_issue, NodeType, ObjectChild, TreeNode,
|
|
};
|
|
use std::collections::HashSet;
|
|
use std::fmt::Write;
|
|
|
|
/// 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<String>,
|
|
|
|
/// The name of the structure to dump
|
|
#[arg(short, long, default_value = "Pet")]
|
|
struct_name: String,
|
|
|
|
/// Treat "example" field as "examples"
|
|
#[arg(long)]
|
|
fix_example: bool,
|
|
|
|
/// The action to perform
|
|
#[clap(subcommand)]
|
|
action: Action,
|
|
}
|
|
|
|
#[derive(Subcommand, Debug, Eq, PartialEq)]
|
|
enum Action {
|
|
/// Dump as JSON
|
|
Json,
|
|
|
|
/// Dump JSON schema
|
|
JsonSchema,
|
|
|
|
/// Dump JSON example
|
|
JsonExample,
|
|
|
|
/// Dump as Graphviz graph
|
|
Graph,
|
|
|
|
/// Dump as Tex list
|
|
Tex {
|
|
/// Dump a single entry
|
|
#[arg(short, long)]
|
|
single: bool,
|
|
},
|
|
}
|
|
|
|
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!"),
|
|
};
|
|
|
|
let schema = match args.fix_example {
|
|
true => parse_schema_fix_example_issue(&file_content),
|
|
false => parse_schema(&file_content),
|
|
};
|
|
let components = schema.components.as_ref().unwrap();
|
|
|
|
if args.action == (Action::Tex { single: false }) {
|
|
for entry in components.schemas.keys() {
|
|
let tree = build_tree(entry, components);
|
|
println!("{}", tex_export(&tree));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
let tree = build_tree(&args.struct_name, components);
|
|
|
|
match args.action {
|
|
Action::Json => println!("{}", serde_json::to_string(&tree).unwrap()),
|
|
Action::Graph => println!("{}", graphviz_export(&tree)),
|
|
Action::Tex { .. } => println!("{}", tex_export(&tree)),
|
|
Action::JsonSchema => println!("{}", json_schema(&tree)),
|
|
Action::JsonExample => {
|
|
println!("{}", serde_json::to_string(&tree.example_value()).unwrap())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn recurse_export(
|
|
node: &TreeNode,
|
|
parent_name: &str,
|
|
out: &mut String,
|
|
already_processed: &mut HashSet<String>,
|
|
) {
|
|
let key = format!("{} #> {}", parent_name, node.name);
|
|
if already_processed.contains(&key) {
|
|
return;
|
|
}
|
|
already_processed.insert(key);
|
|
|
|
if !parent_name.is_empty() && matches!(node.r#type, NodeType::Object { .. }) {
|
|
writeln!(out, "\"{}\" -> \"{}\";", parent_name, node.name).unwrap();
|
|
}
|
|
|
|
match &node.r#type {
|
|
NodeType::Array { item } => {
|
|
let mut item = item.clone();
|
|
item.name.push_str(" []");
|
|
recurse_export(&item, parent_name, out, already_processed);
|
|
}
|
|
NodeType::Object { children, .. } => {
|
|
for child in children {
|
|
recurse_export(&child.node, &node.name, out, already_processed);
|
|
}
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn graphviz_export(tree: &TreeNode) -> String {
|
|
let mut out = "digraph G {\n".to_string();
|
|
|
|
let mut already_processed = HashSet::new();
|
|
|
|
recurse_export(tree, "", &mut out, &mut already_processed);
|
|
|
|
out.push_str("}\n");
|
|
out
|
|
}
|
|
|
|
fn tex_escape_str(s: &str) -> String {
|
|
s.replace('_', "\\_")
|
|
.trim_matches('\n')
|
|
.replace("\n\n", "\n")
|
|
.replace('\n', "\\newline\n")
|
|
}
|
|
|
|
fn tex_type_str(t: &TreeNode) -> String {
|
|
match &t.r#type {
|
|
NodeType::Null => "NULL".to_string(),
|
|
NodeType::Boolean => "bool".to_string(),
|
|
NodeType::Array { item } => format!("{}[]", tex_type_str(item)),
|
|
NodeType::Object { .. } => t.name.to_string(),
|
|
NodeType::String => "string".to_string(),
|
|
NodeType::Number => "number".to_string(),
|
|
NodeType::Integer => "integer".to_string(),
|
|
}
|
|
}
|
|
|
|
fn tex_export_inner(tree: &ObjectChild, out: &mut String, required: bool) {
|
|
let type_str = tex_type_str(&tree.node);
|
|
|
|
writeln!(
|
|
out,
|
|
"\\schemaprop{{{}}}{{{}}}{{{}}}{{{}}}{{{}}}",
|
|
tex_escape_str(&tree.name),
|
|
match required {
|
|
true => "true",
|
|
false => "false",
|
|
},
|
|
tex_escape_str(&type_str),
|
|
match &tree.node.description {
|
|
None => "".to_string(),
|
|
Some(d) => tex_escape_str(d),
|
|
},
|
|
match (&tree.node.r#type, tree.node.examples.get(0)) {
|
|
(_, Some(e)) => tex_escape_str(e),
|
|
(NodeType::Array { item }, _) if !item.examples.is_empty() => {
|
|
format!("[{}]", &item.examples.get(0).unwrap())
|
|
}
|
|
_ => "".to_string(),
|
|
}
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
fn tex_adapt_name(i: &str) -> String {
|
|
i.replace('0', "zero")
|
|
.replace('1', "one")
|
|
.replace('2', "two")
|
|
.replace('3', "three")
|
|
.replace('4', "four")
|
|
.replace('5', "five")
|
|
.replace('6', "six")
|
|
.replace('7', "seven")
|
|
.replace('8', "height")
|
|
.replace('9', "nine")
|
|
}
|
|
|
|
fn tex_export(tree: &TreeNode) -> String {
|
|
let mut out = String::new();
|
|
writeln!(out, "% START OF EXPORT OF SCHEMA {}", tree.name).unwrap();
|
|
writeln!(
|
|
out,
|
|
"\\newcommand{{\\schemadef{}}}{{",
|
|
tex_adapt_name(&tree.name)
|
|
)
|
|
.unwrap();
|
|
|
|
match &tree.r#type {
|
|
NodeType::Object { children, required } => {
|
|
writeln!(out, "\\schemaname{{{}}}", tex_escape_str(&tree.name)).unwrap();
|
|
|
|
if let Some(description) = &tree.description {
|
|
writeln!(
|
|
out,
|
|
"\\schemadescription{{{}}}",
|
|
tex_escape_str(description)
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
out.push_str("\\begin{schemaprops}\n");
|
|
for child in children {
|
|
tex_export_inner(
|
|
child,
|
|
&mut out,
|
|
required
|
|
.as_ref()
|
|
.map(|r| r.contains(&child.name))
|
|
.unwrap_or(false),
|
|
);
|
|
}
|
|
out.push_str("\\end{schemaprops}\n");
|
|
}
|
|
_ => tex_export_inner(
|
|
&ObjectChild {
|
|
name: tree.name.to_string(),
|
|
node: tree.clone(),
|
|
},
|
|
&mut out,
|
|
false,
|
|
),
|
|
}
|
|
|
|
out.push_str("}\n");
|
|
writeln!(out, "% END OF EXPORT OF SCHEMAS {}", tree.name).unwrap();
|
|
out
|
|
}
|
|
|
|
fn json_schema(tree: &TreeNode) -> String {
|
|
serde_json::to_string(&tree.json_schema()).unwrap()
|
|
}
|