1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//The quote macro can require a high recursion limit
#![recursion_limit = "256"]

extern crate proc_macro;

use butane_core::*;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::TokenTree;
use quote::quote;
use std::path::PathBuf;
use syn::{Expr, Ident};

mod filter;

/// Attribute macro which marks a struct as being a data model and
/// generates an implementation of [DataObject][crate::DataObject]. This
/// macro will also write information to disk at compile time necessary to
/// generate migrations
///
/// ## Restrictions on model types:
/// 1. The type of each field must implement [`FieldType`] or be [`Many`].
/// 2. There must be a primary key field. This must be either annotated with a `#[pk]` attribute or named `id`.
///
/// ## Helper Attributes
/// * `#[table = "NAME"]` used on the struct to specify the name of the table (defaults to struct name)
/// * `#[pk]` on a field to specify that it is the primary key.
/// * `#[auto]` on a field indicates that the field's value is
///    initialized based on serial/autoincrement. Currently supported
///    only on the primary key and only if the primary key is an integer
///    type
/// * `[default]` should be used on fields added by later migrations to avoid errors on existing objects.
///     Unnecessary if the new field is an `Option<>`
///
/// For example
/// ```ignore
/// #[model]
/// #[table = "posts"]
/// pub struct Post {
///   #[auto]
///   #[pk] // unnecessary if identifier were named id instead
///   pub identifier: i32,
///   pub title: String,
///   pub content: String,
///   #[default = false]
///   pub published: bool,
/// }
/// ```
///
///
/// [`FieldType`]: crate::FieldType
/// [`Many`]: butane_core::many::Many
#[proc_macro_attribute]
pub fn model(_args: TokenStream, input: TokenStream) -> TokenStream {
    codegen::model_with_migrations(input.into(), &mut migrations_for_dir()).into()
}

#[proc_macro_attribute]
pub fn dataresult(args: TokenStream, input: TokenStream) -> TokenStream {
    codegen::dataresult(args.into(), input.into()).into()
}

#[proc_macro]
pub fn filter(input: TokenStream) -> TokenStream {
    let input: TokenStream2 = input.into();
    let args: Vec<TokenTree> = input.into_iter().collect();
    if args.len() < 2 {
        return make_compile_error!("Expected filter!(Type, expression)").into();
    }
    let tyid: Ident = match &args[0] {
        TokenTree::Ident(tyid) => tyid.clone(),
        TokenTree::Group(g) => match syn::parse2::<Ident>(g.stream()) {
            Ok(ident) => ident,
            Err(_) => {
                return make_compile_error!("Unexpected tokens in database object type {:?}", &g)
                    .into()
            }
        },
        _ => {
            return make_compile_error!("Unexpected tokens in database object type {:?}", &args[0])
                .into()
        }
    };

    if let TokenTree::Punct(_) = args[1] {
    } else {
        return make_compile_error!("Expected filter!(Type, expression)").into();
    }

    let expr: TokenStream2 = args.into_iter().skip(2).collect();
    let expr: Expr = match syn::parse2(expr) {
        Ok(expr) => expr,
        Err(_) => {
            return make_compile_error!(
                "Expected filter!(Type, expression) but could not parse expression"
            )
            .into()
        }
    };
    filter::for_expr(&tyid, &expr).into()
}

/// Attribute macro which marks a type as being available to butane
/// for use in models.
///
/// May be used on type aliases, structs, or enums. Except when used
/// on type aliases, it must be given a parameter specifying the
/// SqlType it can be converted to.
///
/// E.g.
/// ```ignore
/// #[butane_type]
/// pub type CurrencyAmount = f64;
///
/// #[butane_type(Text)]
/// pub enum Currency {
///   Dollars,
///   Pounds,
///   Euros,
/// }
/// impl ToSql for Currency {
///   fn to_sql(&self) -> SqlVal {
///      SqlVal::Text(
///          match self {
///              Self::Dollars => "dollars",
///              Self::Pounds => "pounds",
///              Self::Euros => "euros",
///          }
///          .to_string())
///  }
/// }
/// ```
#[proc_macro_attribute]
pub fn butane_type(args: TokenStream, input: TokenStream) -> TokenStream {
    codegen::butane_type_with_migrations(args.into(), input.into(), &mut migrations_for_dir())
        .into()
}

fn migrations_for_dir() -> migrations::FsMigrations {
    migrations::from_root(&migrations_dir())
}

fn migrations_dir() -> PathBuf {
    let mut dir = PathBuf::from(
        std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR expected to be set"),
    );
    dir.push(".butane");
    dir.push("migrations");
    dir
}