Struct Router

Source
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>
where Ctx: Clone + Send + Sync + 'static,

Source

pub fn new() -> Self

Create a new Router.

Unless you add additional routes this will ignore all requests.

Source

pub fn route<I, J, T>(self, job_id: I, job: J) -> Self
where I: Into<JobId>, J: Job<T, Ctx>, T: 'static,

Add a Job to the router, with the given job ID.

The job will be called when a JobCall with the given job ID is received by the router.

Source

pub fn route_service<T>(self, job_id: u32, service: T) -> Self
where T: Service<JobCall, Error = BoxError> + Clone + Send + Sync + 'static, T::Response: IntoJobResult, T::Future: Send + 'static,

Add a Service to the router, with the given job ID.

§Panics

Panics if service is a Router.

Source

pub fn always<J, T>(self, job: J) -> Self
where J: Job<T, Ctx>, T: 'static,

Add a Job that always gets called, regardless of the job ID

This is useful for jobs that want to watch for certain events. Any JobCall received by router will be passed to the job, regardless if another route matches.

Source

pub fn fallback<J, T>(self, job: J) -> Self
where J: Job<T, Ctx>, T: 'static,

Add a Job that gets called if no other route matches

NOTE: This will replace any existing fallback route.

This will only be called when:

  • No other route matches the job ID
  • No always route is present
Source

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));
Source

pub fn has_routes(&self) -> bool

Whether the router currently has at least one route added.

Source

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 Routers 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.

Source

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<Ctx> Clone for Router<Ctx>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<Ctx> Debug for Router<Ctx>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<Ctx> Default for Router<Ctx>
where Ctx: Clone + Send + Sync + 'static,

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<B> Service<JobCall<B>> for Router<()>
where B: Into<Bytes>,

Source§

type Response = Option<Vec<JobResult>>

Responses given by the service.
Source§

type Error = Box<dyn Error + Send + Sync>

Errors produced by the service.
Source§

type Future = Pin<Box<dyn Future<Output = Result<<Router as Service<JobCall<B>>>::Response, <Router as Service<JobCall<B>>>::Error>> + Send>>

The future response value.
Source§

fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>>

Returns Poll::Ready(Ok(())) when the service is able to process requests. Read more
Source§

fn call(&mut self, call: JobCall<B>) -> Self::Future

Process the request and return the response asynchronously. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, Request> ServiceExt<Request> for T
where T: Service<Request> + ?Sized,

Source§

fn ready(&mut self) -> Ready<'_, Self, Request>
where Self: Sized,

Yields a mutable reference to the service when it is ready to accept a request.
Source§

fn ready_oneshot(self) -> ReadyOneshot<Self, Request>
where Self: Sized,

Yields the service when it is ready to accept a request.
Source§

fn oneshot(self, req: Request) -> Oneshot<Self, Request>
where Self: Sized,

Consume this Service, calling it with the provided request once it is ready.
Source§

fn call_all<S>(self, reqs: S) -> CallAll<Self, S>
where Self: Sized, S: Stream<Item = Request>,

Process all requests from the given Stream, and produce a Stream of their responses. Read more
Source§

fn and_then<F>(self, f: F) -> AndThen<Self, F>
where Self: Sized, F: Clone,

Executes a new future after this service’s future resolves. This does not alter the behaviour of the poll_ready method. Read more
Source§

fn map_response<F, Response>(self, f: F) -> MapResponse<Self, F>
where Self: Sized, F: FnOnce(Self::Response) -> Response + Clone,

Maps this service’s response value to a different value. This does not alter the behaviour of the poll_ready method. Read more
Source§

fn map_err<F, Error>(self, f: F) -> MapErr<Self, F>
where Self: Sized, F: FnOnce(Self::Error) -> Error + Clone,

Maps this service’s error value to a different value. This does not alter the behaviour of the poll_ready method. Read more
Source§

fn map_result<F, Response, Error>(self, f: F) -> MapResult<Self, F>
where Self: Sized, Error: From<Self::Error>, F: FnOnce(Result<Self::Response, Self::Error>) -> Result<Response, Error> + Clone,

Maps this service’s result type (Result<Self::Response, Self::Error>) to a different value, regardless of whether the future succeeds or fails. Read more
Source§

fn map_request<F, NewRequest>(self, f: F) -> MapRequest<Self, F>
where Self: Sized, F: FnMut(NewRequest) -> Request,

Composes a function in front of the service. Read more
Source§

fn then<F, Response, Error, Fut>(self, f: F) -> Then<Self, F>
where Self: Sized, Error: From<Self::Error>, F: FnOnce(Result<Self::Response, Self::Error>) -> Fut + Clone, Fut: Future<Output = Result<Response, Error>>,

Composes an asynchronous function after this service. Read more
Source§

fn map_future<F, Fut, Response, Error>(self, f: F) -> MapFuture<Self, F>
where Self: Sized, F: FnMut(Self::Future) -> Fut, Error: From<Self::Error>, Fut: Future<Output = Result<Response, Error>>,

Composes a function that transforms futures produced by the service. Read more
Source§

fn boxed(self) -> BoxService<Request, Self::Response, Self::Error>
where Self: Sized + Send + 'static, Self::Future: Send + 'static,

Convert the service into a Service + Send trait object. Read more
Source§

fn boxed_clone(self) -> BoxCloneService<Request, Self::Response, Self::Error>
where Self: Sized + Clone + Send + 'static, Self::Future: Send + 'static,

Convert the service into a Service + Clone + Send trait object. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more