Skip to main content

harmos_domain/model/operations/
lifecycle.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17use std::borrow::Cow;
18// Third Party Imports
19use serde::{Deserialize, Serialize};
20// Local Imports
21use crate::operations::{Contract, ContractError, OperationPolicy, PolicyError, RetryPolicy};
22use crate::schema::declaration::{Field, Kind};
23use crate::schema::{Cardinality, Described};
24
25/* === Constants === */
26
27// The Declared Bound Of Both Actions; Each Plugin Binds Its Own `Policy`.
28pub const POLICY: OperationPolicy = OperationPolicy {
29    timeout: std::time::Duration::from_secs(30),
30    attempt_timeout: std::time::Duration::from_secs(30),
31    retries: RetryPolicy::Never,
32};
33
34/* === Macros === */
35
36/* === Type Aliases === */
37
38/* === Structs === */
39
40#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(into = "EncodedDescriptor", try_from = "EncodedDescriptor")]
42pub struct Descriptor {
43    action: Action,
44    contract: Contract,
45}
46
47#[derive(Serialize, Deserialize)]
48#[serde(deny_unknown_fields)]
49struct EncodedDescriptor {
50    action: Action,
51    fingerprint: crate::Fingerprint,
52    policy: OperationPolicy,
53}
54
55/* === Enums === */
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(rename_all = "snake_case")]
59pub enum Action {
60    Initialize,
61    Shutdown,
62}
63
64#[derive(Debug, thiserror::Error)]
65pub enum Error {
66    #[error("invalid SDK lifecycle policy: {0}")]
67    Policy(#[from] PolicyError),
68
69    #[error("SDK lifecycle operations require explicit retry, never automatic replay")]
70    AutomaticRetry,
71
72    #[error("unsupported SDK lifecycle fingerprint {0}")]
73    Fingerprint(crate::Fingerprint),
74
75    #[error("invalid SDK lifecycle contract: {0}")]
76    Contract(#[from] ContractError),
77}
78
79/* === Traits === */
80
81pub trait Policy: 'static {
82    const INITIALIZATION_POLICY: OperationPolicy;
83    const SHUTDOWN_POLICY: OperationPolicy;
84}
85
86/* === Implementations === */
87
88pub struct InitializationSchema;
89pub struct ShutdownSchema;
90impl Described for InitializationSchema {
91    const NAME: &'static str = "harmos.lifecycle.Initialize";
92    const FIELDS: &'static [Field] = &[
93        Field {
94            name: Cow::Borrowed("config_json"),
95            tag: 1,
96            kind: Kind::Bytes,
97            cardinality: Cardinality::Required,
98        },
99        Field {
100            name: Cow::Borrowed("generation_context"),
101            tag: 2,
102            kind: Kind::Bytes,
103            cardinality: Cardinality::Required,
104        },
105        Field {
106            name: Cow::Borrowed("assignments"),
107            tag: 3,
108            kind: Kind::Bytes,
109            cardinality: Cardinality::Required,
110        },
111    ];
112    const MARKER: &'static [u8] = &crate::schema::declaration::encode::<
113        { crate::schema::declaration::encoded_len(Self::NAME, Self::FIELDS) },
114    >(Self::NAME, Self::FIELDS);
115}
116
117impl Described for ShutdownSchema {
118    const NAME: &'static str = "harmos.lifecycle.Shutdown";
119    const FIELDS: &'static [Field] = &[];
120    const MARKER: &'static [u8] = &crate::schema::declaration::encode::<
121        { crate::schema::declaration::encoded_len(Self::NAME, Self::FIELDS) },
122    >(Self::NAME, Self::FIELDS);
123}
124
125impl Descriptor {
126    pub fn new(action: Action, policy: OperationPolicy) -> Result<Self, Error> {
127        policy.validate()?;
128        if policy.retries != RetryPolicy::Never {
129            return Err(Error::AutomaticRetry);
130        }
131        let contract = Contract::lifecycle(action, policy)?;
132        Ok(Self { action, contract })
133    }
134
135    pub fn of_initialize<P: Policy>() -> Result<Self, Error> {
136        Self::new(Action::Initialize, P::INITIALIZATION_POLICY)
137    }
138
139    pub fn of_shutdown<P: Policy>() -> Result<Self, Error> {
140        Self::new(Action::Shutdown, P::SHUTDOWN_POLICY)
141    }
142
143    pub fn action(&self) -> Action {
144        self.action
145    }
146
147    pub fn contract(&self) -> &Contract {
148        &self.contract
149    }
150}
151
152impl From<Descriptor> for Contract {
153    fn from(descriptor: Descriptor) -> Self {
154        descriptor.contract
155    }
156}
157
158impl From<Descriptor> for EncodedDescriptor {
159    fn from(descriptor: Descriptor) -> Self {
160        Self {
161            action: descriptor.action,
162            fingerprint: descriptor.contract.fingerprint(),
163            policy: descriptor.contract.operation().policy,
164        }
165    }
166}
167
168impl TryFrom<EncodedDescriptor> for Descriptor {
169    type Error = Error;
170
171    fn try_from(encoded: EncodedDescriptor) -> Result<Self, Error> {
172        let descriptor = Self::new(encoded.action, encoded.policy)?;
173        if descriptor.contract.fingerprint() != encoded.fingerprint {
174            return Err(Error::Fingerprint(encoded.fingerprint));
175        }
176        Ok(descriptor)
177    }
178}
179
180/* === Public Functions === */
181
182/* === Private Functions === */
183
184/* === Tests === */
185
186impl Action {
187    pub const fn id(self) -> &'static str {
188        match self {
189            Self::Initialize => "harmos.initialize",
190            Self::Shutdown => "harmos.shutdown",
191        }
192    }
193    pub const fn request(self) -> crate::schema::declaration::Definition {
194        match self {
195            Self::Initialize => InitializationSchema::DEFINITION,
196            Self::Shutdown => ShutdownSchema::DEFINITION,
197        }
198    }
199}