uni_localservice_macro 0.0.2

Macroses for basical components for service implementation for uniui_* crate family
Documentation
extern crate proc_macro;
use proc_macro::TokenStream;

use quote::quote;

use syn::{
	parse::Parse,
	Token,
};

const INCORRECT_FORMAT1: &'static str = "PUT THERE SOMETHING";

fn the_crate_name() -> proc_macro2::TokenStream {
	quote::quote! {
		uni_localservice
	}
}

struct ServiceImpl {
	struct_name: syn::Type,
	service_mod: syn::Expr,
	env: Option<syn::Expr>,
	method_name: syn::Expr,
	generic: Option<syn::Generics>,
}

impl Parse for ServiceImpl {
	fn parse(parser: syn::parse::ParseStream<'_>) -> syn::parse::Result<Self> {
		let mut struct_name = None;
		let mut service_mod = None;
		let mut env = None;
		let mut method_name = None;
		let mut generic = None;

		while parser.peek(syn::Ident) {
			let ident =
				parser.parse::<syn::Ident>().expect(INCORRECT_FORMAT1).to_string();

			let _ = parser.parse::<Token![:]>().expect(INCORRECT_FORMAT1);
			if ident == "base" {
				let error = format!(
					"{}\n{}",
					"Expected *identifier* for 'base'", INCORRECT_FORMAT1
				);
				struct_name = Some(parser.parse::<syn::Type>().expect(&error));
			} else if ident == "service" {
				let error =
					format!("{}\n{}", "Expected *type* for 'service'", INCORRECT_FORMAT1);
				service_mod = Some(parser.parse::<syn::Expr>().expect(&error));
			} else if ident == "method" {
				let error =
					format!("{}\n{}", "Expected *type* for 'method'", INCORRECT_FORMAT1);
				method_name = Some(parser.parse::<syn::Expr>().expect(&error));
			} else if ident == "generic" {
				let error =
					format!("{}\n{}", "Expected *type* for 'generic'", INCORRECT_FORMAT1);
				generic = Some(parser.parse::<syn::Generics>().expect(&error));
			} else if ident == "env" {
				let error =
					format!("{}\n{}", "Expected *type* for 'env'", INCORRECT_FORMAT1);
				let pre_env = parser.parse::<syn::Expr>().expect(&error);
				match &pre_env {
					syn::Expr::Tuple(t) if t.elems.len() == 0 => {},
					_ => env = Some(pre_env),
				}
			} else {
				panic!("Unexpected parameter:{}.\n{}", ident, INCORRECT_FORMAT1);
			}

			parser.parse::<Token![,]>().expect(INCORRECT_FORMAT1);
		}

		return Ok(ServiceImpl {
			struct_name: struct_name
				.expect(&format!("{} not found.\n{}", "base", INCORRECT_FORMAT1)),
			service_mod: service_mod
				.expect(&format!("{} not found.\n{}", "service", INCORRECT_FORMAT1)),
			env,
			method_name: method_name
				.expect(&format!("{} not found.\n{}", "method", INCORRECT_FORMAT1)),
			generic,
		});
	}
}


/// Simplifies Service implementation
///
/// Example (see explanation below):
/// ```
/// uni_components::define_service! {
///     doc: "The service returns input increased by 42",
///     name: my_service,
///     kind: Kind::Get,
///     input: i32,
///     outputs: [
///         Ok {
///             code: 200,
///             body: i32,
///         },
///         AccessDenied {
///             code: 403,
///         },
///         NotFound {
///             code: 404,
///         },
///         InternalErrorTryLater {
///             code: 503,
///         },
///     ],
///     path: "/my_service/v1/",
/// }
///
/// #[derive(Clone)]
/// pub struct MyStruct {
/// }
///
/// impl MyStruct {
///     async fn exec(
///         &mut self,
///         input: my_service::In,
///         env: (),
///     ) -> my_service::Out {
///         return my_service::Out::Ok(input + 42);
///     }
/// }
///
/// uni_localservice::u_service_impl! {
///     base: MyStruct,
///     service: my_service,
///     method: exec,
///     env: (),
/// }
/// ```
/// To implement predefined service (`my_service`) for a structure
/// the structure (`MyStructure`) should have a method (`exec`) which
/// * Has 3 parameters,
/// * First parameter is `&mut self`,
/// * Second parameter's type match service's input type
/// * Return type match service's output type
/// * Third parameter is env, you can keep it `()` or read more about env [there](???)
///
/// Then `u_service_impl` can be used:
/// * `base` -- name of the structure
/// * `service` -- name of the service
/// * `method` -- name of the method
/// * `env` -- environment type (keep it `()` if not sure)
///
/// Define env variable UNI_LOCALSERVICE_MACRO_DEBUG to have debug output
#[proc_macro]
pub fn u_service_impl(input_tokens: TokenStream) -> TokenStream {
	let service_impl = syn::parse_macro_input!(input_tokens as ServiceImpl);

	let (relay_name, env) = match service_impl.env {
		None => (quote! {RelayNoEnv}, quote! {()}),
		Some(t) => (quote! {Relay}, quote! {#t}),
	};

	let generic = match service_impl.generic {
		Some(g) => quote!(#g),
		None => quote!(),
	};


	let crate_name = the_crate_name();
	let struct_name = service_impl.struct_name;
	let service_mod = service_impl.service_mod;
	let method_name = service_impl.method_name;

	let result = quote! {
		#[#crate_name::async_trait::async_trait]
		impl #generic #crate_name::LocalService for #struct_name {
			type In = <
				#service_mod::Service
				as
				#crate_name::uni_components::service::Service
			>::In;
			type Out = <
				#service_mod::Service
				as
				#crate_name::uni_components::service::Service
			>::Out;
			type Env = #env;
			type Service = #service_mod::Service;

			async fn exec(&mut self, data: Self::In, env: &Self::Env) -> Self::Out {
				return self.#method_name(data, env).await;
			}

			fn service(&self) -> &'static Self::Service {
				return #service_mod::REF;
			}
		}

		impl #generic #struct_name {
			pub fn register<UNI_T: 'static + Send + Sync + Clone>(
				self,
				server: &mut #crate_name::tide::Server<UNI_T>,
			) {
				use #crate_name::LocalService;
				use #crate_name::uni_components::service::Service;

				let r = #crate_name::#relay_name {
					ls: self,
				};

				let mut zz = server.at(r.ls.service().path());

				match r.ls.service().kind() {
					#crate_name::uni_components::Kind::Get => zz.get(r),
					#crate_name::uni_components::Kind::Post => zz.post(r),
				};
			}
		}
	};

	if let Ok(_) = std::env::var("UNI_LOCALSERVICE_MACRO_DEBUG") {
		println!("u_service_impl:\n{:#}\n", result);
	}

	return result.into();
}