218 lines
5.7 KiB
Rust
218 lines
5.7 KiB
Rust
use indexmap::IndexMap;
|
|
use newtype::Newtype;
|
|
use object::Object;
|
|
use parameter::Parameter;
|
|
use path::Path;
|
|
use proc_macro2::TokenStream;
|
|
use r#enum::Enum;
|
|
use scope::Scope;
|
|
|
|
use crate::openapi::{r#type::OpenApiType, schema::OpenApiSchema};
|
|
|
|
pub mod r#enum;
|
|
pub mod newtype;
|
|
pub mod object;
|
|
pub mod parameter;
|
|
pub mod path;
|
|
pub mod scope;
|
|
pub mod union;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum Model {
|
|
Newtype(Newtype),
|
|
Enum(Enum),
|
|
Object(Object),
|
|
Unresolved,
|
|
}
|
|
|
|
impl Model {
|
|
pub fn is_display(&self, resolved: &ResolvedSchema) -> bool {
|
|
match self {
|
|
Self::Enum(r#enum) => r#enum.is_display(resolved),
|
|
Self::Newtype(_) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct ResolvedSchema {
|
|
pub models: IndexMap<String, Model>,
|
|
pub paths: IndexMap<String, Path>,
|
|
pub parameters: Vec<Parameter>,
|
|
}
|
|
|
|
impl ResolvedSchema {
|
|
pub fn from_open_api(schema: &OpenApiSchema) -> Self {
|
|
let mut result = Self::default();
|
|
|
|
for (name, r#type) in &schema.components.schemas {
|
|
result.models.insert(
|
|
name.to_string(),
|
|
resolve(r#type, name, &schema.components.schemas),
|
|
);
|
|
}
|
|
|
|
for (path, body) in &schema.paths {
|
|
result.paths.insert(
|
|
path.to_string(),
|
|
Path::from_schema(path, body, &schema.components.parameters).unwrap(),
|
|
);
|
|
}
|
|
|
|
for (name, param) in &schema.components.parameters {
|
|
result
|
|
.parameters
|
|
.push(Parameter::from_schema(name, param).unwrap());
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
pub fn codegen_models(&self) -> TokenStream {
|
|
let mut output = TokenStream::default();
|
|
|
|
for model in self.models.values() {
|
|
output.extend(model.codegen(self));
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
pub fn codegen_requests(&self) -> TokenStream {
|
|
let mut output = TokenStream::default();
|
|
|
|
for path in self.paths.values() {
|
|
output.extend(path.codegen_request(self));
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
pub fn codegen_parameters(&self) -> TokenStream {
|
|
let mut output = TokenStream::default();
|
|
|
|
for param in &self.parameters {
|
|
output.extend(param.codegen(self));
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
pub fn codegen_scopes(&self) -> TokenStream {
|
|
let mut output = TokenStream::default();
|
|
|
|
let scopes = Scope::from_paths(self.paths.values().cloned().collect());
|
|
|
|
for scope in scopes {
|
|
output.extend(scope.codegen());
|
|
}
|
|
|
|
output
|
|
}
|
|
}
|
|
|
|
pub fn resolve(r#type: &OpenApiType, name: &str, schemas: &IndexMap<&str, OpenApiType>) -> Model {
|
|
match r#type {
|
|
OpenApiType {
|
|
r#enum: Some(_), ..
|
|
} => Enum::from_schema(name, r#type).map_or(Model::Unresolved, Model::Enum),
|
|
OpenApiType {
|
|
r#type: Some("object"),
|
|
..
|
|
} => Object::from_schema_object(name, r#type, schemas)
|
|
.map_or(Model::Unresolved, Model::Object),
|
|
OpenApiType {
|
|
r#type: Some(_), ..
|
|
} => Newtype::from_schema(name, r#type).map_or(Model::Unresolved, Model::Newtype),
|
|
OpenApiType {
|
|
one_of: Some(types),
|
|
..
|
|
} => Enum::from_one_of(name, types).map_or(Model::Unresolved, Model::Enum),
|
|
OpenApiType {
|
|
all_of: Some(types),
|
|
..
|
|
} => Object::from_all_of(name, types, schemas).map_or(Model::Unresolved, Model::Object),
|
|
_ => Model::Unresolved,
|
|
}
|
|
}
|
|
|
|
impl Model {
|
|
pub fn codegen(&self, resolved: &ResolvedSchema) -> Option<TokenStream> {
|
|
match self {
|
|
Self::Newtype(newtype) => newtype.codegen(),
|
|
Self::Enum(r#enum) => r#enum.codegen(resolved),
|
|
Self::Object(object) => object.codegen(resolved),
|
|
Self::Unresolved => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use crate::openapi::schema::test::get_schema;
|
|
|
|
#[test]
|
|
fn resolve_newtypes() {
|
|
let schema = get_schema();
|
|
|
|
let user_id_schema = schema.components.schemas.get("UserId").unwrap();
|
|
|
|
let user_id = resolve(user_id_schema, "UserId", &schema.components.schemas);
|
|
|
|
assert_eq!(
|
|
user_id,
|
|
Model::Newtype(Newtype {
|
|
name: "UserId".to_owned(),
|
|
description: None,
|
|
inner: newtype::NewtypeInner::I32,
|
|
copy: true,
|
|
ord: true
|
|
})
|
|
);
|
|
|
|
let attack_code_schema = schema.components.schemas.get("AttackCode").unwrap();
|
|
|
|
let attack_code = resolve(attack_code_schema, "AttackCode", &schema.components.schemas);
|
|
|
|
assert_eq!(
|
|
attack_code,
|
|
Model::Newtype(Newtype {
|
|
name: "AttackCode".to_owned(),
|
|
description: None,
|
|
inner: newtype::NewtypeInner::Str,
|
|
copy: false,
|
|
ord: false
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_all() {
|
|
let schema = get_schema();
|
|
|
|
let mut unresolved = vec![];
|
|
let total = schema.components.schemas.len();
|
|
|
|
for (name, desc) in &schema.components.schemas {
|
|
if resolve(desc, name, &schema.components.schemas) == Model::Unresolved {
|
|
unresolved.push(name);
|
|
}
|
|
}
|
|
|
|
if !unresolved.is_empty() {
|
|
panic!(
|
|
"Failed to resolve {}/{} types. Could not resolve [{}]",
|
|
unresolved.len(),
|
|
total,
|
|
unresolved
|
|
.into_iter()
|
|
.map(|u| format!("`{u}`"))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
}
|
|
}
|