Skip to content

Commit e4fa487

Browse files
committed
WIP
1 parent 9fb4465 commit e4fa487

File tree

7 files changed

+480
-395
lines changed

7 files changed

+480
-395
lines changed

examples/hello_database/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![feature(custom_attribute)]
2+
13
#[macro_use]
24
extern crate diesel as other_diesel;
35

@@ -13,8 +15,7 @@ use uuid::Uuid;
1315
use crate::schema::worlds;
1416
mod schema;
1517

16-
17-
#[resource(schema = worlds, table = "worlds")]
18+
#[resource]
1819
struct World {
1920
uuid: Uuid,
2021
name: String,

postgres_resource_derive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ authors = ["technetos <[email protected]>"]
55
edition = "2018"
66

77
[dependencies]
8-
syn = { version = "0.15.13", features = ["extra-traits", "full"]}
8+
syn = { version = "0.15.23", features = ["extra-traits", "full"]}
99
quote = "0.6.8"
1010
proc-macro2 = "0.4.20"
1111

postgres_resource_derive/src/attr.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use proc_macro2::{Span, TokenTree::Literal};
2+
use syn::{
3+
parse::{Parse, ParseStream, Result},
4+
punctuated::Punctuated,
5+
token, Attribute, Ident, LitStr,
6+
Meta::*,
7+
MetaNameValue,
8+
Error,
9+
Lit,
10+
};
11+
12+
#[derive(Debug)]
13+
pub struct Attrs {
14+
db_conn: Option<LitStr>,
15+
table: Option<LitStr>,
16+
}
17+
18+
impl Parse for Attrs {
19+
fn parse(input: ParseStream) -> Result<Self> {
20+
let attrs = input.call(Attribute::parse_outer)?;
21+
22+
let mut db_conn: Option<LitStr> = None;
23+
let mut table: Option<LitStr> = None;
24+
25+
for attr in &attrs {
26+
if attr.path.is_ident("table") {
27+
table = Self::parse_attr(attr, "table")?;
28+
}
29+
if attr.path.is_ident("env_var") {
30+
db_conn = Self::parse_attr(attr, "env_var")?;
31+
}
32+
}
33+
34+
println!("{:#?}", table);
35+
36+
Ok(Attrs { db_conn, table })
37+
}
38+
}
39+
40+
impl Attrs {
41+
fn parse_attr(attr: &Attribute, expected: &str) -> Result<Option<LitStr>> {
42+
match attr.parse_meta()? {
43+
NameValue(MetaNameValue { lit: Lit::Str(lit_str), .. }) => {
44+
lit_str.parse().map(Some)
45+
}
46+
_ => {
47+
let error_span = attr.bracket_token.span;
48+
let message = &format!("expected #[{} = \"...\"]", expected);
49+
Err(syn::Error::new(error_span, message))
50+
}
51+
}
52+
}
53+
}
54+
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use crate::{attr::*, IdentExt, camel_to_snake, r#struct::*};
2+
3+
use proc_macro2::Span;
4+
use syn::{
5+
parse::{Parse, ParseStream, Result},
6+
punctuated::Punctuated,
7+
token, Attribute, Ident, LitStr,
8+
Meta::*,
9+
MetaNameValue,
10+
};
11+
12+
pub struct Input {
13+
pub parsed_struct: Struct,
14+
}
15+
16+
pub trait Builder<'i> {
17+
fn build(self, input: &'i Input) -> Result<proc_macro2::TokenStream>;
18+
}
19+
20+
pub struct InferredTableMacro;
21+
22+
impl<'i> Builder<'i> for InferredTableMacro {
23+
fn build(self, input: &'i Input) -> Result<proc_macro2::TokenStream> {
24+
let model_name = camel_to_snake(&input.parsed_struct.ident.to_string()[..]) + "s";
25+
let literal = LitStr::new(&model_name, Span::call_site());
26+
Ok(quote!(#[table_name = #literal]))
27+
}
28+
}
29+
30+
pub struct CustomTableMacro<B>(pub B);
31+
32+
impl<'i, B: Builder<'i>> Builder<'i> for CustomTableMacro<B> {
33+
fn build(self, input: &'i Input) -> Result<proc_macro2::TokenStream> {
34+
self.0.build(input)
35+
}
36+
}
37+
38+
pub struct ModelWithId<B>(pub B);
39+
40+
impl<'i, B: Builder<'i>> Builder<'i> for ModelWithId<B> {
41+
fn build(self, input: &'i Input) -> Result<proc_macro2::TokenStream> {
42+
let model_name_with_id = input.parsed_struct.ident.append("WithId");
43+
let fields = self.0.build(input)?;
44+
45+
Ok(quote! {
46+
pub struct #model_name_with_id {
47+
#fields
48+
}
49+
})
50+
}
51+
}
52+
53+
pub struct Fields;
54+
55+
impl<'i> Builder<'i> for Fields {
56+
fn build(self, input: &'i Input) -> Result<proc_macro2::TokenStream> {
57+
let mut fields = Vec::new();
58+
fields.push(quote!(pub id: i32));
59+
60+
let model_name = &input.parsed_struct.ident;
61+
fields.push(quote!(pub inner: #model_name));
62+
63+
input.parsed_struct.fields.iter().for_each(|field| {
64+
if field.fk() {
65+
let ty = field.ty();
66+
let name = &field.name;
67+
fields.push(quote!(pub #name: #ty));
68+
}
69+
});
70+
71+
Ok(quote!(#(#fields,)*))
72+
}
73+
}
74+
75+
pub struct Model<B>(pub B);
76+
77+
impl<'i, B: Builder<'i>> Builder<'i> for Model<B> {
78+
fn build(self, input: &'i Input) -> Result<proc_macro2::TokenStream> {
79+
let model_name = &input.parsed_struct.ident;
80+
let fields = self.0.build(input)?;
81+
82+
Ok(quote! {
83+
pub struct #model_name {
84+
#fields
85+
}
86+
})
87+
}
88+
}
89+
90+
pub struct InnerFields;
91+
92+
impl<'i> Builder<'i> for InnerFields {
93+
fn build(self, input: &'i Input) -> Result<proc_macro2::TokenStream> {
94+
let mut fields = Vec::new();
95+
input.parsed_struct.fields.iter().for_each(|field| {
96+
if !field.fk() {
97+
let ty = field.ty();
98+
let name = &field.name;
99+
fields.push(quote!(pub #name: #ty));
100+
}
101+
});
102+
103+
Ok(quote!(#(#fields,)*))
104+
}
105+
}
106+
107+
pub struct Table;
108+
109+
impl<'i> Builder<'i> for Table {
110+
fn build(self, input: &'i Input) -> Result<proc_macro2::TokenStream> {
111+
let model = camel_to_snake(&input.parsed_struct.ident.to_string()[..]) + "s";
112+
quote!(crate::schema::#model)
113+
}
114+
}

postgres_resource_derive/src/field.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use syn::{
2+
parse::{Parse, ParseStream, Result},
3+
punctuated::Punctuated,
4+
token, Attribute, Ident,
5+
};
6+
7+
#[derive(Debug, PartialEq)]
8+
enum FieldAttr {
9+
Optional,
10+
ForeignKey,
11+
}
12+
13+
#[derive(Debug)]
14+
pub struct Field {
15+
attr: Vec<FieldAttr>,
16+
pub name: Ident,
17+
ty: Ident,
18+
}
19+
20+
impl Parse for Field {
21+
fn parse(input: ParseStream) -> Result<Self> {
22+
let attr = Self::parse_attr(&input)?;
23+
let name: Ident = input.parse()?;
24+
let _: Token![:] = input.parse()?;
25+
let ty = input.parse()?;
26+
27+
Ok(Field { attr, name, ty })
28+
}
29+
}
30+
31+
impl Field {
32+
fn parse_attr(input: &ParseStream) -> Result<Vec<FieldAttr>> {
33+
let mut result = Vec::new();
34+
if let Some(attrs) = input.call(Attribute::parse_outer).ok() {
35+
attrs.iter().for_each(|attr| {
36+
let ident = &attr.path.segments[0].ident;
37+
38+
match &ident.to_string()[..] {
39+
"optional" => result.push(FieldAttr::Optional),
40+
"fk" => result.push(FieldAttr::ForeignKey),
41+
_ => panic!("Invalid attribute for field"),
42+
}
43+
});
44+
}
45+
Ok(result)
46+
}
47+
48+
pub fn ty(&self) -> proc_macro2::TokenStream {
49+
let ty = &self.ty;
50+
let mut ty_tokens = quote!(#ty);
51+
52+
if self.attr.contains(&FieldAttr::Optional) {
53+
ty_tokens = quote!(Option<#ty>);
54+
}
55+
56+
ty_tokens
57+
}
58+
59+
pub fn fk(&self) -> bool {
60+
self.attr.contains(&FieldAttr::ForeignKey)
61+
}
62+
}

0 commit comments

Comments
 (0)