inline-sql-macros 0.2.0

procedural macros for inline-sql
Documentation
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;

use crate::input::{Arguments, SqlFunction, QueryType, Query, QueryMacro};
use crate::util::{return_type_ok_span, return_type_err_span};

pub fn expand_sql_function(errors: &mut Vec<syn::Error>, function: SqlFunction, args: Arguments) -> TokenStream {
	let SqlFunction {
		attributes,
		visibility,
		signature,
		brace_token,
		body,
	} = function;

	let Arguments {
		client,
		map_row,
		map_err,
	} = args;

	let query = match syn::parse2::<QueryMacro>(body) {
		Ok(x) => x.query,
		Err(e) => {
			errors.push(e);
			Query {
				query: String::new(),
				placeholders: Vec::new(),
			}
		},
	};
	let Query {
		query,
		placeholders
	} = query;

	let _ = brace_token;

	let query_type = match &signature.output {
		syn::ReturnType::Default => {
			errors.push(syn::Error::new_spanned(&signature.ident, "#[inline_sql]: Function must return a `Result<_, _>`"));
			None
		},
		syn::ReturnType::Type(_, typ) => {
			QueryType::from_return_type(typ)
				.map_err(|e| errors.push(e))
				.ok()
		}
	};

	let await_future = if signature.asyncness.is_some() {
		Some(quote!(.await))
	} else {
		None
	};

	let client = client.unwrap_or_else(|| syn::parse_quote!(client));

	let handle_err = match map_err {
		Some(map_err) => quote_spanned!(map_err.span() => {
			result.map_err(#map_err)?
		}),
		None => quote_spanned!(return_type_err_span(&signature) => {
			#[allow(clippy::useless_conversion)]
			match result.map_err(::core::convert::From::from) {
				Ok(x) => x,
				Err(e) => return Err(e),
			}
		}),
	};
	let map_elem = |typ| match map_row {
		Some(map_elem) => quote_spanned!(map_elem.span() => {
			let elem = ::inline_sql::macro_export__::convert_row(#map_elem, row);
			match elem {
				Ok(x) => x,
				Err(e) => {
					return Err(e)
				},
			}
		}),
		None => quote_spanned!(return_type_ok_span(&signature) => {
			#[allow(clippy::useless_conversion)]
			{
				let result = <#typ as ::core::convert::TryFrom<::tokio_postgres::Row>>::try_from(row);
				#handle_err
			}
		}),
	};

	let mut params = TokenStream::new();
	for placeholder in &placeholders {
		params.extend(quote_spanned!(
			placeholder.span() => &#placeholder as &(dyn ::tokio_postgres::types::ToSql + ::core::marker::Sync),
		));
	}
	let params = quote!(&[#params]);

	let body = match query_type.unwrap_or(QueryType::Execute) {
		QueryType::Execute => quote! {
			let params: &[&(dyn ::tokio_postgres::types::ToSql + ::core::marker::Sync)] = #params;
			let result: ::core::result::Result<u64, ::tokio_postgres::Error> = #client.execute(#query, params)#await_future;
			let result = #handle_err;
			Ok(())
		},
		QueryType::CountRows => quote! {
			let params: &[&(dyn ::tokio_postgres::types::ToSql + ::core::marker::Sync)] = #params;
			let result: ::core::result::Result<u64, ::tokio_postgres::Error> = #client.execute(#query, params)#await_future;
			let result = #handle_err;
			Ok(result)
		},
		QueryType::List(elem_type) => {
			let map_elem = map_elem(elem_type);
			quote! {
				let params: &[&(dyn ::tokio_postgres::types::ToSql + ::core::marker::Sync)] = #params;
				let params = params.iter().map(|x| *x as &dyn ::tokio_postgres::types::ToSql);
				let result: ::core::result::Result<::tokio_postgres::RowStream, ::tokio_postgres::Error> = #client.query_raw(#query, params)#await_future;
				let stream: ::tokio_postgres::RowStream = #handle_err;
				let mut stream = ::core::pin::pin!(stream);
				let mut output = ::std::vec::Vec::<#elem_type>::new();
				while let ::core::option::Option::Some(result) = stream.next()#await_future {
					let row = #handle_err;
					let elem = #map_elem;
					output.push(elem);
				}
				::core::result::Result::Ok(output)
			}
		},
		QueryType::Stream => quote! {
			let params: &[&(dyn ::tokio_postgres::types::ToSql + ::core::marker::Sync)] = #params;
			let params = params.iter().map(|x| *x as &dyn ::tokio_postgres::types::ToSql);
			let result: ::core::result::Result<::tokio_postgres::RowStream, ::tokio_postgres::Error> = #client.query_raw(#query, params)#await_future;
			::core::result::Result::Ok(#handle_err)
		},
		QueryType::Optional(elem_type) => {
			let map_elem = map_elem(elem_type);
			quote! {
				let params: &[&(dyn ::tokio_postgres::types::ToSql + ::core::marker::Sync)] = #params;
				let result: ::core::result::Result<::core::option::Option<::tokio_postgres::Row>, ::tokio_postgres::Error> = #client.query_opt(#query, params)#await_future;
				match #handle_err {
					::core::option::Option::None => ::core::result::Result::Ok(::core::option::Option::None),
					::core::option::Option::Some(row) => {
						let elem = #map_elem;
						::core::result::Result::Ok(::core::option::Option::Some(elem))
					},
				}
			}
		},
		QueryType::One(elem_type) => {
			let map_elem = map_elem(elem_type);
			quote! {
				let params: &[&(dyn ::tokio_postgres::types::ToSql + ::core::marker::Sync)] = #params;
				let result: ::core::result::Result<::tokio_postgres::Row, ::tokio_postgres::Error> = #client.query_one(#query, params)#await_future;
				let row = #handle_err;
				#map_elem
			}
		},
	};

	quote! {
		#(#attributes)*
		#visibility #signature {
			#[allow(unused_imports)]
			use ::inline_sql::macro_export__::prelude::*;
			#body
		}
	}
}