Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ tcp-transport = ["dep:tokio"]
[dependencies]
a3s-acl = { version = "0.2.1", optional = true }
a3s-boot-macros = { version = "0.1.2", path = "macros", optional = true }
a3s-event = { version = "0.3.0", path = "../event", default-features = false, optional = true }
a3s-lane = { version = "0.5.1", path = "../lane", default-features = false, optional = true }
a3s-event = { version = "0.3.0", default-features = false, optional = true }
a3s-lane = { version = "0.5.1", default-features = false, optional = true }
async-nats = { version = "0.49.1", default-features = false, optional = true }
axum = { version = "0.8", features = ["ws"], optional = true }
bytes = { version = "1", optional = true }
Expand Down
261 changes: 208 additions & 53 deletions ROADMAP.md

Large diffs are not rendered by default.

24 changes: 15 additions & 9 deletions macros/src/controller/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ use syn::{Ident, LitStr, Result};
pub(super) fn raw_or_json_request_handler(
method_ident: &Ident,
input: RouteMethodInput,
receiver: &proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let controller_name = format_ident!("__a3s_boot_{}", method_ident);
Ok(match input.into_legacy_arg()? {
Some(MethodArg { ident, ty, .. }) => quote! {
{
let #controller_name = ::std::sync::Arc::clone(&self);
let #controller_name = ::std::sync::Arc::clone(#receiver);
move |#ident: #ty| {
let #controller_name = ::std::sync::Arc::clone(&#controller_name);
async move { #controller_name.#method_ident(#ident).await }
Expand All @@ -22,7 +23,7 @@ pub(super) fn raw_or_json_request_handler(
},
None => quote! {
{
let #controller_name = ::std::sync::Arc::clone(&self);
let #controller_name = ::std::sync::Arc::clone(#receiver);
move |_request: ::a3s_boot::BootRequest| {
let #controller_name = ::std::sync::Arc::clone(&#controller_name);
async move { #controller_name.#method_ident().await }
Expand All @@ -35,12 +36,13 @@ pub(super) fn raw_or_json_request_handler(
pub(super) fn json_body_handler(
method_ident: &Ident,
input: MethodArg,
receiver: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let controller_name = format_ident!("__a3s_boot_{}", method_ident);
let MethodArg { ident, ty, .. } = input;
quote! {
{
let #controller_name = ::std::sync::Arc::clone(&self);
let #controller_name = ::std::sync::Arc::clone(#receiver);
move |#ident: #ty| {
let #controller_name = ::std::sync::Arc::clone(&#controller_name);
async move { #controller_name.#method_ident(#ident).await }
Expand All @@ -52,6 +54,7 @@ pub(super) fn json_body_handler(
pub(super) fn extracted_raw_handler(
method_ident: &Ident,
input: RouteMethodInput,
receiver: &proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let controller_name = format_ident!("__a3s_boot_{}", method_ident);
let ExtractedArguments {
Expand All @@ -64,7 +67,7 @@ pub(super) fn extracted_raw_handler(

Ok(quote! {
{
let #controller_name = ::std::sync::Arc::clone(&self);
let #controller_name = ::std::sync::Arc::clone(#receiver);
move |__a3s_boot_request: ::a3s_boot::BootRequest| {
let #controller_name = ::std::sync::Arc::clone(&#controller_name);
async move {
Expand All @@ -83,6 +86,7 @@ pub(super) fn extracted_json_response_handler(
method_ident: &Ident,
input: RouteMethodInput,
status: proc_macro2::TokenStream,
receiver: &proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let controller_name = format_ident!("__a3s_boot_{}", method_ident);
let ExtractedArguments {
Expand All @@ -95,7 +99,7 @@ pub(super) fn extracted_json_response_handler(

Ok(quote! {
{
let #controller_name = ::std::sync::Arc::clone(&self);
let #controller_name = ::std::sync::Arc::clone(#receiver);
move |__a3s_boot_request: ::a3s_boot::BootRequest| {
let #controller_name = ::std::sync::Arc::clone(&#controller_name);
async move {
Expand All @@ -117,6 +121,7 @@ pub(super) fn rendered_view_handler(
input: RouteMethodInput,
view: &LitStr,
status: proc_macro2::TokenStream,
receiver: &proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let controller_name = format_ident!("__a3s_boot_{}", method_ident);

Expand All @@ -130,7 +135,7 @@ pub(super) fn rendered_view_handler(
let apply_response = response_passthrough_apply(response_passthrough);
return Ok(quote! {
{
let #controller_name = ::std::sync::Arc::clone(&self);
let #controller_name = ::std::sync::Arc::clone(#receiver);
move |__a3s_boot_request: ::a3s_boot::BootRequest| {
let #controller_name = ::std::sync::Arc::clone(&#controller_name);
async move {
Expand All @@ -153,7 +158,7 @@ pub(super) fn rendered_view_handler(
Ok(match input.into_legacy_arg()? {
Some(MethodArg { ident, ty, .. }) => quote! {
{
let #controller_name = ::std::sync::Arc::clone(&self);
let #controller_name = ::std::sync::Arc::clone(#receiver);
move |__a3s_boot_request: ::a3s_boot::BootRequest| {
let #controller_name = ::std::sync::Arc::clone(&#controller_name);
async move {
Expand All @@ -170,7 +175,7 @@ pub(super) fn rendered_view_handler(
},
None => quote! {
{
let #controller_name = ::std::sync::Arc::clone(&self);
let #controller_name = ::std::sync::Arc::clone(#receiver);
move |__a3s_boot_request: ::a3s_boot::BootRequest| {
let #controller_name = ::std::sync::Arc::clone(&#controller_name);
async move {
Expand All @@ -190,6 +195,7 @@ pub(super) fn rendered_view_handler(
pub(super) fn extracted_sse_handler(
method_ident: &Ident,
input: RouteMethodInput,
receiver: &proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let controller_name = format_ident!("__a3s_boot_{}", method_ident);
let ExtractedArguments {
Expand All @@ -207,7 +213,7 @@ pub(super) fn extracted_sse_handler(

Ok(quote! {
{
let #controller_name = ::std::sync::Arc::clone(&self);
let #controller_name = ::std::sync::Arc::clone(#receiver);
move |__a3s_boot_request: ::a3s_boot::BootRequest| {
let #controller_name = ::std::sync::Arc::clone(&#controller_name);
async move {
Expand Down
138 changes: 124 additions & 14 deletions macros/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub(crate) fn expand_controller(

let self_ty = item_impl.self_ty.clone();
let mut routes = Vec::new();
let mut provider_routes = Vec::new();
let mut errors: Option<syn::Error> = None;
let (impl_attrs, impl_decorator_errors) = expand_apply_decorators_attrs(&item_impl.attrs);
for error in impl_decorator_errors {
Expand Down Expand Up @@ -283,9 +284,9 @@ pub(crate) fn expand_controller(
let validation_options = route_validation.enabled_options(controller_validation);
let validation_skipped = route_validation.skip;
match route_registration(
route,
route.clone(),
method,
input,
input.clone(),
validation_options,
validation_skipped,
&metadata_specs,
Expand All @@ -298,8 +299,32 @@ pub(crate) fn expand_controller(
version_specs.as_ref(),
serialization_specs.as_ref(),
&openapi_specs,
ControllerRouteKind::Instance,
) {
Ok(registration) => routes.push(registration),
Ok(registration) => {
routes.push(registration);
match route_registration(
route,
method,
input,
validation_options,
validation_skipped,
&metadata_specs,
&cache_specs,
http_code.as_ref(),
&response_specs,
render_spec.as_ref(),
&pipeline_specs,
host_specs.as_ref(),
version_specs.as_ref(),
serialization_specs.as_ref(),
&openapi_specs,
ControllerRouteKind::Provider,
) {
Ok(registration) => provider_routes.push(registration),
Err(error) => push_error(&mut errors, error),
}
}
Err(error) => push_error(&mut errors, error),
}
}
Expand Down Expand Up @@ -344,10 +369,49 @@ pub(crate) fn expand_controller(
)*
Ok(__a3s_boot_controller)
}

pub fn provider_controller() -> ::a3s_boot::Result<::a3s_boot::ControllerDefinition>
where
Self: ::std::marker::Send + ::std::marker::Sync + 'static,
{
let mut __a3s_boot_controller =
::a3s_boot::ControllerDefinition::new(#prefix)?;
#(
__a3s_boot_controller = __a3s_boot_controller.#controller_openapi;
)*
#(
__a3s_boot_controller = __a3s_boot_controller.#controller_metadata?;
)*
#(
__a3s_boot_controller = __a3s_boot_controller.#controller_cache;
)*
#(
__a3s_boot_controller = __a3s_boot_controller.#controller_pipeline;
)*
#(
__a3s_boot_controller = __a3s_boot_controller.#controller_host?;
)*
#(
__a3s_boot_controller = __a3s_boot_controller.#controller_version;
)*
#(
__a3s_boot_controller = __a3s_boot_controller.#controller_serialization;
)*
#(
__a3s_boot_controller = #provider_routes;
)*
Ok(__a3s_boot_controller)
}
}
})
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum ControllerRouteKind {
Instance,
Provider,
}

fn take_route_attrs(attrs: &[Attribute]) -> (Vec<Attribute>, Vec<RouteSpec>, Vec<syn::Error>) {
let mut clean_attrs = Vec::new();
let mut routes = Vec::new();
Expand Down Expand Up @@ -386,6 +450,7 @@ fn route_registration(
version_spec: Option<&VersionSpec>,
serialization_spec: Option<&SerializationSpec>,
openapi_specs: &[RouteOpenApiSpec],
controller_route_kind: ControllerRouteKind,
) -> Result<proc_macro2::TokenStream> {
if method.sig.asyncness.is_none() {
return Err(syn::Error::new_spanned(
Expand All @@ -395,6 +460,10 @@ fn route_registration(
}

let method_ident = &method.sig.ident;
let receiver = match controller_route_kind {
ControllerRouteKind::Instance => quote! { &self },
ControllerRouteKind::Provider => quote! { &__a3s_boot_provider_controller },
};
let explicit_status = route.args.explicit_status(http_code)?;
let status = status_value(explicit_status)?;
let path = route.args.path.clone();
Expand Down Expand Up @@ -435,7 +504,8 @@ fn route_registration(
let route_definition = if let Some(render_spec) = render_spec {
let builder = route.kind.raw_builder_ident();
let view = &render_spec.view;
let handler = rendered_view_handler(method_ident, input.clone(), view, status.clone())?;
let handler =
rendered_view_handler(method_ident, input.clone(), view, status.clone(), &receiver)?;
quote! {
::a3s_boot::RouteDefinition::#builder(#path, #handler)?
.with_response(#status, ::a3s_boot::OpenApiResponse::description("Success"))
Expand All @@ -457,9 +527,9 @@ fn route_registration(
));
}
let handler = if input.has_extractors() {
extracted_sse_handler(method_ident, input)?
extracted_sse_handler(method_ident, input, &receiver)?
} else {
raw_or_json_request_handler(method_ident, input)?
raw_or_json_request_handler(method_ident, input, &receiver)?
};
quote! {
::a3s_boot::RouteDefinition::sse(#path, #handler)?
Expand All @@ -474,9 +544,9 @@ fn route_registration(
}
let builder = route.kind.raw_builder_ident();
let handler = if input.has_extractors() {
extracted_raw_handler(method_ident, input)?
extracted_raw_handler(method_ident, input, &receiver)?
} else {
raw_or_json_request_handler(method_ident, input)?
raw_or_json_request_handler(method_ident, input, &receiver)?
};
quote! {
::a3s_boot::RouteDefinition::#builder(#path, #handler)?
Expand All @@ -485,8 +555,12 @@ fn route_registration(
RouteFlavor::JsonRequest => {
if input.has_extractors() {
let builder = route.kind.raw_builder_ident();
let handler =
extracted_json_response_handler(method_ident, input, status.clone())?;
let handler = extracted_json_response_handler(
method_ident,
input,
status.clone(),
&receiver,
)?;
json_success_status = Some(status.clone());
quote! {
::a3s_boot::RouteDefinition::#builder(#path, #handler)?
Expand All @@ -498,7 +572,7 @@ fn route_registration(
"this HTTP method does not support JSON route inference",
)
})?;
let handler = raw_or_json_request_handler(method_ident, input)?;
let handler = raw_or_json_request_handler(method_ident, input, &receiver)?;
quote! {
::a3s_boot::RouteDefinition::#builder(#path, #status, #handler)?
}
Expand All @@ -507,8 +581,12 @@ fn route_registration(
RouteFlavor::JsonBody => {
if input.has_extractors() {
let builder = route.kind.raw_builder_ident();
let handler =
extracted_json_response_handler(method_ident, input, status.clone())?;
let handler = extracted_json_response_handler(
method_ident,
input,
status.clone(),
&receiver,
)?;
json_success_status = Some(status.clone());
quote! {
::a3s_boot::RouteDefinition::#builder(#path, #handler)?
Expand All @@ -526,7 +604,7 @@ fn route_registration(
"this HTTP method does not support JSON route inference",
)
})?;
let handler = json_body_handler(method_ident, input);
let handler = json_body_handler(method_ident, input, &receiver);
quote! {
::a3s_boot::RouteDefinition::#builder(#path, #status, #handler)?
}
Expand All @@ -535,6 +613,38 @@ fn route_registration(
}
};

let mut route_definition = route_definition;
if controller_route_kind == ControllerRouteKind::Provider {
let method = route.kind.http_method_ident();
let provider_route = route_definition;
route_definition = quote! {
::a3s_boot::RouteDefinition::new_provider::<Self, _>(
::a3s_boot::HttpMethod::#method,
#path,
move |__a3s_boot_provider_controller: ::std::sync::Arc<Self>| {
let __a3s_boot_provider_route = #provider_route;
Ok(__a3s_boot_provider_route)
},
)?
};

if let Some(render_spec) = render_spec {
let view = &render_spec.view;
route_definition = quote! {
(#route_definition)
.with_response(
#status,
::a3s_boot::OpenApiResponse::description("Success")
)
.with_metadata("render:view", #view)?
};
} else if matches!(flavor, RouteFlavor::JsonRequest | RouteFlavor::JsonBody)
&& !metadata_input.has_extractors()
{
json_success_status = Some(status.clone());
}
}

let route_definition = validation_route_definition(
route_definition,
&metadata_input,
Expand Down
Loading
Loading