use proc_macro::{Literal, Span};
use crate::parse::Meta;
use crate::{Error, Result};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Format {
Both,
Json,
Beve,
}
pub(crate) struct Container {
pub(crate) rename_all: Option<Literal>,
pub(crate) tag: Option<Literal>,
pub(crate) array: Option<Span>,
pub(crate) element: Option<Literal>,
pub(crate) transparent: Option<Span>,
pub(crate) format: Format,
pub(crate) write_only: Option<Span>,
pub(crate) krate: Option<Literal>,
}
pub(crate) struct FieldOpts {
pub(crate) rename: Option<Literal>,
pub(crate) aliases: Vec<Literal>,
pub(crate) skip: Option<Span>,
pub(crate) required: Option<Span>,
pub(crate) with: Option<Literal>,
}
pub(crate) struct VariantOpts {
pub(crate) rename: Option<Literal>,
pub(crate) aliases: Vec<Literal>,
}
const CASES: &[&str] = &[
"lowercase",
"UPPERCASE",
"PascalCase",
"camelCase",
"snake_case",
"SCREAMING_SNAKE_CASE",
"kebab-case",
"SCREAMING-KEBAB-CASE",
];
fn stage_of(name: &str) -> Option<u8> {
match name {
"content" => Some(2),
"default" | "skip_if" | "skip_read" | "skip_write" => Some(3),
_ => None,
}
}
fn unknown(meta: &Meta, place: &str, accepted: &str) -> Error {
let name = meta.name.to_string();
let message = match stage_of(&name) {
Some(stage) => format!(
"`{name}` is a stage {stage} attribute and this derive does not \
implement it yet; see docs/derive.md for what each stage adds"
),
None => {
format!("unknown attribute `{name}` on {place}; the attributes here are {accepted}")
}
};
Error::new(meta.name.span(), message)
}
fn flag(meta: &Meta) -> Result<Span> {
match &meta.value {
None => Ok(meta.name.span()),
Some(value) => Err(Error::new(
value.span(),
format!("`{}` takes no value", meta.name),
)),
}
}
fn string(meta: &Meta) -> Result<Literal> {
match &meta.value {
Some(value) => {
string_content(value)?;
Ok(value.clone())
}
None => Err(Error::new(
meta.name.span(),
format!(
"`{name}` takes a string: `{name} = \"..\"`",
name = meta.name
),
)),
}
}
pub(crate) fn string_content(lit: &Literal) -> Result<String> {
let text = lit.to_string();
let inner = text
.strip_prefix('"')
.and_then(|t| t.strip_suffix('"'))
.ok_or_else(|| Error::new(lit.span(), "expected a plain string literal"))?;
if inner.contains('\\') {
return Err(Error::new(
lit.span(),
"an escape in an attribute string is not supported; write the \
characters themselves",
));
}
Ok(inner.to_string())
}
fn once<T>(slot: &mut Option<T>, meta: &Meta, value: T) -> Result<()> {
if slot.is_some() {
return Err(Error::new(
meta.name.span(),
format!("`{}` is given twice", meta.name),
));
}
*slot = Some(value);
Ok(())
}
pub(crate) fn container(metas: &[Meta], is_enum: bool) -> Result<Container> {
let mut out = Container {
rename_all: None,
tag: None,
array: None,
element: None,
transparent: None,
format: Format::Both,
write_only: None,
krate: None,
};
let mut format_at: Option<Span> = None;
let (place, accepted) = if is_enum {
(
"an enum",
"`rename_all`, `tag`, `json`, `beve`, `write_only` and `crate`",
)
} else {
(
"a struct",
"`rename_all`, `array`, `element`, `transparent`, `json`, `beve`, \
`write_only` and `crate`",
)
};
for meta in metas {
match meta.name.to_string().as_str() {
"rename_all" => {
let lit = string(meta)?;
let rule = string_content(&lit)?;
if !CASES.contains(&rule.as_str()) {
return Err(Error::new(
lit.span(),
format!(
"`{rule}` is not a case rule; the rules are {}",
CASES
.iter()
.map(|c| format!("`{c}`"))
.collect::<Vec<_>>()
.join(", ")
),
));
}
once(&mut out.rename_all, meta, lit)?;
}
"tag" if is_enum => once(&mut out.tag, meta, string(meta)?)?,
"tag" => {
return Err(Error::new(
meta.name.span(),
"`tag` names the member an enum's variant name is written \
under; a struct has no variant to tag",
));
}
"array" if !is_enum => once(&mut out.array, meta, flag(meta)?)?,
"element" if !is_enum => once(&mut out.element, meta, string(meta)?)?,
"transparent" if !is_enum => once(&mut out.transparent, meta, flag(meta)?)?,
"array" | "element" => {
return Err(Error::new(
meta.name.span(),
"`array` declares a struct's fields by position; an enum \
is written as its variant's name",
));
}
"transparent" => {
return Err(Error::new(
meta.name.span(),
"`transparent` writes a struct as the one field it holds; \
an enum is written as its variant's name, and a variant \
that carries a value already writes that value and \
nothing around it but the tag",
));
}
"json" | "beve" => {
let at = flag(meta)?;
if format_at.is_some() {
return Err(Error::new(
at,
"`json` and `beve` each narrow the derive to one \
format; leave both out to generate for both",
));
}
format_at = Some(at);
out.format = if meta.name.to_string() == "json" {
Format::Json
} else {
Format::Beve
};
}
"write_only" => once(&mut out.write_only, meta, flag(meta)?)?,
"crate" => once(&mut out.krate, meta, string(meta)?)?,
_ => return Err(unknown(meta, place, accepted)),
}
}
if let Some(element) = &out.element
&& out.array.is_none()
{
return Err(Error::new(
element.span(),
"`element` names the type every element of a positional struct \
has, so it goes with `array`",
));
}
if let (Some(rule), Some(_)) = (&out.rename_all, out.array) {
return Err(Error::new(
rule.span(),
"a positional struct writes no keys, so a case rule has nothing \
to convert",
));
}
if let Some(at) = out.transparent {
let other = [
out.array.map(|_| "array"),
out.element.as_ref().map(|_| "element"),
out.rename_all.as_ref().map(|_| "rename_all"),
]
.into_iter()
.flatten()
.next();
if let Some(other) = other {
return Err(Error::new(
at,
format!(
"a transparent struct is written as its one field, with no \
object and no array around it, so `{other}` has nothing to \
apply to"
),
));
}
}
Ok(out)
}
pub(crate) fn field(metas: &[Meta], positional: bool) -> Result<FieldOpts> {
let mut out = FieldOpts {
rename: None,
aliases: Vec::new(),
skip: None,
required: None,
with: None,
};
for meta in metas {
let name = meta.name.to_string();
if positional && name != "skip" && stage_of(&name).is_none() {
return Err(Error::new(
meta.name.span(),
format!(
"`{name}` has no meaning on a field of a positional struct: \
an element is found by its position, has no key, and is \
required by the array's length. Only `skip` applies"
),
));
}
match name.as_str() {
"rename" => once(&mut out.rename, meta, string(meta)?)?,
"alias" => out.aliases.push(string(meta)?),
"skip" => once(&mut out.skip, meta, flag(meta)?)?,
"required" => once(&mut out.required, meta, flag(meta)?)?,
"with" => once(&mut out.with, meta, string(meta)?)?,
"write_only" => {
return Err(Error::new(
meta.name.span(),
"`write_only` describes the whole type, so it goes on the \
type: a declaration generates one direction or both, not \
one direction for one field. A field that is written but \
not read is `skip_read`, a stage 3 attribute",
));
}
_ => {
return Err(unknown(
meta,
"a field",
"`rename`, `alias`, `skip`, `required` and `with`",
));
}
}
}
if let Some(skip) = out.skip {
let other = [
out.rename.as_ref().map(|_| "rename"),
out.aliases.first().map(|_| "alias"),
out.required.as_ref().map(|_| "required"),
out.with.as_ref().map(|_| "with"),
]
.into_iter()
.flatten()
.next();
if let Some(other) = other {
return Err(Error::new(
skip,
format!("a skipped field is not on the wire, so `{other}` has nothing to apply to"),
));
}
}
Ok(out)
}
pub(crate) fn variant(metas: &[Meta]) -> Result<VariantOpts> {
let mut out = VariantOpts {
rename: None,
aliases: Vec::new(),
};
for meta in metas {
match meta.name.to_string().as_str() {
"rename" => once(&mut out.rename, meta, string(meta)?)?,
"alias" => out.aliases.push(string(meta)?),
"skip" => {
return Err(Error::new(
meta.name.span(),
"a variant cannot be skipped: a value holding it would \
have nothing to write, and the declaration has to name \
every variant the enum has",
));
}
_ => return Err(unknown(meta, "a variant", "`rename` and `alias`")),
}
}
Ok(out)
}