use convert_case::{Case, Casing};
use openapiv3::{OpenAPI, ReferenceOr, Schema, SchemaKind};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
pub trait ToToken {
fn to_token(&self, spec: &OpenAPI) -> TokenStream;
}
impl ToToken for Schema {
fn to_token(&self, spec: &OpenAPI) -> TokenStream {
let z = match &self.schema_kind {
SchemaKind::Type(openapiv3::Type::String(s)) => quote!(String),
SchemaKind::Type(openapiv3::Type::Number(_)) => quote!(f64),
SchemaKind::Type(openapiv3::Type::Integer(_)) => quote!(i64),
SchemaKind::Type(openapiv3::Type::Boolean{}) => quote!(bool),
SchemaKind::Type(openapiv3::Type::Object(o)) => {
quote!(serde_json::Value)
}
SchemaKind::Type(openapiv3::Type::Array(a)) => {
let inside = a.items
.as_ref()
.unwrap()
.unbox()
.to_token(spec);
quote! { Vec<#inside> }
}
SchemaKind::Any(..) => quote!(serde_json::Value),
SchemaKind::AllOf{..} => quote!(serde_json::Value),
SchemaKind::OneOf{..} => quote!(serde_json::Value),
SchemaKind::AnyOf { .. } => quote!(serde_json::Value),
_ => {
println!("unimplemented: {:#?}", self);
unimplemented!()
},
};
if self.schema_data.nullable {
quote! { Option<#z> }
} else {
z
}
}
}
pub fn get_struct_name(reference: &str) -> Option<String> {
let mut parts = reference.split('/');
if parts.next() != Some("#") {
return None;
}
if parts.next() != Some("components") {
return None;
}
if parts.next() != Some("schemas") {
return None;
}
parts.next().map(|s| s.to_case(Case::Pascal))
}
impl ToToken for ReferenceOr<Schema> {
fn to_token(&self, spec: &OpenAPI) -> TokenStream {
match self {
ReferenceOr::Reference{ reference } => {
let name = get_struct_name(&reference).unwrap();
syn::Ident::new(&name, Span::call_site()).to_token_stream()
}
ReferenceOr::Item(s) => s.to_token(spec),
}
}
}
pub trait ToIdent {
fn to_struct_name(&self) -> syn::Ident;
fn to_ident(&self) -> syn::Ident;
fn is_restricted(&self) -> bool;
}
impl ToIdent for str {
fn to_struct_name(&self) -> syn::Ident {
let s = if self.is_restricted() {
self.to_case(Case::Pascal) + "Struct"
} else {
self.to_case(Case::Pascal)
};
syn::Ident::new(&s, Span::call_site())
}
fn to_ident(&self) -> Ident {
let s = if self.is_restricted() {
self.to_case(Case::Snake) + "_"
} else {
self.to_case(Case::Snake)
};
let s = s.replace("/", "_");
syn::Ident::new(&s, Span::call_site())
}
fn is_restricted(&self) -> bool {
["type", "use", "ref"].contains(&self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_ident() {
assert_eq!("meta/root".to_ident(), "meta_root");
}
}