use darling::FromAttributes;
use proc_macro::TokenStream;
use quote::quote;
use std::error::Error;
use syn::{DataStruct, Ident};
pub(super) fn impl_js_json_newtype(
name: &Ident,
data: &DataStruct,
) -> Result<TokenStream, Box<dyn Error>> {
let mut encodes = Vec::new();
let mut decodes = Vec::new();
if data.fields.len() == 1 {
let field = data.fields.iter().next().ok_or("Expected one field")?;
let field_opts = crate::jsjson::attributes::FieldOpts::from_attributes(&field.attrs)?;
if field_opts.stringify {
let is_option = match &field.ty {
syn::Type::Path(ty_path) => ty_path
.path
.segments
.last()
.is_some_and(|last| last.ident == "Option"),
_ => false,
};
if is_option {
encodes.push(quote! {
match &self.0 {
Some(val) => vertigo::JsJson::String(format!("{}", val)),
None => vertigo::JsJson::Null,
}
});
decodes.push(quote! {
match json {
vertigo::JsJson::String(v) => {
match v.parse() {
Ok(v) => Ok(Self(Some(v))),
Err(e) => {
let message = format!("Error parsing string '{}': {}", v, e);
Err(ctx.add(message))
}
}
},
vertigo::JsJson::Null => Ok(Self(None)),
other => {
let message = ["String or null expected, received ", other.typename()].concat();
Err(ctx.add(message))
}
}
});
} else {
encodes.push(quote! {
vertigo::JsJson::String(format!("{}", self.0))
});
decodes.push(quote! {
match json {
vertigo::JsJson::String(v) => {
match v.parse() {
Ok(v) => Ok(Self(v)),
Err(e) => {
let message = format!("Error parsing string '{}': {}", v, e);
Err(ctx.add(message))
}
}
},
other => {
let message = ["String expected, received ", other.typename()].concat();
Err(ctx.add(message))
}
}
});
}
} else {
encodes.push(quote! {
self.0.to_json()
});
decodes.push(quote! {
vertigo::JsJsonDeserialize::from_json(ctx, json).map(Self)
});
}
} else {
let (field_idents, field_encodes) = super::tuple_fields::get_encodes(data.fields.iter());
encodes.push(quote! {
let #name (#(#field_idents,)*) = self;
vertigo::JsJson::List(vec![
#(#field_encodes)*
])
});
let fields_number = field_idents.len();
let field_decodes = super::tuple_fields::get_decodes(field_idents);
let name_str = name.to_string();
decodes.push(quote! {
match json {
vertigo::JsJson::List(fields) => {
if fields.len() != #fields_number {
return Err(ctx.add(
format!("Wrong number of fields in tuple for newtype {}. Expected {}, got {}", #name_str, #fields_number, fields.len())
));
}
let mut fields_rev = fields.into_iter().rev().collect::<Vec<_>>();
return Ok(#name(
#(#field_decodes)*
))
}
x => return Err(ctx.add(
format!("Invalid type {} while decoding newtype tuple, expected list", x.typename())
)),
}
});
}
let result = quote! {
impl vertigo::JsJsonSerialize for #name {
fn to_json(self) -> vertigo::JsJson {
#(#encodes)*
}
}
impl vertigo::JsJsonDeserialize for #name {
fn from_json(
ctx: vertigo::JsJsonContext,
json: vertigo::JsJson,
) -> Result<Self, vertigo::JsJsonContext> {
#(#decodes)*
}
}
};
Ok(result.into())
}