use syn::{
Attribute, Error, Expr, Field, Fields, GenericArgument, Ident, Index, ItemStruct, Member, Path,
PathArguments, Result, Token, Type, parenthesized,
parse::{Parse, ParseStream},
spanned::Spanned,
token,
};
use syn_cfg_attr::AttributeHelpers;
#[derive(Clone, Debug)]
pub struct ValidatorAttr {
pub validator: Path,
pub infer_type: bool,
pub explicit_type: Option<Type>,
pub args: Vec<(Ident, Expr)>,
}
impl ValidatorAttr {
const USE_TURBO_SYNTAX_MSG: &'static str = "use turbofish syntax `::<_>` to infer the type";
pub fn name(&self) -> &Ident {
&self
.validator
.segments
.last()
.expect("path should have at least one segment")
.ident
}
pub fn has_args(&self) -> bool {
!self.args.is_empty()
}
pub fn uses_type_inference(&self) -> bool {
self.infer_type
}
pub fn has_explicit_type(&self) -> bool {
self.explicit_type.is_some()
}
}
impl Parse for ValidatorAttr {
fn parse(input: ParseStream) -> Result<Self> {
let mut validator: Path = input.parse()?;
let (infer_type, explicit_type) = {
let validator_span = validator.span();
let last_segment = validator
.segments
.last_mut()
.ok_or_else(|| Error::new(validator_span, "expected validator path"))?;
let args = std::mem::replace(&mut last_segment.arguments, PathArguments::None);
match args {
PathArguments::None => {
if input.peek(Token![<]) {
return Err(Error::new(input.span(), Self::USE_TURBO_SYNTAX_MSG));
}
(false, None)
},
PathArguments::AngleBracketed(mut angle_args) => {
if angle_args.colon2_token.is_none() {
return Err(Error::new(angle_args.span(), Self::USE_TURBO_SYNTAX_MSG));
}
if angle_args.args.len() != 1 {
return Err(Error::new(
angle_args.span(),
"validator type syntax expects exactly one type argument",
));
}
let arg = angle_args.args.pop().expect("len checked").into_value();
match arg {
GenericArgument::Type(Type::Infer(_)) => (true, None),
GenericArgument::Type(ty) => (false, Some(ty)),
_ => Err(Error::new(
arg.span(),
"validator type syntax expects a type argument",
))?,
}
},
PathArguments::Parenthesized(args) => {
return Err(Error::new(
args.span(),
"validator path does not support parenthesized arguments",
));
},
}
};
let args = if input.peek(token::Paren) {
let content;
parenthesized!(content in input);
let mut args = Vec::new();
while !content.is_empty() {
let name: Ident = content.parse()?;
content.parse::<Token![=]>()?;
let value: Expr = content.parse()?;
args.push((name, value));
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
}
}
args
} else {
Vec::new()
};
Ok(ValidatorAttr {
validator,
infer_type,
explicit_type,
args,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct KorumaAttr {
pub field_validators: Vec<ValidatorAttr>,
pub element_validators: Vec<ValidatorAttr>,
pub is_skip: bool,
pub is_nested: bool,
pub is_newtype: bool,
}
impl KorumaAttr {
pub fn has_validators(&self) -> bool {
!self.field_validators.is_empty() || !self.element_validators.is_empty()
}
pub fn is_modifier(&self) -> bool {
self.is_skip || self.is_nested || self.is_newtype
}
}
impl Parse for KorumaAttr {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Ident) {
let fork = input.fork();
let ident: Ident = fork.parse()?;
if ident == "skip" && fork.is_empty() {
input.parse::<Ident>()?; return Ok(KorumaAttr {
field_validators: Vec::new(),
element_validators: Vec::new(),
is_skip: true,
is_nested: false,
is_newtype: false,
});
}
if ident == "nested" && fork.is_empty() {
input.parse::<Ident>()?; return Ok(KorumaAttr {
field_validators: Vec::new(),
element_validators: Vec::new(),
is_skip: false,
is_nested: true,
is_newtype: false,
});
}
if ident == "newtype" {
input.parse::<Ident>()?;
if input.peek(Token![,]) {
input.parse::<Token![,]>()?; let mut field_validators = Vec::new();
let mut element_validators = Vec::new();
while !input.is_empty() {
if input.peek(Ident) {
let fork = input.fork();
let ident: Ident = fork.parse()?;
if ident == "each" && fork.peek(token::Paren) {
input.parse::<Ident>()?; let content;
parenthesized!(content in input);
while !content.is_empty() {
element_validators.push(content.parse::<ValidatorAttr>()?);
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
} else {
break;
}
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
continue;
}
}
field_validators.push(input.parse::<ValidatorAttr>()?);
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
} else {
break;
}
}
return Ok(KorumaAttr {
field_validators,
element_validators,
is_skip: false,
is_nested: false,
is_newtype: true,
});
}
if input.is_empty() {
return Ok(KorumaAttr {
field_validators: Vec::new(),
element_validators: Vec::new(),
is_skip: false,
is_nested: false,
is_newtype: true,
});
}
}
}
let mut field_validators = Vec::new();
let mut element_validators = Vec::new();
while !input.is_empty() {
if input.peek(Ident) {
let fork = input.fork();
let ident: Ident = fork.parse()?;
if ident == "each" && fork.peek(token::Paren) {
input.parse::<Ident>()?; let content;
parenthesized!(content in input);
while !content.is_empty() {
element_validators.push(content.parse::<ValidatorAttr>()?);
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
} else {
break;
}
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
continue;
}
}
field_validators.push(input.parse::<ValidatorAttr>()?);
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
} else {
break;
}
}
Ok(KorumaAttr {
field_validators,
element_validators,
is_skip: false,
is_nested: false,
is_newtype: false,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct StructOptions {
pub try_new: bool,
pub newtype: bool,
pub try_from: bool,
}
#[derive(Clone, Debug, Default)]
pub struct NewtypeOptions {
pub try_from: bool,
}
impl Parse for NewtypeOptions {
fn parse(input: ParseStream) -> Result<Self> {
let mut options = NewtypeOptions::default();
while !input.is_empty() {
let ident: Ident = input.parse()?;
match ident.to_string().as_str() {
"try_from" => options.try_from = true,
other => {
return Err(Error::new(
ident.span(),
format!("unknown newtype option: `{}`. Expected `try_from`", other),
));
},
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
Ok(options)
}
}
impl Parse for StructOptions {
fn parse(input: ParseStream) -> Result<Self> {
let mut options = StructOptions::default();
while !input.is_empty() {
let ident: Ident = input.parse()?;
match ident.to_string().as_str() {
"try_new" => options.try_new = true,
"newtype" => {
options.newtype = true;
if input.peek(syn::token::Paren) {
let content;
syn::parenthesized!(content in input);
let newtype_opts: NewtypeOptions = content.parse()?;
options.try_from = newtype_opts.try_from;
}
},
other => {
return Err(Error::new(
ident.span(),
format!(
"unknown struct-level koruma option: `{}`. Expected `try_new` or `newtype`",
other
),
));
},
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
Ok(options)
}
}
pub fn parse_struct_options(attrs: &[Attribute]) -> Result<StructOptions> {
if let Some(attr) = attrs.to_vec().find_attribute("koruma").first() {
attr.parse_args::<StructOptions>()
} else {
Ok(StructOptions::default())
}
}
#[derive(Clone, Debug, Default)]
pub struct ValidationInfo {
pub field_validators: Vec<ValidatorAttr>,
pub element_validators: Vec<ValidatorAttr>,
pub is_nested: bool,
pub is_newtype: bool,
}
#[derive(Clone, Debug)]
pub struct FieldInfo {
pub name: Ident,
pub member: Member,
pub ty: Type,
pub validation: ValidationInfo,
}
impl FieldInfo {
pub fn has_element_validators(&self) -> bool {
!self.validation.element_validators.is_empty()
}
pub fn has_validators(&self) -> bool {
!self.validation.field_validators.is_empty()
|| !self.validation.element_validators.is_empty()
}
pub fn is_nested(&self) -> bool {
self.validation.is_nested
}
pub fn is_newtype(&self) -> bool {
self.validation.is_newtype
}
pub fn validator_names(&self) -> impl Iterator<Item = &Ident> {
self.validation
.field_validators
.iter()
.chain(self.validation.element_validators.iter())
.map(|v| v.name())
}
}
#[derive(Debug)]
pub enum ParseFieldResult {
Valid(Box<FieldInfo>),
Skip,
Error(Error),
}
impl ParseFieldResult {
pub fn valid(self) -> Option<FieldInfo> {
match self {
ParseFieldResult::Valid(info) => Some(*info),
_ => None,
}
}
pub fn error(self) -> Option<Error> {
match self {
ParseFieldResult::Error(e) => Some(e),
_ => None,
}
}
pub fn is_valid(&self) -> bool {
matches!(self, ParseFieldResult::Valid(_))
}
pub fn is_skip(&self) -> bool {
matches!(self, ParseFieldResult::Skip)
}
pub fn is_error(&self) -> bool {
matches!(self, ParseFieldResult::Error(_))
}
}
pub fn parse_field(field: &Field, index: usize) -> ParseFieldResult {
let (name, member) = match field.ident.clone() {
Some(ident) => (ident.clone(), Member::Named(ident)),
None => (
quote::format_ident!("_{}", index),
Member::Unnamed(Index::from(index)),
),
};
let ty = field.ty.clone();
let mut all_field_validators = Vec::new();
let mut all_element_validators = Vec::new();
let mut is_skip = false;
let mut is_nested = false;
let mut is_newtype = false;
let mut seen_field_validators = std::collections::HashSet::new();
let mut seen_element_validators = std::collections::HashSet::new();
for attr in field.attrs.to_vec().find_attribute("koruma") {
let parsed: Result<KorumaAttr> = attr.parse_args::<KorumaAttr>();
match parsed {
Ok(koruma_attr) => {
if koruma_attr.is_skip {
is_skip = true;
continue;
}
if koruma_attr.is_nested {
is_nested = true;
continue;
}
if koruma_attr.is_newtype {
is_newtype = true;
}
for validator in koruma_attr.field_validators {
let validator_name = validator.name().to_string();
if !seen_field_validators.insert(validator_name.clone()) {
return ParseFieldResult::Error(Error::new(
validator.validator.span(),
format!(
"duplicate validator `{}` on field `{}`",
validator_name, name
),
));
}
all_field_validators.push(validator);
}
for validator in koruma_attr.element_validators {
let validator_name = validator.name().to_string();
if !seen_element_validators.insert(validator_name.clone()) {
return ParseFieldResult::Error(Error::new(
validator.validator.span(),
format!(
"duplicate element validator `{}` on field `{}`",
validator_name, name
),
));
}
all_element_validators.push(validator);
}
},
Err(e) => {
return ParseFieldResult::Error(e);
},
}
}
if is_skip {
return ParseFieldResult::Skip;
}
if is_nested {
return ParseFieldResult::Valid(Box::new(FieldInfo {
name,
member: member.clone(),
ty,
validation: ValidationInfo {
field_validators: all_field_validators,
element_validators: all_element_validators,
is_nested: true,
is_newtype: false,
},
}));
}
if is_newtype {
return ParseFieldResult::Valid(Box::new(FieldInfo {
name,
member: member.clone(),
ty,
validation: ValidationInfo {
field_validators: all_field_validators,
element_validators: all_element_validators,
is_nested: false,
is_newtype: true,
},
}));
}
if all_field_validators.is_empty() && all_element_validators.is_empty() {
return ParseFieldResult::Skip;
}
ParseFieldResult::Valid(Box::new(FieldInfo {
name,
member: member.clone(),
ty,
validation: ValidationInfo {
field_validators: all_field_validators,
element_validators: all_element_validators,
is_nested: false,
is_newtype: false,
},
}))
}
pub fn find_value_field(input: &ItemStruct) -> Option<(Ident, Type)> {
if let Fields::Named(ref fields) = input.fields {
for field in &fields.named {
if let Some(attr) = field.attrs.to_vec().find_attribute("koruma").first()
&& let Ok(ident) = attr.parse_args::<Ident>()
&& ident == "value"
{
return Some((field.ident.clone().unwrap(), field.ty.clone()));
}
}
}
None
}
#[cfg(feature = "internal-showcase")]
#[derive(Clone, Debug)]
pub struct ShowcaseAttr {
pub name: syn::LitStr,
pub description: syn::LitStr,
pub create: syn::ExprClosure,
pub input_type: Option<Ident>,
pub module: Option<syn::LitStr>,
}
#[cfg(feature = "internal-showcase")]
impl Parse for ShowcaseAttr {
fn parse(input: ParseStream) -> Result<Self> {
let mut name: Option<syn::LitStr> = None;
let mut description: Option<syn::LitStr> = None;
let mut create: Option<syn::ExprClosure> = None;
let mut input_type: Option<Ident> = None;
let mut module: Option<syn::LitStr> = None;
while !input.is_empty() {
let ident: Ident = input.parse()?;
input.parse::<Token![=]>()?;
match ident.to_string().as_str() {
"name" => {
name = Some(input.parse()?);
},
"description" => {
description = Some(input.parse()?);
},
"create" => {
create = Some(input.parse()?);
},
"input_type" => {
input_type = Some(input.parse()?);
},
"module" => {
module = Some(input.parse()?);
},
other => {
return Err(Error::new(
ident.span(),
format!("unknown showcase attribute: {}", other),
));
},
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
}
Ok(ShowcaseAttr {
name: name
.ok_or_else(|| Error::new(input.span(), "showcase requires `name` attribute"))?,
description: description.ok_or_else(|| {
Error::new(input.span(), "showcase requires `description` attribute")
})?,
create: create
.ok_or_else(|| Error::new(input.span(), "showcase requires `create` attribute"))?,
input_type,
module,
})
}
}
#[cfg(feature = "internal-showcase")]
pub fn find_showcase_attr(input: &ItemStruct) -> Option<ShowcaseAttr> {
for attr in &input.attrs {
if attr.path().is_ident("showcase")
&& let Ok(parsed) = attr.parse_args::<ShowcaseAttr>()
{
return Some(parsed);
}
}
None
}