use crate::datatypes::{DataType, FieldType};
use convert_case::{Case, Casing};
use std::fmt::{Result, Write};
pub fn write_comment_header<W: Write>(w: &mut W) -> Result {
writeln!(w, "// # OpenApi Types")?;
writeln!(w, "// GENERATED AUTOMATICALLY, ALL THE CHANGES")?;
writeln!(w, "// YOU MAKE WILL BE REWRITTEN DURING")?;
writeln!(w, "// THE NEXT BUILD")?;
writeln!(w)?; Ok(())
}
pub fn write_rust_code<W: Write>(
w: &mut W,
datatypes: &[DataType],
struct_derives: &[String],
enum_derives: &[String],
) -> Result {
let indent = " ";
let mut helper_types = vec![];
writeln!(w, "use serde::Deserialize;")?;
writeln!(w)?;
for dt in datatypes {
match dt {
DataType::Struct { name, fields } => {
for field in fields {
match &field.type_ {
FieldType::Plain(_) => (),
FieldType::OneOf(items) => {
let name = generate_union_name(&items);
if !helper_types.contains(&name) {
helper_types.push(name);
write_union_type(w, &items, struct_derives)?;
}
}
}
}
writeln!(w, "/// {name}")?; writeln!(w, "#[derive({})]", struct_derives.join(", "))?;
writeln!(w, "pub struct {} {{", name.to_case(Case::Pascal))?;
for field in fields {
let rust_name = fix_rust_keyword(field.translated_name.to_case(Case::Snake));
let mut t = match &field.type_ {
FieldType::Plain(t) => get_rust_type(t, &field.type_format),
FieldType::OneOf(items) => generate_union_name(&items),
};
for _ in 0..field.array_dimensions {
t = format!("Vec<{t}>");
}
if field.is_nullable {
t = format!("Option<{t}>");
}
if !field.descr.is_empty() {
for line in field.descr.trim().lines() {
writeln!(w, "{indent}/// {}", line.trim())?;
}
}
if field.translated_name != rust_name || field.name != field.translated_name {
writeln!(w, "{indent}#[serde(rename = {:?})]", field.name)?;
}
if t == "time::OffsetDateTime" {
writeln!(w, "{indent}#[serde(with = \"time::serde::iso8601\")]")?;
} else if t == "Option<time::OffsetDateTime>" {
writeln!(
w,
"{indent}#[serde(with = \"time::serde::iso8601::option\", default)]"
)?;
}
writeln!(w, "{indent}pub {rust_name}: {t},")?;
}
writeln!(w, "}}\n")?;
}
DataType::Enum { name, items } => {
writeln!(w, "/// {name}")?; let derives: Vec<_> = enum_derives
.iter()
.cloned()
.filter(|item| *item != "Display")
.collect();
writeln!(w, "#[derive({})]", derives.join(", "))?;
writeln!(w, "pub enum {} {{", name.to_case(Case::Pascal))?;
for item in items {
let rust_name = item.to_case(Case::Pascal);
if rust_name != *item {
writeln!(w, "{indent}#[serde(rename = {item:?})]")?;
}
writeln!(w, "{indent}{rust_name},")?;
}
writeln!(w, "}}\n")?;
if enum_derives.iter().any(|item| *item == "Display") {
write_display_impl_for_enum(w, dt)?;
}
}
DataType::Alias { alias, info } => {
let mut t = match &info.type_ {
FieldType::Plain(t) => get_rust_type(t, &info.type_format),
FieldType::OneOf(items) => {
let name = generate_union_name(&items);
if !helper_types.contains(&name) {
helper_types.push(name.clone());
write_union_type(w, &items, struct_derives)?;
}
name
}
};
for _ in 0..info.array_dimensions {
t = format!("Vec<{t}>");
}
if info.is_nullable {
t = format!("Option<{t}>");
}
writeln!(w, "/// {alias}")?; writeln!(w, "pub type {} = {t};\n", alias.to_case(Case::Pascal))?;
}
}
}
Ok(())
}
fn write_display_impl_for_enum<W: Write>(w: &mut W, dt: &DataType) -> Result {
let indent1 = " ".repeat(4);
let indent2 = " ".repeat(8);
let indent3 = " ".repeat(12);
if let DataType::Enum { name, items } = dt {
let enum_name = name.to_case(Case::Pascal);
writeln!(w, "impl std::fmt::Display for {enum_name} {{")?;
writeln!(
w,
"{indent1}fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{"
)?;
writeln!(w, "{indent2}match self {{")?;
for item in items {
let item_name = item.to_case(Case::Pascal);
writeln!(
w,
"{indent3}{enum_name}::{item_name} => write!(f, \"{item}\"),"
)?;
}
writeln!(w, "{indent2}}}")?;
writeln!(w, "{indent1}}}")?;
writeln!(w, "}}\n")?;
}
Ok(())
}
fn get_rust_type(typename: &str, format: &str) -> String {
match typename {
"number" => match format {
"float" => "f32".to_owned(),
"double" => "f64".to_owned(),
_ => "f64".to_owned(),
},
"boolean" => "bool".to_owned(),
"string" => match format {
"date" => "time::Date".to_owned(),
"date-time" => "time::OffsetDateTime".to_owned(),
_ => "String".to_owned(),
},
"integer" => match format {
"int32" => "i32".to_owned(),
"int64" => "i64".to_owned(),
_ => "i32".to_owned(),
},
"object" => "serde_json::Value".to_owned(),
struct_name => struct_name.to_case(Case::Pascal),
}
}
fn generate_union_name(one_of: &[String]) -> String {
format!("_Union{}", one_of.join("Or").to_case(Case::Pascal))
}
fn write_union_type<W: Write>(w: &mut W, one_of: &[String], struct_derives: &[String]) -> Result {
let indent = " ";
writeln!(w, "#[derive({})]", struct_derives.join(", "))?;
writeln!(w, "#[serde(untagged)]")?;
writeln!(w, "pub enum {} {{", generate_union_name(one_of))?;
for t in one_of {
let name = t.to_case(Case::Pascal);
writeln!(w, "{indent}{name}({name}),")?;
}
writeln!(w, "}}\n")
}
fn fix_rust_keyword(name: String) -> String {
if matches!(
name.as_str(),
"as" | "break"
| "const"
| "continue"
| "crate"
| "else"
| "enum"
| "extern"
| "false"
| "fn"
| "for"
| "if"
| "impl"
| "in"
| "let"
| "loop"
| "match"
| "mod"
| "move"
| "mut"
| "pub"
| "ref"
| "return"
| "self"
| "static"
| "struct"
| "super"
| "trait"
| "true"
| "type"
| "unsafe"
| "use"
| "where"
| "while"
| "async"
| "await"
| "dyn"
) {
return name + "_";
}
name
}