vorma 0.86.0-pre.4

Rust-first full-stack web framework: typed views and resources, generated TypeScript contracts, and a Vite-integrated frontend.
Documentation
//! What task middleware, view loaders, and API endpoints receive. A context
//! holds request facts only; a handler contributes to the response through
//! its returned result. Every value that depends on the build comes from one
//! build snapshot, which a request keeps from start to finish, so its asset
//! URLs, build ID, critical CSS, and CSP sources always agree.

use std::sync::Arc;

use http::Method;
use http::request::Parts;
use primitives_rs::tasks::TaskCtx;

use crate::build_output::{CspSources, StaticPublicAssets};
use crate::declarations::ApiEndpointKind;
use crate::href::PageHrefs;
use crate::pattern::{ApiPatternParams, DecodedMatch, ViewPatternParams};

/// The request facts that every handler of one request shares.
pub(crate) struct RequestFacts {
	pub(crate) target_resolver: Arc<dyn crate::outcome::ResolveTarget>,
	/// `None` for an endpoint request, which renders no document.
	pub(crate) head_rules: Option<Arc<crate::document::HeadRules>>,
	pub(crate) request_parts: Arc<Parts>,
	pub(crate) latest_known_client_build_id: String,
	pub(crate) requested_client_build_id: Option<String>,
	pub(crate) static_public_assets: Arc<StaticPublicAssets>,
	pub(crate) csp_sources: Arc<CspSources>,
	pub(crate) request_match: RequestMatch,
}

/// The page or endpoint that Vorma matched for a request.
#[derive(Clone, Debug)]
pub enum RequestMatch {
	/// A page request, with its view pattern matches.
	Page(MatchedPage),
	/// An endpoint call.
	ApiEndpoint(MatchedApiEndpoint),
}

/// The view pattern matches that Vorma found for a page request, outermost first.
#[derive(Clone, Debug)]
pub struct MatchedPage {
	pub(crate) pattern_matches: Vec<PatternMatch>,
}

/// One matched view pattern: the pattern as written and the params and splat
/// values that it names.
#[derive(Clone, Debug)]
pub struct PatternMatch {
	pub(crate) pattern: String,
	pub(crate) decoded_match: DecodedMatch,
}

impl PatternMatch {
	/// Creates a pattern match from its pattern as written, the decoded
	/// value of each named param, and the decoded values of its splat.
	pub fn new(
		pattern: impl Into<String>,
		params: Vec<(String, String)>,
		splat_values: Vec<String>,
	) -> Self {
		Self {
			pattern: pattern.into(),
			decoded_match: DecodedMatch::new(params, splat_values),
		}
	}

	/// The matched pattern as written.
	pub fn pattern(&self) -> &str {
		&self.pattern
	}
}

impl MatchedPage {
	/// Creates the matched page of a request from its pattern matches,
	/// outermost first. A test uses it to build a handler context.
	pub fn new(pattern_matches: Vec<PatternMatch>) -> Self {
		Self { pattern_matches }
	}

	/// The params of the pattern match that has `Params`' pattern, or `None`
	/// when no pattern match has that pattern.
	pub fn find_params<Params: ViewPatternParams>(&self) -> Option<Params> {
		let pattern = Params::PATTERN;
		self.pattern_matches
			.iter()
			.find(|pattern_match| pattern_match.pattern == pattern)
			// A pattern match has every param that its own pattern names.
			.and_then(|pattern_match| {
				Params::from_decoded_match(pattern_match.decoded_match.clone()).ok()
			})
	}

	/// The pattern matches, outermost first.
	pub fn pattern_matches(&self) -> impl Iterator<Item = &PatternMatch> {
		self.pattern_matches.iter()
	}
}

/// The API endpoint that Vorma matched for a request.
#[derive(Clone, Debug)]
pub struct MatchedApiEndpoint {
	/// How the browser treats a call to the endpoint.
	pub kind: ApiEndpointKind,
	/// The endpoint's declared method. A `HEAD` request has the method `GET`.
	pub method: Method,
	/// The endpoint's pattern as written.
	pub pattern: &'static str,
	pub(crate) decoded_match: DecodedMatch,
}

impl MatchedApiEndpoint {
	/// Creates the matched endpoint of a request from its kind, declared
	/// method, pattern as written, the decoded value of each named param, and
	/// the decoded values of its splat. A test uses it to build a handler
	/// context.
	pub fn new(
		kind: ApiEndpointKind,
		method: Method,
		pattern: &'static str,
		params: Vec<(String, String)>,
		splat_values: Vec<String>,
	) -> Self {
		Self {
			kind,
			method,
			pattern,
			decoded_match: DecodedMatch::new(params, splat_values),
		}
	}

	/// The endpoint's params when its pattern is `Params`' pattern, and `None`
	/// otherwise.
	pub fn find_params<Params: ApiPatternParams>(&self) -> Option<Params> {
		(Params::PATTERN == self.pattern)
			// A matched endpoint has every param that its pattern names.
			.then(|| Params::from_decoded_match(self.decoded_match.clone()).ok())
			.flatten()
	}
}

/// What task middleware receives.
pub struct TaskMiddlewareCtx<State> {
	/// The state of the task runtime that the application gave to
	/// [`crate::LoadedApp::service`].
	pub state: Arc<State>,
	/// The request's method, URI, headers, and extensions. Vorma reads the
	/// body itself.
	///
	/// For a page request, the URI omits Vorma's transport parameters,
	/// `vorma-json` and, while Vercel skew protection is on, `dpl`, so a link
	/// built from it opens the page as a document even when the request is a
	/// client navigation's data request.
	/// The method, the path, the headers, and the extensions are as they
	/// arrived, and the host server still sees the raw request. For an
	/// endpoint call, the URI is as it arrived.
	pub request_parts: Arc<Parts>,
	/// The request's task context, which the request's task middleware and
	/// handlers share.
	pub task_ctx: TaskCtx<State>,
	/// Each static public asset's published URL by its source file name.
	pub static_public_assets: Arc<StaticPublicAssets>,
	/// Turns page targets into hrefs.
	pub page_hrefs: PageHrefs,
	/// The sources that Vorma's own content needs for each Content-Security-Policy
	/// directive. The lists are empty for an endpoint call.
	pub csp_sources: Arc<CspSources>,
	/// The build that this server uses for the response: the latest build
	/// that it knows of.
	pub latest_known_client_build_id: String,
	/// The build that the request names: the browser's build on an endpoint
	/// call or a navigation data request. `None` when the request names no
	/// build, as on a first page load or from a caller outside Vorma.
	pub requested_client_build_id: Option<String>,
	/// The page or endpoint that Vorma matched for this request. Middleware
	/// runs after the match and before any loader or endpoint starts.
	pub request_match: RequestMatch,
}

/// What a view loader receives.
pub struct ViewLoaderCtx<State, Params, Input> {
	/// The state of the task runtime that the application gave to
	/// [`crate::LoadedApp::service`].
	pub state: Arc<State>,
	/// The loader's decoded pattern params.
	pub params: Params,
	/// The loader's decoded search input.
	pub input: Input,
	/// The loader's pattern as written.
	pub pattern: &'static str,
	/// The request's method, URI, headers, and extensions.
	///
	/// The URI omits Vorma's transport parameters, `vorma-json` and, while
	/// Vercel skew protection is on, `dpl`, so a link built from it opens the
	/// page as a document even when the request is a client navigation's data
	/// request. The method, the path, the
	/// headers, and the extensions are as they arrived, and the host server
	/// still sees the raw request.
	pub request_parts: Arc<Parts>,
	/// The request's task context, which the request's task middleware and
	/// handlers share.
	pub task_ctx: TaskCtx<State>,
	/// Each static public asset's published URL by its source file name.
	pub static_public_assets: Arc<StaticPublicAssets>,
	/// Turns page targets into hrefs.
	pub page_hrefs: PageHrefs,
	/// The sources that Vorma's own content needs for each Content-Security-Policy
	/// directive.
	pub csp_sources: Arc<CspSources>,
	/// The build that this server uses for the response.
	pub latest_known_client_build_id: String,
}

/// What an API endpoint receives.
pub struct ApiEndpointCtx<State, Params, Input> {
	/// The state of the task runtime that the application gave to
	/// [`crate::LoadedApp::service`].
	pub state: Arc<State>,
	/// The endpoint's decoded params.
	pub params: Params,
	/// The endpoint's decoded input.
	pub input: Input,
	/// The endpoint's pattern as written.
	pub pattern: &'static str,
	/// The request's method, URI, headers, and extensions. Vorma reads the
	/// body itself.
	pub request_parts: Arc<Parts>,
	/// The request's task context, which the request's task middleware and
	/// handlers share.
	pub task_ctx: TaskCtx<State>,
	/// Each static public asset's published URL by its source file name.
	pub static_public_assets: Arc<StaticPublicAssets>,
	/// Turns page targets into hrefs.
	pub page_hrefs: PageHrefs,
	/// The build that this server uses for the response.
	pub latest_known_client_build_id: String,
	/// The build that the request names, if any.
	pub requested_client_build_id: Option<String>,
}

impl RequestFacts {
	fn to_page_hrefs(&self) -> PageHrefs {
		PageHrefs::new(Arc::clone(&self.target_resolver))
	}

	pub(crate) fn to_task_middleware_ctx<State>(
		&self,
		state: Arc<State>,
		task_ctx: TaskCtx<State>,
	) -> TaskMiddlewareCtx<State> {
		TaskMiddlewareCtx {
			state,
			request_parts: Arc::clone(&self.request_parts),
			task_ctx,
			static_public_assets: Arc::clone(&self.static_public_assets),
			page_hrefs: self.to_page_hrefs(),
			csp_sources: Arc::clone(&self.csp_sources),
			latest_known_client_build_id: self.latest_known_client_build_id.clone(),
			requested_client_build_id: self.requested_client_build_id.clone(),
			request_match: self.request_match.clone(),
		}
	}

	pub(crate) fn to_view_loader_ctx<State, Params, Input>(
		&self,
		handler_facts: HandlerFacts<State, Params, Input>,
	) -> ViewLoaderCtx<State, Params, Input> {
		ViewLoaderCtx {
			state: handler_facts.state,
			params: handler_facts.params,
			input: handler_facts.input,
			pattern: handler_facts.pattern,
			request_parts: Arc::clone(&self.request_parts),
			task_ctx: handler_facts.task_ctx,
			static_public_assets: Arc::clone(&self.static_public_assets),
			page_hrefs: self.to_page_hrefs(),
			csp_sources: Arc::clone(&self.csp_sources),
			latest_known_client_build_id: self.latest_known_client_build_id.clone(),
		}
	}

	pub(crate) fn to_api_endpoint_ctx<State, Params, Input>(
		&self,
		handler_facts: HandlerFacts<State, Params, Input>,
	) -> ApiEndpointCtx<State, Params, Input> {
		ApiEndpointCtx {
			state: handler_facts.state,
			params: handler_facts.params,
			input: handler_facts.input,
			pattern: handler_facts.pattern,
			request_parts: Arc::clone(&self.request_parts),
			task_ctx: handler_facts.task_ctx,
			static_public_assets: Arc::clone(&self.static_public_assets),
			page_hrefs: self.to_page_hrefs(),
			latest_known_client_build_id: self.latest_known_client_build_id.clone(),
			requested_client_build_id: self.requested_client_build_id.clone(),
		}
	}
}

/// The facts of one view loader or API endpoint invocation that differ by handler.
pub(crate) struct HandlerFacts<State, Params, Input> {
	pub(crate) state: Arc<State>,
	pub(crate) params: Params,
	pub(crate) input: Input,
	pub(crate) pattern: &'static str,
	pub(crate) task_ctx: TaskCtx<State>,
}