use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{Data, DataStruct, DeriveInput, Error, Field, Fields, Ident, Item, Result, Type};
use xxhash_rust::const_xxh3::xxh3_64;
use crate::utils::check_if_state;
pub fn get_state(items: &[Item], mod_span: &Span) -> Result<Ident> {
for item in items {
if let Item::Struct(item_struct) = item {
if check_if_state(item_struct) {
return Ok(item_struct.ident.clone());
}
}
}
Err(Error::new(
*mod_span,
"Each module requires a 'State' - use #[derive(State)]",
))
}
pub fn impl_state(input: DeriveInput) -> Result<TokenStream2> {
let DeriveInput { ident, data, .. } = input;
let fields = parse_input(data)?;
let idents: Vec<Ident> = fields.iter().flat_map(|f| f.ident.clone()).collect();
let ftypes: Vec<Type> = fields.iter().map(|f| f.ty.clone()).collect();
let ident_strings: Vec<_> = idents.iter().map(|f| format!("{f}")).collect();
let errs: Vec<_> = idents
.iter()
.map(|i| format!("failed to read parse field '{i}'"))
.collect();
let storage_keys: Vec<u64> = fields.iter().map(|f| storage_key(f, &ident)).collect();
let storeable = quote! { ::borderless::__private::storage_traits::Storeable };
let to_payload = quote! { ::borderless::__private::storage_traits::ToPayload };
let type_checks = ftypes.iter().map(|ty| {
quote! {
__check_storeable::<#ty>();
}
});
Ok(quote! {
#[doc(hidden)]
const _: () = {
#[allow(unused_extern_crates, clippy::useless_attribute)]
extern crate borderless as _borderless;
#[doc(hidden)]
#[automatically_derived]
const fn __check_storeable<T: _borderless::__private::storage_traits::Storeable>() {}
#(#type_checks)*
#[doc(hidden)]
#[automatically_derived]
const SYMBOLS: &[(&str, u64)] = &[
#(
(#ident_strings, #storage_keys)
),*
];
#[automatically_derived]
impl _borderless::__private::storage_traits::State for #ident {
fn load() -> _borderless::Result<Self> {
#(
let #idents = <#ftypes as #storeable>::decode(#storage_keys);
)*
Ok(Self {
#(#idents),*
})
}
fn init(mut value: _borderless::serialize::Value) -> _borderless::Result<Self> {
use _borderless::Context;
#(
let base_value = value.get_mut(#ident_strings).take().context(#errs)?;
let #idents = <#ftypes as #storeable>::parse_value(base_value.clone(), #storage_keys).context(#errs)?;
)*
Ok(Self {
#(#idents),*
})
}
fn http_get(path: String) -> _borderless::Result<Option<String>> {
use _borderless::Context;
let path = path.strip_prefix('/').unwrap_or(&path);
let (path, _query) = match path.split_once('?') {
Some((path, query)) => (path, Some(query)),
None => (path, None),
};
if path.is_empty() {
let state = <Self as _borderless::__private::storage_traits::State>::load()?;
let mut buf = String::with_capacity(100);
buf.push('{');
#(
let value = <#ftypes as #to_payload>::to_payload(&state.#idents, "")?.context(#errs)?;
buf.push('"');
buf.push_str(#ident_strings);
buf.push('"');
buf.push(':');
buf.push_str(&value);
buf.push(',');
)*
buf.pop();
buf.push('}');
return Ok(Some(buf));
}
let (prefix, suffix) = match path.find('/') {
Some(idx) => path.split_at(idx),
None => (path, ""),
};
match prefix {
#(
#ident_strings => {
let value = <#ftypes as #storeable>::decode(#storage_keys);
<#ftypes as #to_payload>::to_payload(&value, suffix)
}
)*
_ => Ok(None),
}
}
fn commit(self) {
#(
<#ftypes as #storeable>::commit(self.#idents, #storage_keys);
)*
}
fn symbols() -> &'static [(&'static str, u64)] {
SYMBOLS
}
}
};
})
}
fn parse_input(data: Data) -> Result<Vec<Field>> {
match data {
Data::Struct(DataStruct {
struct_token,
fields,
semi_token,
}) => {
let fields = match fields {
Fields::Named(named_fields) => Ok(named_fields.named),
Fields::Unnamed(unnamed) => Err(Error::new_spanned(
unnamed,
"State can only be implemented on structs with named fields",
)),
Fields::Unit => Err(Error::new_spanned(
semi_token,
"State cannot be implemented on unit structs",
)),
}?;
if fields.is_empty() {
return Result::Err(Error::new_spanned(
struct_token,
"State must at least have one field",
));
}
let mut ident_fields = Vec::new();
for field in fields {
if field.ident.is_none() {
unreachable!("State macro is not allowed on tuple structs!");
}
ident_fields.push(field);
}
Ok(ident_fields)
}
Data::Enum(syn::DataEnum { enum_token, .. }) => Err(Error::new_spanned(
enum_token,
"State cannot be implemented on enums. Only structs with named fields are allowed.",
)),
Data::Union(syn::DataUnion { union_token, .. }) => Err(Error::new_spanned(
union_token,
"State cannot be implemented on unions. Only structs with named fields are allowed.",
)),
}
}
fn storage_key(field: &Field, ident: &Ident) -> u64 {
let field_name = field
.ident
.as_ref()
.expect("checked for named fields before calling");
let full_name = format!("{}::{}", ident, field_name);
let storage_key = xxh3_64(full_name.to_uppercase().as_bytes());
storage_key | (1 << 63)
}