pub struct Router<Ctx = ()> { /* private fields */ }
Expand description
The router type for composing jobs and services.
Router<Ctx>
means a router that is missing a context of type Ctx
to be able to handle requests.
Thus, only Router<()>
(i.e. without missing context) can be passed to a BlueprintRunner
. See Router::with_context()
for more details.
Implementations§
Source§impl<Ctx> Router<Ctx>
impl<Ctx> Router<Ctx>
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new Router
.
Unless you add additional routes this will ignore all requests.
Sourcepub fn route_service<T>(self, job_id: u32, service: T) -> Self
pub fn route_service<T>(self, job_id: u32, service: T) -> Self
Sourcepub fn layer<L>(self, layer: L) -> Router<Ctx>where
L: Layer<Route> + Clone + Send + Sync + 'static,
L::Service: Service<JobCall> + Clone + Send + Sync + 'static,
<L::Service as Service<JobCall>>::Response: IntoJobResult + 'static,
<L::Service as Service<JobCall>>::Error: Into<BoxError> + 'static,
<L::Service as Service<JobCall>>::Future: Send + 'static,
pub fn layer<L>(self, layer: L) -> Router<Ctx>where
L: Layer<Route> + Clone + Send + Sync + 'static,
L::Service: Service<JobCall> + Clone + Send + Sync + 'static,
<L::Service as Service<JobCall>>::Response: IntoJobResult + 'static,
<L::Service as Service<JobCall>>::Error: Into<BoxError> + 'static,
<L::Service as Service<JobCall>>::Future: Send + 'static,
Apply a tower::Layer
to all routes in this Router
See Job::layer()
§Examples
use blueprint_sdk::{Job, Router};
use tower::limit::{ConcurrencyLimit, ConcurrencyLimitLayer};
async fn job() { /* ... */
}
async fn another_job() { /* ... */
}
const JOB_ID: u32 = 0;
const ANOTHER_JOB_ID: u32 = 1;
let app = Router::new()
.route(JOB_ID, job)
.route(ANOTHER_JOB_ID, another_job)
// Limit concurrent calls to both `job` and `another_job` to 64
.layer(ConcurrencyLimitLayer::new(64));
Sourcepub fn has_routes(&self) -> bool
pub fn has_routes(&self) -> bool
Whether the router currently has at least one route added.
Sourcepub fn with_context<Ctx2>(self, context: Ctx) -> Router<Ctx2>
pub fn with_context<Ctx2>(self, context: Ctx) -> Router<Ctx2>
Provide the context for the router. Context passed to this method is global and will be used for all requests this router receives.
use blueprint_sdk::{Router, extract::Context, runner::BlueprintRunner};
const MY_JOB_ID: u8 = 0;
#[derive(Clone)]
struct AppContext {}
let routes = Router::new()
.route(MY_JOB_ID, |Context(ctx): Context<AppContext>| async {
// use context
})
.with_context(AppContext {});
let config = /* ... */
let env = /* ... */
let runner = BlueprintRunner::builder(config, env).router(routes);
let result = runner.run().await;
§Returning routers with contexts from functions
When returning Router
s from functions, it is generally recommended not to set the
context directly:
use blueprint_sdk::{Router, extract::Context, runner::BlueprintRunner};
const MY_JOB_ID: u8 = 0;
#[derive(Clone)]
struct AppContext {}
// Don't call `Router::with_context` here
fn routes() -> Router<AppContext> {
Router::new()
.route(MY_JOB_ID, |_: Context<AppContext>| async {})
}
// Instead, do it before you run the server
let routes = routes().with_context(AppContext {});
let config = /* ... */
let env = /* ... */
let runner = BlueprintRunner::builder(config, env).router(routes);
let result = runner.run().await;
If you do need to provide the context, then return Router
without any type parameters:
// Don't return `Router<AppContext>`
fn routes(context: AppContext) -> Router {
Router::new()
.route(MY_JOB_ID, |_: Context<AppContext>| async {})
.with_context(context)
}
let routes = routes(AppContext {});
let config = /* ... */
let env = /* ... */
let runner = BlueprintRunner::builder(config, env).router(routes);
let result = runner.run().await;
This is because we can only call BlueprintRunnerBuilder::router()
on Router<()>
,
not Router<AppContext>
. See below for more details about why that is.
Note that the context defaults to ()
so Router
and Router<()>
is the same.
§What Ctx
in Router<Ctx>
means
Router<Ctx>
means a router that is missing a context of type Ctx
to be able to
handle requests. It does not mean a Router
that has a context of type Ctx
.
For example:
// A router that _needs_ an `AppContext` to handle requests
let router: Router<AppContext> = Router::new()
.route(MY_JOB_ID, |_: Context<AppContext>| async {});
// Once we call `Router::with_context` the router isn't missing
// the context anymore, because we just provided it
//
// Therefore the router type becomes `Router<()>`, i.e a router
// that is not missing any context
let router: Router<()> = router.with_context(AppContext {});
// Only `Router<()>` can be used in a `BlueprintRunner`.
//
// You cannot call `BlueprintRunnerBuilder::router` with a `Router<AppContext>`
// because it is still missing an `AppContext`.
let config = /* ... */
let env = /* ... */
let runner = BlueprintRunner::builder(config, env).router(router);
let result = runner.run().await;
Perhaps a little counter intuitively, Router::with_context
doesn’t always return a
Router<()>
. Instead, you get to pick what the new missing context type is:
let router: Router<AppContext> = Router::new()
.route(MY_JOB_ID, |_: Context<AppContext>| async {});
// When we call `with_context` we're able to pick what the next missing context type is.
// Here we pick `String`.
let string_router: Router<String> = router.with_context(AppContext {});
// That allows us to add new routes that uses `String` as the context type
const NEEDS_STRING_JOB_ID: u8 = 1;
let string_router = string_router
.route(NEEDS_STRING_JOB_ID, |_: Context<String>| async {});
// Provide the `String` and choose `()` as the new missing context.
let final_router: Router<()> = string_router.with_context("foo".to_owned());
// Since we have a `Router<()>` we can run it.
let config = /* ... */
let env = /* ... */
let runner = BlueprintRunner::builder(config, env).router(final_router);
let result = runner.run().await;
This why this returning Router<AppContext>
after calling with_context
doesn’t
work:
// This won't work because we're returning a `Router<AppContext>`
// i.e. we're saying we're still missing an `AppContext`
fn routes(context: AppContext) -> Router<AppContext> {
Router::new()
.route("/", |_: Context<AppContext>| async {})
.with_context(context)
}
let app = routes(AppContext {});
// We can only call `BlueprintRunnerBuilder::router` with a `Router<()>`
// but `app` is a `Router<AppContext>`
let config = /* ... */
let env = /* ... */
let runner = BlueprintRunner::builder(config, env).router(app);
let result = runner.run().await;
Instead, return Router<()>
since we have provided all the context needed:
// We've provided all the context necessary so return `Router<()>`
fn routes(context: AppContext) -> Router<()> {
Router::new()
.route(MY_JOB_ID, |_: Context<AppContext>| async {})
.with_context(context)
}
let app = routes(AppContext {});
// We can now call `BlueprintRunnerBuilder::router`
let config = /* ... */
let env = /* ... */
let runner = BlueprintRunner::builder(config, env).router(app);
let result = runner.run().await;
§A note about performance
If you need a Router
that implements Service
but you don’t need any context (perhaps
you’re making a library that uses blueprint-router
internally) then it is recommended to call this
method before you start serving requests:
use blueprint_sdk::Router;
const MY_JOB_ID: u8 = 0;
let app = Router::new()
.route(MY_JOB_ID, || async { /* ... */ })
// even though we don't need any context, call `with_context(())` anyway
.with_context(());
This is not required but it gives blueprint-router
a chance to update some internals in the router
which may impact performance and reduce allocations.
Sourcepub fn as_service<B>(&mut self) -> RouterAsService<'_, B, Ctx>
pub fn as_service<B>(&mut self) -> RouterAsService<'_, B, Ctx>
Convert the router into a borrowed Service
with a fixed request body type, to aid type
inference.
In some cases when calling methods from tower::ServiceExt
on a Router
you might get
type inference errors along the lines of
let response = router.ready().await?.call(request).await?;
^^^^^ cannot infer type for type parameter `B`
This happens because Router
implements Service
with impl<B> Service<Request<B>> for Router<()>
.
For example:
use blueprint_sdk::{Router, JobCall, Bytes};
use tower::{Service, ServiceExt};
const MY_JOB_ID: u8 = 0;
let mut router = Router::new().route(MY_JOB_ID, || async {});
let request = JobCall::new(MY_JOB_ID, Bytes::new());
let response = router.ready().await?.call(request).await?;
Calling Router::as_service
fixes that:
use blueprint_sdk::{JobCall, Router};
use bytes::Bytes;
use tower::{Service, ServiceExt};
const MY_JOB_ID: u32 = 0;
let mut router = Router::new().route(MY_JOB_ID, || async {});
let request = JobCall::new(MY_JOB_ID, Bytes::new());
let response = router.as_service().ready().await?.call(request).await?;
This is mainly used when calling Router
in tests. It shouldn’t be necessary when running
the Router
normally via the blueprint runner.
Trait Implementations§
Source§impl<B> Service<JobCall<B>> for Router<()>
impl<B> Service<JobCall<B>> for Router<()>
Source§type Future = Pin<Box<dyn Future<Output = Result<<Router as Service<JobCall<B>>>::Response, <Router as Service<JobCall<B>>>::Error>> + Send>>
type Future = Pin<Box<dyn Future<Output = Result<<Router as Service<JobCall<B>>>::Response, <Router as Service<JobCall<B>>>::Error>> + Send>>
Auto Trait Implementations§
impl<Ctx> Freeze for Router<Ctx>
impl<Ctx = ()> !RefUnwindSafe for Router<Ctx>
impl<Ctx> Send for Router<Ctx>
impl<Ctx> Sync for Router<Ctx>
impl<Ctx> Unpin for Router<Ctx>
impl<Ctx = ()> !UnwindSafe for Router<Ctx>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T, Request> ServiceExt<Request> for T
impl<T, Request> ServiceExt<Request> for T
Source§fn ready(&mut self) -> Ready<'_, Self, Request>where
Self: Sized,
fn ready(&mut self) -> Ready<'_, Self, Request>where
Self: Sized,
Source§fn ready_oneshot(self) -> ReadyOneshot<Self, Request>where
Self: Sized,
fn ready_oneshot(self) -> ReadyOneshot<Self, Request>where
Self: Sized,
Source§fn oneshot(self, req: Request) -> Oneshot<Self, Request>where
Self: Sized,
fn oneshot(self, req: Request) -> Oneshot<Self, Request>where
Self: Sized,
Service
, calling it with the provided request once it is ready.Source§fn and_then<F>(self, f: F) -> AndThen<Self, F>
fn and_then<F>(self, f: F) -> AndThen<Self, F>
poll_ready
method. Read moreSource§fn map_response<F, Response>(self, f: F) -> MapResponse<Self, F>
fn map_response<F, Response>(self, f: F) -> MapResponse<Self, F>
poll_ready
method. Read moreSource§fn map_err<F, Error>(self, f: F) -> MapErr<Self, F>
fn map_err<F, Error>(self, f: F) -> MapErr<Self, F>
poll_ready
method. Read moreSource§fn map_result<F, Response, Error>(self, f: F) -> MapResult<Self, F>
fn map_result<F, Response, Error>(self, f: F) -> MapResult<Self, F>
Result<Self::Response, Self::Error>
)
to a different value, regardless of whether the future succeeds or
fails. Read more