use std::sync::Arc;
use http::Method;
use http::request::Parts;
use primitives_rs::async_work::memoized_tasks::TaskCtx;
use crate::build_output::{CspSources, StaticPublicAssets};
use crate::document::HeadRules;
use crate::outcome::{ResolveTarget, ResponseTarget};
use crate::pattern::{ApiPatternParams, DecodedMatch, MissingParamError, ViewPatternParams};
use crate::{declarations::ApiEndpointKind, href::PageHrefs};
pub(crate) struct RequestFacts {
pub(crate) target_resolver: Arc<dyn ResolveTarget>,
pub(crate) request_parts: Arc<Parts>,
pub(crate) latest_known_client_build_id: String,
pub(crate) static_public_assets: Arc<StaticPublicAssets>,
pub(crate) kind: RequestKind,
}
pub(crate) enum RequestKind {
Page {
matched: MatchedPage,
head_rules: Arc<HeadRules>,
csp_sources: Arc<CspSources>,
mode: PageRequestMode,
},
ApiEndpoint {
matched: MatchedApiEndpoint,
requested_client_build_id: Option<String>,
},
}
pub(crate) enum PageRequestMode {
Document,
NavPayload(RequestedBuild),
}
pub(crate) enum RequestedBuild {
Named(String),
Unnamed,
}
impl RequestKind {
fn to_request_match(&self) -> RequestMatch {
match self {
Self::Page { matched, .. } => RequestMatch::Page(matched.clone()),
Self::ApiEndpoint { matched, .. } => RequestMatch::ApiEndpoint(matched.clone()),
}
}
fn requested_client_build_id(&self) -> Option<String> {
match self {
Self::Page {
mode: PageRequestMode::NavPayload(RequestedBuild::Named(id)),
..
} => Some(id.clone()),
Self::Page {
mode:
PageRequestMode::Document | PageRequestMode::NavPayload(RequestedBuild::Unnamed),
..
} => None,
Self::ApiEndpoint {
requested_client_build_id,
..
} => requested_client_build_id.clone(),
}
}
fn to_csp_sources(&self) -> Arc<CspSources> {
match self {
Self::Page { csp_sources, .. } => Arc::clone(csp_sources),
Self::ApiEndpoint { .. } => Arc::new(CspSources::default()),
}
}
}
#[derive(Clone, Debug)]
pub enum RequestMatch {
Page(MatchedPage),
ApiEndpoint(MatchedApiEndpoint),
}
#[derive(Clone, Debug)]
pub struct MatchedPage {
pub(crate) pattern_matches: Vec<PatternMatch>,
}
#[derive(Clone, Debug)]
pub struct PatternMatch {
pub(crate) pattern: String,
pub(crate) decoded_match: DecodedMatch,
}
impl PatternMatch {
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),
}
}
pub fn pattern(&self) -> &str {
&self.pattern
}
}
impl MatchedPage {
pub fn new(pattern_matches: Vec<PatternMatch>) -> Self {
Self { pattern_matches }
}
pub fn find_params<Params: ViewPatternParams>(
&self,
) -> Result<Option<Params>, MissingParamError> {
let pattern = Params::PATTERN;
self.pattern_matches
.iter()
.find(|pattern_match| pattern_match.pattern == pattern)
.map(|pattern_match| Params::from_decoded_match(pattern_match.decoded_match.clone()))
.transpose()
}
pub fn pattern_matches(&self) -> impl Iterator<Item = &PatternMatch> {
self.pattern_matches.iter()
}
}
#[derive(Clone, Debug)]
pub struct MatchedApiEndpoint {
pub kind: ApiEndpointKind,
pub method: Method,
pub pattern: &'static str,
pub(crate) decoded_match: DecodedMatch,
}
impl MatchedApiEndpoint {
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),
}
}
pub fn find_params<Params: ApiPatternParams>(
&self,
) -> Result<Option<Params>, MissingParamError> {
(Params::PATTERN == self.pattern)
.then(|| Params::from_decoded_match(self.decoded_match.clone()))
.transpose()
}
}
pub struct TaskMiddlewareCtx<State> {
pub state: Arc<State>,
pub request_parts: Arc<Parts>,
pub task_ctx: TaskCtx<State>,
pub static_public_assets: Arc<StaticPublicAssets>,
pub page_hrefs: PageHrefs,
pub csp_sources: Arc<CspSources>,
pub latest_known_client_build_id: String,
pub requested_client_build_id: Option<String>,
pub request_match: RequestMatch,
}
pub struct ViewLoaderCtx<State, Params, Input> {
pub state: Arc<State>,
pub params: Params,
pub input: Input,
pub pattern: &'static str,
pub request_parts: Arc<Parts>,
pub task_ctx: TaskCtx<State>,
pub static_public_assets: Arc<StaticPublicAssets>,
pub page_hrefs: PageHrefs,
pub csp_sources: Arc<CspSources>,
pub latest_known_client_build_id: String,
}
pub struct ApiEndpointCtx<State, Params, Input> {
pub state: Arc<State>,
pub params: Params,
pub input: Input,
pub pattern: &'static str,
pub request_parts: Arc<Parts>,
pub task_ctx: TaskCtx<State>,
pub static_public_assets: Arc<StaticPublicAssets>,
pub page_hrefs: PageHrefs,
pub latest_known_client_build_id: String,
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 response_target(&self) -> ResponseTarget<'_> {
match &self.kind {
RequestKind::Page { head_rules, .. } => ResponseTarget::Page { head_rules },
RequestKind::ApiEndpoint { .. } => ResponseTarget::ApiEndpoint,
}
}
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: self.kind.to_csp_sources(),
latest_known_client_build_id: self.latest_known_client_build_id.clone(),
requested_client_build_id: self.kind.requested_client_build_id(),
request_match: self.kind.to_request_match(),
}
}
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: self.kind.to_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.kind.requested_client_build_id(),
}
}
}
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>,
}