aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/tutorial/mod.rs4
-rw-r--r--tosin-macros/src/lib.rs57
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),* ],
};
}
};