harmos_runtime/operations/
registration.rs1use std::sync::Arc;
18#[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
22use super::Binding;
23use super::Manager;
24#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
25use super::manager::Entry;
26#[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
27use crate::catalog::Projection;
28#[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
29use crate::catalog::{Catalog, Visitor};
30
31#[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
32use crate::operations::Declarations;
33use crate::operations::OperationDeclaration;
34#[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
35use crate::operations::OperationDefinition;
36#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
37use crate::plugin::PluginScope;
38
39#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
40use crate::Error;
41use crate::{Result, operations::Lane};
42
43type BindCatalog = dyn Fn(&Manager) -> Result<()> + Send + Sync;
50
51#[derive(Clone)]
54pub struct Registration<L: Lane> {
55 pub(crate) batch: harmos_sdk::RegistrationBatch,
56 pub(crate) lane: Option<L>,
57 pub(crate) defaults: std::collections::BTreeMap<&'static str, L>,
58}
59
60#[derive(Clone)]
61pub(crate) struct CatalogBinding {
62 bind: Arc<BindCatalog>,
63}
64
65#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
66pub struct Bindings<'a> {
67 pub(super) operations: &'a Manager,
68 owner: &'a str,
69
70 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
71 pub(super) scope: Option<&'a PluginScope>,
72
73 pub(super) entries: Vec<Arc<Entry>>,
74}
75
76#[must_use = "retain plugin operation registration until shutdown settles its invocations"]
77#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
78pub struct PluginRegistration {
79 scope: Arc<PluginScope>,
80 operations: Manager,
81 entries: Vec<Arc<Entry>>,
82}
83
84pub trait RegistrationExt {
89 fn lane<L: Lane>(self, lane: L) -> Registration<L>;
90 fn defaults<L: Lane>(self) -> Registration<L>;
91}
92
93impl RegistrationExt for harmos_sdk::RegistrationBatch {
96 fn lane<L: Lane>(self, lane: L) -> Registration<L> {
97 Registration {
98 batch: self,
99 lane: Some(lane),
100 defaults: std::collections::BTreeMap::new(),
101 }
102 }
103
104 fn defaults<L: Lane>(self) -> Registration<L> {
105 Registration {
106 batch: self,
107 lane: None,
108 defaults: std::collections::BTreeMap::new(),
109 }
110 }
111}
112
113impl<L: Lane> Registration<L> {
114 pub fn default_lane_for<O: OperationDeclaration>(mut self, lane: L) -> Self {
115 self.defaults.insert(O::ID, lane);
116 self
117 }
118}
119
120impl CatalogBinding {
121 pub(crate) fn new<L: Lane>(registration: Registration<L>) -> Self {
122 Self {
123 bind: Arc::new(move |operations| operations.register_native(registration.clone())),
124 }
125 }
126 pub(crate) fn bind(&self, operations: &Manager) -> Result<()> {
127 (self.bind)(operations)
128 }
129}
130
131impl Manager {
132 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
133 pub fn stage_plugin(&self, scope: &PluginScope, configure: impl FnOnce(&mut Bindings<'_>) -> Result<()>) -> Result<PluginRegistration> {
134 if scope.is_closed() {
135 return Err(Error::Closing { domain: "plugin scope" });
136 }
137 if scope.has_committed() {
138 return Err(Error::InvalidInput {
139 field: "operation catalog",
140 reason: "plugin generation is already committed",
141 });
142 }
143 let mut bindings = Bindings {
144 operations: self,
145 owner: scope.owner(),
146 scope: Some(scope),
147 entries: Vec::new(),
148 };
149 configure(&mut bindings)?;
150 scope.validate_operations(bindings.entries.iter().map(|entry| entry.declared()))?;
151 scope.validate_wire_contracts(bindings.entries.iter().filter_map(|entry| entry.contract.as_ref()))?;
152 self.reserve(&bindings.entries)?;
153 #[cfg(feature = "plugin-sidecar")]
154 for entry in &bindings.entries {
155 if let Some(native) = &entry.native
156 && let Err(error) = native.capture_observe_contract()
157 {
158 self.fence(&bindings.entries, true);
159 self.remove_settled(&bindings.entries);
160 return Err(error);
161 }
162 }
163 let receipt_scope = Arc::new(scope.clone());
164 let staged = self
165 .track_staging(&receipt_scope)
166 .and_then(|_| self.validate_staging(&bindings.entries))
167 .and_then(|_| scope.bind_operations())
168 .and_then(|_| self.validate_staging(&bindings.entries));
169 if let Err(error) = staged {
170 scope.close_staging();
171 self.fence(&bindings.entries, true);
172 self.remove_settled(&bindings.entries);
173 return Err(error);
174 }
175 Ok(PluginRegistration {
176 scope: receipt_scope,
177 operations: self.clone(),
178 entries: bindings.entries,
179 })
180 }
181}
182
183#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
184impl Bindings<'_> {
185 pub fn register<L: Lane>(&mut self, registration: Registration<L>) -> Result<()> {
186 let entries = self.operations.native_entries(registration, self.owner, self.scope)?;
187 self.entries.extend(entries);
188 Ok(())
189 }
190
191 #[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
192 pub(super) fn stage_implementation<D, O>(&mut self, lane: &'static str, dependencies: harmos_sdk::Dependencies) -> Result<()>
193 where
194 D: OperationDeclaration,
195 O: harmos_sdk::operations::Carrier<Declaration = D>,
196 {
197 let contract = harmos_sdk::operations::WireDeclaration::of::<D>()
198 .contract_with_settings(&self.operations.schema_settings)
199 .map_err(|source| Error::OperationFailed {
200 operation: D::ID.to_owned(),
201 source: Arc::new(source),
202 })?;
203 let wire = (contract.input().is_none() && contract.output().is_none())
204 .then_some(crate::operations::wire::dispatch_native as crate::operations::wire::Dispatch);
205 let mut batch = harmos_sdk::RegistrationBatch::new(dependencies).carrier::<D, O>();
206 let factory = batch.operations.pop().expect("one native implementation");
207 let mut entry = self.operations.binding_erased(self.owner, lane, Arc::new(factory), D::definition(), wire, self.scope.cloned())?;
208 #[cfg(feature = "grpc")]
209 if contract.grpc().is_some() {
210 Arc::get_mut(&mut entry).expect("new native entry").grpc = Some(crate::grpc::routing::handler::<D>());
211 }
212 Arc::get_mut(&mut entry).expect("new native entry").contract = Some(contract);
213 self.entries.push(entry);
214 Ok(())
215 }
216}
217
218#[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
219impl Bindings<'_> {
220 #[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
221 pub(super) fn project<C, V, H>(&mut self, lane: impl Lane, handler: Arc<H>) -> Result<()>
222 where
223 C: Catalog<Declarations> + Catalog<V>,
224 V: Visitor<Entry = Binding<H>>,
225 H: Send + Sync + 'static,
226 {
227 self.project_at::<C, V, H>(lane.name(), handler)
228 }
229
230 #[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
231 fn project_at<C, V, H>(&mut self, lane: &'static str, handler: Arc<H>) -> Result<()>
232 where
233 C: Catalog<Declarations> + Catalog<V>,
234 V: Visitor<Entry = Binding<H>>,
235 H: Send + Sync + 'static,
236 {
237 let definitions = Declarations::of::<C>().map_err(|source| Error::OperationDeclaration { source })?;
238 self.apply_projection(lane, handler, &definitions, &<C as Catalog<V>>::ENTRIES)
239 }
240
241 fn apply_projection<H: Send + Sync + 'static>(
242 &mut self, lane: &'static str, handler: Arc<H>, definitions: &[OperationDefinition], entries: &impl Projection<Binding<H>>,
243 ) -> Result<()> {
244 let previous = self.entries.len();
245 if let Err(error) = entries.try_for_each(|entry| entry.apply(self, lane, Arc::clone(&handler))) {
246 self.entries.truncate(previous);
247 return Err(error);
248 }
249 let projected = &self.entries[previous..];
250 if projected.len() != definitions.len() || projected.iter().zip(definitions).any(|(entry, declared)| entry.declared() != declared) {
251 self.entries.truncate(previous);
252 return Err(Error::InvalidInput {
253 field: "operation catalog",
254 reason: "metadata and handler projections differ",
255 });
256 }
257 Ok(())
258 }
259}
260
261#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
262impl PluginRegistration {
263 pub async fn shutdown(&mut self) -> Result<()> {
264 self.scope.close();
265 self.entries = self.operations.scope_entries(&self.scope);
266 self.scope.unbind_operations();
267 self.operations.fence(&self.entries, false);
268 self.operations.settle_entries(&self.entries).await?;
269 self.operations.resident.shutdown_plugin(&self.scope).await?;
270 self.entries.clear();
271 Ok(())
272 }
273}
274
275#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
276impl Drop for PluginRegistration {
277 fn drop(&mut self) {
278 self.scope.close();
279 self.entries = self.operations.scope_entries(&self.scope);
280 self.scope.unbind_operations();
281 self.operations.fence(&self.entries, true);
282 self.operations.remove_settled(&self.entries);
283 }
286}
287
288