Skip to main content

cloud_sdk/operation/prepared/
construction.rs

1use crate::Method;
2use crate::authentication::AuthenticationScopePolicy;
3use crate::operation::{OperationImpact, OperationMetadata, RequestIdPolicy, ResponsePolicy};
4use crate::transport::{RawResponsePolicy, TransportRequest};
5
6use super::{
7    ApprovedReadOnlyPostQuery, BodyReplayability, PreparedRequest, PreparedRequestPolicyError,
8    ProviderService, RequestBodySensitivity,
9};
10
11impl<'request> PreparedRequest<'request> {
12    /// Validates cross-policy invariants before provider storage is borrowed.
13    ///
14    /// Provider preparation code should call this before writing sensitive
15    /// request bytes. [`Self::new`] repeats the same validation when it binds
16    /// the complete request.
17    pub fn validate_construction_policy(
18        method: Method,
19        metadata: OperationMetadata,
20        raw_response_policy: RawResponsePolicy<'_>,
21    ) -> Result<(), PreparedRequestPolicyError> {
22        if matches!(metadata.impact(), OperationImpact::ReadOnly)
23            && !method.permits_direct_read_only()
24        {
25            return Err(PreparedRequestPolicyError::ReadOnlyMethodMismatch);
26        }
27        if metadata.request_id_policy() != RequestIdPolicy::Discard
28            && !raw_response_policy.admits_header("x-request-id")
29        {
30            return Err(PreparedRequestPolicyError::MissingRequestIdHeader);
31        }
32        Ok(())
33    }
34
35    /// Creates a complete prepared request after checking cross-policy invariants.
36    ///
37    /// # Errors
38    ///
39    /// Returns [`PreparedRequestPolicyError::MissingRequestIdHeader`] when
40    /// operation metadata protects or retains request IDs but the raw response
41    /// policy does not admit `x-request-id`. Returns
42    /// [`PreparedRequestPolicyError::ReadOnlyMethodMismatch`] when read-only
43    /// metadata is paired with any method other than `GET` or `HEAD`.
44    /// `body_sensitivity` is mandatory so provider implementations cannot omit
45    /// the confidential-body review and silently receive a public default.
46    pub fn new(
47        request: TransportRequest<'request>,
48        service: ProviderService<'request>,
49        metadata: OperationMetadata,
50        response_policy: ResponsePolicy,
51        authentication_policy: AuthenticationScopePolicy<'request>,
52        raw_response_policy: RawResponsePolicy<'request>,
53        body_sensitivity: RequestBodySensitivity,
54    ) -> Result<Self, PreparedRequestPolicyError> {
55        Self::new_inner(
56            request,
57            service,
58            metadata,
59            response_policy,
60            authentication_policy,
61            &raw_response_policy,
62            body_sensitivity,
63            None,
64        )
65    }
66
67    /// Creates a registry-approved read-only query carried by `POST`.
68    ///
69    /// The closed approval entry validates provider, service, official
70    /// endpoint, operation ID, method, target, headers, body presence,
71    /// sensitive-body classification, authentication scope, and complete
72    /// safety metadata before permitless execution is admitted.
73    #[allow(clippy::too_many_arguments)]
74    pub fn new_read_only_post_query(
75        approval: ApprovedReadOnlyPostQuery,
76        request: TransportRequest<'request>,
77        service: ProviderService<'request>,
78        metadata: OperationMetadata,
79        response_policy: ResponsePolicy,
80        authentication_policy: AuthenticationScopePolicy<'request>,
81        raw_response_policy: RawResponsePolicy<'request>,
82        body_sensitivity: RequestBodySensitivity,
83    ) -> Result<Self, PreparedRequestPolicyError> {
84        let operation_id = approval
85            .validate(
86                request,
87                service,
88                metadata,
89                authentication_policy,
90                body_sensitivity,
91            )
92            .ok_or(PreparedRequestPolicyError::ReadOnlyPostQueryMismatch)?;
93        let mut prepared = Self::new_inner(
94            request,
95            service,
96            metadata,
97            response_policy,
98            authentication_policy,
99            &raw_response_policy,
100            body_sensitivity,
101            Some(approval),
102        )?;
103        prepared.operation_id = Some(operation_id);
104        Ok(prepared)
105    }
106
107    #[allow(clippy::too_many_arguments)]
108    fn new_inner(
109        request: TransportRequest<'request>,
110        service: ProviderService<'request>,
111        metadata: OperationMetadata,
112        response_policy: ResponsePolicy,
113        authentication_policy: AuthenticationScopePolicy<'request>,
114        raw_response_policy: &RawResponsePolicy<'request>,
115        body_sensitivity: RequestBodySensitivity,
116        read_only_post_approval: Option<ApprovedReadOnlyPostQuery>,
117    ) -> Result<Self, PreparedRequestPolicyError> {
118        if read_only_post_approval.is_none() {
119            Self::validate_construction_policy(request.method(), metadata, *raw_response_policy)?;
120        } else if metadata.request_id_policy() != RequestIdPolicy::Discard
121            && !raw_response_policy.admits_header("x-request-id")
122        {
123            return Err(PreparedRequestPolicyError::MissingRequestIdHeader);
124        }
125        Ok(Self {
126            request,
127            service,
128            metadata,
129            response_policy,
130            authentication_policy,
131            raw_response_policy: *raw_response_policy,
132            operation_id: None,
133            body_replayability: if request.body().is_empty() {
134                BodyReplayability::Replayable
135            } else {
136                BodyReplayability::NotReplayable
137            },
138            body_sensitivity,
139            authorization_evidence_required: false,
140            read_only_post_approval,
141        })
142    }
143}