sword-macros 0.3.0

Structured web framework built on top of tokio ecosystem, providing powerful features for building robust web applications.
Documentation
use super::shared::{ControllerStruct, ParsedControllerKind};
use crate::shared::{gen_build, gen_clone};

use proc_macro::TokenStream;
use quote::quote;
use syn::Error;

pub fn expand_grpc_controller(input: &ControllerStruct) -> syn::Result<TokenStream> {
    let ParsedControllerKind::Grpc { service } = &input.kind else {
        return Err(Error::new(input.name.span(), "Expected a gRPC controller"));
    };

    let self_name = &input.name;
    let self_fields = &input.fields;
    let interceptors = &input.interceptors;

    let build_impl = gen_build(self_name, self_fields);
    let clone_impl = gen_clone(self_name, self_fields);

    #[cfg(feature = "grpc-reflection")]
    let reflection_descriptor_set = quote! {
        Some(include_bytes!(concat!(env!("OUT_DIR"), "/sword_descriptor_set.bin")))
    };

    #[cfg(not(feature = "grpc-reflection"))]
    let reflection_descriptor_set = quote! { None };

    let interceptor_wrappers = interceptors.iter().rev().map(|interceptor| match interceptor {
        crate::interceptor::InterceptorArgs::Traditional(path) => {
            quote! {
                let interceptor = state.borrow::<#path>()
                    .unwrap_or_else(|err| {
                        ::sword::internal::core::sword_error! {
                            title: "Failed to retrieve gRPC interceptor from State",
                            reason: err,
                            context: {
                                "controller" => stringify!(#self_name),
                                "interceptor" => stringify!(#path),
                            },
                            hints: ["Ensure the interceptor is registered and built before gRPC controller setup"],
                        }
                    });

                let service = ::sword::internal::grpc::tonic_async_interceptor::AsyncInterceptedService::new(
                    service,
                    move |req: ::sword::internal::grpc::tonic::Request<()>| {
                        let interceptor = ::std::sync::Arc::clone(&interceptor);
                        async move {
                            <#path as ::sword::grpc::OnRequest>::on_request(&*interceptor, req).await
                        }
                    },
                );
            }
        }
        crate::interceptor::InterceptorArgs::WithConfig {
            interceptor,
            config,
        } => {
            quote! {
                let interceptor = state.borrow::<#interceptor>()
                    .unwrap_or_else(|err| {
                        ::sword::internal::core::sword_error!(
                            title: "Failed to retrieve gRPC interceptor from State",
                            reason: err,
                            context: {
                                "controller" => stringify!(#self_name),
                                "interceptor" => stringify!(#interceptor),
                            },
                            hints: ["Ensure the interceptor is registered and built before gRPC controller setup"],
                        )
                    });

                let service = ::sword::internal::grpc::tonic_async_interceptor::AsyncInterceptedService::new(
                    service,
                    move |req: ::sword::internal::grpc::tonic::Request<()>| {
                        let interceptor = ::std::sync::Arc::clone(&interceptor);
                        async move {
                            <#interceptor as ::sword::grpc::OnRequestWithConfig<_>>::on_request(
                                &*interceptor,
                                #config,
                                req,
                            ).await
                        }
                    },
                );
            }
        }
        crate::interceptor::InterceptorArgs::Expression(expr) => {
            quote! {
                let service = ::sword::internal::grpc::tonic_async_interceptor::AsyncInterceptedService::new(
                    service,
                    #expr,
                );
            }
        }
    });

    let expanded = quote! {
        #build_impl
        #clone_impl

        ::sword::internal::inventory::submit! {
            ::sword::internal::grpc::GrpcControllerRegistrar {
                controller_id: ::std::any::TypeId::of::<#self_name>(),
                reflection_descriptor_set: #reflection_descriptor_set,
                build: |state: &::sword::internal::core::State| {
                    state.insert::<#self_name>(#self_name::build(state).unwrap_or_else(|e| {
                        ::sword::internal::core::sword_error! {
                            title: "Failed to build gRPC controller",
                            reason: "An error occurred while building the gRPC controller",
                            context: {
                                "controller_name" => stringify!(#self_name),
                                "service" => stringify!(#service),
                                "error" => format!("{e:?}"),
                                "source" => "GrpcControllerRegistrar::build",
                            },
                            hints: ["Check the error message for details on what went wrong during construction"],
                        }
                    }));
                },
                register: |_state: &::sword::internal::core::State, _registry: &mut ::sword::internal::grpc::GrpcServiceRegistry| {
                    let state = _state;
                    let registry = _registry;

                    let controller = state.borrow::<#self_name>().unwrap_or_else(|err| {
                        ::sword::internal::core::sword_error! {
                            title: "Failed to retrieve gRPC controller from State",
                            reason: err,
                            context: {
                                "controller_name" => stringify!(#self_name),
                                "service" => stringify!(#service),
                                "source" => "GrpcControllerRegistrar::register",
                            },
                            hints: ["Ensure the controller is built before registration"],
                        }
                    });

                    let body_limit = state.borrow::<::sword::internal::grpc::GrpcBodyLimitValue>()
                        .unwrap_or_else(|err| {
                            ::sword::internal::core::sword_error!(
                                title: "Failed to retrieve gRPC body-limit settings from State",
                                reason: err,
                                context: {
                                    "controller_name" => stringify!(#self_name),
                                    "service" => stringify!(#service),
                                    "source" => "GrpcControllerRegistrar::register",
                                },
                                hints: ["Ensure gRPC body-limit settings are inserted into State before controller registration"],
                            )
                        });

                    let service = <#service<#self_name>>::new((*controller).clone())
                        .max_decoding_message_size(body_limit.max_decoding_message_size)
                        .max_encoding_message_size(body_limit.max_encoding_message_size);
                    #(#interceptor_wrappers)*
                    registry.routes_builder_mut().add_service(service);
                    registry.mark_service_registered_with_name(<#service<#self_name> as ::sword::internal::grpc::tonic::server::NamedService>::NAME);
                },
            }
        }

        impl ::sword::internal::grpc::GrpcController for #self_name {}

        impl ::sword::internal::core::ControllerSpec for #self_name {
            fn kind() -> ::sword::internal::core::Controller {
                ::sword::internal::core::Controller::Grpc
            }
        }
    };

    Ok(TokenStream::from(expanded))
}