use proc_macro2::{Literal, TokenStream};
use quote::quote;
use serde_json::Value;
use syn::{Ident, Visibility};
use crate::error::{ForgeError, ForgeResult};
use crate::schema::types::{SchemaType, StructSchema, TopLevel};
use super::types::{schema_type_tokens, struct_def};
use super::util::{make_ident, to_screaming_snake};
pub fn generate(
vis: &Visibility,
data_vis: &Visibility,
name: &Ident,
top: &TopLevel,
root: &Value,
) -> ForgeResult<TokenStream> {
match top {
TopLevel::Map { entry } => generate_phf_map(vis, data_vis, name, entry, root),
TopLevel::Array { entry } => generate_static_array(vis, data_vis, name, entry, root),
TopLevel::Struct(schema) => generate_single_struct(vis, data_vis, name, schema, root),
}
}
fn generate_phf_map(
vis: &Visibility,
data_vis: &Visibility,
name: &Ident,
schema: &StructSchema,
root: &Value,
) -> ForgeResult<TokenStream> {
use phf_generator::generate_hash;
let obj = root
.as_object()
.ok_or_else(|| ForgeError::call_site("expected a JSON object at top level"))?;
let keys: Vec<&str> = obj.keys().map(String::as_str).collect();
let values: Vec<&Value> = obj.values().collect();
let hash_state = generate_hash(&keys);
let phf_key = Literal::u64_suffixed(hash_state.key);
let disps: Vec<TokenStream> = hash_state
.disps
.iter()
.map(|&(d1, d2)| {
let d1 = Literal::u32_suffixed(d1);
let d2 = Literal::u32_suffixed(d2);
quote! { (#d1, #d2) }
})
.collect();
let entries: Vec<TokenStream> = hash_state
.map
.iter()
.map(|&idx| {
let k = keys[idx];
let v = values[idx];
let struct_lit = value_to_struct_literal(v, schema, name)?;
Ok(quote! { (#k, #struct_lit) })
})
.collect::<ForgeResult<_>>()?;
let struct_ts = struct_def(vis, name, schema, true);
let static_name = make_ident(&to_screaming_snake(&name.to_string()));
let entry_type = quote! { #name };
Ok(quote! {
#struct_ts
#data_vis static #static_name: ::phf::Map<&'static str, #entry_type> = ::phf::Map {
key: #phf_key,
disps: &[#(#disps),*],
entries: &[#(#entries),*],
};
})
}
fn generate_static_array(
vis: &Visibility,
data_vis: &Visibility,
name: &Ident,
entry_ty: &SchemaType,
root: &Value,
) -> ForgeResult<TokenStream> {
let arr = root
.as_array()
.ok_or_else(|| ForgeError::call_site("expected a JSON array at top level"))?;
let len = arr.len();
let struct_ts = match entry_ty {
SchemaType::Struct(schema) => struct_def(vis, name, schema, true),
_ => quote! {},
};
let item_tokens: Vec<TokenStream> = arr
.iter()
.map(|v| value_to_tokens(v, entry_ty, name))
.collect::<ForgeResult<_>>()?;
let static_name = make_ident(&format!("{}_DATA", to_screaming_snake(&name.to_string())));
let rust_ty = if matches!(entry_ty, SchemaType::Struct(_)) {
quote! { #name }
} else {
schema_type_tokens(entry_ty, true)
};
Ok(quote! {
#struct_ts
#data_vis static #static_name: [#rust_ty; #len] = [
#(#item_tokens),*
];
})
}
fn generate_single_struct(
vis: &Visibility,
data_vis: &Visibility,
name: &Ident,
schema: &StructSchema,
root: &Value,
) -> ForgeResult<TokenStream> {
let struct_ts = struct_def(vis, name, schema, true);
let static_name = make_ident(&to_screaming_snake(&name.to_string()));
let instance = value_to_struct_literal(root, schema, name)?;
Ok(quote! {
#struct_ts
#data_vis static #static_name: #name = #instance;
})
}
fn value_to_struct_literal(
val: &Value,
schema: &StructSchema,
name: &Ident,
) -> ForgeResult<TokenStream> {
let obj = val
.as_object()
.ok_or_else(|| ForgeError::call_site(format!("expected JSON object, got: {}", val)))?;
let fields: Vec<TokenStream> = schema
.fields
.iter()
.map(|f| {
let field_ident = make_ident(&f.rust_name);
let json_val = obj.get(&f.json_key).unwrap_or(&Value::Null);
let val_ts = value_to_tokens(json_val, &f.ty, name)?;
Ok(quote! { #field_ident: #val_ts })
})
.collect::<ForgeResult<_>>()?;
Ok(quote! { #name { #(#fields,)* } })
}
fn value_to_tokens(val: &Value, ty: &SchemaType, struct_name: &Ident) -> ForgeResult<TokenStream> {
match (val, ty) {
(Value::Null, SchemaType::Optional(_)) => Ok(quote! { ::core::option::Option::None }),
(_, SchemaType::Optional(inner)) => {
let inner_ts = value_to_tokens(val, inner, struct_name)?;
Ok(quote! { ::core::option::Option::Some(#inner_ts) })
},
(Value::Bool(b), SchemaType::Bool) => Ok(quote! { #b }),
(Value::Number(n), SchemaType::Integer) => {
let v = n.as_i64().unwrap_or_default();
Ok(quote! { #v })
},
(Value::Number(n), SchemaType::Float) => {
let v = n.as_f64().unwrap_or_default();
Ok(quote! { #v })
},
(Value::String(s), SchemaType::Str) => Ok(quote! { #s }),
(Value::Array(arr), SchemaType::Array(inner)) => {
let items: Vec<TokenStream> = arr
.iter()
.map(|v| value_to_tokens(v, inner, struct_name))
.collect::<ForgeResult<_>>()?;
Ok(quote! { &[#(#items),*] })
},
(Value::Object(_), SchemaType::Struct(schema)) => {
value_to_struct_literal(val, schema, struct_name)
},
(Value::Null, SchemaType::Str) => Ok(quote! { "" }),
(Value::Null, SchemaType::Bool) => Ok(quote! { false }),
(Value::Null, SchemaType::Integer) => Ok(quote! { 0i64 }),
(Value::Null, SchemaType::Float) => Ok(quote! { 0.0f64 }),
(Value::Null, SchemaType::Array(_)) => Ok(quote! { &[] }),
_ => Err(ForgeError::call_site(format!(
"cannot represent JSON value `{}` as type `{:?}`",
val, ty
))),
}
}