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
impl AssembledChain
Sourcepub fn mount<S>(self, svc: S) -> Selfwhere
S: Service<Request<Body>, Error = Infallible> + NamedService + Clone + Send + Sync + 'static,
S::Response: IntoResponse,
S::Future: Send + 'static,
pub fn mount<S>(self, svc: S) -> Selfwhere
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.
Sourcepub fn mount_tenant_scoped<S, ResBody>(self, svc: S) -> Selfwhere
S: Service<Request<Body>, Response = Response<ResBody>, Error = Infallible> + NamedService + Clone + Send + Sync + 'static,
S::Future: Send + 'static,
ResBody: Default + 'static,
Response<ResBody>: IntoResponse,
pub fn mount_tenant_scoped<S, ResBody>(self, svc: S) -> Selfwhere
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::Tenant →
Some, TenantScope::Global
→ None) 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.
Sourcepub fn addr(&self) -> SocketAddr
pub fn addr(&self) -> SocketAddr
The bind address the engine resolved from config. The downstream serves here.
Sourcepub fn mounted(&self) -> &[String]
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.
Sourcepub async fn bind(self) -> Result<BoundChain, ServerError>
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.
Sourcepub async fn serve(
self,
shutdown: impl Future<Output = ()> + Send + 'static,
) -> Result<(), ServerError>
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.
Sourcepub fn into_axum_router(self) -> (Router, ChainParts)
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.
Sourcepub fn into_layered_axum_router(self) -> (Router, ChainParts)
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§
impl !RefUnwindSafe for AssembledChain
impl !UnwindSafe for AssembledChain
impl Freeze for AssembledChain
impl Send for AssembledChain
impl Sync for AssembledChain
impl Unpin for AssembledChain
impl UnsafeUnpin for AssembledChain
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> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
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>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
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)
fn as_any(&self) -> &(dyn Any + 'static)
&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)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&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
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<T> ErasedDestructor for Twhere
T: 'static,
impl<T> Fruit for T
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> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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 moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::RequestSource§impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
fn into_sample(self) -> T
Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.