blob: 3be35cf23c0baedafc144d0664402fc62b52b236 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![recursion_limit = "128"]
extern crate proc_macro;
use heck::CamelCase;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::convert::TryFrom;
use syn::spanned::Spanned;
use syn::{punctuated::*, token::*, *};
type Result<T> = std::result::Result<T, Error>;
const INVALID_FIDL_FIELD_ATTRIBUTE_MSG: &str =
"fidl_field_type attribute must be required, optional, or default = value";
#[proc_macro_derive(
ValidFidlTable,
attributes(fidl_table_src, fidl_field_type, fidl_table_validator)
)]
pub fn validate_fidl_table(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: DeriveInput = syn::parse(input).unwrap();
match input.data {
Data::Struct(DataStruct { fields: syn::Fields::Named(fields), .. }) => {
match impl_valid_fidl_table(&input.ident, fields.named, &input.attrs) {
Ok(v) => v.into(),
Err(e) => e.to_compile_error().into(),
}
}
_ => Error::new(input.span(), "ValidateFidlTable only supports non-tuple structs!")
.to_compile_error()
.into(),
}
}
fn metas<'a>(attrs: &'a [Attribute]) -> impl Iterator<Item = Meta> + 'a {
attrs.iter().filter_map(|a| match a.parse_meta() {
Ok(meta) => Some(meta),
Err(_) => None,
})
}
fn list_with_arg(meta: Meta, call: &str) -> Option<NestedMeta> {
match meta {
Meta::List(list) => {
if list.path.is_ident(call) && list.nested.len() == 1 {
Some(list.nested.into_pairs().next().unwrap().into_value())
} else {
None
}
}
_ => None,
}
}
fn unique_list_with_arg(attrs: &[Attribute], call: &str) -> Result<Option<NestedMeta>> {
let metas = metas(attrs);
let mut lists_with_arg: Vec<NestedMeta> =
metas.filter_map(|meta| list_with_arg(meta, call)).collect();
if lists_with_arg.len() > 1 {
return Err(Error::new(
lists_with_arg[1].span(),
&format!("The {} attribute should only be declared once.", call),
));
}
Ok(lists_with_arg.pop())
}
fn fidl_table_path(span: Span, attrs: &[Attribute]) -> Result<Path> {
match unique_list_with_arg(attrs, "fidl_table_src")? {
Some(nested) => match nested {
NestedMeta::Meta(Meta::Path(fidl_table_path)) => Ok(fidl_table_path),
_ => Err(Error::new(
span,
concat!(
"The #[fidl_table_src(FidlTableType)] attribute ",
"takes only one argument, a type name."
),
)),
},
_ => Err(Error::new(
span,
concat!(
"To derive ValidFidlTable, struct needs ",
"#[fidl_table_src(FidlTableType)] attribute to mark ",
"source Fidl type."
),
)),
}
}
fn fidl_table_validator(span: Span, attrs: &[Attribute]) -> Result<Option<Ident>> {
unique_list_with_arg(attrs, "fidl_table_validator")?
.map(|nested| match nested {
NestedMeta::Meta(Meta::Path(fidl_table_validator)) => fidl_table_validator
.get_ident()
.cloned()
.ok_or(Error::new(fidl_table_validator.span(), "Invalid Identifier")),
_ => Err(Error::new(
span,
concat!(
"The #[fidl_table(FidlTableType)] attribute takes ",
"only one argument, a type name."
),
)),
})
.transpose()
}
#[derive(Clone, Debug)]
struct FidlField {
ident: Ident,
#[allow(unused)]
in_vec: bool,
kind: FidlFieldKind,
}
impl TryFrom<Field> for FidlField {
type Error = Error;
fn try_from(src: Field) -> Result<Self> {
let ident = match src.ident {
Some(ident) => Ok(ident),
None => Err(Error::new(
src.span(),
"ValidFidlTable can only be derived for non-tuple structs.",
)),
}?;
let attrs = src.attrs;
let kind = FidlFieldKind::try_from(attrs.as_slice())?;
let in_vec = match src.ty {
syn::Type::Path(path) if path.qself.is_none() => {
let mut segments = path.path.segments.iter();
let mut get_segment = || segments.next().map(|s| format!("{}", s.ident));
let first_segment = get_segment();
let second_segment = get_segment();
match (kind.clone(), first_segment, second_segment) {
(FidlFieldKind::Required, Some(segment), _) if segment == "Vec" => true,
(FidlFieldKind::Optional, _, Some(segment)) if segment == "Vec" => true,
_ => false,
}
}
_ => false,
};
Ok(FidlField { ident, in_vec, kind })
}
}
impl FidlField {
fn camel_case(&self) -> Ident {
let name = self.ident.to_string().to_camel_case();
Ident::new(&name, Span::call_site())
}
fn generate_try_from(&self, missing_field_error_type: &Ident) -> TokenStream {
let ident = &self.ident;
match &self.kind {
FidlFieldKind::Required => {
let camel_case = self.camel_case();
match self.in_vec {
true => quote!(
#ident: {
let src_vec = src.#ident.ok_or(#missing_field_error_type::#camel_case)?;
src_vec
.into_iter()
.map(std::convert::TryFrom::try_from)
.map(|r| r.map_err(anyhow::Error::from))
.collect::<std::result::Result<_, anyhow::Error>>()?
},
),
false => quote!(
#ident: std::convert::TryFrom::try_from(
src.#ident.ok_or(#missing_field_error_type::#camel_case)?
).map_err(anyhow::Error::from)?,
),
}
}
FidlFieldKind::Optional => quote!(
#ident: if let Some(field) = src.#ident {
Some(
std::convert::TryFrom::try_from(
field
).map_err(anyhow::Error::from)?
)
} else {
None
},
),
FidlFieldKind::HasDefault(value) => quote!(
#ident: src.#ident.unwrap_or(#value),
),
}
}
}
#[derive(Clone, Debug)]
enum FidlFieldKind {
Required,
Optional,
HasDefault(Lit),
}
impl TryFrom<&[Attribute]> for FidlFieldKind {
type Error = Error;
fn try_from(attrs: &[Attribute]) -> Result<Self> {
match unique_list_with_arg(attrs, "fidl_field_type")? {
Some(NestedMeta::Meta(Meta::Path(field_type))) => field_type
.get_ident()
.and_then(|i| match i.to_string().as_str() {
"required" => Some(FidlFieldKind::Required),
"optional" => Some(FidlFieldKind::Optional),
_ => None,
})
.ok_or(Error::new(field_type.span(), INVALID_FIDL_FIELD_ATTRIBUTE_MSG)),
Some(NestedMeta::Meta(Meta::NameValue(ref default_value)))
if default_value.path.is_ident("default") =>
{
Ok(FidlFieldKind::HasDefault(default_value.lit.clone()))
}
None => Ok(FidlFieldKind::Required),
Some(meta) => Err(Error::new(meta.span(), INVALID_FIDL_FIELD_ATTRIBUTE_MSG)),
}
}
}
fn impl_valid_fidl_table(
name: &Ident,
fields: Punctuated<Field, Comma>,
attrs: &[Attribute],
) -> Result<TokenStream> {
let fidl_table_path = fidl_table_path(name.span(), attrs)?;
let fidl_table_type = match fidl_table_path.segments.last() {
Some(segment) => segment.ident.clone(),
None => {
return Err(Error::new(
name.span(),
concat!(
"The #[fidl_table_src(FidlTableType)] attribute ",
"takes only one argument, a type name."
),
))
}
};
let missing_field_error_type = {
let mut error_type_name = fidl_table_type.to_string();
error_type_name.push_str("MissingFieldError");
Ident::new(&error_type_name, Span::call_site())
};
let error_type_name = {
let mut error_type_name = fidl_table_type.to_string();
error_type_name.push_str("ValidationError");
Ident::new(&error_type_name, Span::call_site())
};
let custom_validator = fidl_table_validator(name.span(), attrs)?;
let custom_validator_error = custom_validator
.as_ref()
.map(|validator| quote!(Logical(<#validator as Validate<#name>>::Error),));
let custom_validator_call =
custom_validator.as_ref().map(|validator| quote!(#validator::validate(&maybe_valid)?;));
let custom_validator_error_from_impl = custom_validator.map(|validator| {
quote!(
impl From<<#validator as Validate<#name>>::Error> for #error_type_name {
fn from(src: <#validator as Validate<#name>>::Error) -> Self {
#error_type_name::Logical(src)
}
}
)
});
let fields: Vec<FidlField> = fields
.into_pairs()
.map(Pair::into_value)
.map(FidlField::try_from)
.collect::<Result<Vec<FidlField>>>()?;
let mut field_validations = TokenStream::new();
field_validations.extend(fields.iter().map(|f| f.generate_try_from(&missing_field_error_type)));
let mut field_intos = TokenStream::new();
field_intos.extend(fields.iter().map(|field| {
let ident = &field.ident;
match &field.kind {
FidlFieldKind::Required | FidlFieldKind::HasDefault(_) => match field.in_vec {
true => quote!(
#ident: Some(
src.#ident.into_iter().map(Into::into).collect()
),
),
false => quote!(
#ident: Some(
src.#ident.into()
),
),
},
FidlFieldKind::Optional => quote!(
#ident: if let Some(field) = src.#ident {
Some(field.into())
} else {
None
},
),
}
}));
let mut field_errors = TokenStream::new();
field_errors.extend(
fields
.iter()
.filter(|field| match field.kind {
FidlFieldKind::Required => true,
_ => false,
})
.map(FidlField::camel_case)
.map(|camel_case| quote!(#camel_case,)),
);
Ok(quote!(
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum #missing_field_error_type {
#field_errors
}
#[derive(Debug)]
pub enum #error_type_name {
MissingField(#missing_field_error_type),
InvalidField(anyhow::Error),
#custom_validator_error
}
impl std::fmt::Display for #error_type_name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Validation error: {:?}", self)
}
}
impl std::error::Error for #error_type_name {}
impl From<anyhow::Error> for #error_type_name {
fn from(src: anyhow::Error) -> Self {
#error_type_name::InvalidField(src)
}
}
impl From<#missing_field_error_type> for #error_type_name {
fn from(src: #missing_field_error_type) -> Self {
#error_type_name::MissingField(src)
}
}
#custom_validator_error_from_impl
impl std::convert::TryFrom<#fidl_table_path> for #name {
type Error = #error_type_name;
fn try_from(src: #fidl_table_path) -> std::result::Result<Self, Self::Error> {
use ::fidl_table_validation::Validate;
let maybe_valid = Self {
#field_validations
};
#custom_validator_call
Ok(maybe_valid)
}
}
impl std::convert::From<#name> for #fidl_table_path {
fn from(src: #name) -> #fidl_table_path {
Self {
#field_intos
..fidl_table_validation::Decodable::new_empty()
}
}
}
))
}