chuchi-postgres-derive 0.1.0

Derive types for chuchi-postgres
Documentation
use std::fmt::Write;

use ::quote::{quote, ToTokens};

use syn::Fields;
use syn::{DeriveInput, Error};

use proc_macro2::TokenStream;

type Result<T> = std::result::Result<T, Error>;

macro_rules! err {
	($input:expr, $msg:expr) => {
		Error::new_spanned($input.into_token_stream(), $msg)
	};
}

pub fn expand_to_row(
	input: &DeriveInput,
	name: &TokenStream,
) -> Result<proc_macro::TokenStream> {
	match &input.data {
		syn::Data::Struct(data) => match &data.fields {
			Fields::Named(fields) => {
				let (impl_gens, ty_gens, where_clause) =
					input.generics.split_for_impl();

				let row = quote!(#name::row);
				let macros = quote!(#name::macros);
				let ident = &input.ident;

				let mut insert_columns = String::new();
				let mut insert_values = String::new();
				let mut update_columns = String::new();
				let mut params = quote!();

				for (i, field) in fields.named.iter().enumerate() {
					let ident = &field.ident;
					let ident_str = ident.as_ref().unwrap().to_string();

					if !insert_columns.is_empty() {
						insert_columns.push_str(", ");
						insert_values.push_str(", ");
						update_columns.push_str(", ");
					}
					let i = i + 1;
					write!(&mut insert_columns, "\"{ident_str}\"").unwrap();
					write!(&mut insert_values, "${i}").unwrap();
					write!(&mut update_columns, "\"{ident_str}\" = ${i}")
						.unwrap();

					params.extend(quote!(
						&self.#ident as &(dyn #macros::ToSql + std::marker::Sync),
					));
				}

				let fields_len = fields.named.len();
				let toks = quote!(
					impl #impl_gens #row::ToRowStatic for #ident #ty_gens #where_clause {
						fn insert_columns() -> &'static str {
							#insert_columns
						}

						fn insert_values() -> &'static str {
							#insert_values
						}

						fn update_columns() -> &'static str {
							#update_columns
						}

						fn params_len() -> usize {
							#fields_len
						}

						fn params(&self) -> impl std::iter::ExactSizeIterator<Item=&(dyn #macros::ToSql + std::marker::Sync)> {
							[
								#params
							].into_iter()
						}
					}
				);

				Ok(toks.into())
			}
			f => Err(err!(f, "not supported")),
		},
		_ => Err(err!(input, "is not supported")),
	}
}