Skip to main content

AssembledChain

Struct AssembledChain 

Source
pub struct AssembledChain { /* private fields */ }
Expand description

The engine’s fully-assembled gRPC chain, ready for a downstream to mount additional services onto before serving.

Holds a tonic::service::Routes with the engine’s services pre-added (Flight SQL + CatalogService + the tier/engine services, including AuditService) and any resource whose lifetime must span the serve loop (the embedded training worker). A downstream chains Self::mount (un-gated) or Self::mount_tenant_scoped (bound by the engine’s single tenant resolver) to add its own services beside the engine’s, then Self::serves — or splits to compose one listener of its own: Self::into_layered_axum_router is the safe default (the engine’s transport stack pre-applied, ready for axum::serve()), and Self::into_axum_router is the expert, layer-free split for nesting under a listener that already frames gRPC-web.

The transport layer stack (accept_http1 + MetricsLayer + GrpcWebTrailersLayer + GrpcWebLayer) is applied by Self::serve, not baked into the routes — see that method, Self::into_layered_axum_router, and Self::into_axum_router for the seam contract each path honours.

Implementations§

Source§

impl AssembledChain

Source

pub fn mount<S>(self, svc: S) -> Self
where S: Service<Request<Body>, Error = Infallible> + NamedService + Clone + Send + Sync + 'static, S::Response: IntoResponse, S::Future: Send + 'static,

Mount a downstream service beside the engine’s, un-gated by the engine’s tenant resolver. Delegates tonic::service::Routes::add_service (by value, chainable) and records the service’s NamedService::NAME in the mounted ledger for the startup tracing line — so the ledger cannot drift from what is actually mounted. Generic: the engine names no consumer. The service inherits the transport layer stack Self::serve applies, exactly as the engine’s own services do — but NOT the tenant-binding layer, so no SessionTenant extension is bound on its requests.

Reach for this for a service that must run BEFORE a tenant is known — a pre-auth handshake (e.g. a login/bootstrap verb) that a caller reaches without a resolvable credential yet. Use Self::mount_tenant_scoped instead for a service that wants the engine’s resolved tenant bound uniformly.

Source

pub fn mount_tenant_scoped<S, ResBody>(self, svc: S) -> Self
where S: Service<Request<Body>, Response = Response<ResBody>, Error = Infallible> + NamedService + Clone + Send + Sync + 'static, S::Future: Send + 'static, ResBody: Default + 'static, Response<ResBody>: IntoResponse,

Mount a downstream service beside the engine’s, wrapped by the engine’s SAME single TenantResolverLayer every engine service is wrapped with (retained on self from assemble_grpc_chain — there is still exactly ONE Arc<dyn TenantResolver>, never a second resolver forked off here). Per request, the wrapper resolves the tenant BEFORE the service’s handler runs: Ok(scope) binds the SessionTenant extension the handler reads (TenantScope::TenantSome, TenantScope::GlobalNone) and calls it; Err(status) returns that status and the handler NEVER runs — see crate::tenant_resolver_layer for the full per-request contract. The wrapper forwards the inner service’s NamedService::NAME, so the mounted ledger and the proto route both stay keyed to the service’s own name, exactly as Self::mount records it.

Reach for this so a downstream service gets SessionTenant bound uniformly with every engine service, dropping its own per-handler resolve. Use plain Self::mount instead for a service that must stay un-gated (e.g. a pre-auth login/bootstrap handshake reached before any tenant is known).

Do NOT mount a service here that already binds its own SessionTenant internally (or resolves tenant identity some other way) — the two binds would race and the later one silently wins (last-writer-wins on the request extension), which is exactly the double-bind the engine’s single-binder invariant exists to prevent. Wrap a service with this method at most once, and never alongside a self-resolving service.

PRE-SPLIT ONLY: this method lives on AssembledChain, before Self::into_axum_router / Self::into_layered_axum_router split the routes off. ChainParts does NOT carry the layer forward — a downstream that splits to compose its own listener re-applies the (already pub) TenantResolverLayer itself via tower::Layer::layer before nesting its service, exactly as this method does internally.

Source

pub fn addr(&self) -> SocketAddr

The bind address the engine resolved from config. The downstream serves here.

Source

pub fn mounted(&self) -> &[String]

The ledger of mounted service names, in mount order (engine’s first, then any the downstream added via Self::mount). Read for a startup log; the ledger cannot drift from what is actually on the routes.

Source

pub async fn bind(self) -> Result<BoundChain, ServerError>

Bind the gRPC + Flight SQL listener eagerly and return a BoundChain holding it live. The listener is opened HERE, so BoundChain::addr reports the ACTUAL bound address — the real port even when the chain was assembled at an ephemeral :0 — and the port stays held with no release window until BoundChain::serve_with_shutdown serves on the very same listener. A caller that needs the resolved port before serving (a test harness building a client, a downstream logging its startup address) reads it off the bound handle rather than pre-binding-and-dropping a throwaway listener.

nodelay is set to match the tonic::transport::Server default the addr-based serve path applies, so the served connections behave identically to a chain served straight from a configured address.

Source

pub async fn serve( self, shutdown: impl Future<Output = ()> + Send + 'static, ) -> Result<(), ServerError>

Serve the assembled chain (engine core + any downstream-mounted services) until shutdown resolves. A thin composition of Self::bind + BoundChain::serve_with_shutdown — binds the listener, then serves on it; the training-worker guard stays alive for the whole serve loop. The transport layer stack is applied by BoundChain::serve_with_shutdown.

Source

pub fn into_axum_router(self) -> (Router, ChainParts)

Split into a plain, LAYER-FREE axum::Router (via tonic::service::Routes::into_axum_router) plus the ChainParts remainder — the EXPERT split, for a downstream that nests the engine’s gRPC routes UNDER a listener that ALREADY carries its own gRPC-web layer stack. Most single-listener consumers want Self::into_layered_axum_router instead (the safe default — see below).

SEAM CONTRACT: the returned router carries NO transport layers. The gRPC-web framing + trailer-repair + metrics layers are applied by Self::serve on the serve path, NOT baked into the routes. A downstream that serves this router on its OWN listener must therefore re-apply the full stack itself — GrpcWebLayer + GrpcWebTrailersLayer + the engine’s MetricsLayer (via ChainParts::metrics) — or gRPC-web clients break: a trailers-only error response would miss the in-body trailer frame.

PATH-SPECIFIC LAYERING: accept_http1(true) is a tonic::transport::Server builder method and applies ONLY on the Self::serve path — there is no accept_http1 to call on the axum path; HTTP/1 is implicit in axum::serve(). On axum, re-apply the layers with Router::layer in inner→outer call order (axum runs the LAST .layer call as the outermost service, the inverse of the tonic builder), i.e. .layer(GrpcWebLayer::new()).layer(GrpcWebTrailersLayer::new()) .layer(MetricsLayer::new(metrics)). This is exactly what Self::into_layered_axum_router does for you — reach for the layer-free split only when your outer listener already frames gRPC-web (re-applying here would double-frame).

The router also carries a gRPC unimplemented fallback (from Routes’ default), so a composing consumer must nest it under a path prefix or reconcile its own fallback, NOT blind-.merge() it.

The downstream must hold ChainParts (specifically its training-worker guard) alive for the lifetime of its own serve loop.

Source

pub fn into_layered_axum_router(self) -> (Router, ChainParts)

Split into a layered axum::Router plus the ChainParts remainder — the SAFE DEFAULT for a downstream that composes ONE listener of its own. The returned router is ready to hand to axum::serve() DIRECTLY: it carries the engine’s full transport contract, so the consumer re-applies nothing.

CANONICAL LAYER STACK: this applies the SAME stack Self::serve applies — the whole-server MetricsLayer (outermost, observing every method path) wrapping GrpcWebTrailersLayer (the trailers-only error repair) wrapping GrpcWebLayer (gRPC-web framing) wrapping the routes. axum runs the LAST .layer call as the OUTERMOST service — the inverse of the tonic tonic::transport::Server builder, where the FIRST .layer is outermost — so the calls are ordered inner→outer here to land the exact same outermost→innermost stack serve builds. accept_http1 has no axum analogue: HTTP/1 is implicit in axum::serve().

ERGONOMIC GUARANTEE: the returned value is a plain axum::Router (state (), request body axum::body::Body) that axum::serve() accepts with no further ceremony. The layer stack rewrites the response body type; that normalization is resolved INTERNALLY (the layered routes are re-nested under a fresh axum::Router), so the consumer needs no Router::<()>::new().merge(...) re-nest of its own.

Prefer this over Self::into_axum_router unless you are nesting under a listener that ALREADY frames gRPC-web — that expert path is layer-free precisely so it does not double-frame in that case.

The downstream must hold ChainParts (specifically its training-worker guard) alive for the lifetime of its own serve loop.

Auto Trait Implementations§

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> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSend for T
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_sync(self: Box<T>) -> Box<dyn Any + Sync + Send>

Converts Box<Trait> (where Trait: DowncastSync) to Box<dyn Any + Send + Sync>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Converts Arc<Trait> (where Trait: DowncastSync) to Arc<Any>, which can then be downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Fruit for T
where T: Send + Downcast,

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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

Source§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
Source§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
Source§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
Source§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
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<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

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