Skip to main content

structured_cms/timestamp/response/
check.rs

1//! Response execution delegates all timestamp semantics to the existing core.
2
3use super::{Response, Status};
4use crate::timestamp::{
5    CheckContext, CheckError, CheckedTimestamp, ImprintOperand, ProfileCheckError, ProfileFailure,
6    ProfileInput, RequestFieldError, TokenExtension,
7};
8use crate::{Error, ErrorCode, provider::SignatureProvider, stream::Source};
9use core::convert::Infallible;
10use structured_asn1::{Reader, memory::Execution};
11
12/// Completed response facts. A rejection/waiting/notification has no timestamp
13/// evidence; a granted status still does not override negative token facts.
14pub struct CheckedResponse<'a> {
15    status: Status,
16    timestamp: Option<CheckedTimestamp<'a>>,
17}
18impl<'a> CheckedResponse<'a> {
19    /// Original unauthenticated response status, not a cryptographic assertion.
20    #[must_use]
21    pub fn status(&self) -> Status {
22        self.status
23    }
24    /// Original complete token checks, when a token was present.
25    #[must_use]
26    pub fn timestamp(&self) -> Option<&CheckedTimestamp<'a>> {
27        self.timestamp.as_ref()
28    }
29    /// Move the token result without copying retained evidence or diagnostics.
30    pub fn into_timestamp(self) -> Option<CheckedTimestamp<'a>> {
31        self.timestamp
32    }
33}
34impl<'doc> Response<'doc> {
35    /// Check a response against its actual live parsed request. `context.requested`
36    /// is required; its absence is a caller-context `InvalidLocation` refusal, not
37    /// a malformed response or a downgrade to archival checking. Negative status
38    /// responses start no provider, profile, recognizer or covered-content work.
39    /// All token, request, source, callback and resource causes remain unchanged.
40    #[expect(
41        clippy::result_large_err,
42        reason = "preserve the existing original token/source refusal without allocating an error box on resource exhaustion"
43    )]
44    pub fn check<'a, P, S: Source, PE, RE>(
45        &'a self,
46        provider: &'a P,
47        query: &Reader<'a>,
48        context: &CheckContext<'a, '_>,
49        covered: &mut S,
50        recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
51    ) -> Result<CheckedResponse<'a>, CheckError<S::Error, PE, RE>>
52    where
53        'doc: 'a,
54        P: for<'scope> SignatureProvider<'scope, Error = PE>,
55    {
56        self.check_with_profile(provider, query, context, covered, recognizes, |_| {
57            Ok::<(), ProfileFailure<Infallible>>(())
58        })
59        .map_err(ProfileCheckError::into_common)
60    }
61    /// Same shared token kernel with its original pre-crypto profile hook. No
62    /// response status, nonce or certificate hint bypasses the selected profile.
63    #[expect(
64        clippy::result_large_err,
65        reason = "retain the existing typed token/profile/source cause without another fallible allocation"
66    )]
67    pub fn check_with_profile<'a, P, S: Source, PE, RE, FE>(
68        &'a self,
69        provider: &'a P,
70        query: &Reader<'a>,
71        context: &CheckContext<'a, '_>,
72        covered: &mut S,
73        recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
74        profile: impl for<'view, 'scope> FnOnce(
75            ProfileInput<'view, 'doc, 'a, 'scope>,
76        ) -> Result<(), ProfileFailure<FE>>,
77    ) -> Result<CheckedResponse<'a>, ProfileCheckError<S::Error, PE, RE, FE>>
78    where
79        'doc: 'a,
80        P: for<'scope> SignatureProvider<'scope, Error = PE>,
81    {
82        let Some(requested) = &context.requested else {
83            return Err(CheckError::Processing(Error::schema(
84                ErrorCode::InvalidLocation,
85                "timeStampResp.requestContext",
86                self.element().span().start,
87            ))
88            .into());
89        };
90        requested
91            .request
92            .imprint()
93            .require_session(requested.parent)
94            .map_err(|source| {
95                CheckError::RequestField(RequestFieldError {
96                    operand: ImprintOperand::Requested,
97                    source,
98                })
99            })?;
100        let timestamp = match self.token_in(query).map_err(CheckError::Processing)? {
101            Some((token, reader)) => Some(
102                token
103                    .check_with_profile(provider, &reader, context, covered, recognizes, profile)?,
104            ),
105            None => None,
106        };
107        Ok(CheckedResponse {
108            status: self.status().status(),
109            timestamp,
110        })
111    }
112}