fn gen_async_factory_builder()

in shed/facet/proc_macros/factory_impl.rs [216:448]


fn gen_async_factory_builder(
    facet_crate: &Ident,
    factory_ty: &Ident,
    builder_ident: &Ident,
    params: &Params,
    facets: &Facets,
) -> Result<TokenStream, Error> {
    let builder_facets_ident = format_ident!("{}BuilderFacets", factory_ty);
    let builder_facets_needed_ident = format_ident!("{}BuilderFacetsNeeded", factory_ty);
    let builder_params_ident = format_ident!("{}BuilderParams", factory_ty);

    let param_idents = &params.param_idents;
    let param_types = &params.param_types;
    let facet_idents = &facets.facet_idents;
    let facet_types = &facets.facet_types;
    let facet_types_map = facet_idents
        .iter()
        .zip(facet_types)
        .collect::<BTreeMap<_, _>>();

    let mut heads: BTreeSet<_> = facet_idents.iter().collect();
    let mut facet_build_futs = BTreeMap::new();
    let mut facet_build_graph = BTreeMap::new();
    let mut builder_impls = Vec::new();
    let mut build_facets = Vec::new();
    let mut store_facets = Vec::new();

    for (facet_ident, facet_type, fallibility, asyncness, facet_params) in facets.iter() {
        let mut dependent_facets = Vec::new();
        let mut mark_facets_needed = Vec::new();
        let mut call_params = Vec::new();
        let mut deps = Vec::new();

        for facet_param in facet_params {
            match facet_param {
                FactoryParam::Facet(ident) => {
                    let param_type = facet_types_map
                        .get(ident)
                        .ok_or_else(|| Error::new(ident.span(), "unrecognised facet name"))?;
                    mark_facets_needed.push(quote! {
                        ::#facet_crate::AsyncBuilderFor::<#param_type>::need(self);
                    });
                    dependent_facets.push(ident);
                    call_params.push(quote!(#ident.as_ref().unwrap()));
                    heads.remove(&ident);
                    deps.push(ident);
                }
                FactoryParam::Param(ident) => {
                    call_params.push(quote!(&__self_params.#ident));
                }
            }
        }

        let maybe_dot_await_factory = asyncness.maybe(quote!(.await));
        let maybe_map_err = fallibility.maybe(quote! {
            .map_err(|e| ::#facet_crate::AsyncFactoryError::from(
                ::#facet_crate::FactoryError::FacetBuildFailed {
                    name: stringify!(#facet_ident),
                    source: e.into(),
                }))?
        });

        facet_build_graph.insert(facet_ident, deps);
        builder_impls.push(quote! {

            impl ::#facet_crate::AsyncBuilderFor<#facet_type> for #builder_ident<'_> {

                fn need(&mut self) {
                    self.needed.#facet_ident = true;
                    #( #mark_facets_needed )*
                }

                fn get(&self) -> #facet_type {
                    // The proc macro should have arranged for all needed
                    // facets to have been marked as needed and thus built. It
                    // is invalid for this to be called if the facet wasn't
                    // built.
                    self.facets.#facet_ident.clone().expect(
                        concat!(
                            "bug in #[facet::factory]: facet '",
                            stringify!(#facet_ident),
                            "' was not marked as needed",
                        )
                    )
                }
            }

        });

        let get_dependent_facets = if dependent_facets.is_empty() {
            quote!()
        } else {
            quote! {
                let ( #( #dependent_facets, )* ) =
                    ::#facet_crate::futures::try_join!(
                        #( #dependent_facets.clone(), )*
                    )?;
            }
        };

        facet_build_futs.insert(
            facet_ident,
            quote! {
                let #facet_ident = async {
                    if __self_needed.#facet_ident {
                        #get_dependent_facets
                        Ok::<_, ::#facet_crate::AsyncFactoryError>(Some(
                            __self_factory.#facet_ident( #( #call_params, )* )
                                #maybe_dot_await_factory
                                #maybe_map_err
                        ))
                    } else {
                        Ok::<_, ::#facet_crate::AsyncFactoryError>(None)
                    }
                }.shared();
            },
        );

        store_facets.push(quote! {
            __self_facets.#facet_ident = #facet_ident;
        });
    }

    // Group facets into based on their depth from the heads of the dependency
    // graph.  This will be used to order construction of the facets in
    // topological order.
    let mut ident_depths = BTreeMap::new();
    let mut queue: VecDeque<_> = heads.into_iter().map(|head| (head, 0)).collect();
    let mut max_depth = 0;
    while let Some((ident, depth)) = queue.pop_front() {
        ident_depths.insert(ident, depth);
        max_depth = depth;
        for dep in facet_build_graph.get(&ident).unwrap().iter() {
            queue.push_back((dep, depth + 1));
        }
    }
    let mut levels = vec![vec![]; max_depth + 1];
    for (ident, depth) in ident_depths.into_iter() {
        levels[depth].push(ident);
    }

    for idents in levels.into_iter().rev() {
        for ident in idents.iter() {
            build_facets.push(facet_build_futs.remove(ident).unwrap());
        }
    }

    let builder = quote! {
        #[doc(hidden)]
        pub struct #builder_params_ident {
            #(
                #param_idents: #param_types,
            )*
        }

        #[doc(hidden)]
        #[derive(Default)]
        pub struct #builder_facets_ident {
            #(
                #facet_idents: ::std::option::Option<#facet_types>,
            )*
        }

        #[doc(hidden)]
        #[derive(Default)]
        pub struct #builder_facets_needed_ident {
            #(
                #facet_idents: bool,
            )*
        }

        impl #builder_params_ident {
            #[doc(hidden)]
            pub fn new( #( #param_idents: #param_types, )* ) -> Self {
                Self {
                    #( #param_idents, )*
                }
            }
        }

        #[::#facet_crate::async_trait::async_trait]
        impl ::#facet_crate::AsyncBuilder for #builder_ident<'_> {
            async fn build_needed(
                &mut self
            ) -> ::std::result::Result<(), ::#facet_crate::FactoryError> {
                use ::#facet_crate::futures::future::FutureExt;
                let __self_facets = &mut self.facets;
                let __self_needed = &self.needed;
                let __self_params = &self.params;
                let __self_factory = self.factory;
                #( #build_facets )*
                let ( #( #facet_idents, )* ) =
                    ::#facet_crate::futures::try_join!( #( #facet_idents.clone(), )* )
                    .map_err(|e| e.factory_error())?;
                #( #store_facets )*
                Ok(())
            }
        }

        #(
            #builder_impls
        )*

        #[doc(hidden)]
        pub struct #builder_ident<'factory> {
            factory: &'factory #factory_ty,
            params: #builder_params_ident,
            facets: #builder_facets_ident,
            needed: #builder_facets_needed_ident,
        }

        impl #factory_ty {
            /// Build an instance of a container from this factory.
            pub async fn build<'factory, 'builder, T>(
                &'factory self,
                #( #param_idents: #param_types ),*
            ) -> ::std::result::Result<T, ::#facet_crate::FactoryError>
            where
                T: ::#facet_crate::AsyncBuildable<'builder, #builder_ident<'factory>>,
            {
                let builder = #builder_ident {
                    factory: &self,
                    params: #builder_params_ident::new(#( #param_idents, )*),
                    facets: #builder_facets_ident::default(),
                    needed: #builder_facets_needed_ident::default(),
                };
                T::build_async(builder).await
            }
        }
    };

    Ok(builder)
}