use syn::{Attribute, Result};
pub(crate) struct DiagnosticAttrs {
pub(crate) code: Option<String>,
pub(crate) help: Option<String>,
pub(crate) url: Option<String>,
pub(crate) severity: Option<String>,
}
fn validate_error_code(path: &syn::Path) -> Result<String> {
let segments: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
if segments.len() < 2 {
return Err(syn::Error::new_spanned(
path,
format!(
"error code must have at least 2 segments (namespace::code), got {} segment(s): `{}`",
segments.len(),
segments.join("::")
),
));
}
for segment in &segments {
if segment != &segment.to_lowercase() {
return Err(syn::Error::new_spanned(
path,
format!(
"error code segments must be lowercase, found `{}` in `{}`",
segment,
segments.join("::")
),
));
}
}
Ok(segments.join("::"))
}
pub(crate) fn parse_diagnostic_attrs(attrs: &[Attribute]) -> Result<DiagnosticAttrs> {
let mut diagnostic_attrs = DiagnosticAttrs {
code: None,
help: None,
url: None,
severity: None,
};
for attr in attrs {
if attr.path().is_ident("diagnostic") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("code") {
let content;
syn::parenthesized!(content in meta.input);
let path: syn::Path = content.parse()?;
let code = validate_error_code(&path)?;
diagnostic_attrs.code = Some(code);
Ok(())
} else if meta.path.is_ident("help") {
let content;
syn::parenthesized!(content in meta.input);
let lit: syn::LitStr = content.parse()?;
diagnostic_attrs.help = Some(lit.value());
Ok(())
} else if meta.path.is_ident("url") {
let content;
syn::parenthesized!(content in meta.input);
let lit: syn::LitStr = content.parse()?;
diagnostic_attrs.url = Some(lit.value());
Ok(())
} else if meta.path.is_ident("severity") {
let content;
syn::parenthesized!(content in meta.input);
let ident: syn::Ident = content.parse()?;
diagnostic_attrs.severity = Some(ident.to_string());
Ok(())
} else {
Err(meta.error(
"unknown diagnostic attribute; expected one of: code, help, url, severity",
))
}
})?;
}
}
Ok(diagnostic_attrs)
}