use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{parse::Parse, Ident, Token, Type};
use crate::utils::{to_snake_case, to_snake_case_ext};
use super::method::ServiceMethod;
pub mod kw {
syn::custom_keyword!(readonly);
syn::custom_keyword!(unwatched);
syn::custom_keyword!(err);
}
pub struct ServiceState {
pub access_level: ServiceStateAccessLevel,
pub ident: Ident,
pub colon_token: Token![:],
pub ty: Type,
pub err_ty: Option<Type>,
pub get_method: Option<ServiceMethod>,
pub set_method: Option<ServiceMethod>,
pub subscribe_method: Option<ServiceMethod>,
}
impl ServiceState {
pub fn to_state_trait_def_item(&self, tokens: &mut TokenStream2) {
if let Some(get_method) = &self.get_method {
get_method.to_tokens(tokens);
}
if let Some(set_method) = &self.set_method {
set_method.to_tokens(tokens);
}
if let Some(subscribe_method) = &self.subscribe_method {
subscribe_method.to_tokens(tokens);
}
}
pub fn methods(&self) -> impl Iterator<Item = &ServiceMethod> {
self.get_method
.iter()
.chain(self.set_method.iter())
.chain(self.subscribe_method.iter())
}
pub fn stateful_auto_impl_methods(&self) -> TokenStream2 {
let Self { ty, err_ty, .. } = &self;
let mut tokens = quote! {};
let field_ident = to_snake_case(&self.ident);
let err_ty = err_ty
.clone()
.unwrap_or(syn::parse2(quote! { ::dremoc::remoc::rtc::CallError }).unwrap());
if let Some(get_method) = &self.get_method {
let method_ident = &get_method.sig.ident;
tokens.append_all(quote! {
#[inline(always)]
async fn #method_ident(&self) -> Result<#ty, #err_ty> {
Ok(self.get_state().#field_ident.remote_get())
}
});
}
if let Some(set_method) = &self.set_method {
let method_ident = &set_method.sig.ident;
tokens.append_all(quote! {
#[inline(always)]
async fn #method_ident(&mut self, value: #ty) -> Result<(), #err_ty> {
Ok(self.get_state_mut().#field_ident.remote_set(value))
}
});
}
if let Some(subscribe_method) = &self.subscribe_method {
let method_ident = &subscribe_method.sig.ident;
tokens.append_all(quote! {
#[inline(always)]
async fn #method_ident(&self) -> Result<::dremoc::sync::watch::Receiver<#ty>, #err_ty> {
Ok(self.get_state().#field_ident.subscribe())
}
});
}
tokens
}
pub fn function_argument(&self) -> TokenStream2 {
let Self { ident, ty, .. } = &self;
let ident = to_snake_case(&ident);
quote! { #ident: #ty }
}
pub fn ctor_argument(&self) -> TokenStream2 {
let Self { ident, ty, .. } = &self;
let ident = to_snake_case(&ident);
let read_access = self.access_level.read_access();
let write_access = self.access_level.write_access();
let watch_access = self.access_level.watch_access();
quote! { #ident: ::dremoc::service::State::<#ty, #read_access, #write_access, #watch_access>::new(#ident) }
}
pub fn state_set_field(&self) -> TokenStream2 {
let Self { ident, ty, .. } = &self;
let read_access = self.access_level.read_access();
let write_access = self.access_level.write_access();
let watch_access = self.access_level.watch_access();
let ident = to_snake_case(&ident);
quote! { #ident: ::dremoc::service::State<#ty, #read_access, #write_access, #watch_access> }
}
fn with_service_methods(mut self) -> Self {
let Self {
ident, ty, err_ty, ..
} = &self;
let err_ty = err_ty
.clone()
.unwrap_or(syn::parse2(quote! { ::dremoc::remoc::rtc::CallError }).unwrap());
let get_method = if self.access_level.read_access() {
let method_ident = to_snake_case_ext(ident, "get", "");
Some(
syn::parse2(quote! { async fn #method_ident(&self) -> Result<#ty, #err_ty>; })
.unwrap(),
)
} else {
None
};
let set_method = if self.access_level.write_access() {
let method_ident = to_snake_case_ext(ident, "set", "");
Some(
syn::parse2(
quote! { async fn #method_ident(&mut self, value: #ty) -> Result<(), #err_ty>; },
)
.unwrap(),
)
} else {
None
};
let subscribe_method = if self.access_level.watch_access() {
let method_ident = to_snake_case_ext(ident, "subscribe", "");
Some(
syn::parse2(
quote! { async fn #method_ident(&self) -> Result<::dremoc::sync::watch::Receiver<#ty>, #err_ty>; },
)
.unwrap(),
)
} else {
None
};
self.get_method = get_method;
self.set_method = set_method;
self.subscribe_method = subscribe_method;
self
}
}
impl Parse for ServiceState {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let access_level = input.parse()?;
let ident = input.parse()?;
let colon_token = input.parse()?;
let ty = input.parse()?;
let err_ty = if input.peek(kw::err) {
let _err_token = input.parse::<kw::err>()?;
let content;
syn::parenthesized!(content in input);
Some(content.parse()?)
} else {
None
};
Ok(Self {
access_level,
ident,
colon_token,
ty,
err_ty,
get_method: None,
set_method: None,
subscribe_method: None,
}
.with_service_methods())
}
}
impl ToTokens for ServiceState {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.access_level.to_tokens(tokens);
self.ident.to_tokens(tokens);
self.colon_token.to_tokens(tokens);
self.ty.to_tokens(tokens);
self.err_ty.to_tokens(tokens);
}
}
pub enum ServiceStateAccessLevel {
ReadWrite {
unwatched_token: Option<kw::unwatched>,
},
Readonly {
readonly_token: kw::readonly,
unwatched_token: Option<kw::unwatched>,
},
}
impl ServiceStateAccessLevel {
pub fn watch_access(&self) -> bool {
match self {
Self::ReadWrite {
unwatched_token: unwatched,
} => unwatched.is_none(),
Self::Readonly {
unwatched_token: unwatched,
..
} => unwatched.is_none(),
}
}
pub fn read_access(&self) -> bool {
match self {
Self::ReadWrite { .. } => true,
Self::Readonly { .. } => true,
}
}
pub fn write_access(&self) -> bool {
match self {
Self::ReadWrite { .. } => true,
Self::Readonly { .. } => false,
}
}
}
impl Parse for ServiceStateAccessLevel {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(kw::readonly) {
let readonly_token = input.parse::<kw::readonly>()?;
Ok(Self::Readonly {
readonly_token,
unwatched_token: input.parse()?,
})
} else {
Ok(Self::ReadWrite {
unwatched_token: input.parse()?,
})
}
}
}
impl ToTokens for ServiceStateAccessLevel {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
Self::ReadWrite { unwatched_token } => {
if let Some(unwatched_token) = unwatched_token {
unwatched_token.to_tokens(tokens);
}
}
Self::Readonly {
readonly_token,
unwatched_token,
} => {
readonly_token.to_tokens(tokens);
if let Some(unwatched_token) = unwatched_token {
unwatched_token.to_tokens(tokens);
}
}
}
}
}