Skip to main content

cloud_sdk/operation/
metadata.rs

1//! Explicit operation safety and retry classification.
2
3/// Provider operation impact.
4#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
5pub enum OperationImpact {
6    /// The operation cannot change provider state.
7    ReadOnly,
8    /// The operation changes provider state without being inherently destructive.
9    Mutation,
10    /// The operation deletes, disables, resets, detaches, or otherwise destroys state.
11    Destructive,
12}
13
14/// HTTP request semantics independent of provider impact.
15#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
16pub enum RequestSemantics {
17    /// Repeating the request is both read-only and idempotent.
18    Safe,
19    /// Repeating the request has the same intended effect, but it changes state.
20    Idempotent,
21    /// Repeating the request can create an additional or different effect.
22    NonIdempotent,
23}
24
25/// Whether caller-owned retry policy may retry the operation.
26#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
27pub enum RetryEligibility {
28    /// Retrying is not admitted by operation metadata.
29    Never,
30    /// An explicit caller policy may retry eligible transient failures.
31    ExplicitPolicy,
32}
33
34/// Whether executing the operation may directly incur provider charges.
35#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
36pub enum CostIntent {
37    /// The operation has no known direct resource cost.
38    NoKnownCost,
39    /// The operation may create or enlarge a billed resource.
40    MayIncurCost,
41}
42
43/// Lifecycle policy for provider request identifiers.
44#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
45pub enum RequestIdPolicy {
46    /// The identifier may move into a cleanup-owning retained metadata value.
47    Retain,
48    /// The identifier may be inspected only while the checked guard owns it.
49    Protected,
50    /// The identifier is cleared during response-policy admission.
51    Discard,
52}
53
54/// Incoherent operation metadata.
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub enum OperationMetadataError {
57    /// Read-only operations must use safe request semantics.
58    ReadOnlyMustBeSafe,
59    /// Mutating and destructive operations cannot use safe semantics.
60    StateChangeCannotBeSafe,
61    /// Non-idempotent operations cannot be retry eligible.
62    NonIdempotentRetry,
63}
64
65impl_static_error!(OperationMetadataError,
66    Self::ReadOnlyMustBeSafe => "read-only operation must use safe semantics",
67    Self::StateChangeCannotBeSafe => "state-changing operation cannot use safe semantics",
68    Self::NonIdempotentRetry => "non-idempotent operation cannot be retry eligible",
69);
70
71/// Complete operation safety metadata without permissive defaults.
72///
73/// ```compile_fail
74/// use cloud_sdk::operation::OperationMetadata;
75///
76/// let _: OperationMetadata = Default::default();
77/// ```
78#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
79pub struct OperationMetadata {
80    impact: OperationImpact,
81    semantics: RequestSemantics,
82    retry: RetryEligibility,
83    cost: CostIntent,
84    request_id: RequestIdPolicy,
85}
86
87impl OperationMetadata {
88    /// Creates complete metadata after checking safety invariants.
89    pub const fn new(
90        impact: OperationImpact,
91        semantics: RequestSemantics,
92        retry: RetryEligibility,
93        cost: CostIntent,
94        request_id: RequestIdPolicy,
95    ) -> Result<Self, OperationMetadataError> {
96        match (impact, semantics, retry) {
97            (OperationImpact::ReadOnly, semantics, _)
98                if !matches!(semantics, RequestSemantics::Safe) =>
99            {
100                return Err(OperationMetadataError::ReadOnlyMustBeSafe);
101            }
102            (
103                OperationImpact::Mutation | OperationImpact::Destructive,
104                RequestSemantics::Safe,
105                _,
106            ) => {
107                return Err(OperationMetadataError::StateChangeCannotBeSafe);
108            }
109            (_, RequestSemantics::NonIdempotent, RetryEligibility::ExplicitPolicy) => {
110                return Err(OperationMetadataError::NonIdempotentRetry);
111            }
112            _ => {}
113        }
114        Ok(Self {
115            impact,
116            semantics,
117            retry,
118            cost,
119            request_id,
120        })
121    }
122
123    /// Returns provider-state impact.
124    #[must_use]
125    pub const fn impact(self) -> OperationImpact {
126        self.impact
127    }
128
129    /// Returns HTTP request semantics.
130    #[must_use]
131    pub const fn semantics(self) -> RequestSemantics {
132        self.semantics
133    }
134
135    /// Returns explicit retry eligibility.
136    #[must_use]
137    pub const fn retry_eligibility(self) -> RetryEligibility {
138        self.retry
139    }
140
141    /// Returns direct cost intent.
142    #[must_use]
143    pub const fn cost_intent(self) -> CostIntent {
144        self.cost
145    }
146
147    /// Returns the provider request-identifier lifecycle policy.
148    #[must_use]
149    pub const fn request_id_policy(self) -> RequestIdPolicy {
150        self.request_id
151    }
152}