harmos_domain/operations/lifecycle/
plugin.rs1mod sealed {
13 pub trait Leaf {}
14 impl Leaf for super::Initialize {}
15 impl Leaf for super::Shutdown {}
16}
17
18use std::borrow::Cow;
24use serde::{Deserialize, Serialize};
26use crate::MemorySize;
28use crate::operations::{Contract, ContractError, OperationDeclaration, OperationPolicy, PolicyError, RestartPolicy, RetryPolicy};
29use crate::schema::declaration::{Field, Kind};
30use crate::schema::{Cardinality, Described};
31
32pub 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
42crate::declare!(Initialize { id: "harmos.initialize", version: 1, policy: POLICY, response: () });
45
46crate::declare!(Shutdown { id: "harmos.shutdown", version: 1, policy: POLICY, response: () });
47
48#[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#[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
103pub 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
114crate::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 EncodedDescriptor {
188 fn from(descriptor: Descriptor) -> Self {
189 Self { action: descriptor.action, version: descriptor.contract.version(), policy: descriptor.contract.operation().policy }
190 }
191}
192
193impl TryFrom<EncodedDescriptor> for Descriptor {
194 type Error = Error;
195
196 fn try_from(encoded: EncodedDescriptor) -> Result<Self, Error> {
197 if encoded.version != 1 {
198 return Err(Error::Version(encoded.version));
199 }
200 Self::new(encoded.action, encoded.policy)
201 }
202}
203
204