diff options
-rw-r--r-- | tests/tutorial/mod.rs | 4 | ||||
-rw-r--r-- | tosin-macros/src/lib.rs | 57 |
2 files changed, 56 insertions, 5 deletions
diff --git a/tests/tutorial/mod.rs b/tests/tutorial/mod.rs index fb05066..b26aa39 100644 --- a/tests/tutorial/mod.rs +++ b/tests/tutorial/mod.rs @@ -277,9 +277,9 @@ fn settings() -> Settings<impl Connectable> { tosin::main!(urls(), settings()); "#).unwrap(); - // cargo run make-migrations polls + // cargo run make-migrations Command::new(CARGO) - .args(&["run", "make-migrations", "polls"]) + .args(&["run", "make-migrations"]) .status() .unwrap() .check(); diff --git a/tosin-macros/src/lib.rs b/tosin-macros/src/lib.rs index 86750e1..7028274 100644 --- a/tosin-macros/src/lib.rs +++ b/tosin-macros/src/lib.rs @@ -2,6 +2,7 @@ extern crate proc_macro; +use std::collections::HashMap; use std::fs; use proc_macro::TokenStream; @@ -17,14 +18,64 @@ pub fn model_derive(input: TokenStream) -> TokenStream { impl_model(&ast) } +fn to_field_spec(field: &syn::Field) -> impl quote::ToTokens { + fn parse_type(ty: &str) -> syn::Type { + syn::parse_str(ty).unwrap() + } + let field_name = &field.ident; + let field_type = &field.ty; + let model_options: HashMap<syn::Path, syn::Lit> = field.attrs.iter() + .filter_map(|attr| attr.parse_meta().ok()) + .filter_map(|meta| if let syn::Meta::List(meta) = meta { Some(meta) } else { None }) + .filter(|meta| meta.path.get_ident().map_or(false, |path| path == "model")) + .flat_map(|model| { + model.nested.into_iter().filter_map(|item| { + if let syn::NestedMeta::Meta(syn::Meta::NameValue(data)) = item { + Some((data.path, data.lit)) + } else { + None + } + }) + }) + .collect(); + if field_type == &parse_type("Option<Id>") { + quote! { ::tosin::db::models::Field::IntField { name: stringify!(#field_name) } } + } else if field_type == &parse_type("Id") { + // TODO foreign key constraint + quote! { ::tosin::db::models::Field::IntField { name: stringify!(#field_name) } } + } else if field_type == &parse_type("usize") { + // TODO default + quote! { ::tosin::db::models::Field::IntField { name: stringify!(#field_name) } } + } else if field_type == &parse_type("String") { + let max_length = model_options.iter() + .find(|(name, _value)| name.get_ident().map_or(false, |path| path == "max_length")) + .map(|(_name, value)| value); + if let Some(max_length) = max_length { + quote! { ::tosin::db::models::Field::CharField { name: stringify!(#field_name), max_length: Some(#max_length) } } + } else { + quote! { ::tosin::db::models::Field::CharField { name: stringify!(#field_name), max_length: None } } + } + } else if field_type == &parse_type("time::PrimitiveDateTime") { + quote! { ::tosin::db::models::Field::DateTimeField { name: stringify!(#field_name) } } + } else { + use quote::ToTokens; + panic!("can't handle {}", field.to_token_stream()) + } +} + fn impl_model(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; + let fields = if let syn::Data::Struct(ast) = &ast.data { + ast.fields.iter() + .map(to_field_spec) + } else { + panic!("not on a struct"); + }; let gen = quote! { - use tosin::db::models::ModelMeta; impl #name { - pub const META: ModelMeta = ModelMeta { + pub const META: ::tosin::db::models::ModelMeta = ::tosin::db::models::ModelMeta { name: stringify!(#name), - fields: &[], + fields: &[ #(#fields),* ], }; } }; |