use crate::parse::util::Dbg;
use deluxe::{ExtractAttributes, ParseAttributes};
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use std::collections::HashSet;
use syn::{Attribute, Expr, Ident, Type, TypePath};
mod field;
mod parse;
pub const DEFAULT_DATALOADER_CACHE_SIZE: usize = 100_000;
pub const DEFAULT_DATALOADER_BATCH_SIZE: usize = 1_000;
pub const DEFAULT_DATALOADER_DELAY_MS: u64 = 10;
pub const DEFAULT_MAX_LOAD_ITEMS: u64 = 100_000;
pub const DEFAULT_MAX_SKIP_ITEMS: u64 = 10_000;
pub const DEFAULT_PART_MAX_ROWS: u64 = u64::MAX;
pub const DEFAULT_PART_MAX_BYTES: u64 = 256 * 1024 * 1024;
use crate::parse::util::{Attributes, SqlSafeName};
#[derive(Debug, Clone)]
pub struct TransformerStruct {
pub name: Ident,
pub attrs: Vec<Attribute>,
pub vis: syn::Visibility,
pub struct_opts: TransformerOptions,
pub struct_type: StructType,
pub id_offset: usize,
pub fields: Vec<TransformerField>,
pub derived: Vec<DerivedField>,
}
impl TransformerStruct {
#[allow(clippy::too_many_lines)]
pub fn parse(
args: TokenStream,
input: TokenStream,
struct_type: StructType,
) -> deluxe::Result<Self> {
let transformer_options = deluxe::parse2::<parse::TransformerParseOptions>(args)?;
let mut t = syn::parse2::<syn::ItemStruct>(input)?;
super::ensure_generics_zero(&t.generics)?;
let name = t.ident.clone();
let id_name: Ident = syn::parse_quote!(id);
let mut fields = Vec::with_capacity(t.fields.len());
let mut derived_fields = Vec::new();
let mut id_offset = usize::MAX;
for (idx, field) in t.fields.iter_mut().enumerate() {
let name = field
.ident
.clone()
.ok_or_else(|| crate::err(field, "All fields require names"))?;
let mut aliases = HashSet::new();
let mut ty = field.ty.clone();
let mut id = false;
let mut entity_or_event = None;
{
let id_field = field::Flag::<field::IdField>::extract_attributes(field)?;
if id_field.set || name == id_name {
if id {
return Err(crate::parse::err_span(
id_field.span,
"#[id] can only be specified once for now. A field named `id` is treated as having the annotation.",
));
}
id_offset = idx;
id = true;
}
}
let alias = field::Alias::extract_attributes(field)?;
for alias in alias.0 {
aliases.insert(alias);
}
let static_field = field::Flag::<field::StaticField>::extract_attributes(field)?;
if static_field.set && id {
return Err(crate::parse::err_span(
static_field.span,
"#[id] implies #[static_field]",
));
}
let mut indexed = field::Flag::<field::Indexed>::extract_attributes(field)?;
if indexed.set && id {
return Err(crate::parse::err_span(
indexed.span,
"#[id] implies #[indexed]",
));
}
{
let entity = field::Flag::<field::Entity>::extract_attributes(field)?;
if entity.set {
let field_ty = &field.ty;
ty =
syn::parse_quote! { <#field_ty as ::cido::__internal::ToIdentifiable>::Identifiable };
if id {
return Err(crate::parse::err_span(
entity.span,
"#[entity] cannot be specified with #[id]",
));
}
if indexed.set {
return Err(crate::parse::err_span(
entity.span,
"#[entity] implies #[indexed]",
));
}
indexed.set = true;
entity_or_event = Some(StructType::Entity);
}
}
{
let event = field::Flag::<field::Event>::extract_attributes(field)?;
if event.set {
let field_ty = &field.ty;
ty =
syn::parse_quote! { <#field_ty as ::cido::__internal::ToIdentifiable>::Identifiable };
if id {
return Err(crate::parse::err_span(
event.span,
"#[event] cannot be specified with #[id]",
));
}
if entity_or_event.is_some() {
return Err(crate::parse::err_span(
event.span,
"Only one of #[entity] or #[event] can be specified",
));
}
if indexed.set {
return Err(crate::parse::err_span(
event.span,
"#[event] implies #[indexed]",
));
}
indexed.set = true;
entity_or_event = Some(StructType::Event);
}
}
let sql_exists =
field::ActuallyExists::<_, field::TransformerFieldSqlParseConfig>::parse_attributes(field)?
.exists;
let sql = field::TransformerFieldSqlParseConfig::extract_attributes(field)?;
let sql_config = TransformerFieldSqlConfig {
name: sql
.name
.unwrap_or_else(|| SqlSafeName::new(name.to_string())),
ty: sql.ty,
nullable: sql.nullable.is_set()
|| field
.ty
.to_token_stream()
.to_string()
.starts_with("Option<"),
};
let db_exists =
field::ActuallyExists::<_, field::TransformerFieldDbParseConfig>::parse_attributes(field)?
.exists;
let db = field::TransformerFieldDbParseConfig::extract_attributes(field)?;
let field_ty = &field.ty;
if entity_or_event.is_some() && db.ty.is_some() {
return Err(crate::err(
db.ty.as_ref().unwrap(),
"Cannot specify db field type for entities or events",
));
}
let db_config = TransformerFieldDbConfig {
name: db.name.unwrap_or_else(|| name.clone()),
ty: Dbg(db.ty.unwrap_or_else(|| {
if entity_or_event.is_some() {
syn::parse_quote!(
<#field_ty as ::cido::__internal::ToIdentifiable>::Identifiable
)
} else {
ty.clone()
}
})),
field_to_db: db.field_to_db,
db_to_field: db.db_to_field,
};
let gql_exists =
field::ActuallyExists::<_, field::TransformerFieldGraphqlParseConfig>::parse_attributes(
field,
)?
.exists;
let gql = field::TransformerFieldGraphqlParseConfig::extract_attributes(field)?;
let graphql_config = TransformerFieldGraphqlConfig {
name: gql.name.unwrap_or_else(|| name.clone()),
attrs: gql.attrs,
hidden: gql.hidden.is_set(),
comparison: None,
complexity: gql.complexity,
input_type: gql.input.ty,
output_type: gql.output.ty,
field_to_output: gql.output.field_to_gql,
input_to_field: gql.input.gql_to_field,
};
let derived_from_exists =
field::ActuallyExists::<_, field::DerivedField>::parse_attributes(field)?;
if derived_from_exists.exists {
if id
|| static_field.set
|| indexed.set
|| entity_or_event.is_some()
|| sql_exists
|| db_exists
|| gql_exists
{
return Err(crate::parse::err_span(
derived_from_exists.span,
"#[derived_from] is incompatible with all other annotations",
));
}
let derived = field::DerivedField::extract_attributes(field)?;
derived_fields.push(DerivedField {
name,
ty,
alias: aliases.into_iter().collect(),
field_lookup: derived.field,
complexity: derived.complexity,
multiple: derived.multiple.is_set(),
is_entity: false,
is_event: false,
});
continue;
}
fields.push(TransformerField {
field: Dbg(field.clone()),
ty: Dbg(ty),
name,
alias: aliases,
id,
is_static: static_field.set,
sql_config,
db_config,
graphql_config,
entity_or_event,
indexed: indexed.set,
});
}
if id_offset == usize::MAX {
return Err(crate::err(
&t,
"#[id] must be specified on a single field. A field named `id` is treated as having the annotation.",
));
};
let this = TransformerStruct {
attrs: t.attrs,
vis: t.vis,
struct_opts: transformer_options.into_opts(&name, struct_type),
struct_type,
id_offset,
fields,
derived: derived_fields,
name,
};
Ok(this)
}
}
#[derive(Debug, Clone)]
#[must_use]
pub struct TransformerOptions {
pub db_opts: TransformerDbOptions,
pub sql_name: SqlSafeName,
pub graphql_opts: TransformerGraphqlOptions,
pub cache_policy: CachePolicy,
pub cidomap: TypePath,
pub embed_generated_code: bool,
}
#[derive(Debug, Clone)]
pub struct TransformerDbOptions {
pub name: Ident,
pub attrs: Attributes,
pub partition_max_rows: u64,
pub partition_max_bytes: u64,
}
#[derive(Debug, Clone)]
pub struct TransformerGraphqlOptions {
pub name: Ident,
pub gql_name: Ident,
pub loader_cache_size: usize,
pub loader_delay_ms: u64,
pub loader_max_batch_size: usize,
pub max_return_items: u64,
pub max_skip_items: u64,
pub attrs: Attributes,
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum StructType {
Entity,
Event,
}
impl StructType {
#[allow(unused)]
pub fn is_entity(self) -> bool {
matches!(self, Self::Entity)
}
#[allow(unused)]
pub fn is_event(self) -> bool {
matches!(self, Self::Event)
}
pub fn default_cache_policy(self) -> CachePolicy {
match self {
Self::Entity => CachePolicy::Always,
Self::Event => CachePolicy::Block,
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Default, Clone)]
pub enum Conversion {
#[default]
Identity,
From(Dbg<Expr>),
}
impl deluxe::ParseMetaItem for Conversion {
fn parse_meta_item(
input: syn::parse::ParseStream,
_mode: deluxe::ParseMode,
) -> syn::Result<Self> {
if let Ok(s) = input.parse::<syn::LitStr>() {
s.parse::<syn::Expr>()
} else {
input.parse::<syn::Expr>()
}
.map(|e| Self::From(Dbg(e)))
}
}
impl Conversion {
pub fn conversion(&self, ident: &Ident) -> TokenStream {
match self {
Self::Identity => {
quote!(#ident.try_into()?)
}
Self::From(e) => quote!(#e),
}
}
}
#[derive(Debug, Clone)]
pub struct TransformerField {
pub field: Dbg<syn::Field>,
pub ty: Dbg<syn::Type>,
pub name: Ident,
pub alias: HashSet<Ident>,
pub id: bool,
pub is_static: bool,
pub sql_config: TransformerFieldSqlConfig,
pub db_config: TransformerFieldDbConfig,
pub graphql_config: TransformerFieldGraphqlConfig,
pub entity_or_event: Option<StructType>,
pub indexed: bool,
}
impl TransformerField {
pub fn self_type(&self) -> syn::Type {
if self.entity_or_event.is_some() {
let field_ty = &self.field.ty;
syn::parse_quote! { <#field_ty as ::cido::__internal::ToIdentifiable>::Identifiable }
} else {
self.field.ty.clone()
}
}
pub fn db_type(&self) -> syn::Type {
(*self.db_config.ty).clone()
}
pub fn graphql_input_type(&self) -> syn::Type {
self
.graphql_config
.input_type
.clone()
.unwrap_or_else(|| self.db_type())
}
pub fn graphql_input_name(&self) -> &Ident {
&self.graphql_config.name
}
pub fn graphql_output_name(&self) -> &Ident {
&self.graphql_config.name
}
pub fn graphql_output_type(&self) -> syn::Type {
self.self_type()
}
pub fn graphql_comparison_sdl_name_string(&self) -> String {
self.graphql_config.name.to_string()
}
pub fn graphql_sdl_output_name(&self) -> String {
self.graphql_config.name.to_string()
}
pub fn graphql_sdl_output_type(&self) -> syn::Type {
self
.graphql_config
.output_type
.clone()
.unwrap_or_else(|| self.graphql_output_type())
}
pub fn graphql_order_by_name(&self) -> String {
self.graphql_sdl_output_name()
}
}
#[derive(Debug, Clone)]
pub struct DerivedField {
pub name: Ident,
pub ty: syn::Type,
pub alias: Vec<Ident>,
pub field_lookup: Ident,
pub complexity: Option<Expr>,
pub multiple: bool,
pub is_entity: bool,
pub is_event: bool,
}
#[derive(Debug, Clone)]
pub struct TransformerFieldSqlConfig {
pub name: SqlSafeName,
pub ty: Option<Expr>,
pub nullable: bool,
}
#[derive(Debug, Clone)]
pub struct TransformerFieldDbConfig {
pub name: Ident,
pub ty: Dbg<syn::Type>,
pub field_to_db: Conversion,
pub db_to_field: Conversion,
}
#[derive(Debug, Clone)]
pub struct TransformerFieldGraphqlConfig {
pub name: Ident,
pub attrs: Attributes,
pub comparison: Option<syn::Type>,
pub complexity: Option<Expr>,
pub hidden: bool,
pub input_type: Option<syn::Type>,
pub output_type: Option<syn::Type>,
pub field_to_output: Conversion,
pub input_to_field: Conversion,
}
#[derive(Copy, Clone, Debug)]
pub enum CachePolicy {
Always,
Lru { size: usize, max_size: usize },
Block,
Never,
Custom,
}