use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::collections::HashSet;
fn generate_array(
id: &str,
pre: &mut TokenStream2,
definition: &serde_json::Value,
) -> TokenStream2 {
let defn = definition.as_object().unwrap();
if defn.contains_key("items") {
let subtype = single_type(format!("{}_element", id).as_str(), pre, &defn["items"]);
return quote! {
Vec<#subtype>
}
}
else {
return quote! {
Vec<serde_json::Value>
}
}
}
fn generate_object(
id: &str,
pre: &mut TokenStream2,
definition: &serde_json::Value,
) -> TokenStream2 {
let ident = quote::format_ident!("{}", id);
let mut current_struct = TokenStream2::new();
let defn = definition.as_object().expect("Definition is an object");
if defn.contains_key("$ref") {
todo!("References NYI!");
}
if defn.contains_key("patternProperties") {
let patterns = defn["patternProperties"].as_object().unwrap();
for pattern in patterns {
if pattern.0 != ".*" {
panic!("Only support \".*\" patterns, not {}!", pattern.0);
}
let ty = single_type(id, pre, pattern.1);
return quote! {
std::collections::HashMap<String, #ty>
}
}
}
let extra_are_errors = false;
let mut required: HashSet<String> = HashSet::new();
if defn.contains_key("requiredProperties") {
for prop in defn["requiredProperties"].as_array().unwrap() {
required.insert(prop.as_str().unwrap().to_string());
}
}
if defn.contains_key("properties") {
for prop in defn["properties"].as_object().unwrap() {
let prop_name = quote::format_ident!("{}", prop.0);
let prop_type = single_type(format!("{}_{}", id, prop.0).as_str(), pre, prop.1);
if required.contains(prop.0) {
current_struct.extend(quote! {
pub #prop_name : #prop_type,
})
} else {
current_struct.extend(quote! {
pub #prop_name : Option<#prop_type>,
})
}
}
}
let extra = if extra_are_errors {
quote! {}
} else {
quote! {
#[serde(flatten)]
additional: std::collections::HashMap<String, serde_json::Value>,
}
};
pre.extend(quote! {
#[derive(Deserialize,Serialize,Debug)]
pub struct #ident {
#current_struct
#extra
}
});
quote! {#ident}
}
fn single_def(id: &str, pre: &mut TokenStream2, definition: &serde_json::Value) -> TokenStream2 {
let defn = definition.as_object().unwrap();
let ident = quote::format_ident!("{}", id);
let element_type = defn["type"].as_str().unwrap();
match element_type {
"string" => {
quote! { pub type #ident = String; }
}
"int" => {
quote! { pub type #ident = i64; }
}
"array" => {
let a = generate_array(id, pre, definition);
quote! { pub type #ident = #a; }
}
"object" => {
generate_object(id, pre, definition);
TokenStream2::new()
}
_ => {
panic!("Unknown object type {}!", element_type);
}
}
}
fn single_type(id: &str, pre: &mut TokenStream2, definition: &serde_json::Value) -> TokenStream2 {
let defn = definition.as_object().expect("Schema error: element definition expects a JSON object");
let element_type = defn["type"].as_str().unwrap();
match element_type {
"string" => {
quote! { String }
}
"int" => {
quote! { i64 }
}
"array" => {
generate_array(id, pre, definition)
}
"object" => {
let objname = generate_object(id, pre, definition);
quote! { #objname }
}
_ => {
panic!("Unknown object type {}!", element_type);
}
}
}
pub fn generate_schema(all_data: &serde_json::Value) -> TokenStream2 {
let mut pre = TokenStream2::new();
let mut schema_defs = crate::schema::single_def("SchemaRoot", &mut pre, all_data);
let all_obj = all_data.as_object().expect("Schema error: root element expects a JSON object");
if all_obj.contains_key("$defs") {
for defn in all_obj["$defs"].as_object().unwrap() {
schema_defs.extend(crate::schema::single_def(defn.0, &mut pre, defn.1));
}
};
quote! { #pre #schema_defs }
}