#![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<Attribute>,
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<TokenStream, Error> {
let variants = extract_variants(ast)?;
if variants.is_empty() {
panic!("Provider API needs at least one variant active");
}
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<Vec<ApiType>, 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<ApiType, Error> {
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::<MetaNameValue, Token![,]>::parse_terminated)
.unwrap_or_default()
.into_iter()
.collect()
}
_ => vec![],
})
.collect::<Vec<_>>();
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::<Vec<_>>();
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)]
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<Self, Self::Err> {
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<R: Requester> RailwayProvider<R> {
pub fn new<RB: rcore::RequesterBuilder<Requester = R>>(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<R: Requester> rcore::Provider<R> for RailwayProvider<R> {
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<rcore::JourneysResponse, rcore::Error<<R as Requester>::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<rcore::RefreshJourneyResponse, rcore::Error<<R as Requester>::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<rcore::LocationsResponse, rcore::Error<<R as Requester>::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)
}
}