// Copyright 2020 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.

extern crate proc_macro;

use {
    maplit::hashset,
    proc_macro::TokenStream,
    proc_macro2::{Ident, TokenStream as TokenStream2},
    quote::{quote, ToTokens, TokenStreamExt},
    std::collections::HashSet,
    syn,
};

/// Macro that provides a `serde::de::Deserialize` implementation for a `Vec<T>`. Attributes
/// are provided with `#[checked_vec(...)]`.
///
/// Attributes:
/// - `expected` (required): The `expected` string attached to the serde deserializer.
/// - `min_length`: The minimum length of the vector.
/// - `unique_items`: If true, all elements of the vector must be unique. Requires `T` to
///   implement the `Hash` trait.
///
/// Example:
///
/// ```rust
/// #[derive(CheckedVec)]
/// #[checked_vec(
///     expected = "a nonempty array of rights, with unique elements",
///     min_length = 1,
///     unique_items = true,
/// )]
/// pub struct Rights(pub Vec<Right>);
/// ```
#[proc_macro_derive(CheckedVec, attributes(checked_vec))]
pub fn derive_checked_vec(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).expect("could not parse input");
    impl_derive_checked_vec(ast).unwrap_or_else(|err| err.to_compile_error()).into()
}

fn impl_derive_checked_vec(ast: syn::DeriveInput) -> Result<TokenStream2, syn::Error> {
    let attrs = parse_checked_vec_attributes(&ast)?;

    struct Deserialize<'a> {
        ty: &'a syn::Path,
        inner_type: &'a syn::Path,
        expected: &'a syn::LitStr,
        min_length: Option<usize>,
        unique_items: bool,
    }
    impl ToTokens for Deserialize<'_> {
        fn to_tokens(&self, tokens: &mut TokenStream2) {
            let ty = self.ty;
            let inner_type = self.inner_type;
            let expected = self.expected;
            let min_length = self.min_length;
            let unique_items = self.unique_items;
            let visit_seq =
                gen_visit_seq(quote!(#ty), inner_type, expected, min_length, unique_items);
            tokens.append_all(quote! {
                impl<'de> serde::de::Deserialize<'de> for #ty {
                    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
                    where
                        D: serde::de::Deserializer<'de>,
                    {
                        struct Visitor;
                        impl<'de> serde::de::Visitor<'de> for Visitor {
                            type Value = #ty;

                            fn expecting(
                                &self,
                                formatter: &mut fmt::Formatter<'_>
                            ) -> fmt::Result {
                                formatter.write_str(#expected)
                            }

                            #visit_seq
                        }
                        deserializer.deserialize_seq(Visitor)
                    }
                }
            });
        }
    }
    let deserialize = Deserialize {
        ty: &attrs.ty,
        inner_type: &attrs.inner_type,
        expected: &attrs.expected,
        min_length: attrs.min_length,
        unique_items: attrs.unique_items,
    };
    let tokens = quote! {
        #deserialize
    };
    Ok(tokens)
}

fn gen_visit_str(ty: Option<TokenStream2>, expected: &syn::LitStr) -> TokenStream2 {
    let ret = match ty {
        Some(ty) => quote!(#ty(value)),
        None => quote!(value),
    };
    quote! {
        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            let value = value
                .parse()
                .map_err(|e| match e {
                    ParseError::InvalidValue => {
                        E::invalid_value(serde::de::Unexpected::Str(value), &#expected)
                    }
                    ParseError::InvalidLength => {
                        E::invalid_length(value.len(), &#expected)
                    }
                    e => {
                        panic!("unexpected parse error: {:?}", e);
                    }
                })?;
            Ok(#ret)
        }
    }
}

fn gen_visit_seq(
    ty: TokenStream2,
    inner_type: &syn::Path,
    expected: &syn::LitStr,
    min_length: Option<usize>,
    unique_items: bool,
) -> TokenStream2 {
    let inner = {
        let mut tokens = quote!();
        tokens.append_all(quote! {
            let mut elements = vec![];
            while let Some(e) = seq.next_element::<#inner_type>()? {
                elements.push(e);
            }
        });
        if let Some(min_length) = min_length {
            tokens.append_all(quote! {
                if elements.len() < #min_length {
                    return Err(serde::de::Error::invalid_length(elements.len(), &#expected));
                }
            });
        }
        if unique_items {
            tokens.append_all(quote! {
                let mut items = std::collections::HashSet::new();
                for e in &elements {
                    if !items.insert(e) {
                        return Err(serde::de::Error::invalid_value(
                            serde::de::Unexpected::Other(
                                "array with duplicate element"),
                            &#expected)
                        );
                    }
                }
                Ok(#ty(elements))
            });
        } else {
            tokens.append_all(quote! {
                Ok(#ty(elements))
            });
        }
        tokens
    };
    let mut tokens = quote!();
    tokens.append_all(quote! {
        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
        where
            A: serde::de::SeqAccess<'de>
        {
            #inner
        }
    });
    tokens
}

/// Attributes extracted from the `derive(CheckedVec)` macro.
struct CheckedVecAttributes {
    /// Type of the struct.
    ty: syn::Path,
    /// Type inside the Vec.
    inner_type: syn::Path,
    /// `expecting` string to return from the deserializer.
    expected: syn::LitStr,
    /// The minimum length of the vector, if any.
    min_length: Option<usize>,
    /// Whether all items in the vector must be unique.
    unique_items: bool,
}

fn parse_checked_vec_attributes(
    ast: &syn::DeriveInput,
) -> Result<CheckedVecAttributes, syn::Error> {
    let inner_type;
    match &ast.data {
        syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Unnamed(fields), .. }) => {
            inner_type = get_vec_inner_type(&fields).map_err(|_| {
                syn::Error::new_spanned(
                    ast,
                    "CheckedVec must be derived on a struct with one unnamed Vec field",
                )
            })?;
        }
        _ => {
            return Err(syn::Error::new_spanned(
                ast,
                "CheckedVec must be derived on a struct with one unnamed Vec field",
            ));
        }
    }
    let mut expected = None;
    let mut min_length = None;
    let mut unique_items = None;
    for attr in &ast.attrs {
        if !attr.path.is_ident("checked_vec") {
            continue;
        }
        match attr
            .parse_meta()
            .map_err(|_| syn::Error::new_spanned(ast, "`checked_vec` attribute is not valid"))?
        {
            syn::Meta::List(l) => {
                for attr in l.nested {
                    match attr {
                        syn::NestedMeta::Meta(syn::Meta::NameValue(attr)) => {
                            let ident = ident_from_path(&attr.path);
                            match &ident as &str {
                                "expected" => extract_expected(ast, attr, &mut expected)?,
                                "min_length" => extract_min_length(ast, attr, &mut min_length)?,
                                "unique_items" => {
                                    extract_unique_items(ast, attr, &mut unique_items)?
                                }
                                _ => {
                                    return Err(syn::Error::new_spanned(
                                        ast,
                                        "`checked_vec` attribute is not valid",
                                    ));
                                }
                            }
                        }
                        _ => {
                            return Err(syn::Error::new_spanned(
                                ast,
                                "`checked_vec` attribute must contain name-value pairs",
                            ))?
                        }
                    }
                }
            }
            _ => {
                return Err(syn::Error::new_spanned(
                    ast,
                    "`checked_vec` attribute value must be a list",
                ));
            }
        }
    }

    let ty = ast.ident.clone().into();
    let expected =
        expected.ok_or_else(|| syn::Error::new_spanned(ast, "`expected` attribute is missing"))?;
    let unique_items = unique_items.unwrap_or(false);

    Ok(CheckedVecAttributes { ty, inner_type, expected, min_length, unique_items })
}

#[derive(Debug)]
enum ParseError {
    InvalidAttributes,
}

fn get_vec_inner_type(fields: &syn::FieldsUnnamed) -> Result<syn::Path, ParseError> {
    if fields.unnamed.len() != 1 {
        return Err(ParseError::InvalidAttributes);
    }
    let field = fields.unnamed.first().unwrap();
    match &field.ty {
        syn::Type::Path(ty) => {
            if ty.path.segments.len() != 1 {
                return Err(ParseError::InvalidAttributes);
            }
            let seg = &ty.path.segments.first().unwrap();
            if &seg.ident.to_string() != "Vec" {
                return Err(ParseError::InvalidAttributes);
            }
            match &seg.arguments {
                syn::PathArguments::AngleBracketed(a) => {
                    if a.args.len() != 1 {
                        return Err(ParseError::InvalidAttributes);
                    }
                    match a.args.first().unwrap() {
                        syn::GenericArgument::Type(syn::Type::Path(ty)) => {
                            return Ok(ty.path.clone());
                        }
                        _ => {}
                    }
                }
                _ => {}
            }
        }
        _ => {}
    }
    Err(ParseError::InvalidAttributes)
}

/// Macro that provides a `serde::de::Deserialize` implementation for `OneOrMany<T>`.
/// Attributes are provided with `#[one_or_many(...)]`
///
/// The type which is derived is merely a dummy type to serve as a target for the macro. The trait
/// implementation is actually on `OneOrMany<T>`.
///
/// Attributes:
/// - `expected` (required): The `expected` string attached to the serde deserializer.
/// - `inner_type` (required): The `T` of `OneOrMany<T>`.
/// - `min_length`: The minimum length of the vector.
/// - `unique_items`: If true, all elements of the vector must be unique. Requires `T` to
///   implemente the `Hash` trait.
///
/// Example:
///
/// ```rust
/// #[derive(OneOrMany)]
/// #[one_or_many(
///     expected = "a single name or a nonempty array of name, with unique elements",
///     inner_type = "Name",
///     min_length = 1,
///     unique_items = true,
/// )]
/// pub struct OneOrManyNames;
/// ```
#[proc_macro_derive(OneOrMany, attributes(one_or_many))]
pub fn derive_one_or_many(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).expect("could not parse input");
    impl_derive_one_or_many(ast).unwrap_or_else(|err| err.to_compile_error()).into()
}

fn impl_derive_one_or_many(ast: syn::DeriveInput) -> Result<TokenStream2, syn::Error> {
    let attrs = parse_one_or_many_attributes(&ast)?;

    struct Deserialize<'a> {
        inner_type: &'a syn::Path,
        expected: &'a syn::LitStr,
        min_length: Option<usize>,
        unique_items: bool,
    }
    impl ToTokens for Deserialize<'_> {
        fn to_tokens(&self, tokens: &mut TokenStream2) {
            let inner_type = self.inner_type;
            let expected = self.expected;
            let min_length = self.min_length;
            let unique_items = self.unique_items;
            let visit_str = gen_visit_str(Some(quote!(OneOrMany::One)), expected);
            let visit_seq = gen_visit_seq(
                quote!(OneOrMany::Many),
                inner_type,
                expected,
                min_length,
                unique_items,
            );
            tokens.append_all(quote! {
                impl<'de> serde::de::Deserialize<'de> for OneOrMany<#inner_type> {
                    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
                    where
                        D: serde::de::Deserializer<'de>,
                    {
                        struct Visitor;
                        impl<'de> serde::de::Visitor<'de> for Visitor {
                            type Value = OneOrMany<#inner_type>;

                            fn expecting(
                                &self,
                                formatter: &mut fmt::Formatter<'_>
                            ) -> fmt::Result {
                                formatter.write_str(#expected)
                            }

                            #visit_str

                            #visit_seq
                        }
                        deserializer.deserialize_any(Visitor)
                    }
                }
            });
        }
    }
    let deserialize = Deserialize {
        inner_type: &attrs.inner_type,
        expected: &attrs.expected,
        min_length: attrs.min_length,
        unique_items: attrs.unique_items,
    };
    let tokens = quote! {
        #deserialize
    };
    Ok(tokens)
}

/// Attributes extracted from the `derive(OneOrMany)` macro.
struct OneOrManyAttributes {
    /// Type inside the `OneOrMany`.
    inner_type: syn::Path,
    /// `expecting` string to return from the deserializer.
    expected: syn::LitStr,
    /// The minimum length of the vector, if any.
    min_length: Option<usize>,
    /// Whether all items in the vector must be unique.
    unique_items: bool,
}

fn parse_one_or_many_attributes(ast: &syn::DeriveInput) -> Result<OneOrManyAttributes, syn::Error> {
    match ast.data {
        syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Unit, .. }) => {}
        _ => {
            return Err(syn::Error::new_spanned(
                ast,
                "OneOrMany must be derived on a struct with no fields",
            ));
        }
    }
    let mut expected = None;
    let mut inner_type = None;
    let mut min_length = None;
    let mut unique_items = None;
    for attr in &ast.attrs {
        if !attr.path.is_ident("one_or_many") {
            continue;
        }
        match attr
            .parse_meta()
            .map_err(|_| syn::Error::new_spanned(ast, "`one_or_many` attribute is not valid"))?
        {
            syn::Meta::List(l) => {
                for attr in l.nested {
                    match attr {
                        syn::NestedMeta::Meta(syn::Meta::NameValue(attr)) => {
                            let ident = ident_from_path(&attr.path);
                            match &ident as &str {
                                "expected" => extract_expected(ast, attr, &mut expected)?,
                                "inner_type" => extract_inner_type(ast, attr, &mut inner_type)?,
                                "min_length" => extract_min_length(ast, attr, &mut min_length)?,
                                "unique_items" => {
                                    extract_unique_items(ast, attr, &mut unique_items)?
                                }
                                _ => {
                                    return Err(syn::Error::new_spanned(
                                        ast,
                                        "`one_or_many` attribute is not valid",
                                    ));
                                }
                            }
                        }
                        _ => {
                            return Err(syn::Error::new_spanned(
                                ast,
                                "`one_or_many` attribute must contain name-value pairs",
                            ))?
                        }
                    }
                }
            }
            _ => {
                return Err(syn::Error::new_spanned(
                    ast,
                    "`one_or_many` attribute value must be a list",
                ));
            }
        }
    }

    let inner_type: syn::Path = inner_type
        .ok_or_else(|| syn::Error::new_spanned(ast, "`inner_type` attribute is missing"))?
        .parse()
        .map_err(|_| syn::Error::new_spanned(ast, "`inner_type` attribute is not a valid path"))?;
    let expected =
        expected.ok_or_else(|| syn::Error::new_spanned(ast, "`expected` attribute is missing"))?;
    let unique_items = unique_items.unwrap_or(false);
    Ok(OneOrManyAttributes { inner_type, expected, min_length, unique_items })
}

/// Macro that provides trait implementations for a CML reference enum type that wishes to be serde
/// deserializable. This makes it possible to easily create context-specific reference types that
/// encode their accepted variants. Attributes are provided with `#[reference(...)]`.
///
/// The following enum variants are accepted:
/// - Named(Name),
/// - Parent,
/// - Framework,
/// - Debug,
/// - Self_,
///
/// Attributes:
/// - `expected` (required): The `expected` string attached to the serde deserializer.
///
/// This macro implements the following traits:
/// - `std::str::FromStr`
/// - `std::fmt::Display`
/// - `serde::de::Deserialize`
/// - `From<#this> for cml::AnyRef` (type erasure into the universal reference type)
///
/// Example:
///
/// ```rust
/// #[derive(Reference)]
/// #[reference(expected = "a registration reference")]
/// pub enum RegistrationRef {
///     /// A reference to a child.
///     Named(Name),
///     /// A reference to the parent.
///     Parent,
///     /// A reference to this component.
///     Self_,
/// }
/// ```
#[proc_macro_derive(Reference, attributes(reference))]
pub fn derive_reference(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).expect("could not parse input");
    impl_derive_ref(ast).unwrap_or_else(|err| err.to_compile_error()).into()
}

fn impl_derive_ref(ast: syn::DeriveInput) -> Result<TokenStream2, syn::Error> {
    let attrs = parse_reference_attributes(&ast)?;

    struct Display<'a> {
        name: &'a Ident,
        variants: &'a HashSet<String>,
    }
    impl ToTokens for Display<'_> {
        fn to_tokens(&self, tokens: &mut TokenStream2) {
            let name = self.name;
            let inner = {
                let mut tokens = quote!();
                if self.variants.contains("Named") {
                    tokens.append_all(quote! {
                        Self::Named(name) => write!(f, "#{}", name),
                    });
                }
                if self.variants.contains("Parent") {
                    tokens.append_all(quote! {
                        Self::Parent => write!(f, "parent"),
                    });
                }
                if self.variants.contains("Framework") {
                    tokens.append_all(quote! {
                        Self::Framework => write!(f, "framework"),
                    });
                }
                if self.variants.contains("Debug") {
                    tokens.append_all(quote! {
                        Self::Debug => write!(f, "debug"),
                    });
                }
                if self.variants.contains("Self_") {
                    tokens.append_all(quote! {
                        Self::Self_ => write!(f, "self"),
                    });
                }
                tokens
            };
            tokens.append_all(quote! {
                impl<'a> std::fmt::Display for #name {
                    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                        match self {
                            #inner
                        }
                    }
                }
            });
        }
    }

    struct FromStr<'a> {
        name: &'a Ident,
        variants: &'a HashSet<String>,
    }
    impl ToTokens for FromStr<'_> {
        fn to_tokens(&self, tokens: &mut TokenStream2) {
            let name = self.name;
            let inner = {
                let mut tokens = quote!();
                tokens.append_all(quote! {
                    if value.len() == 0 || value.len() > 101 {
                        return Err(ParseError::InvalidLength);
                    }
                });
                if self.variants.contains("Named") {
                    tokens.append_all(quote! {
                        if value.starts_with("#") {
                            return value[1..]
                                .parse::<Name>()
                                .map(Self::Named)
                                .map_err(|_| ParseError::InvalidValue);
                        }
                    });
                }
                let inner = {
                    let mut tokens = quote!();
                    if self.variants.contains("Parent") {
                        tokens.append_all(quote! {
                            "parent" => Ok(Self::Parent),
                        });
                    }
                    if self.variants.contains("Framework") {
                        tokens.append_all(quote! {
                            "framework" => Ok(Self::Framework),
                        });
                    }
                    if self.variants.contains("Debug") {
                        tokens.append_all(quote! {
                            "debug" => Ok(Self::Debug),
                        });
                    }
                    if self.variants.contains("Self_") {
                        tokens.append_all(quote! {
                            "self" => Ok(Self::Self_),
                        });
                    }
                    tokens.append_all(quote! {_ => Err(ParseError::InvalidValue) });
                    tokens
                };
                tokens.append_all(quote! {
                    match value {
                        #inner
                    }
                });
                tokens
            };
            tokens.append_all(quote! {
                impl std::str::FromStr for #name {
                    type Err = ParseError;

                    fn from_str(value: &str) -> Result<Self, Self::Err> {
                        #inner
                    }
                }
            });
        }
    }

    struct AnyRefFrom<'a> {
        name: &'a Ident,
        variants: &'a HashSet<String>,
    }
    impl ToTokens for AnyRefFrom<'_> {
        fn to_tokens(&self, tokens: &mut TokenStream2) {
            let name = self.name;
            let inner = {
                let mut tokens = quote!();
                if self.variants.contains("Parent") {
                    tokens.append_all(quote! {
                        #name::Parent => Self::Parent,
                    });
                }
                if self.variants.contains("Framework") {
                    tokens.append_all(quote! {
                        #name::Framework => Self::Framework,
                    });
                }
                if self.variants.contains("Debug") {
                    tokens.append_all(quote! {
                        #name::Debug => Self::Debug,
                    });
                }
                if self.variants.contains("Self_") {
                    tokens.append_all(quote! {
                        #name::Self_ => Self::Self_,
                    });
                }
                if self.variants.contains("Named") {
                    tokens.append_all(quote! {
                        #name::Named(ref n) => Self::Named(n),
                    });
                }
                tokens
            };
            tokens.append_all(quote! {
                impl<'a> From<&'a #name> for AnyRef<'a> {
                    fn from(r: &'a #name) -> Self {
                        match r {
                            #inner
                        }
                    }
                }
            });
        }
    }

    struct Deserialize<'a> {
        name: &'a Ident,
        expected: &'a syn::LitStr,
    }
    impl ToTokens for Deserialize<'_> {
        fn to_tokens(&self, tokens: &mut TokenStream2) {
            let name = self.name;
            let expected = self.expected;
            let visit_str = gen_visit_str(None, self.expected);
            tokens.append_all(quote! {
                impl<'de> serde::de::Deserialize<'de> for #name {
                    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
                    where
                        D: serde::de::Deserializer<'de>,
                    {
                        struct RefVisitor;
                        impl<'de> serde::de::Visitor<'de> for RefVisitor {
                            type Value = #name;

                            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                                formatter.write_str(#expected)
                            }

                            #visit_str
                        }
                        deserializer.deserialize_str(RefVisitor)
                    }
                }
            });
        }
    }

    let display = Display { name: &attrs.name, variants: &attrs.variants };
    let from_str = FromStr { name: &attrs.name, variants: &attrs.variants };
    let anyref_from = AnyRefFrom { name: &attrs.name, variants: &attrs.variants };
    let deserialize = Deserialize { name: &attrs.name, expected: &attrs.expected };
    let tokens = quote! {
        #display

        #from_str

        #anyref_from

        #deserialize
    };
    Ok(tokens)
}

/// Attributes extracted from the `derive(Reference)` macro.
struct ReferenceAttributes {
    /// Name of the reference enum.
    name: Ident,
    /// `expecting` string to return from the deserializer.
    expected: syn::LitStr,
    /// The variants that the reference can be. Valid choices: "named", "self", "parent",
    /// "framework".
    variants: HashSet<String>,
}

fn parse_reference_attributes(ast: &syn::DeriveInput) -> Result<ReferenceAttributes, syn::Error> {
    let variants: HashSet<_> = if let syn::Data::Enum(enum_) = &ast.data {
        enum_.variants.iter().map(|v| v.ident.to_string()).collect()
    } else {
        return Err(syn::Error::new_spanned(ast, "Ref must be derived on an enum."));
    };
    let allowed_variants = hashset! {
        "Named",
        "Parent",
        "Framework",
        "Debug",
        "Self_",
    };
    for ty in variants.iter() {
        if !allowed_variants.contains(ty as &str) {
            return Err(syn::Error::new_spanned(
                ast,
                &format!("enum variant not supported: {}", ty),
            ));
        }
    }
    let mut expected = None;
    for attr in &ast.attrs {
        if !attr.path.is_ident("reference") {
            continue;
        }
        match attr
            .parse_meta()
            .map_err(|_| syn::Error::new_spanned(ast, "`reference` attribute is not valid"))?
        {
            syn::Meta::List(l) => {
                for attr in l.nested {
                    match attr {
                        syn::NestedMeta::Meta(syn::Meta::NameValue(attr)) => {
                            let ident = ident_from_path(&attr.path);
                            match &ident as &str {
                                "expected" => extract_expected(ast, attr, &mut expected)?,
                                _ => {
                                    return Err(syn::Error::new_spanned(
                                        ast,
                                        "`reference` attribute is not valid",
                                    ));
                                }
                            }
                        }
                        _ => {
                            return Err(syn::Error::new_spanned(
                                ast,
                                "`reference` attribute must contain name-value pairs",
                            ))?
                        }
                    }
                }
            }
            _ => {
                return Err(syn::Error::new_spanned(
                    ast,
                    "`reference` attribute value must be a list",
                ));
            }
        }
    }

    let expected =
        expected.ok_or_else(|| syn::Error::new_spanned(ast, "`expected` attribute is missing"))?;

    Ok(ReferenceAttributes { name: ast.ident.clone(), expected, variants })
}

fn extract_expected(
    ast: &syn::DeriveInput,
    attr: syn::MetaNameValue,
    expected: &mut Option<syn::LitStr>,
) -> Result<(), syn::Error> {
    match attr.lit {
        syn::Lit::Str(l) => {
            if expected.is_some() {
                return Err(syn::Error::new_spanned(ast, "duplicate `expected` attribute"));
            }
            *expected = Some(l);
        }
        _ => {
            return Err(syn::Error::new_spanned(ast, "`expected` attribute value must be string"));
        }
    }
    Ok(())
}

fn extract_inner_type(
    ast: &syn::DeriveInput,
    attr: syn::MetaNameValue,
    inner_type: &mut Option<syn::LitStr>,
) -> Result<(), syn::Error> {
    match attr.lit {
        syn::Lit::Str(l) => {
            if inner_type.is_some() {
                return Err(syn::Error::new_spanned(ast, "duplicate `inner_type` attribute"));
            }
            *inner_type = Some(l);
        }
        _ => {
            return Err(syn::Error::new_spanned(
                ast,
                "`inner_type` attribute value must be string",
            ));
        }
    }
    Ok(())
}

fn extract_min_length(
    ast: &syn::DeriveInput,
    attr: syn::MetaNameValue,
    min_length: &mut Option<usize>,
) -> Result<(), syn::Error> {
    match attr.lit {
        syn::Lit::Int(l) => {
            if min_length.is_some() {
                return Err(syn::Error::new_spanned(ast, "duplicate `min_length` attribute"));
            }
            let l: usize = l.base10_parse().map_err(|_| {
                syn::Error::new_spanned(ast, "`min_length` attribute is not base 10")
            })?;
            *min_length = Some(l);
        }
        _ => {
            return Err(syn::Error::new_spanned(ast, "`min_length` attribute value must be int"));
        }
    }
    Ok(())
}

fn extract_unique_items(
    ast: &syn::DeriveInput,
    attr: syn::MetaNameValue,
    unique_items: &mut Option<bool>,
) -> Result<(), syn::Error> {
    match attr.lit {
        syn::Lit::Bool(b) => {
            if unique_items.is_some() {
                return Err(syn::Error::new_spanned(ast, "duplicate `unique_items` attribute"));
            }
            *unique_items = Some(b.value);
        }
        _ => {
            return Err(syn::Error::new_spanned(
                ast,
                "`unique_items` attribute value must be bool",
            ));
        }
    }
    Ok(())
}

fn ident_from_path(path: &syn::Path) -> String {
    path.get_ident().map(|i| i.to_string()).unwrap_or_else(|| String::new())
}
