1use std::sync::Arc;
18use super::manager::Entry;
22use super::{Binding, Implementations, WireImplementations};
23use super::{Context, OperationManager};
24use crate::catalog::{Catalog, Projection, Visitor};
25
26#[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
27use crate::operations::Declarations;
28use crate::operations::{AuthoredDeclarations, Definition, OperationDeclaration, WireDeclaration};
29#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
30use crate::plugin::PluginScope;
31use crate::schema::Described;
32use crate::{Error, Message, Result, operations::Lane};
33
34type BindCatalog = dyn Fn(&OperationManager) -> Result<()> + Send + Sync;
41
42pub(super) struct WireProjection {
45 declaration: std::any::TypeId,
46 contract: crate::operations::Contract,
47 dispatch: Option<super::wire::Dispatch>,
48
49 #[cfg(feature = "grpc")]
50 grpc: Option<crate::grpc::routing::Handler>,
51}
52
53#[derive(Clone)]
54pub(crate) struct CatalogBinding {
55 bind: Arc<BindCatalog>,
56}
57
58pub struct Bindings<'a> {
59 pub(super) operations: &'a OperationManager,
60 owner: &'a str,
61
62 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
63 pub(super) scope: Option<&'a PluginScope>,
64
65 pub(super) entries: Vec<Arc<Entry>>,
66}
67
68#[must_use = "retain plugin operation registration until shutdown settles its invocations"]
69#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
70pub struct PluginRegistration {
71 scope: Arc<PluginScope>,
72 operations: OperationManager,
73 entries: Vec<Arc<Entry>>,
74}
75
76impl CatalogBinding {
83 pub(crate) fn operation<T>(owner: String, lane: impl Lane, resources: Arc<T::Resources>) -> Self
84 where
85 T: for<'ctx> super::Operation<Context<'ctx> = Context<'ctx>>,
86 {
87 let lane = lane.name();
88 Self { bind: Arc::new(move |operations| operations.register_operation_at::<T>(owner.clone(), lane, resources.clone())) }
89 }
90
91 pub(crate) fn local<C, R>(owner: String, lane: impl Lane, resources: Arc<R>) -> Self
92 where
93 C: Catalog<AuthoredDeclarations> + Catalog<Implementations<R>>,
94 R: Send + Sync + 'static,
95 {
96 let lane = lane.name();
97 Self {
98 bind: Arc::new(move |operations| {
99 operations.register_with(owner.clone(), |bindings| {
100 bindings.project_owned_at::<C, Implementations<R>, R>(lane, Arc::clone(&resources))
101 })
102 }),
103 }
104 }
105
106 pub(crate) fn wire<C, R>(owner: String, lane: impl Lane, resources: Arc<R>) -> Self
107 where
108 C: Catalog<AuthoredDeclarations> + Catalog<WireImplementations<R>>,
109 R: Send + Sync + 'static,
110 {
111 let lane = lane.name();
112 Self {
113 bind: Arc::new(move |operations| {
114 operations.register_with(owner.clone(), |bindings| {
115 bindings.project_owned_at::<C, WireImplementations<R>, R>(lane, Arc::clone(&resources))
116 })
117 }),
118 }
119 }
120
121 pub(crate) fn bind(&self, operations: &OperationManager) -> Result<()> {
122 (self.bind)(operations)
123 }
124}
125
126impl OperationManager {
127 pub fn register_operations<C, R>(&self, owner: impl Into<String>, lane: impl Lane, resources: Arc<R>) -> Result<()>
128 where
129 R: Send + Sync + 'static,
130 C: Catalog<AuthoredDeclarations> + Catalog<Implementations<R>>,
131 {
132 self.register_with(owner, |bindings| bindings.register_operations::<C, R>(lane, resources))
133 }
134
135 pub fn register_wire_operations<C, R>(&self, owner: impl Into<String>, lane: impl Lane, resources: Arc<R>) -> Result<()>
136 where
137 R: Send + Sync + 'static,
138 C: Catalog<AuthoredDeclarations> + Catalog<WireImplementations<R>>,
139 {
140 self.register_with(owner, |bindings| bindings.register_wire_operations::<C, R>(lane, resources))
141 }
142
143 pub(super) fn register_with(&self, owner: impl Into<String>, configure: impl FnOnce(&mut Bindings<'_>) -> Result<()>) -> Result<()> {
144 let owner = owner.into();
145 let mut bindings = Bindings {
146 operations: self,
147 owner: &owner,
148 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
149 scope: None,
150 entries: Vec::new(),
151 };
152 configure(&mut bindings)?;
153 self.reserve(&bindings.entries)
154 }
155
156 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
157 pub fn stage_plugin(&self, scope: &PluginScope, configure: impl FnOnce(&mut Bindings<'_>) -> Result<()>) -> Result<PluginRegistration> {
158 if scope.is_closed() {
159 return Err(Error::Closing { domain: "plugin scope" });
160 }
161 if scope.has_committed() {
162 return Err(Error::InvalidInput { field: "operation catalog", reason: "plugin generation is already committed" });
163 }
164 let mut bindings = Bindings { operations: self, owner: scope.owner(), scope: Some(scope), entries: Vec::new() };
165 configure(&mut bindings)?;
166 scope.validate_operations(bindings.entries.iter().map(|entry| entry.declared()))?;
167 scope.validate_wire_contracts(bindings.entries.iter().filter_map(|entry| entry.contract.as_ref()))?;
168 self.reserve(&bindings.entries)?;
169 #[cfg(feature = "plugin-sidecar")]
170 for entry in &bindings.entries {
171 if let Some(native) = &entry.native
172 && let Err(error) = native.capture_observe_contract()
173 {
174 self.fence(&bindings.entries, true);
175 self.remove_settled(&bindings.entries);
176 return Err(error);
177 }
178 }
179 let receipt_scope = Arc::new(scope.clone());
180 let staged = self
181 .track_staging(&receipt_scope)
182 .and_then(|_| self.validate_staging(&bindings.entries))
183 .and_then(|_| scope.bind_operations())
184 .and_then(|_| self.validate_staging(&bindings.entries));
185 if let Err(error) = staged {
186 scope.close_staging();
187 self.fence(&bindings.entries, true);
188 self.remove_settled(&bindings.entries);
189 return Err(error);
190 }
191 Ok(PluginRegistration { scope: receipt_scope, operations: self.clone(), entries: bindings.entries })
192 }
193}
194
195impl Bindings<'_> {
196 pub fn register_operations<C, R>(&mut self, lane: impl Lane, resources: Arc<R>) -> Result<()>
197 where
198 R: Send + Sync + 'static,
199 C: Catalog<AuthoredDeclarations> + Catalog<Implementations<R>>,
200 {
201 self.project_owned_at::<C, Implementations<R>, R>(lane.name(), resources)
202 }
203
204 pub fn register_wire_operations<C, R>(&mut self, lane: impl Lane, resources: Arc<R>) -> Result<()>
205 where
206 R: Send + Sync + 'static,
207 C: Catalog<AuthoredDeclarations> + Catalog<WireImplementations<R>>,
208 {
209 self.project_owned_at::<C, WireImplementations<R>, R>(lane.name(), resources)
210 }
211
212 pub fn register_operation<T>(&mut self, lane: impl Lane, resources: Arc<T::Resources>) -> Result<()>
213 where
214 T: for<'ctx> super::Operation<Context<'ctx> = Context<'ctx>>,
215 {
216 self.register_owned_at::<T>(lane.name(), resources)
217 }
218
219 pub(super) fn register_owned_at<T>(&mut self, lane: &'static str, resources: Arc<T::Resources>) -> Result<()>
220 where
221 T: for<'ctx> super::Operation<Context<'ctx> = Context<'ctx>>,
222 {
223 self.register_operation_id_at::<T>(T::Declaration::ID, lane, resources)
224 }
225
226 pub fn register_operation_id<T>(&mut self, id: impl Into<String>, lane: impl Lane, resources: Arc<T::Resources>) -> Result<()>
227 where
228 T: for<'ctx> super::Operation<Context<'ctx> = Context<'ctx>>,
229 {
230 self.register_operation_id_at::<T>(id, lane.name(), resources)
231 }
232
233 fn register_operation_id_at<T>(&mut self, id: impl Into<String>, lane: &'static str, resources: Arc<T::Resources>) -> Result<()>
234 where
235 T: for<'ctx> super::Operation<Context<'ctx> = Context<'ctx>>,
236 {
237 let mut definition = Definition::of::<T::Declaration>().map_err(|source| Error::OperationDeclaration { source })?;
238 definition.id = crate::Identifier::new(id.into()).map_err(|source| Error::OperationDeclaration { source: source.into() })?;
239 let id = definition.id.as_str();
240 if self.entries.iter().any(|entry| entry.id() == id) {
241 return Err(Error::AlreadyRegistered { domain: "operation", name: id.to_owned() });
242 }
243 let mut projected = project::<T::Declaration>()?;
244 if projected.declaration != std::any::TypeId::of::<T::Declaration>() {
245 return Err(Error::InvalidInput {
246 field: "operation wire declaration",
247 reason: "wire projection must match the original typed declaration",
248 });
249 }
250 if projected.contract.operation() != &definition {
252 let original = &projected.contract;
253 if original.grpc().is_some() {
254 return Err(Error::InvalidInput {
255 field: "operation wire declaration",
256 reason: "an operation that binds a gRPC method keeps its declared identity",
257 });
258 }
259 projected.contract =
260 crate::operations::Contract::new(definition.clone(), original.version(), original.codec(), original.schema().clone())
261 .map_err(|source| Error::OperationFailed { operation: definition.id.to_string(), source: Arc::new(source) })?;
262 }
263 let projected = Some(projected);
264 let mut entry = self.operations.binding_owned_definition::<T>(
265 self.owner,
266 lane,
267 resources,
268 definition,
269 projected.as_ref().and_then(|wire| wire.dispatch),
270 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
271 self.scope.cloned(),
272 )?;
273 if let Some(projected) = projected {
274 let original = Arc::get_mut(&mut entry).expect("new owned entry has one owner");
275 original.contract = Some(projected.contract);
276 #[cfg(feature = "grpc")]
277 {
278 original.grpc = projected.grpc;
279 }
280 }
281 self.entries.push(entry);
282 Ok(())
283 }
284
285 pub fn register_wire_operation<T>(&mut self, lane: impl Lane, resources: Arc<T::Resources>) -> Result<()>
286 where
287 T: for<'ctx> super::Operation<Context<'ctx> = Context<'ctx>>,
288 T::Declaration: crate::operations::OperationDeclaration,
289 T::Declaration: Message + Described,
290 {
291 self.register_owned_wire_at::<T>(lane.name(), resources)
292 }
293
294 pub(super) fn register_owned_wire_at<T>(&mut self, lane: &'static str, resources: Arc<T::Resources>) -> Result<()>
295 where
296 T: for<'ctx> super::Operation<Context<'ctx> = Context<'ctx>>,
297 T::Declaration: crate::operations::OperationDeclaration,
298 T::Declaration: Message + Described,
299 {
300 let contract = WireDeclaration::of::<T::Declaration>()
301 .contract()
302 .map_err(|source| Error::OperationFailed { operation: T::Declaration::ID.to_owned(), source: Arc::new(source) })?;
303 self.register_wire_contract_at::<T>(&contract, lane, resources)
304 }
305
306 pub fn register_wire_contract<T>(
307 &mut self, contract: &crate::operations::Contract, lane: impl Lane, resources: Arc<T::Resources>,
308 ) -> Result<()>
309 where
310 T: for<'ctx> super::Operation<Context<'ctx> = Context<'ctx>>,
311 T::Declaration: crate::operations::OperationDeclaration,
312 T::Declaration: Message + Described,
313 {
314 self.register_wire_contract_at::<T>(contract, lane.name(), resources)
315 }
316
317 fn register_wire_contract_at<T>(
318 &mut self, contract: &crate::operations::Contract, lane: &'static str, resources: Arc<T::Resources>,
319 ) -> Result<()>
320 where
321 T: for<'ctx> super::Operation<Context<'ctx> = Context<'ctx>>,
322 T::Declaration: crate::operations::OperationDeclaration,
323 T::Declaration: Message + Described,
324 {
325 let projected = project::<T::Declaration>()?;
326 let declared = &projected.contract;
327 let mut definition = declared.operation().clone();
328 definition.id = contract.operation().id.clone();
329 if definition != *contract.operation()
330 || declared.version() != contract.version()
331 || declared.codec() != contract.codec()
332 || ![
333 (Some(declared.request()), Some(contract.request())),
334 (Some(declared.response()), Some(contract.response())),
335 (declared.input(), contract.input()),
336 (declared.output(), contract.output()),
337 ]
338 .into_iter()
339 .all(|(left, right)| match (left, right) {
340 (None, None) => true,
341 (Some(left), Some(right)) => left
342 .schema()
343 .equivalent(left.index(), right.schema(), right.index(), &harmos_domain::config::SchemaSettings::default())
344 .unwrap_or(false),
345 _ => false,
346 })
347 || declared.grpc() != contract.grpc()
348 {
349 return Err(Error::InvalidInput { field: "operation contract", reason: "runtime binding may override identity only" });
350 }
351 let id = contract.operation().id.as_str();
352 if self.entries.iter().any(|entry| entry.id() == id) {
353 return Err(Error::AlreadyRegistered { domain: "operation", name: id.to_owned() });
354 }
355 let mut entry = self.operations.binding_owned_definition::<T>(
356 self.owner,
357 lane,
358 resources,
359 contract.operation().clone(),
360 projected.dispatch,
361 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
362 self.scope.cloned(),
363 )?;
364 let original = Arc::get_mut(&mut entry).expect("new owned entry has one owner");
365 original.contract = Some(contract.clone());
366 #[cfg(feature = "grpc")]
367 {
368 original.grpc = projected.grpc;
369 }
370 self.entries.push(entry);
371 Ok(())
372 }
373
374 #[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
375 pub(super) fn project<C, V, H>(&mut self, lane: impl Lane, handler: Arc<H>) -> Result<()>
376 where
377 C: Catalog<Declarations> + Catalog<V>,
378 V: Visitor<Entry = Binding<H>>,
379 H: Send + Sync + 'static,
380 {
381 self.project_at::<C, V, H>(lane.name(), handler)
382 }
383
384 #[cfg(any(feature = "plugin-sidecar", all(feature = "plugin-guest", not(target_arch = "wasm32"))))]
385 fn project_at<C, V, H>(&mut self, lane: &'static str, handler: Arc<H>) -> Result<()>
386 where
387 C: Catalog<Declarations> + Catalog<V>,
388 V: Visitor<Entry = Binding<H>>,
389 H: Send + Sync + 'static,
390 {
391 let definitions = Declarations::of::<C>().map_err(|source| Error::OperationDeclaration { source })?;
392 self.apply_projection(lane, handler, &definitions, &<C as Catalog<V>>::ENTRIES)
393 }
394
395 fn project_owned_at<C, V, R>(&mut self, lane: &'static str, resources: Arc<R>) -> Result<()>
396 where
397 C: Catalog<AuthoredDeclarations> + Catalog<V>,
398 V: Visitor<Entry = Binding<R>>,
399 R: Send + Sync + 'static,
400 {
401 let definitions = AuthoredDeclarations::of::<C>().map_err(|source| Error::OperationDeclaration { source })?;
402 self.apply_projection(lane, resources, &definitions, &<C as Catalog<V>>::ENTRIES)
403 }
404
405 fn apply_projection<H: Send + Sync + 'static>(
406 &mut self, lane: &'static str, handler: Arc<H>, definitions: &[Definition], entries: &impl Projection<Binding<H>>,
407 ) -> Result<()> {
408 let previous = self.entries.len();
409 if let Err(error) = entries.try_for_each(|entry| entry.apply(self, lane, Arc::clone(&handler))) {
410 self.entries.truncate(previous);
411 return Err(error);
412 }
413 let projected = &self.entries[previous..];
414 if projected.len() != definitions.len() || projected.iter().zip(definitions).any(|(entry, declared)| entry.declared() != declared) {
415 self.entries.truncate(previous);
416 return Err(Error::InvalidInput { field: "operation catalog", reason: "metadata and handler projections differ" });
417 }
418 Ok(())
419 }
420}
421
422#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
423impl PluginRegistration {
424 pub async fn shutdown(&mut self) -> Result<()> {
425 self.scope.unbind_operations();
426 self.operations.fence(&self.entries, false);
427 self.operations.settle_entries(&self.entries).await?;
428 self.entries.clear();
429 Ok(())
430 }
431}
432
433#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
434impl Drop for PluginRegistration {
435 fn drop(&mut self) {
436 self.scope.close_staging();
437 self.scope.unbind_operations();
438 self.operations.fence(&self.entries, true);
439 self.operations.remove_settled(&self.entries);
440 }
443}
444
445pub(super) fn project<O: crate::operations::OperationDeclaration>() -> Result<WireProjection> {
450 let contract = WireDeclaration::of::<O>()
451 .contract()
452 .map_err(|source| Error::OperationFailed { operation: O::ID.to_owned(), source: Arc::new(source) })?;
453 let streaming = contract.input().is_some() || contract.output().is_some();
454 #[cfg(feature = "grpc")]
455 let grpc = contract.grpc().map(|_| crate::grpc::routing::handler::<O>());
456 Ok(WireProjection {
457 declaration: std::any::TypeId::of::<O>(),
458 contract,
459 dispatch: if streaming { None } else { Some(super::wire::dispatch::<O>) },
460 #[cfg(feature = "grpc")]
461 grpc,
462 })
463}
464
465