es-entity-macros 0.10.36

Proc macros for es-entity
Documentation
use darling::ToTokens;
use proc_macro2::TokenStream;
use quote::{TokenStreamExt, quote};

use super::options::{RepoField, RepositoryOptions};

pub struct Nested<'a> {
    field: &'a RepoField,
    parent_modify_error: syn::Ident,
}

impl<'a> Nested<'a> {
    pub fn new(field: &'a RepoField, opts: &'a RepositoryOptions) -> Nested<'a> {
        Nested {
            field,
            parent_modify_error: opts.modify_error(),
        }
    }
}

impl ToTokens for Nested<'_> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let parent_modify_error = &self.parent_modify_error;
        let repo_field = self.field.ident();

        let nested_repo_ty = &self.field.ty;
        let create_fn_name = self.field.create_nested_fn_name();
        let update_fn_name = self.field.update_nested_fn_name();
        let find_fn_name = self.field.find_nested_fn_name();
        let delete_fn_name = self.field.delete_nested_fn_name();
        let find_include_deleted_fn_name = self.field.find_nested_include_deleted_fn_name();

        tokens.append_all(quote! {
            async fn #create_fn_name<OP, P>(&self, op: &mut OP, entity: &mut P) -> Result<(), <#nested_repo_ty as es_entity::EsRepo>::CreateError>
                where
                    P: es_entity::Parent<<#nested_repo_ty as EsRepo>::Entity>,
                    OP: es_entity::AtomicOperation
            {
                let new_children = entity.new_children_mut();
                if new_children.is_empty() {
                    return Ok(());
                }

                let new_children = new_children.drain(..).collect();
                let children = self.#repo_field.create_all_in_op(op, new_children).await?;
                entity.inject_children(children);
                Ok(())
            }

            async fn #update_fn_name<OP, P>(&self, op: &mut OP, entity: &mut P) -> Result<(), #parent_modify_error>
                where
                    P: es_entity::Parent<<#nested_repo_ty as EsRepo>::Entity>,
                    OP: es_entity::AtomicOperation
            {
                for entity in entity.iter_persisted_children_mut() {
                    self.#repo_field.update_in_op(op, entity).await?;
                }
                self.#create_fn_name(op, entity).await?;
                Ok(())
            }

            async fn #find_fn_name<OP, P, __EsErr>(op: &mut OP, entities: &mut [P]) -> Result<(), __EsErr>
                where
                    OP: es_entity::AtomicOperation,
                    P: es_entity::Parent<<#nested_repo_ty as es_entity::EsRepo>::Entity> + es_entity::EsEntity,
                    #nested_repo_ty: es_entity::PopulateNested<<<P as es_entity::EsEntity>::Event as es_entity::EsEvent>::EntityId>,
                    __EsErr: From<sqlx::Error> + From<es_entity::EntityHydrationError> + Send,
            {
                let lookup = entities.iter_mut().map(|e| (e.events().entity_id.clone(), e)).collect();
                <#nested_repo_ty>::populate_in_op::<_, _, __EsErr>(op, lookup).await?;
                Ok(())
            }

            async fn #find_include_deleted_fn_name<OP, P, __EsErr>(op: &mut OP, entities: &mut [P]) -> Result<(), __EsErr>
                where
                    OP: es_entity::AtomicOperation,
                    P: es_entity::Parent<<#nested_repo_ty as es_entity::EsRepo>::Entity> + es_entity::EsEntity,
                    #nested_repo_ty: es_entity::PopulateNested<<<P as es_entity::EsEntity>::Event as es_entity::EsEvent>::EntityId>,
                    __EsErr: From<sqlx::Error> + From<es_entity::EntityHydrationError> + Send,
            {
                let lookup = entities.iter_mut().map(|e| (e.events().entity_id.clone(), e)).collect();
                <#nested_repo_ty>::populate_in_op_include_deleted::<_, _, __EsErr>(op, lookup).await?;
                Ok(())
            }

            async fn #delete_fn_name<OP, P, __EsErr>(op: &mut OP, entity: &P) -> Result<(), __EsErr>
                where
                    OP: es_entity::AtomicOperation,
                    P: es_entity::EsEntity,
                    #nested_repo_ty: es_entity::CascadeDeleteNested<<<P as es_entity::EsEntity>::Event as es_entity::EsEvent>::EntityId>,
                    __EsErr: From<sqlx::Error> + Send,
            {
                <#nested_repo_ty>::cascade_delete_in_op::<_, __EsErr>(op, &entity.events().entity_id).await?;
                Ok(())
            }
        });
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use proc_macro2::Span;
    use syn::{Ident, parse_quote};

    #[test]
    fn nested() {
        let field = RepoField {
            ident: Some(Ident::new("users", Span::call_site())),
            ty: parse_quote! { UserRepo },
            nested: true,
            pool: false,
            clock: false,
            entity: None,
        };

        let cursor = Nested {
            field: &field,
            parent_modify_error: syn::Ident::new(
                "ParentModifyError",
                proc_macro2::Span::call_site(),
            ),
        };

        let mut tokens = TokenStream::new();
        cursor.to_tokens(&mut tokens);

        let expected = quote! {
            async fn create_nested_users_in_op<OP, P>(&self, op: &mut OP, entity: &mut P) -> Result<(), <UserRepo as es_entity::EsRepo>::CreateError>
                where
                    P: es_entity::Parent<<UserRepo as EsRepo>::Entity>,
                    OP: es_entity::AtomicOperation
            {
                let new_children = entity.new_children_mut();
                if new_children.is_empty() {
                    return Ok(());
                }

                let new_children = new_children.drain(..).collect();
                let children = self.users.create_all_in_op(op, new_children).await?;
                entity.inject_children(children);
                Ok(())
            }

            async fn update_nested_users_in_op<OP, P>(&self, op: &mut OP, entity: &mut P) -> Result<(), ParentModifyError>
                where
                    P: es_entity::Parent<<UserRepo as EsRepo>::Entity>,
                    OP: es_entity::AtomicOperation
            {
                for entity in entity.iter_persisted_children_mut() {
                    self.users.update_in_op(op, entity).await?;
                }
                self.create_nested_users_in_op(op, entity).await?;
                Ok(())
            }

            async fn find_nested_users_in_op<OP, P, __EsErr>(op: &mut OP, entities: &mut [P]) -> Result<(), __EsErr>
                where
                    OP: es_entity::AtomicOperation,
                    P: es_entity::Parent<<UserRepo as es_entity::EsRepo>::Entity> + es_entity::EsEntity,
                    UserRepo: es_entity::PopulateNested<<<P as es_entity::EsEntity>::Event as es_entity::EsEvent>::EntityId>,
                    __EsErr: From<sqlx::Error> + From<es_entity::EntityHydrationError> + Send,
            {
                let lookup = entities.iter_mut().map(|e| (e.events().entity_id.clone(), e)).collect();
                <UserRepo>::populate_in_op::<_, _, __EsErr>(op, lookup).await?;
                Ok(())
            }

            async fn find_nested_users_include_deleted_in_op<OP, P, __EsErr>(op: &mut OP, entities: &mut [P]) -> Result<(), __EsErr>
                where
                    OP: es_entity::AtomicOperation,
                    P: es_entity::Parent<<UserRepo as es_entity::EsRepo>::Entity> + es_entity::EsEntity,
                    UserRepo: es_entity::PopulateNested<<<P as es_entity::EsEntity>::Event as es_entity::EsEvent>::EntityId>,
                    __EsErr: From<sqlx::Error> + From<es_entity::EntityHydrationError> + Send,
            {
                let lookup = entities.iter_mut().map(|e| (e.events().entity_id.clone(), e)).collect();
                <UserRepo>::populate_in_op_include_deleted::<_, _, __EsErr>(op, lookup).await?;
                Ok(())
            }

            async fn delete_nested_users_in_op<OP, P, __EsErr>(op: &mut OP, entity: &P) -> Result<(), __EsErr>
                where
                    OP: es_entity::AtomicOperation,
                    P: es_entity::EsEntity,
                    UserRepo: es_entity::CascadeDeleteNested<<<P as es_entity::EsEntity>::Event as es_entity::EsEvent>::EntityId>,
                    __EsErr: From<sqlx::Error> + Send,
            {
                <UserRepo>::cascade_delete_in_op::<_, __EsErr>(op, &entity.events().entity_id).await?;
                Ok(())
            }
        };

        assert_eq!(tokens.to_string(), expected.to_string());
    }
}