Skip to main content

harmos_domain/operations/lifecycle/
plugin.rs

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