Skip to main content

harmos_domain/operations/common/
mod.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12mod catalog;
13mod context;
14mod contract;
15mod direction;
16mod grpc;
17pub mod instance;
18mod key;
19pub(crate) mod payload;
20mod policy;
21
22/* === Re-Exports === */
23
24pub use crate::observers;
25pub use catalog::{Declarations, Entry};
26pub use context::{CallContext, CallValue, ContextError, ContextValue, PORTABLE_BYTES, PORTABLE_VALUES};
27pub use contract::{Codec, Contract, ContractError, Contracts, WireDeclaration};
28pub use direction::{Direction, NoStream, Stream, item_types};
29pub use grpc::Grpc;
30pub use key::{CacheKey, CacheKeyError, MAX_CACHE_KEY_BYTES};
31pub use policy::{Backoff, FailureKind, Jitter, OperationPolicy, PolicyError, ReplaySafety, RestartPolicy, RetryPolicy};
32
33/* === Imports === */
34
35// Standard Library Imports
36
37// Third Party Imports
38use serde::{Deserialize, Serialize};
39// Local Imports
40use crate::Identifier;
41
42/* === Constants === */
43
44// A deadline no caller reaches: resident and watch policies run until stopped.
45pub const UNBOUNDED: std::time::Duration = std::time::Duration::from_secs(1 << 32);
46
47/* === Macros === */
48
49// Declare An Operation On Its Request Type. Fields Keep This Order; Optional
50// Ones May Be Omitted. Absent Streams Are `NoStream`.
51#[macro_export]
52macro_rules! declare {
53    ($ty:ty {
54        id: $id:expr,
55        version: $version:expr,
56        policy: $policy:expr,
57        response: $response:ty
58        $(, input: $input:ty)?
59        $(, output: $output:ty)?
60        $(, description: $description:expr)?
61        $(, visibility: $visibility:expr)?
62        $(, replay: $replay:expr)?
63        $(, grpc: ($package:expr, $service:expr, $method:expr))?
64        $(, grant: $grant:expr)?
65        $(, key: $key:expr)?
66        $(,)?
67    }) => {
68        impl $crate::operations::OperationDeclaration for $ty {
69            type Response = $response;
70            type Input = $crate::declare!(@direction $($input)?);
71            type Output = $crate::declare!(@direction $($output)?);
72            const ID: &'static str = $id;
73            const VERSION: u32 = $version;
74            const POLICY: $crate::operations::OperationPolicy = $policy;
75            $(const DESCRIPTION: &'static str = $description;)?
76            $(const VISIBILITY: $crate::operations::Visibility = $visibility;)?
77            $(const REPLAY: $crate::operations::ReplaySafety = $replay;)?
78            $(const GRPC: ::core::option::Option<$crate::operations::Grpc> =
79                ::core::option::Option::Some($crate::operations::Grpc::new($package, $service, $method));)?
80            $(fn grant(&self) -> ::core::option::Option<$crate::plugin::Grant> {
81                ($grant)(self)
82            })?
83            $(fn key(&self) -> ::core::option::Option<$crate::operations::CacheKey> {
84                ($key)(self)
85            })?
86        }
87    };
88    (@direction) => { $crate::operations::NoStream };
89    (@direction $item:ty) => { $crate::operations::Stream<$item> };
90}
91
92/* === Type Aliases === */
93
94/* === Structs === */
95
96#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
97pub struct Definition {
98    pub id: Identifier,
99    pub description: String,
100    pub policy: OperationPolicy,
101    pub visibility: Visibility,
102    pub replay: ReplaySafety,
103}
104
105// The decoder constructs the canonical type directly, then checks admission.
106// No second public metadata contract or JSON-value staging exists.
107#[derive(Deserialize)]
108#[serde(remote = "Definition", deny_unknown_fields)]
109struct DecodedDefinition {
110    id: Identifier,
111    description: String,
112    policy: OperationPolicy,
113    visibility: Visibility,
114    replay: ReplaySafety,
115}
116
117/* === Enums === */
118
119#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
120#[serde(rename_all = "snake_case")]
121pub enum Visibility {
122    Private,
123    Shared,
124    Host,
125}
126
127/* === Traits === */
128
129// The Operation Is Its Request Type. `declare!` (Through `#[operation]`) Is The
130// Only Writer Of This Impl; Every Bound A Projection Needs Is Named Here, So One
131// Generic Walk Serves The Catalog, gRPC, Descriptor Export And Bindings.
132pub trait OperationDeclaration: crate::Portable {
133    type Response: crate::Portable;
134    type Input: Direction;
135    type Output: Direction;
136
137    const ID: &'static str;
138    const VERSION: u32;
139    const POLICY: OperationPolicy;
140    const DESCRIPTION: &'static str = "";
141    const VISIBILITY: Visibility = Visibility::Private;
142    const REPLAY: ReplaySafety = ReplaySafety::Unknown;
143    const GRPC: Option<Grpc> = None;
144
145    fn grant(&self) -> Option<crate::plugin::Grant> {
146        None
147    }
148
149    fn key(&self) -> Option<CacheKey> {
150        None
151    }
152}
153
154pub trait Declares {
155    type Declaration: OperationDeclaration;
156}
157
158/* === Implementations === */
159
160impl<O: OperationDeclaration> Declares for O {
161    type Declaration = O;
162}
163
164impl<'de> Deserialize<'de> for Definition {
165    fn deserialize<D: serde::Deserializer<'de>>(decoder: D) -> Result<Self, D::Error> {
166        let definition = DecodedDefinition::deserialize(decoder)?;
167        definition.validate().map_err(serde::de::Error::custom)?;
168        Ok(definition)
169    }
170}
171
172impl Definition {
173    pub fn validate(&self) -> crate::Result<()> {
174        if crate::reserved(self.id.as_str()) {
175            return Err(crate::Error::ReservedOperation { id: self.id.as_str().to_owned() });
176        }
177        self.policy.validate()?;
178        Ok(())
179    }
180
181    pub fn of<O: OperationDeclaration>() -> crate::Result<Self> {
182        Entry::of::<O>().definition()
183    }
184}
185
186/* === Public Functions === */
187
188/* === Private Functions === */
189
190/* === Tests === */