Skip to content

Commit

Permalink
rust server: support for trait method that intercepts requests (use c…
Browse files Browse the repository at this point in the history
…ase: auth)

* The handler trait gets an additional method `intercept_handler_pre(&self, &req, ctx)`
* `ctx` is an associated type of the handler trait
* `ctx`'s role is to store data extracted by the interceptor

Check out the newly added example `humblegen/tests/rust/service-authorization-using-interceptor`.

- [ ] Problem: I do not know how I should deal with this warning:

```
WARNINGS:
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
warning: the trait `protocol::BlogApi` cannot be made into an object
   --> $DIR/spec.rs:100:14
    |
98  | pub trait BlogApi {
    |           ------- this trait cannot be made into an object...
99  |     type Context: Default + Sized + Send + Sync;
100 |     async fn intercept_handler_pre(
    |              ^^^^^^^^^^^^^^^^^^^^^ ...because method `intercept_handler_pre` references the `Self` type in its `where` clause
    |
    = note: `#[warn(where_clauses_object_safety)]` on by default
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #51443 <rust-lang/rust#51443>
    = help: consider moving `intercept_handler_pre` to another trait
┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
```

ISTM that it is related to `async-trait` because it goes away if we remove the `async` in front of the `async fn intercept_handler_pre` decl.
  • Loading branch information
problame authored and reiner-dolp committed Jun 18, 2020
1 parent 1b0dff9 commit cb3dbbb
Show file tree
Hide file tree
Showing 8 changed files with 551 additions and 56 deletions.
108 changes: 108 additions & 0 deletions example/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 20 additions & 4 deletions example/src/api_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ pub struct MonsterApiImpl {

#[async_trait(Sync)]
impl protocol::Godzilla for MonsterApiImpl {
async fn get_foo(&self) -> protocol::Response<u32> {
type Context = ();

async fn get_foo(&self, _ctx: Self::Context) -> protocol::Response<u32> {
// simulate authorization failure for every other request
let v = self.ctr.fetch_add(1, SeqCst);
if v % 2 == 0 {
Expand All @@ -24,6 +26,7 @@ impl protocol::Godzilla for MonsterApiImpl {

async fn get_monsters_id(
&self,
_ctx: Self::Context,
_id: i32,
) -> protocol::Response<Result<protocol::Monster, protocol::MonsterError>> {
// demonstrate how service-specific errors are handled
Expand All @@ -32,6 +35,7 @@ impl protocol::Godzilla for MonsterApiImpl {

async fn get_monsters(
&self,
_ctx: Self::Context,
query: Option<protocol::MonsterQuery>,
) -> protocol::Response<Vec<protocol::Monster>> {
// the query-part of the URL is deserialized into argument `query` if specified by the user
Expand All @@ -43,6 +47,7 @@ impl protocol::Godzilla for MonsterApiImpl {

async fn post_monsters(
&self,
_ctx: Self::Context,
post_body: protocol::MonsterData,
) -> protocol::Response<Result<protocol::Monster, protocol::MonsterError>> {
// the POST body is made available as argument `post_body`
Expand All @@ -55,6 +60,7 @@ impl protocol::Godzilla for MonsterApiImpl {

async fn get_monsters_2(
&self,
_ctx: Self::Context,
query: Option<String>,
) -> protocol::Response<Vec<protocol::Monster>> {
dbg!(query);
Expand All @@ -63,29 +69,35 @@ impl protocol::Godzilla for MonsterApiImpl {

async fn get_monsters_3(
&self,
_ctx: Self::Context,
query: Option<i32>,
) -> protocol::Response<Vec<protocol::Monster>> {
// non-struct queries are deserialized
dbg!(query);
unimplemented!()
}

async fn get_monsters_4(&self) -> protocol::Response<Vec<protocol::Monster>> {
async fn get_monsters_4(
&self,
_ctx: Self::Context,
) -> protocol::Response<Vec<protocol::Monster>> {
unimplemented!()
}

async fn get_version(&self) -> protocol::Response<String> {
async fn get_version(&self, _ctx: Self::Context) -> protocol::Response<String> {
unimplemented!()
}

async fn get_tokio_police_locations(
&self,
_ctx: Self::Context,
) -> protocol::Response<Result<Vec<protocol::PoliceCar>, protocol::PoliceError>> {
unimplemented!()
}

async fn delete_monster_id(
&self,
_ctx: Self::Context,
id: String,
) -> protocol::Response<Result<(), protocol::MonsterError>> {
println!("would delete id={}", id);
Expand All @@ -94,6 +106,7 @@ impl protocol::Godzilla for MonsterApiImpl {

async fn put_monsters_id(
&self,
_ctx: Self::Context,
monster: protocol::Monster,
id: String,
) -> protocol::Response<Result<(), protocol::MonsterError>> {
Expand All @@ -103,6 +116,7 @@ impl protocol::Godzilla for MonsterApiImpl {

async fn patch_monsters_id(
&self,
_ctx: Self::Context,
patch: protocol::MonsterPatch,
id: String,
) -> protocol::Response<Result<(), protocol::MonsterError>> {
Expand All @@ -115,4 +129,6 @@ impl protocol::Godzilla for MonsterApiImpl {
#[derive(Default)]
pub struct MoviesApiImpl {}

impl protocol::Movies for MoviesApiImpl {}
impl protocol::Movies for MoviesApiImpl {
type Context = ();
}
2 changes: 1 addition & 1 deletion humblegen-rt/src/service_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct ErrorResponse {
pub kind: ErrorResponseKind,
}

pub(crate) trait ToErrorResponse {
pub trait ToErrorResponse {
fn to_error_response(self) -> ErrorResponse;
}

Expand Down
33 changes: 26 additions & 7 deletions humblegen/src/rust/service_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ pub fn render_services<'a, I: Iterator<Item = &'a ast::ServiceDef>>(
#[allow(unused_imports)]
use ::std::sync::Arc;
use std::net::SocketAddr;
#[allow(unused_imports)]
use ::humblegen_rt::hyper;

/// Builds an HTTP server that exposes services implemented by handler trait objects.
#[derive(Debug)]
Expand All @@ -119,7 +121,7 @@ pub fn render_services<'a, I: Iterator<Item = &'a ast::ServiceDef>>(
/// and `root="/api"` will expose
/// * handler method `fn bar() -> i32` at `/api/bar` and
/// * handler method `fn baz() -> String` at `/api/baz`
pub fn add(mut self, root: &str, handler: Handler) -> Self {
pub fn add<Context: Default + Sized + Send + Sync>(mut self, root: &str, handler: Handler<Context>) -> Self {
if !root.starts_with('/') {
panic!("root must start with \"/\"")
} else if root.ends_with('/') {
Expand Down Expand Up @@ -153,7 +155,7 @@ pub fn render_services<'a, I: Iterator<Item = &'a ast::ServiceDef>>(
.map(|s| {
let Service { trait_name, .. } = s;
quote! {
#trait_name(Arc<dyn #trait_name + Send + Sync>)
#trait_name(Arc<dyn #trait_name<Context=Context> + Send + Sync>)
}
})
.collect();
Expand Down Expand Up @@ -186,19 +188,19 @@ pub fn render_services<'a, I: Iterator<Item = &'a ast::ServiceDef>>(
/// Wrapper enum with one variant for each service defined in the humble spec.
/// Used to pass instantiated handler trait objects to `Builder::add`.
#[allow(dead_code)]
pub enum Handler {
pub enum Handler<Context: Default + Sized + Send + Sync + 'static> {
#(#handler_enum_variants,)*
}

impl Handler {
impl<Context: Default + Sized + Send + Sync + 'static> Handler<Context> {
fn into_routes(self) -> Vec<Route> {
match self {
#(#handler_into_routes_match_arms,)*
}
}
}

impl std::fmt::Debug for Handler {
impl<Context: Default + Sized + Send + Sync + 'static> std::fmt::Debug for Handler<Context> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#(#handler_debug_arms,)*
Expand Down Expand Up @@ -237,6 +239,7 @@ fn render_service(service: &Service) -> TokenStream {
} = r;
let mut param_list = vec![];
param_list.push(quote! {&self});
param_list.push(quote! {ctx: Self::Context});
param_list.extend(post_body_type.iter().map(|t| quote! { post_body: #t }));
param_list.extend(query_type.iter().map(|t| quote! { query: Option<#t> }));
param_list.extend(components.iter().filter_map(|c| match c {
Expand Down Expand Up @@ -265,10 +268,19 @@ fn render_service(service: &Service) -> TokenStream {
})
.unzip();
let trait_name = &service.trait_name;
let trait_def_interceptor_fn = quote! {
type Context: Default + Sized + Send + Sync;
async fn intercept_handler_pre(&self,
_req: &hyper::Request<hyper::Body>,
) -> Result<Self::Context, ServiceError> {
Ok(Self::Context::default())
}
};
let trait_def_as_doc_comment = {
let d = quote! {
#[humblegen_rt::async_trait(Sync)]
pub trait #trait_name {
#trait_def_interceptor_fn
#(#trait_fns_without_comment ;)*
}
};
Expand All @@ -279,6 +291,7 @@ fn render_service(service: &Service) -> TokenStream {
#[doc = #trait_def_as_doc_comment ]
#[humblegen_rt::async_trait(Sync)]
pub trait #trait_name {
#trait_def_interceptor_fn
#(#trait_fns_with_comment ;)*
}
};
Expand Down Expand Up @@ -368,7 +381,13 @@ fn render_service(service: &Service) -> TokenStream {
#(let #route_param_vars = #route_param_vars2?;)*
#query_def
#post_body_def
Ok(handler_response_to_hyper_response(handler.#traitfn_ident( #(#arg_list),* ).await))
// Invoke the interceptor
use ::humblegen_rt::service_protocol::ToErrorResponse;
let ctx = handler.intercept_handler_pre(&req).await
.map_err(::humblegen_rt::service_protocol::ServiceError::from)
.map_err(|e| e.to_error_response())?;
// Invoke handler if interceptor doesn't return a ServiceError
Ok(handler_response_to_hyper_response(handler.#traitfn_ident( ctx, #(#arg_list),* ).await))
})
}
),
Expand All @@ -386,7 +405,7 @@ fn render_service(service: &Service) -> TokenStream {
#[allow(non_snake_case)]
#[allow(clippy::trivial_regex)]
#[allow(clippy::single_char_pattern)]
fn #routes_factory_name(handler: Arc<dyn #trait_name + Send + Sync>) -> Vec<Route> {
fn #routes_factory_name<Context: Default + Sized + Send + Sync + 'static>(handler: Arc<dyn #trait_name<Context=Context> + Send + Sync>) -> Vec<Route> {
vec![#(#routes),*]
}

Expand Down
Loading

0 comments on commit cb3dbbb

Please sign in to comment.