harmos_domain/model/operations/
lifecycle.rs1use std::borrow::Cow;
18use serde::{Deserialize, Serialize};
20use crate::operations::{Contract, ContractError, OperationPolicy, PolicyError, RetryPolicy};
22use crate::schema::declaration::{Field, Kind};
23use crate::schema::{Cardinality, Described};
24
25pub 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#[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#[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
79pub trait Policy: 'static {
82 const INITIALIZATION_POLICY: OperationPolicy;
83 const SHUTDOWN_POLICY: OperationPolicy;
84}
85
86pub 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
180impl 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}