tower-web-macros 0.3.5

Macros for tower-web
Documentation
use http::StatusCode;
use http::header::{HeaderName, HeaderValue};
use syn;

#[derive(Debug)]
pub(crate) struct Attribute {
    pub kind: Kind,
    pub source: syn::Attribute,
}

#[derive(Debug)]
pub(crate) enum Kind {
    Status(Option<StatusCode>),
    Header {
        name: Option<HeaderName>,
        value: Option<HeaderValue>,
    },
    Template(String),
    Either
}

impl Attribute {
    pub(crate) fn is_web_attribute(attr: &syn::Attribute) -> bool {
        attr.path.segments.len() == 1 && attr.path.segments[0].ident == "web"
    }

    pub(crate) fn from_ast(attrs: &[syn::Attribute])
        -> Result<Vec<Attribute>, String>
    {
        use syn::{Meta, NestedMeta};

        let mut ret = vec![];

        for attr in attrs {
            if !Attribute::is_web_attribute(attr) {
                continue;
            }

            let meta = match attr.interpret_meta() {
                Some(meta) => meta,
                None => continue,
            };

            let source = attr.clone();

            match meta {
                Meta::List(meta_list) => {
                    for meta in &meta_list.nested {
                        let meta = match meta {
                            NestedMeta::Meta(meta) => meta,
                            NestedMeta::Literal(_) => {
                                unimplemented!("unexpected attribute literal; file={}; line={}", file!(), line!())
                            }
                        };

                        let attr = match meta {
                            Meta::Word(meta) => {
                                if meta == "header" {
                                    Attribute::header_from_word(&source)
                                } else if meta == "status" {
                                    Attribute::status_from_word(&source)
                                } else if meta == "either" {
                                    Attribute::either_from_word(&source)
                                } else if meta == "template" {
                                    let actual = quote!(#meta);

                                    return Err(format!("invalid struct level `template` annotation. The attribute must be formatted as:\n\n\

                                                        `#[web(template = \"foo\")]`\n\n\

                                                        Actual: {}", actual.to_string()));
                                } else {
                                    unimplemented!("error handling");
                                }
                            }
                            Meta::List(meta) => {
                                if meta.ident == "header" {
                                    Attribute::header_from_list(meta, &source)
                                } else {
                                    let actual = quote!(#meta);

                                    return Err(format!("invalid struct level `status` annotation. The attribute must be in one of \
                                                        the following formats:\n\n\

                                                       `#[web(status)]`\n\
                                                       `#[web(status = \"201\")]`\n\n\

                                                       Actual: {}", actual.to_string()));
                                }
                            }
                            Meta::NameValue(meta) => {
                                if meta.ident == "status" {
                                    Attribute::status_from_name_value(meta, &source)
                                } else if meta.ident == "template" {
                                    Attribute::template_from_name_value(meta, &source)
                                } else if meta.ident == "header" {
                                    unimplemented!("unexpected attribute; {:?}", meta);
                                } else {
                                    unimplemented!("unexpected attribute; {:?}", meta);
                                }
                            }
                        };

                        ret.push(attr);
                    }
                }
                _ => {
                    unimplemented!("file={}; line={}", file!(), line!());
                }
            }
        }

        Ok(ret)
    }

    fn status_from_word(source: &syn::Attribute) -> Attribute {
        Attribute {
            kind: Kind::Status(None),
            source: source.clone(),
        }
    }

    fn header_from_word(source: &syn::Attribute) -> Attribute {
        Attribute {
            kind: Kind::Header {
                name: None,
                value: None,
            },
            source: source.clone()
        }
    }

    fn either_from_word(source: &syn::Attribute) -> Attribute {
        Attribute {
            kind: Kind::Either,
            source: source.clone(),
        }
    }

    fn status_from_name_value(
        meta: &syn::MetaNameValue,
        source: &syn::Attribute
    ) -> Attribute
    {
        use syn::Lit;

        let kind = match meta.lit {
            Lit::Str(ref lit_str) => {
                let lit_str = lit_str.value();
                let bytes = lit_str.as_bytes();
                let status = StatusCode::from_bytes(bytes)
                    .unwrap();

                Kind::Status(Some(status))
            }
            ref meta => unimplemented!("unsupported meta: {:?}", meta),
        };

        Attribute {
            kind,
            source: source.clone(),
        }
    }

    fn template_from_name_value(
        meta: &syn::MetaNameValue,
        source: &syn::Attribute,
    ) -> Attribute
    {
        use syn::Lit;

        let kind = match meta.lit {
            Lit::Str(ref lit_str) => {
                let lit_str = lit_str.value();

                Kind::Template(lit_str)
            }
            ref meta => unimplemented!("unsupported meta: {:?}", meta),
        };

        Attribute {
            kind,
            source: source.clone(),
        }
    }

    fn header_from_list(meta: &syn::MetaList, source: &syn::Attribute) -> Attribute {
        use syn::{NestedMeta, Meta, Lit};

        let mut name = None;
        let mut value = None;

        for meta in &meta.nested {
            match meta {
                NestedMeta::Meta(Meta::NameValue(meta)) => {
                    if meta.ident == "name" {
                        match meta.lit {
                            Lit::Str(ref v) => {
                                let hdr = v.value()
                                    .parse()
                                    .unwrap(); // TODO: Error handling

                                name = Some(hdr);
                            }
                            _ => unimplemented!("file={}; line={}", file!(), line!()),
                        }
                    } else if meta.ident == "value" {
                        match meta.lit {
                            Lit::Str(ref lit_str) => {
                                let lit_str = lit_str.value();
                                let bytes = lit_str.as_bytes();
                                let hdr_val = HeaderValue::from_bytes(bytes)
                                    .unwrap();

                                value = Some(hdr_val);
                            }
                            _ => unimplemented!("file={}; line={}", file!(), line!()),
                        }
                    } else {
                        unimplemented!("file={}; line={}", file!(), line!());
                    }
                }
                meta => unimplemented!("unsupported meta: {:?}", meta),
            }
        }

        Attribute {
            kind: Kind::Header { name, value },
            source: source.clone(),
        }
    }
}