diesel_codegen 0.16.1

Custom derive and procedural macros for Diesel
Documentation
use syn;

use attr::Attr;
use util::*;

pub struct Model {
    pub ty: syn::Ty,
    pub attrs: ModelAttrs,
    pub name: syn::Ident,
    pub generics: syn::Generics,
    pub primary_key_names: Vec<syn::Ident>,
    table_name_from_annotation: Option<syn::Ident>,
}

impl Model {
    pub fn from_item(item: &syn::DeriveInput, derived_from: &str) -> Result<Self, String> {
        let attrs = match item.body {
            syn::Body::Enum(..) => return Err(format!(
                "#[derive({})] cannot be used with enums", derived_from)),
            syn::Body::Struct(ref fields) => ModelAttrs::from_struct_body(fields),
        };
        let ty = struct_ty(item.ident.clone(), &item.generics);
        let name = item.ident.clone();
        let generics = item.generics.clone();
        let primary_key_names = list_value_of_attr_with_name(&item.attrs, "primary_key")
            .map(|v| v.into_iter().cloned().collect())
            .unwrap_or_else(|| vec![syn::Ident::new("id")]);
        let table_name_from_annotation = str_value_of_attr_with_name(
            &item.attrs, "table_name").map(syn::Ident::new);

        Ok(Model {
            ty: ty,
            attrs: attrs,
            name: name,
            generics: generics,
            primary_key_names: primary_key_names,
            table_name_from_annotation: table_name_from_annotation,
        })
    }

    pub fn table_name(&self) -> syn::Ident {
        self.table_name_from_annotation.clone().unwrap_or_else(|| {
            syn::Ident::new(infer_table_name(self.name.as_ref()))
        })
    }

    pub fn has_table_name_annotation(&self) -> bool {
        self.table_name_from_annotation.is_some()
    }

    pub fn is_tuple_struct(&self) -> bool {
        self.attrs.is_tuple()
    }
}

pub fn infer_association_name(name: &str) -> String {
    let mut result = String::with_capacity(name.len());
    result.push_str(&name[..1].to_lowercase());
    for character in name[1..].chars() {
        if character.is_uppercase() {
            result.push('_');
            for lowercase in character.to_lowercase() {
                result.push(lowercase);
            }
        } else {
            result.push(character);
        }
    }
    result
}

fn infer_table_name(name: &str) -> String {
    let mut result = infer_association_name(name);
    result.push('s');
    result
}

pub enum ModelAttrs {
    Struct(Vec<Attr>),
    Tuple(Vec<Attr>),
}

impl ModelAttrs {
    fn from_struct_body(body: &syn::VariantData) -> Self {
        let attrs = body.fields()
            .into_iter()
            .enumerate()
            .map(Attr::from_struct_field)
            .collect();

        match *body {
            syn::VariantData::Struct(_) => ModelAttrs::Struct(attrs),
            _ => ModelAttrs::Tuple(attrs),
        }
    }

    pub fn as_slice(&self) -> &[Attr] {
        match *self {
            ModelAttrs::Struct(ref attrs) |
            ModelAttrs::Tuple(ref attrs) => &*attrs,
        }
    }

    pub fn is_tuple(&self) -> bool {
        match *self {
            ModelAttrs::Struct(_) => false,
            ModelAttrs::Tuple(_) => true,
        }
    }
}

#[test]
fn infer_table_name_pluralizes_and_downcases() {
    assert_eq!("foos", &infer_table_name("Foo"));
    assert_eq!("bars", &infer_table_name("Bar"));
}

#[test]
fn infer_table_name_properly_handles_underscores() {
    assert_eq!("foo_bars", &infer_table_name("FooBar"));
    assert_eq!("foo_bar_bazs", &infer_table_name("FooBarBaz"));
}