1use std::future::Future;
21
22use super::caller::Source;
26use super::{CallContext, Context, ContextError, ContextValue, Manager, OperationDeclaration, Response, Running};
27use super::{EndpointSet, Lane, RetryPolicy};
28use crate::{Error, Result};
29
30#[must_use = "an invocation runs only when start or run is awaited"]
39pub struct Invocation<O: OperationDeclaration, L: Lane, E: EndpointSet = ()> {
40 target: Target,
41 request: O::Request,
42 retry: Option<RetryPolicy>,
43 lane: Option<L>,
44 endpoints: E,
45}
46
47enum Target {
50 Host(Manager),
51
52 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
53 Plugin(crate::plugin::PluginScope),
54}
55
56impl<O: OperationDeclaration, L: Lane> Invocation<O, L> {
61 pub(crate) fn new(operations: Manager, request: O::Request) -> Self {
62 Self {
63 target: Target::Host(operations),
64 request,
65 retry: None,
66 lane: None,
67 endpoints: (),
68 }
69 }
70}
71
72impl<O: OperationDeclaration, L: Lane, E: EndpointSet> Invocation<O, L, E> {
73 pub fn retry(mut self, policy: RetryPolicy) -> Self {
74 self.retry = Some(policy);
75 self
76 }
77
78 pub fn lane(mut self, lane: L) -> Self {
79 self.lane = Some(lane);
80 self
81 }
82
83 pub fn endpoints<F: EndpointSet>(self, endpoints: F) -> Invocation<O, L, F> {
84 Invocation {
85 target: self.target,
86 request: self.request,
87 retry: self.retry,
88 lane: self.lane,
89 endpoints,
90 }
91 }
92
93 pub async fn start(self) -> Result<Running<O>> {
94 if E::item_types() != super::item_types::<O>() {
95 return Err(Error::InvalidInput {
96 field: "operation endpoints",
97 reason: "endpoint items differ from the declaration",
98 });
99 }
100 let mut request = super::binding::Request::from(self.request);
101 request.retry = self.retry;
102 request.lane = self.lane.map(|lane| lane.name());
103 let (operations, source) = match self.target {
104 Target::Host(operations) => (operations, Source::Host),
105 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
106 Target::Plugin(scope) => (scope.operation_manager()?, Source::Scope(scope)),
107 };
108 operations.admit::<O, E>((request, self.endpoints), None, &[], &source, None)
109 }
110
111 pub async fn run(self) -> Result<Response<O::Response>> {
112 self.start().await?.join().await
113 }
114}
115
116#[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
117impl crate::plugin::PluginScope {
118 pub fn operation<O: OperationDeclaration<Request = O>, L: Lane>(&self, request: O) -> Invocation<O, L> {
119 Invocation {
120 target: Target::Plugin(self.clone()),
121 request,
122 retry: None,
123 lane: None,
124 endpoints: (),
125 }
126 }
127
128 pub async fn register<C: harmos_sdk::operations::OperationCatalog, L: Lane>(&self, lane: L) -> Result<()> {
129 use super::RegistrationExt;
130 self.register_batch(C::batch().lane(lane)).await
131 }
132
133 pub(crate) async fn register_batch<L: Lane>(&self, mut registration: super::Registration<L>) -> Result<()> {
134 if !self.is_ready() {
135 return Err(Error::Closing { domain: "plugin scope" });
136 }
137 let operations = self.operation_manager()?;
138 let services = std::mem::take(&mut registration.batch.services);
139 if services.is_empty() {
140 let entries = operations.native_entries(registration, self.owner(), Some(self))?;
141 return operations.reserve(&entries);
142 }
143 let mut batch = harmos_sdk::__private::RegistrationBatch::new();
144 batch.services = services;
145 let manager = crate::ServiceManager::scoped(operations.clone(), self.clone());
146 let (staged, ids) = manager.stage(batch)?;
147 operations.register_scoped_services(registration, staged, self.owner(), Some(self))?;
148 manager.activate(&ids).await
149 }
150
151 pub async fn unregister(&self, id: &harmos_domain::QualifiedId) -> Result<()> {
152 let (owner, _) = id.as_str().split_once('.').expect("validated qualified identity");
153 if owner != self.owner() {
154 return Err(Error::PermissionDenied);
155 }
156 let operations = self.operation_manager()?;
157 if matches!(operations.resident.definition(id.as_str()), Some(super::resident::Definition::Service(_))) {
158 return crate::ServiceManager::scoped(operations, self.clone()).unregister(id).await;
159 }
160 operations.unregister(id).await
161 }
162}
163
164impl<A: crate::Application> crate::Runtime<A> {
165 pub fn services(&self) -> crate::ServiceManager {
166 crate::ServiceManager::new(self.operations.execution())
167 }
168
169 pub async fn register<C: harmos_sdk::operations::OperationCatalog>(&self, lane: A::Lanes) -> Result<()> {
170 use super::RegistrationExt;
171 self.register_batch(C::batch().lane(lane)).await
172 }
173
174 pub(crate) async fn register_batch(&self, mut registration: super::Registration<A::Lanes>) -> Result<()> {
175 let services = std::mem::take(&mut registration.batch.services);
176 if services.is_empty() {
177 return self.operations.register_batch(registration).await;
178 }
179 let mut batch = harmos_sdk::__private::RegistrationBatch::new();
180 batch.services = services;
181 let manager = self.services();
182 let (staged, ids) = manager.stage(batch)?;
183 self.operations.execution().register_with_services(registration, staged)?;
184 manager.activate(&ids).await
185 }
186
187 pub async fn unregister(&self, id: &harmos_domain::QualifiedId) -> Result<()> {
188 if !id.as_str().starts_with("host.") {
189 return Err(Error::PermissionDenied);
190 }
191 if matches!(self.operations.resident.definition(id.as_str()), Some(super::resident::Definition::Service(_))) {
192 return self.services().unregister(id).await;
193 }
194 self.operations.unregister(id).await
195 }
196
197 pub fn operation<O: OperationDeclaration<Request = O>>(&self, request: O) -> Invocation<O, A::Lanes> {
198 Invocation::new(self.operations.execution(), request)
199 }
200}
201
202impl<A: crate::Application> crate::runtime::Handle<A> {
203 pub fn services(&self) -> crate::ServiceManager {
204 crate::ServiceManager::new(self.operations.execution())
205 }
206
207 pub async fn register<C: harmos_sdk::operations::OperationCatalog>(&self, lane: A::Lanes) -> Result<()> {
208 use super::RegistrationExt;
209 self.register_batch(C::batch().lane(lane)).await
210 }
211
212 pub(crate) async fn register_batch(&self, mut registration: super::Registration<A::Lanes>) -> Result<()> {
213 let services = std::mem::take(&mut registration.batch.services);
214 if services.is_empty() {
215 return self.operations.register_batch(registration).await;
216 }
217 let mut batch = harmos_sdk::__private::RegistrationBatch::new();
218 batch.services = services;
219 let manager = self.services();
220 let (staged, ids) = manager.stage(batch)?;
221 self.operations.execution().register_with_services(registration, staged)?;
222 manager.activate(&ids).await
223 }
224
225 pub async fn unregister(&self, id: &harmos_domain::QualifiedId) -> Result<()> {
226 if !id.as_str().starts_with("host.") {
227 return Err(Error::PermissionDenied);
228 }
229 if matches!(self.operations.resident.definition(id.as_str()), Some(super::resident::Definition::Service(_))) {
230 return self.services().unregister(id).await;
231 }
232 self.operations.unregister(id).await
233 }
234
235 pub fn operation<O: OperationDeclaration<Request = O>>(&self, request: O) -> Invocation<O, A::Lanes> {
236 Invocation::new(self.operations.execution(), request)
237 }
238}
239
240impl<L: Lane> harmos_sdk::operations::Host for super::OperationManager<L> {
241 fn publish_on<S: harmos_domain::StreamSpec>(
242 &self, row: &S::Row,
243 ) -> impl std::future::Future<Output = std::result::Result<(), Self::Error>> + Send
244 where
245 S::Row: harmos_domain::Message,
246 {
247 use harmos_domain::Message;
248 let published = self.call::<harmos_operations::streams::Publish>(harmos_operations::streams::Publish {
249 stream: S::ID.to_owned(),
250 schema: S::VERSION,
251 sequence: None,
252 published_unix_micros: None,
253 context: None,
254 payload: row.encode(),
255 following: Vec::new(),
256 });
257 async move {
258 published.await?;
259 Ok(())
260 }
261 }
262
263 type Error = Error;
264 type Instance<O: OperationDeclaration> = Running<O>;
265
266 fn context(&self) -> &CallContext {
267 self.call_context()
268 }
269
270 fn with_value<T: ContextValue>(&self, value: T) -> std::result::Result<Self, ContextError> {
271 let context = self.call_context().with_value(value)?;
272 Ok(Self::new(self.execution().with_context(context)))
273 }
274
275 fn submit<O: OperationDeclaration>(&self, request: O::Request) -> impl Future<Output = Result<Running<O>>> + Send {
276 std::future::ready(Manager::submit::<O>(self, request))
277 }
278
279 fn submit_owned<O: OperationDeclaration>(
280 &self, request: O::Request, claims: super::RequestClaims,
281 ) -> impl Future<Output = Result<Running<O>>> + Send {
282 std::future::ready(Manager::submit_owned::<O>(self, request, claims))
283 }
284
285 fn call<O: OperationDeclaration>(&self, request: O::Request) -> impl Future<Output = Result<Response<O::Response>>> + Send {
286 let submitted = Manager::submit::<O>(self, request);
287 async move { submitted?.await }
288 }
289}
290
291impl harmos_sdk::operations::Host for Manager {
292 fn publish_on<S: harmos_domain::StreamSpec>(
293 &self, row: &S::Row,
294 ) -> impl std::future::Future<Output = std::result::Result<(), Self::Error>> + Send
295 where
296 S::Row: harmos_domain::Message,
297 {
298 use harmos_domain::Message;
299 let published = self.call::<harmos_operations::streams::Publish>(harmos_operations::streams::Publish {
300 stream: S::ID.to_owned(),
301 schema: S::VERSION,
302 sequence: None,
303 published_unix_micros: None,
304 context: None,
305 payload: row.encode(),
306 following: Vec::new(),
307 });
308 async move {
309 published.await?;
310 Ok(())
311 }
312 }
313
314 type Error = Error;
315 type Instance<O: OperationDeclaration> = Running<O>;
316
317 fn context(&self) -> &CallContext {
318 self.call_context()
319 }
320
321 fn with_value<T: ContextValue>(&self, value: T) -> std::result::Result<Self, ContextError> {
322 let context = self.call_context().with_value(value)?;
323 Ok(self.with_context(context))
324 }
325
326 fn submit<O: OperationDeclaration>(&self, request: O::Request) -> impl Future<Output = Result<Running<O>>> + Send {
327 std::future::ready(Manager::submit::<O>(self, request))
328 }
329
330 fn submit_owned<O: OperationDeclaration>(
331 &self, request: O::Request, claims: super::RequestClaims,
332 ) -> impl Future<Output = Result<Running<O>>> + Send {
333 std::future::ready(Manager::submit_owned::<O>(self, request, claims))
334 }
335
336 fn call<O: OperationDeclaration>(&self, request: O::Request) -> impl Future<Output = Result<Response<O::Response>>> + Send {
337 let submitted = Manager::submit::<O>(self, request);
338 async move { submitted?.await }
339 }
340}
341
342impl harmos_sdk::operations::Host for Context<'_> {
343 fn publish_on<S: harmos_domain::StreamSpec>(
344 &self, row: &S::Row,
345 ) -> impl std::future::Future<Output = std::result::Result<(), Self::Error>> + Send
346 where
347 S::Row: harmos_domain::Message,
348 {
349 use harmos_domain::Message;
350 let published = self.call::<harmos_operations::streams::Publish>(harmos_operations::streams::Publish {
351 stream: S::ID.to_owned(),
352 schema: S::VERSION,
353 sequence: None,
354 published_unix_micros: None,
355 context: None,
356 payload: row.encode(),
357 following: Vec::new(),
358 });
359 async move {
360 published.await?;
361 Ok(())
362 }
363 }
364
365 type Error = Error;
366 type Instance<O: OperationDeclaration> = Running<O>;
367
368 fn context(&self) -> &CallContext {
369 Context::context(self)
370 }
371
372 fn with_value<T: ContextValue>(&self, value: T) -> std::result::Result<Self, ContextError> {
373 let context = Context::context(self).with_value(value)?;
374 let mut child = self.clone();
375 child.operations = child.operations.with_context(context);
376 Ok(child)
377 }
378
379 fn submit<O: OperationDeclaration>(&self, request: O::Request) -> impl Future<Output = Result<Running<O>>> + Send {
380 std::future::ready(Context::submit::<O>(self, request))
381 }
382
383 fn submit_owned<O: OperationDeclaration>(
384 &self, request: O::Request, claims: super::RequestClaims,
385 ) -> impl Future<Output = Result<Running<O>>> + Send {
386 std::future::ready(Context::submit_owned::<O>(self, request, claims))
387 }
388
389 fn call<O: OperationDeclaration>(&self, request: O::Request) -> impl Future<Output = Result<Response<O::Response>>> + Send {
390 let submitted = Context::submit::<O>(self, request);
391 async move { submitted?.await }
392 }
393
394 }
397
398#[cfg(test)]
405mod tests {
406 use super::*;
407 use crate::Application;
408 use crate::operations::RegistrationExt;
409 use std::sync::{
410 Arc,
411 atomic::{AtomicUsize, Ordering},
412 };
413
414 #[crate::lanes]
415 enum Lanes {
416 #[default]
417 Default,
418 }
419
420 struct Model;
421 impl crate::Lifecycle for Model {
422 type Config = ();
423 type Error = std::convert::Infallible;
424 type Context = crate::Startup<Self>;
425
426 async fn prepare(_: (), _: &mut crate::Setup<Self>) -> std::result::Result<Self, Self::Error> {
427 Ok(Self)
428 }
429
430 async fn initialize(&mut self, _: &Self::Context) -> std::result::Result<(), Self::Error> {
431 Ok(())
432 }
433
434 async fn stop(&mut self, _: &Self::Context) -> std::result::Result<(), Self::Error> {
435 Ok(())
436 }
437
438 async fn cleanup(&mut self) -> std::result::Result<(), Self::Error> {
439 Ok(())
440 }
441 }
442
443 impl Application for Model {
444 type Lanes = Lanes;
445 type State = ();
446 type Transaction = crate::state::Never;
447 type Record = ();
448 type Streams = ();
449 }
450
451 #[crate::operation(harmos_path = crate, resources = [AtomicUsize, crate::operations::HostContext],
452 id = "test.probe", response = u64,
453 policy = crate::OperationPolicy {
454 timeout: std::time::Duration::from_secs(1),
455 attempt_timeout: std::time::Duration::from_secs(1),
456 retries: RetryPolicy::Never,
457 },
458 )]
459 struct Probe {
460 value: u64,
461 }
462
463 impl crate::Operation for Probe {
464 type Error = std::io::Error;
465
466 async fn run(self, ctx: crate::Context<'_, Self>) -> std::io::Result<Response<u64>> {
467 let host = ctx.resource::<crate::operations::HostContext>();
468 let attempt = host.borrow().attempt();
469 if attempt == 0 {
470 ctx.resource::<AtomicUsize>().fetch_add(1, Ordering::SeqCst);
471 }
472 if u64::try_from(attempt).unwrap() < self.value {
473 return Err(std::io::Error::other("retry this request"));
474 }
475 Ok(0.into())
476 }
477
478 fn classify(_: &std::io::Error) -> super::super::FailureKind {
479 super::super::FailureKind::Transient
480 }
481 }
482
483 struct Idle;
484
485 impl harmos_sdk::Service for Idle {
486 type Error = std::io::Error;
487 async fn initialize(_: harmos_sdk::ExecutionContext) -> std::io::Result<Self> {
488 Ok(Self)
489 }
490 async fn run(&mut self, _: harmos_sdk::ExecutionContext) -> std::io::Result<()> {
491 std::future::pending().await
492 }
493 }
494
495 #[tokio::test]
496 async fn mixed_registration_releases_invalid_batch_reservations() {
497 let runtime = crate::Runtime::<Model>::launch_with((), (), |setup| {
498 setup.resource::<AtomicUsize>(Arc::new(AtomicUsize::new(0)));
499 })
500 .await
501 .unwrap();
502 let batch = harmos_sdk::__private::RegistrationBatch::new().operation::<Probe>().service::<Idle>(
503 "idle",
504 crate::ServicePolicy {
505 stop_timeout: std::time::Duration::from_secs(1),
506 restart: crate::RestartPolicy::Never,
507 },
508 );
509 assert!(
510 runtime
511 .register_batch(batch.clone().operation::<Probe>().lane(<Lanes as crate::operations::Lane>::default_lane()))
512 .await
513 .is_err()
514 );
515 assert!(runtime.operations.definition(Probe::ID).is_err());
516 assert!(runtime.operations.resident.definition("host.idle").is_none());
517 runtime.register_batch(batch.lane(<Lanes as crate::operations::Lane>::default_lane())).await.unwrap();
518 assert!(runtime.operations.definition(Probe::ID).is_ok());
519 assert!(runtime.operations.resident.definition("host.idle").is_some());
520 runtime.unregister(&crate::QualifiedId::parse("host.idle").unwrap()).await.unwrap();
521 runtime.unregister(&crate::QualifiedId::parse("host.test.probe").unwrap()).await.unwrap();
522 runtime.shutdown().await.unwrap();
523 }
524
525 #[tokio::test]
526 async fn operation_and_service_share_one_identity_namespace() {
527 let runtime = crate::Runtime::<Model>::launch_with((), (), |setup| {
528 setup.resource::<AtomicUsize>(Arc::new(AtomicUsize::new(0)));
529 })
530 .await
531 .unwrap();
532 let operation = harmos_sdk::__private::RegistrationBatch::new().operation::<Probe>();
533 let policy = crate::ServicePolicy {
534 stop_timeout: std::time::Duration::from_secs(1),
535 restart: crate::RestartPolicy::Never,
536 };
537 let service = harmos_sdk::__private::RegistrationBatch::new().service::<Idle>("test.probe", policy);
538 assert!(
539 runtime
540 .register_batch(
541 operation.clone().service::<Idle>("test.probe", policy).lane(<Lanes as crate::operations::Lane>::default_lane())
542 )
543 .await
544 .is_err()
545 );
546 assert!(runtime.operations.definition(Probe::ID).is_err());
547 assert!(!runtime.operations.resident.contains("host.test.probe"));
548 runtime.operations.register_batch(operation.clone().lane(<Lanes as crate::operations::Lane>::default_lane())).await.unwrap();
549 assert!(runtime.services().register_batch(service.clone()).await.is_err());
550 runtime.unregister(&crate::QualifiedId::parse("host.test.probe").unwrap()).await.unwrap();
551 runtime.services().register_batch(service).await.unwrap();
552 assert!(runtime.operations.register_batch(operation.lane(<Lanes as crate::operations::Lane>::default_lane())).await.is_err());
553 runtime.unregister(&crate::QualifiedId::parse("host.test.probe").unwrap()).await.unwrap();
554 runtime.shutdown().await.unwrap();
555 }
556
557 #[tokio::test]
558 async fn native_registration_is_atomic_and_invocation_policy_is_local() {
559 let initialized = Arc::new(AtomicUsize::new(0));
560 let runtime = crate::Runtime::<Model>::launch_with((), (), |setup| {
561 setup.resource::<AtomicUsize>(initialized.clone());
562 })
563 .await
564 .unwrap();
565 let batch = harmos_sdk::__private::RegistrationBatch::new().operation::<Probe>();
566 assert!(
567 runtime
568 .register_batch(batch.clone().operation::<Probe>().lane(<Lanes as crate::operations::Lane>::default_lane()))
569 .await
570 .is_err()
571 );
572 assert!(runtime.operations.definition(Probe::ID).is_err());
573 let mut mismatched = batch.clone();
574 mismatched.operations[0].definition.id = crate::Identifier::new("different").unwrap();
575 assert!(runtime.register_batch(mismatched.lane(<Lanes as crate::operations::Lane>::default_lane())).await.is_err());
576 assert!(runtime.operations.definition("different").is_err());
577 runtime.operations.register_batch(batch.lane(<Lanes as crate::operations::Lane>::default_lane())).await.unwrap();
578 assert!(matches!(
579 runtime.operations.call_encoded("host.test.probe", Vec::new()).await,
580 Err(Error::InvalidInput { field: "operation preparation bytes", .. })
581 ));
582 let invocation = runtime.operation(Probe { value: 1 });
583 assert_eq!(initialized.load(Ordering::SeqCst), 0);
584 assert!(invocation.run().await.is_err());
585 assert_eq!(initialized.load(Ordering::SeqCst), 1);
586
587 let retry = RetryPolicy::OnTransient {
588 max_retries: 1,
589 backoff: crate::Backoff::Fixed {
590 delay: std::time::Duration::from_millis(1),
591 jitter: crate::Jitter::None,
592 },
593 };
594 assert_eq!(*runtime.operation(Probe { value: 1 }).retry(retry).run().await.unwrap(), 0);
595 assert_eq!(initialized.load(Ordering::SeqCst), 2);
596 assert!(runtime.operation(Probe { value: 1 }).run().await.is_err());
597 assert_eq!(initialized.load(Ordering::SeqCst), 3);
598 assert_eq!(runtime.operations.definition(Probe::ID).unwrap().policy.retries, RetryPolicy::Never);
599 runtime.operations.unregister(&crate::QualifiedId::parse("host.test.probe").unwrap()).await.unwrap();
600 assert!(runtime.operation(Probe { value: 0 }).start().await.is_err());
601 runtime.shutdown().await.unwrap();
602 }
603}