railway-api-derive-0.1.0/.cargo_vcs_info.json0000644000000001600000000000100145220ustar { "git": { "sha1": "0333950f69474deb120b3ba39113766a949cb0f8" }, "path_in_vcs": "railway-api-derive" }railway-api-derive-0.1.0/Cargo.toml0000644000000020270000000000100125240ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "railway-api-derive" version = "0.1.0" authors = ["Julian Schmidhuber "] description = "Derive macro for railway-api" readme = "README.md" keywords = [ "railway-backend", "train", "public-transport", ] license = "AGPL-3.0-or-later OR EUPL-1.2" repository = "https://gitlab.com/schmiddi-on-mobile/railway-backend" [lib] proc-macro = true [dependencies.convert_case] version = "0.6" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "2.0" railway-api-derive-0.1.0/Cargo.toml.orig000064400000000000000000000010631046102023000162040ustar 00000000000000[package] name = "railway-api-derive" version = "0.1.0" authors = ["Julian Schmidhuber "] edition = "2021" description = "Derive macro for railway-api" repository = "https://gitlab.com/schmiddi-on-mobile/railway-backend" license = "AGPL-3.0-or-later OR EUPL-1.2" keywords = ["railway-backend", "train", "public-transport"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] proc-macro = true [dependencies] syn = "2.0" quote = "1.0" proc-macro2 = "1.0" convert_case = "0.6" railway-api-derive-0.1.0/README.md000064400000000000000000000002751046102023000146000ustar 00000000000000# Railway Derive API Derive macro for [railway-api](https://crates.io/crates/railway-api). This crate is part of [railway-backend](https://gitlab.com/schmiddi-on-mobile/railway-backend). railway-api-derive-0.1.0/src/lib.rs000064400000000000000000000222261046102023000152240ustar 00000000000000#![doc = include_str!("../README.md")] use proc_macro::TokenStream as PmTokenStream; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{ parse_macro_input, punctuated::Punctuated, spanned::Spanned, Attribute, Data, DeriveInput, Error, Ident, Meta, MetaNameValue, Token, Variant, }; struct ApiType { name: Ident, cfgs: Vec, constructor: TokenStream, } impl ApiType { fn feature(&self) -> TokenStream { let cfgs = &self.cfgs; quote! { #(#cfgs)* } } } #[proc_macro_derive(ProviderApi, attributes(provider))] pub fn provider_api_derive(input: PmTokenStream) -> PmTokenStream { let ast = parse_macro_input!(input as DeriveInput); match provider_api_derive_error(&ast) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } fn provider_api_derive_error(ast: &DeriveInput) -> Result { let variants = extract_variants(ast)?; let type_list = impl_type_list(&variants); let type_list_variants = impl_type_list_variants(&variants); let type_list_from_str = impl_type_list_from_str(&variants); let type_list_to_str = impl_type_list_to_str(&variants); let constructor = impl_constructor(&variants); let provider_impl = impl_provider_impl(&variants); let result = quote! { #type_list #type_list_variants #type_list_from_str #type_list_to_str #constructor #provider_impl }; Ok(result) } fn extract_variants(ast: &DeriveInput) -> Result, Error> { let Data::Enum(data) = &ast.data else { return Err(Error::new( ast.span(), "ProviderApi-macro only applicable to enums", )); }; data.variants.iter().map(variant_to_api_variant).collect() } fn variant_to_api_variant(var: &Variant) -> Result { let name = var.ident.clone(); let my_attrs = var .attrs .iter() .flat_map(|a| match &a.meta { Meta::List(l) if l.path.get_ident().map(|i| i.to_string()) == Some("provider".to_string()) => { l.parse_args_with(Punctuated::::parse_terminated) .unwrap_or_default() .into_iter() .collect() } _ => vec![], }) .collect::>(); let Some(constructor) = my_attrs .iter() .filter(|a| a.path.get_ident().map(|i| i.to_string()) == Some("constructor".to_string())) .map(|n| n.value.to_token_stream()) .next() .or_else(|| { my_attrs .iter() .filter(|a| a.path.get_ident().map(|i| i.to_string()) == Some("hafas".to_string())) .map(|n| &n.value) .map(|v| quote! { |r| rhafas::client::HafasClient::new(#v, r) }) .next() }) else { return Err(Error::new( var.span(), "Provider does not have a constructor or hafas", )); }; let cfg_attrs = var .attrs .iter() .filter(|a| { matches!(&a.meta, Meta::List(l) if l.path.get_ident().map(|i| i.to_string()) == Some("cfg".to_string())) }) .cloned() .collect::>(); Ok(ApiType { name, constructor: constructor.clone(), cfgs: cfg_attrs, }) } fn impl_type_list(types: &[ApiType]) -> TokenStream { let parts = types.iter().map(impl_type_list_single); quote! { #[automatically_derived] #[derive(Debug, Clone, PartialEq, Eq)] /// An enumeration listing all available [`Provider`s](rcore::Provider) in the [`RailwayProvider`]. pub enum RailwayProviderType { #(#parts),* } } } fn impl_type_list_single(t: &ApiType) -> TokenStream { let name = &t.name; let feature = t.feature(); quote! { #feature #name } } fn impl_type_list_variants(types: &[ApiType]) -> TokenStream { let parts = types.iter().map(impl_type_list_single_variant); quote! { #[automatically_derived] impl RailwayProviderType { pub fn variants() -> &'static [Self] { &[ #(#parts),* ] } } } } fn impl_type_list_single_variant(t: &ApiType) -> TokenStream { let name = &t.name; let feature = t.feature(); quote! { #feature Self::#name } } fn impl_type_list_from_str(types: &[ApiType]) -> TokenStream { let parts = types.iter().map(impl_type_list_from_str_single); quote! { #[automatically_derived] impl std::str::FromStr for RailwayProviderType { type Err = (); fn from_str(input: &str) -> Result { let input = input.to_lowercase().replace(&['-', '_'][..], ""); match &input[..] { #(#parts)* _ => Err(()), } } } } } fn impl_type_list_from_str_single(t: &ApiType) -> TokenStream { let name = &t.name; let string = t.name.to_string().to_lowercase(); let feature = t.feature(); quote! { #feature #string => Ok(Self::#name), } } fn impl_type_list_to_str(types: &[ApiType]) -> TokenStream { let parts = types.iter().map(impl_type_list_to_str_single); quote! { #[automatically_derived] impl std::fmt::Display for RailwayProviderType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let s = match self { #(#parts)* }; write!(f, "{}", s) } } } } fn impl_type_list_to_str_single(t: &ApiType) -> TokenStream { use convert_case::{Case, Casing}; let name = &t.name; let string = t.name.to_string().to_case(Case::Kebab); let feature = t.feature(); quote! { #feature Self::#name => #string, } } fn impl_constructor(types: &[ApiType]) -> TokenStream { let parts = types.iter().map(impl_constructor_single); quote! { #[automatically_derived] impl RailwayProvider { pub fn new>(r#type: RailwayProviderType, builder: RB) -> Self { match r#type { #(#parts),* } } } } } fn impl_constructor_single(t: &ApiType) -> TokenStream { let name = &t.name; let constructor = &t.constructor; let feature = t.feature(); quote! { #feature RailwayProviderType::#name => Self::#name((#constructor)(builder)) } } fn impl_provider_impl(types: &[ApiType]) -> TokenStream { let journeys = impl_provider_impl_journeys(types); let locations = impl_provider_impl_locations(types); let refresh = impl_provider_impl_refresh(types); quote! { #[automatically_derived] #[cfg_attr(feature = "rt-multi-thread", async_trait::async_trait)] #[cfg_attr(not(feature = "rt-multi-thread"), async_trait::async_trait(?Send))] impl rcore::Provider for RailwayProvider { type Error = BoxedError; #journeys #locations #refresh } } } fn impl_provider_impl_journeys(types: &[ApiType]) -> TokenStream { let parts = types .iter() .map(|t| impl_provider_impl_single(t, quote! {journeys(from, to, opts)})); quote! { async fn journeys( &self, from: rcore::Place, to: rcore::Place, opts: rcore::JourneysOptions, ) -> Result::Error, Self::Error>> { use rcore::Provider; match self { #(#parts),* } } } } fn impl_provider_impl_locations(types: &[ApiType]) -> TokenStream { let parts = types .iter() .map(|t| impl_provider_impl_single(t, quote! {refresh_journey(journey, opts)})); quote! { async fn refresh_journey( &self, journey: &rcore::Journey, opts: rcore::RefreshJourneyOptions, ) -> Result::Error, Self::Error>> { use rcore::Provider; match self { #(#parts),* } } } } fn impl_provider_impl_refresh(types: &[ApiType]) -> TokenStream { let parts = types .iter() .map(|t| impl_provider_impl_single(t, quote! {locations(opts)})); quote! { async fn locations( &self, opts: rcore::LocationsOptions, ) -> Result::Error, Self::Error>> { use rcore::Provider; match self { #(#parts),* } } } } fn impl_provider_impl_single(t: &ApiType, code: TokenStream) -> TokenStream { let name = &t.name; let feature = t.feature(); quote! { #feature RailwayProvider::#name(p) => p.#code.await.map_err(transform_error) } }