You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
6.2 KiB
Rust

use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{bracketed, parse_macro_input, Expr, Ident, LitStr, Token};
extern crate proc_macro;
extern crate self as joinrs_proc;
#[proc_macro]
pub fn query_parsed(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let macro_params = parse_macro_input!(input as QueryChecked);
let connection = macro_params.connection;
let query = macro_params.query;
let params = macro_params.params;
let column_mapping = macro_params.column_mapping;
let return_type = get_return_type(&column_mapping);
let query = quote! {
sqlx::query!(#query, #params)
.fetch_all(#connection)
.await.expect("Query failed").into_iter().peekable()
};
let parse = generate_parse_expression(&column_mapping);
let result = quote! {
{
let mut rows = #query;
let mut parse_closure = || -> joinrs::anyhow::Result<#return_type> {
if rows.len() < 1 {
// anyhow::bail!(sqly::err::QueryError::RowNotFound)
panic!("Not enough rows");
}
let result = #parse;
Ok(result)
};
parse_closure()
}
};
proc_macro::TokenStream::from(result)
}
fn generate_parse_expression(value: &TypeMapping) -> proc_macro2::TokenStream {
match value.clone() {
TypeMapping::Column(name) => generate_parse_column(name),
TypeMapping::Vec(mapping) => generate_parse_vec(mapping),
TypeMapping::Object {
struct_name,
properties,
} => generate_parse_object(struct_name, properties),
}
}
fn generate_parse_column(column: Ident) -> proc_macro2::TokenStream {
quote! {
{
let row = rows.peek().expect("Too few rows");
row.#column.clone()
}
}
}
fn generate_parse_object(
struct_name: Ident,
properties: Vec<TypeProperty>,
) -> proc_macro2::TokenStream {
let property_lines =
properties
.iter()
.map(|property: &TypeProperty| -> proc_macro2::TokenStream {
let name = &property.name;
let value = generate_parse_expression(&property.mapping);
quote! {
#name: #value
}
});
quote! {
#struct_name {
#( #property_lines ),*
}
}
}
fn generate_parse_vec(inner_type: Box<TypeMapping>) -> proc_macro2::TokenStream {
let value_expression = generate_parse_expression(&*inner_type);
quote! {
{
let mut vec = Vec::new();
vec.push(#value_expression);
vec
}
}
}
fn get_return_type(mapping: &TypeMapping) -> Ident {
match mapping {
TypeMapping::Column(_) => panic!("Cannot generate type mapping for a single column: Unknown type"),
TypeMapping::Vec(mapping) => get_return_type(mapping),
TypeMapping::Object { struct_name, properties: _ } => struct_name.clone(),
}
}
#[derive(Clone, Debug)]
struct QueryChecked {
connection: Expr,
query: LitStr,
params: Punctuated<Expr, Token![,]>,
column_mapping: TypeMapping,
}
#[derive(Clone, Debug)]
enum TypeMapping {
Column(Ident),
Vec(Box<TypeMapping>),
Object {
struct_name: Ident,
properties: Vec<TypeProperty>,
},
}
#[derive(Clone, Debug)]
struct TypeProperty {
name: Ident,
mapping: TypeMapping,
}
impl Parse for QueryChecked {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut connection: Option<Expr> = None;
let mut query: Option<LitStr> = None;
let mut params: Option<Punctuated<Expr, Token![,]>> = None;
let mut return_type: Option<TypeMapping> = None;
while !input.is_empty() {
let identifier = input.parse::<syn::Ident>()?;
input.parse::<Token![=]>()?;
match identifier.to_string().as_str() {
"connection" => connection = Some(input.parse()?),
"query" => query = Some(input.parse()?),
"params" => {
let content;
bracketed!(content in input);
params = Some(Punctuated::parse_terminated(&content)?)
}
"return_type" => return_type = Some(input.parse()?),
unknown => {
return Err(input.error(format!("Unknown property {}", unknown)));
}
}
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
Ok(QueryChecked {
connection: connection.ok_or(input.error("Expected connection property"))?,
query: query.ok_or(input.error("Expected query property"))?,
params: params.unwrap_or(Punctuated::new()),
column_mapping: return_type.ok_or(input.error("Expected return type property"))?,
})
}
}
impl Parse for TypeMapping {
fn parse(input: ParseStream) -> syn::Result<Self> {
let identifier = input.parse::<syn::Ident>()?;
if !input.is_empty() && input.peek(Token![<]) && identifier.to_string() == "Vec" {
input.parse::<Token![<]>()?;
let internal: TypeMapping = input.parse()?;
input.parse::<Token![>]>()?;
Ok(TypeMapping::Vec(Box::new(internal)))
} else if !input.is_empty() && input.peek(syn::token::Brace) {
let internal;
syn::braced!(internal in input);
let properties: Punctuated<TypeProperty, Token![,]> =
Punctuated::parse_terminated(&internal)?;
let properties = properties.iter().map(|p| p.clone()).collect();
Ok(TypeMapping::Object {
struct_name: identifier,
properties,
})
} else {
Ok(TypeMapping::Column(identifier))
}
}
}
impl Parse for TypeProperty {
fn parse(input: ParseStream) -> syn::Result<Self> {
let identifier = input.parse::<syn::Ident>()?;
input.parse::<Token![:]>()?;
let property_type: TypeMapping = input.parse()?;
Ok(TypeProperty {
name: identifier,
mapping: property_type,
})
}
}