use darling::{FromMeta, ast::NestedMeta};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, format_ident, quote};
use syn::{Error, Expr, ExprRange, Ident, ItemEnum, Token, Variant, punctuated::Punctuated};
pub fn quic_parameters(item: TokenStream) -> Result<TokenStream2, Error> {
let r#enum = syn::parse::<ItemEnum>(item)?;
let enum_name = &r#enum.ident;
let mut try_from_varint_match_arms = quote! {};
let mut into_varint_match_arms = quote! {};
let mut validate_match_arms = quote! {};
let mut default_value_match_arms = quote! {};
let mut value_type_match_arms = quote! {};
for variant in &r#enum.variants {
let discriminant = match variant.discriminant.as_ref() {
Some((_eq, discriminant)) => discriminant,
None => {
return Err(Error::new_spanned(
variant,
"Each variant must have a discriminant, e.g., `= 0`",
));
}
};
let ident = &variant.ident;
try_from_varint_match_arms.extend(quote! {
#discriminant => #enum_name::#ident,
});
into_varint_match_arms.extend(quote! {
#enum_name::#ident => #discriminant,
});
let param_args = parse_variant_attrs(variant)?;
let validate =
(param_args.gen_validate(ident)).map_err(|msg| Error::new_spanned(variant, msg))?;
validate_match_arms.extend(quote! {
#enum_name::#ident => { #validate }
});
let default_value = param_args.gen_default_value();
default_value_match_arms.extend(quote! {
#enum_name::#ident => { #default_value }
});
let value_type = param_args.gen_value_type();
value_type_match_arms.extend(quote! {
#enum_name::#ident => #value_type,
});
}
Ok(quote! {
impl ::core::convert::TryFrom<VarInt> for #enum_name {
type Error = Error;
fn try_from(value: VarInt) -> Result<Self, Self::Error> {
Ok(match value.into_u64() {
#try_from_varint_match_arms
unknown => return Err(Error::UnknownParameterId(value))
})
}
}
impl From<#enum_name> for VarInt {
fn from(value: #enum_name) -> Self {
VarInt::from_u64(match value {
#into_varint_match_arms
}).expect("All variants should have a valid discriminant")
}
}
impl #enum_name {
pub fn validate(&self, value: &ParameterValue) -> Result<(), Error> {
match self {
#validate_match_arms
}
Ok(())
}
pub fn default_value(&self) -> Option<ParameterValue> {
match self {
#default_value_match_arms
}
}
pub fn value_type(&self) -> ParameterValueType {
match self {
#value_type_match_arms
}
}
}
})
}
fn parse_variant_attrs(variant: &Variant) -> Result<ParamArgs, Error> {
let param_attr = variant
.attrs
.iter()
.find(|attr| attr.path().is_ident("param"))
.ok_or_else(|| {
Error::new_spanned(
variant,
"Each variant must have a `#[param(...)]` attribute",
)
})?;
let param_metas = param_attr
.parse_args_with(Punctuated::<NestedMeta, Token![,]>::parse_terminated)?
.into_iter()
.collect::<Vec<_>>();
ParamArgs::from_list(¶m_metas).map_err(|de| de.into())
}
#[derive(darling::FromMeta)]
struct ParamArgs {
value_type: ParamType,
#[darling(default)]
default: Option<Expr>,
#[darling(default)]
bound: Option<ExprRange>,
}
impl ParamArgs {
fn gen_validate(&self, id: &Ident) -> Result<TokenStream2, &'static str> {
let Some(bound) = &self.bound else {
return Ok(quote! {});
};
let value_type = format_ident!("{}", format!("{:?}", self.value_type));
let mut convert_value = quote! {
let ParameterValue::#value_type(v) = value else {
return Err(Error::InvalidValueType(
Self::#id,
value.value_type(),
));
};
};
convert_value.extend(match self.value_type {
ParamType::VarInt => quote! { v.into_u64() },
ParamType::Duration => quote! { v.as_millis() as u64 },
_ => return Err("Bound is only applicable to VarInt or Duration types"),
});
Ok(quote! {
let value = { #convert_value };
if !(#bound).contains(&value) {
return Err(Error::OutOfBounds (
Self::#id,
value,
#bound,
));
}
})
}
fn gen_default_value(&self) -> TokenStream2 {
match &self.default {
Some(default) => quote! { Some((#default).into()) },
None => quote! { None },
}
}
fn gen_value_type(&self) -> TokenStream2 {
let value_type = format_ident!("{}", format!("{:?}", self.value_type));
quote! { ParameterValueType::#value_type }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ParamType {
VarInt,
Boolean,
Bytes,
Duration,
ResetToken,
ConnectionId,
PreferredAddress,
}
impl FromMeta for ParamType {
fn from_string(lit: &str) -> ::darling::Result<Self> {
match lit {
"VarInt" => Ok(ParamType::VarInt),
"Boolean" => Ok(ParamType::Boolean),
"Bytes" => Ok(ParamType::Bytes),
"Duration" => Ok(ParamType::Duration),
"ResetToken" => Ok(ParamType::ResetToken),
"ConnectionId" => Ok(ParamType::ConnectionId),
"PreferredAddress" => Ok(ParamType::PreferredAddress),
__other => Err(::darling::Error::unknown_value(__other)),
}
}
fn from_expr(expr: &Expr) -> darling::Result<Self> {
match *expr {
Expr::Lit(ref lit) => Self::from_value(&lit.lit),
Expr::Group(ref group) => {
Self::from_expr(&group.expr)
}
Expr::Path(ref path) => return Self::from_string(&path.to_token_stream().to_string()),
_ => Err(darling::Error::unexpected_expr_type(expr)),
}
.map_err(|e| e.with_span(expr))
}
}