use syn::{Attribute, DataStruct, Fields, Ident, Lit, Result};
use crate::ir::StructDefinition;
use super::diagnostic::parse_diagnostic_attrs;
use super::field::parse_field;
use super::http_status::parse_http_status;
use super::jsonrpc_code::parse_jsonrpc_code;
pub(crate) fn parse_struct(
name: Ident,
generics: syn::Generics,
attrs: &[Attribute],
data_struct: DataStruct,
) -> Result<StructDefinition> {
let error_message = parse_error_message(attrs)?.ok_or_else(|| {
syn::Error::new_spanned(&name, "struct must have #[error(\"message\")] attribute")
})?;
let diagnostic_attrs = parse_diagnostic_attrs(attrs)?;
let diagnostic_code = diagnostic_attrs.code.ok_or_else(|| {
syn::Error::new_spanned(&name, "struct must have #[diagnostic(code(...))] attribute")
})?;
let help_text = diagnostic_attrs.help;
let url = diagnostic_attrs.url;
let severity = diagnostic_attrs.severity;
let http_status = parse_http_status(attrs)?;
let jsonrpc_code = parse_jsonrpc_code(attrs)?;
let fields = match data_struct.fields {
Fields::Named(fields) => fields
.named
.into_iter()
.map(parse_field)
.collect::<Result<Vec<_>>>()?,
Fields::Unnamed(_) => {
return Err(syn::Error::new_spanned(
name,
"tuple structs are not supported, use named fields instead",
));
}
Fields::Unit => Vec::new(),
};
let from_count = fields.iter().filter(|f| f.is_from).count();
if from_count > 1 {
return Err(syn::Error::new_spanned(
&name,
"struct can have at most one #[from] field",
));
}
Ok(StructDefinition {
name,
generics,
error_message,
diagnostic_code,
help_text,
url,
severity,
http_status,
jsonrpc_code,
fields,
})
}
fn parse_error_message(attrs: &[Attribute]) -> Result<Option<String>> {
for attr in attrs {
if attr.path().is_ident("error") {
let lit: Lit = attr.parse_args()?;
if let Lit::Str(s) = lit {
return Ok(Some(s.value()));
}
}
}
Ok(None)
}