1use std::any::Any;
18use std::future::Future;
19use std::pin::Pin;
20
21use super::{
25 Carrier, Contract, Executable, ExecutionContext, OperationDeclaration, OperationDefinition, Resource, ResourceSet, ServingError,
26 WireDeclaration, WireTable,
27};
28use crate::services::Service;
29use crate::{Message, ServicePolicy};
30
31pub type ServiceFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, ServingError>> + Send + 'a>>;
38type Install = fn(&mut WireTable, ResourceSet, Contract) -> Result<(), ServingError>;
39type InitializeService = fn(ExecutionContext) -> ServiceFuture<'static, Box<dyn ServiceInstance>>;
40type PrepareNative = fn(ResourceSet, Box<dyn Any + Send>, Box<dyn Any + Send>) -> Result<Box<dyn NativeInstance>, ServingError>;
41type PrepareEncoded = fn(ResourceSet, &[u8]) -> Result<PreparedNative, ServingError>;
42
43#[derive(Clone, Copy)]
46pub struct Catalog {
47 pub name: &'static str,
48 pub declarations: &'static [crate::catalog::Node<WireDeclaration>],
49 pub callees: &'static [crate::catalog::Node<&'static [WireDeclaration]>],
50 identity: fn() -> std::any::TypeId,
51 factory: fn() -> RegistrationBatch,
52}
53
54#[derive(Clone, Default)]
55pub struct RegistrationBatch {
56 pub operations: Vec<OperationFactory>,
57 pub services: Vec<ServiceFactory>,
58}
59
60pub struct PreparedNative {
61 #[cfg(feature = "streaming")]
62 pub streams: Option<super::WireStreams>,
63
64 pub instance: Box<dyn NativeInstance>,
65 pub grant: Option<crate::plugin::Grant>,
66 pub key: Option<super::CacheKey>,
67}
68
69#[derive(Clone)]
70pub struct OperationFactory {
71 pub definition: OperationDefinition,
72 pub operations: &'static [WireDeclaration],
73 declared: fn() -> OperationDefinition,
74 builtin: bool,
75 resources: &'static [Resource],
76 contract: fn(&harmos_domain::config::SchemaSettings) -> Result<Contract, super::ContractError>,
77 install: Install,
78 native: PrepareNative,
79 endpoints_valid: fn() -> bool,
80 native_types: fn() -> (std::any::TypeId, std::any::TypeId, std::any::TypeId),
81 encoded: PrepareEncoded,
82
83 #[cfg(all(feature = "grpc", feature = "streaming"))]
84 grpc: fn(ResourceSet, &[u8], super::WireStreamLimits) -> Result<PreparedNative, ServingError>,
85
86 #[cfg(all(feature = "grpc", feature = "streaming"))]
87 grpc_memory: fn(&[u8]) -> Result<usize, crate::MessageError>,
88
89 decoding_memory: fn(&[u8]) -> Result<usize, crate::MessageError>,
90}
91
92#[derive(Clone)]
93pub struct ServiceFactory {
94 pub local_id: String,
95 pub policy: ServicePolicy,
96 initialize: InitializeService,
97}
98
99struct InitializedService<S: Service> {
100 service: S,
101}
102
103struct NativeOperation<O: Carrier> {
104 dependencies: ResourceSet,
105 operation: Option<O>,
106 request: <O::Declaration as OperationDeclaration>::Request,
107 endpoints: O::StreamEndpoints,
108 stopped: bool,
109 encoded: bool,
110
111 #[cfg(all(feature = "grpc", feature = "streaming"))]
112 protobuf: bool,
113
114 #[cfg(feature = "streaming")]
115 streams: Option<super::WireStreams>,
116
117 #[cfg(feature = "streaming")]
118 epoch: u64,
119}
120
121pub trait OperationCatalog: 'static {
126 const CATALOG: Catalog;
127
128 #[doc(hidden)]
129 fn batch() -> RegistrationBatch;
130}
131
132pub trait ServiceInstance: Send {
133 fn run(&mut self, context: ExecutionContext) -> ServiceFuture<'_, ()>;
134 fn stop(&mut self) -> ServiceFuture<'_, ()>;
135 fn cleanup(&mut self) -> ServiceFuture<'_, ()>;
136}
137
138pub trait NativeInstance: Send {
139 fn classify(&self, error: &(dyn std::error::Error + Send + Sync + 'static)) -> crate::operations::FailureKind;
140 fn initialize(&mut self, context: ExecutionContext) -> ServiceFuture<'_, ()>;
141 fn run(&mut self, context: ExecutionContext) -> ServiceFuture<'_, super::response::ErasedResponse>;
142 fn stop(&mut self) -> ServiceFuture<'_, ()>;
143 fn cleanup(&mut self) -> ServiceFuture<'_, ()>;
144 fn transferred(&self) -> bool;
145}
146
147impl Catalog {
150 pub const fn of<C: OperationCatalog>(
151 name: &'static str, declarations: &'static [crate::catalog::Node<WireDeclaration>],
152 callees: &'static [crate::catalog::Node<&'static [WireDeclaration]>],
153 ) -> Self {
154 Self {
155 name,
156 declarations,
157 callees,
158 identity: std::any::TypeId::of::<C>,
159 factory: C::batch,
160 }
161 }
162
163 #[doc(hidden)]
164 pub fn identity(&self) -> std::any::TypeId {
165 (self.identity)()
166 }
167
168 #[doc(hidden)]
169 pub fn batch(&self) -> RegistrationBatch {
170 (self.factory)()
171 }
172}
173
174impl RegistrationBatch {
175 pub fn new() -> Self {
176 Self::default()
177 }
178
179 pub fn operation<O: Executable>(self) -> Self {
180 self.with::<O, O::Carrier>(O::RESOURCES)
181 }
182
183 #[doc(hidden)]
184 pub fn carrier<D, O>(self) -> Self
185 where
186 D: OperationDeclaration,
187 O: Carrier<Declaration = D>,
188 {
189 self.with::<D, O>(&[])
190 }
191
192 fn with<D, O>(mut self, resources: &'static [Resource]) -> Self
193 where
194 D: OperationDeclaration,
195 O: Carrier<Declaration = D>,
196 {
197 self.operations.push(OperationFactory {
198 resources,
199 operations: D::OPERATIONS,
200 definition: D::definition(),
201 declared: D::definition,
202 builtin: std::any::TypeId::of::<D>() == std::any::TypeId::of::<crate::observers::Control>(),
203 contract: |settings| {
204 if std::any::TypeId::of::<D>() == std::any::TypeId::of::<crate::observers::Control>() {
205 return crate::observers::contract();
206 }
207 WireDeclaration::of::<D>().contract_with_settings(settings)
208 },
209 install: WireTable::install::<D, O>,
210 native_types: || {
211 (
212 std::any::TypeId::of::<<O::Declaration as OperationDeclaration>::Request>(),
213 std::any::TypeId::of::<<O::Declaration as OperationDeclaration>::Response>(),
214 std::any::TypeId::of::<O::StreamEndpoints>(),
215 )
216 },
217 endpoints_valid: || {
218 use super::EndpointSet;
219 O::StreamEndpoints::item_types() == super::item_types::<D>()
220 },
221 native: |dependencies, request, endpoints| {
222 use super::EndpointSet;
223 if O::StreamEndpoints::item_types() != super::item_types::<D>() {
224 return Err(ServingError::Catalog);
225 }
226 let request = request.downcast::<<O::Declaration as OperationDeclaration>::Request>().map_err(|_| ServingError::Catalog)?;
227 let endpoints = endpoints.downcast::<O::StreamEndpoints>().map_err(|_| ServingError::Catalog)?;
228 Ok(Box::new(NativeOperation::<O> {
229 dependencies,
230 operation: None,
231 request: *request,
232 endpoints: *endpoints,
233 stopped: false,
234 encoded: false,
235 #[cfg(all(feature = "grpc", feature = "streaming"))]
236 protobuf: false,
237 #[cfg(feature = "streaming")]
238 streams: None,
239 #[cfg(feature = "streaming")]
240 epoch: 0,
241 }))
242 },
243 encoded: |dependencies, payload| {
244 use super::EndpointSet;
245 let endpoints = O::StreamEndpoints::empty().ok_or(ServingError::StreamUnsupported)?;
246 let request =
247 <<O::Declaration as OperationDeclaration>::Request as crate::Message>::decode(payload).map_err(ServingError::Codec)?;
248 let grant = D::grant(&request);
249 let key = D::key(&request);
250 let instance = NativeOperation::<O> {
251 dependencies,
252 operation: None,
253 request,
254 endpoints,
255 stopped: false,
256 encoded: true,
257 #[cfg(all(feature = "grpc", feature = "streaming"))]
258 protobuf: false,
259 #[cfg(feature = "streaming")]
260 streams: None,
261 #[cfg(feature = "streaming")]
262 epoch: 0,
263 };
264 Ok(PreparedNative {
265 instance: Box::new(instance),
266 grant,
267 key,
268 #[cfg(feature = "streaming")]
269 streams: None,
270 })
271 },
272 #[cfg(all(feature = "grpc", feature = "streaming"))]
273 grpc: |dependencies, payload, limits| {
274 use super::EndpointSet;
275 let request =
276 crate::grpc::decode::<<O::Declaration as OperationDeclaration>::Request>(crate::grpc::DecodeInput::new(payload))
277 .map_err(ServingError::Codec)?;
278 let (endpoints, streams) = match O::StreamEndpoints::empty() {
279 Some(endpoints) => (endpoints, None),
280 None => {
281 let (endpoints, streams) = O::StreamEndpoints::bind_wire(limits)?;
282 (endpoints, Some(streams.protobuf(super::serving::protobuf::Codec::of::<D>())?))
283 }
284 };
285 let grant = D::grant(&request);
286 let key = D::key(&request);
287 let instance = NativeOperation::<O> {
288 dependencies,
289 operation: None,
290 request,
291 endpoints,
292 stopped: false,
293 encoded: true,
294 protobuf: true,
295 streams: streams.clone(),
296 epoch: 0,
297 };
298 Ok(PreparedNative {
299 instance: Box::new(instance),
300 grant,
301 key,
302 streams,
303 })
304 },
305 #[cfg(all(feature = "grpc", feature = "streaming"))]
306 grpc_memory: |payload| {
307 crate::grpc::prepare::<<O::Declaration as OperationDeclaration>::Request>(crate::grpc::DecodeInput::new(payload))
308 .map(|prepared| prepared.memory())
309 },
310 decoding_memory: <D::Request as crate::Message>::decoding_memory,
311 });
312 self
313 }
314
315 pub fn service<S: Service>(mut self, local_id: &str, policy: ServicePolicy) -> Self {
316 self.services.push(ServiceFactory {
317 local_id: local_id.to_owned(),
318 policy,
319 initialize: |context| {
320 Box::pin(async move {
321 let service = S::initialize(context).await.map_err(service_error)?;
322 Ok(Box::new(InitializedService { service }) as Box<dyn ServiceInstance>)
323 })
324 },
325 });
326 self
327 }
328}
329
330impl OperationFactory {
331 pub fn accepts_target(&self, target: &super::Target) -> bool {
332 (self.native_types)() == ((target.request)(), (target.response)(), std::any::TypeId::of::<()>())
333 }
334
335 pub fn accepts_native<D: OperationDeclaration, P: 'static>(&self) -> bool {
336 (self.native_types)() == (std::any::TypeId::of::<D::Request>(), std::any::TypeId::of::<D::Response>(), std::any::TypeId::of::<P>())
337 }
338
339 pub fn verify_resources(&self, resources: &ResourceSet, provided: &[std::any::TypeId]) -> Result<(), ServingError> {
341 let missing = self.resources.iter().find(|resource| !resources.contains(resource.id()) && !provided.contains(&resource.id()));
342 match missing {
343 Some(resource) => Err(ServingError::Resource {
344 operation: self.definition.id.to_string(),
345 resource: resource.name(),
346 }),
347 None => Ok(()),
348 }
349 }
350
351 pub fn validate(&self) -> Result<(), ServingError> {
352 if !self.builtin {
353 self.definition.validate().map_err(ServingError::from)?;
354 }
355 if self.definition != (self.declared)() {
356 return Err(ServingError::Catalog);
357 }
358 if !(self.endpoints_valid)() {
359 return Err(ServingError::EndpointTypes);
360 }
361 Ok(())
362 }
363
364 #[doc(hidden)]
365 pub fn missing_resources(&self, resources: &ResourceSet, provided: &[std::any::TypeId]) -> Vec<super::StartupError> {
366 self.resources
367 .iter()
368 .filter(|resource| !resources.contains(resource.id()) && !provided.contains(&resource.id()))
369 .map(|resource| super::StartupError::MissingResource {
370 operation: self.definition.id.to_string(),
371 resource: resource.name(),
372 })
373 .collect()
374 }
375
376 pub fn decoding_memory(&self, payload: &[u8]) -> Result<usize, crate::MessageError> {
377 (self.decoding_memory)(payload)
378 }
379
380 pub fn prepare_encoded(&self, resources: ResourceSet, payload: &[u8]) -> Result<PreparedNative, ServingError> {
381 (self.encoded)(resources, payload)
382 }
383
384 #[cfg(all(feature = "grpc", feature = "streaming"))]
385 pub fn prepare_grpc(
386 &self, resources: ResourceSet, payload: &[u8], limits: super::WireStreamLimits,
387 ) -> Result<PreparedNative, ServingError> {
388 (self.grpc)(resources, payload, limits)
389 }
390 #[cfg(all(feature = "grpc", feature = "streaming"))]
391 pub fn grpc_decoding_memory(&self, payload: &[u8]) -> Result<usize, crate::MessageError> {
392 (self.grpc_memory)(payload)
393 }
394
395 pub fn prepare_native(
396 &self, resources: ResourceSet, request: Box<dyn Any + Send>, endpoints: Box<dyn Any + Send>,
397 ) -> Result<Box<dyn NativeInstance>, ServingError> {
398 (self.native)(resources, request, endpoints)
399 }
400
401 pub fn contract(&self) -> Result<Contract, super::ContractError> {
402 self.contract_with_settings(&harmos_domain::config::SchemaSettings::default())
403 }
404
405 pub fn contract_with_settings(&self, settings: &harmos_domain::config::SchemaSettings) -> Result<Contract, super::ContractError> {
406 (self.contract)(settings)
407 }
408
409 pub fn install(self, table: &mut WireTable) -> Result<(), ServingError> {
410 self.validate()?;
411 self.verify_resources(table.owner_resources(), table.provided())?;
412 let contract = self.contract()?;
413 let resources = table.owner_resources().clone();
414 (self.install)(table, resources, contract)
415 }
416}
417
418impl<O: Carrier> NativeInstance for NativeOperation<O> {
419 fn classify(&self, error: &(dyn std::error::Error + Send + Sync + 'static)) -> crate::operations::FailureKind {
420 error.downcast_ref::<O::Error>().map_or(crate::operations::FailureKind::Permanent, O::classify)
421 }
422
423 fn initialize(&mut self, context: ExecutionContext) -> ServiceFuture<'_, ()> {
424 Box::pin(async move {
425 if self.operation.is_none() {
426 let context = context.with_resources(self.dependencies.clone());
427 self.operation = Some(O::bind(context).await.map_err(operation_error::<O>)?);
428 }
429 Ok(())
430 })
431 }
432
433 fn run(&mut self, context: ExecutionContext) -> ServiceFuture<'_, super::response::ErasedResponse> {
434 Box::pin(async move {
435 let operation = self.operation.as_mut().ok_or(ServingError::Uninitialized)?;
436 let context = context.with_resources(self.dependencies.clone());
437 #[cfg(feature = "streaming")]
438 let attempt = if let Some(streams) = &self.streams {
439 self.epoch = self.epoch.checked_add(1).ok_or(ServingError::StreamPhase)?;
440 Some(streams.start(self.epoch)?)
441 } else {
442 None
443 };
444 let response = operation.attempt(context, &mut self.request, &mut self.endpoints).await.map_err(operation_error::<O>)?;
445 #[cfg(feature = "streaming")]
446 if let Some(attempt) = attempt {
447 attempt.end();
448 }
449 #[cfg(all(feature = "grpc", feature = "streaming"))]
450 if self.protobuf {
451 let mut bytes = Vec::new();
452 crate::grpc::encode(&*response, &mut bytes).map_err(ServingError::Codec)?;
453 return Ok(super::Response::new(bytes).into_erased());
454 }
455 if self.encoded {
456 return Ok(super::Response::new(response.encode()).into_erased());
457 }
458 Ok(response.into_erased())
459 })
460 }
461
462 fn stop(&mut self) -> ServiceFuture<'_, ()> {
463 Box::pin(async move {
464 if self.stopped {
465 return Ok(());
466 }
467 if let Some(operation) = self.operation.as_mut() {
468 operation.stop().await.map_err(operation_error::<O>)?;
469 }
470 self.stopped = true;
471 Ok(())
472 })
473 }
474
475 fn cleanup(&mut self) -> ServiceFuture<'_, ()> {
476 Box::pin(async move {
477 if let Some(operation) = self.operation.as_mut() {
478 operation.cleanup().await.map_err(operation_error::<O>)?;
479 }
480 self.operation = None;
481 Ok(())
482 })
483 }
484
485 fn transferred(&self) -> bool {
486 use super::EndpointSet;
487 self.endpoints.transferred() || self.operation.as_ref().is_some_and(Carrier::transferred)
488 }
489}
490
491impl ServiceFactory {
492 pub fn initialize(&self, context: ExecutionContext) -> ServiceFuture<'static, Box<dyn ServiceInstance>> {
493 (self.initialize)(context)
494 }
495}
496
497impl<S: Service> ServiceInstance for InitializedService<S> {
498 fn run(&mut self, context: ExecutionContext) -> ServiceFuture<'_, ()> {
499 Box::pin(async move { self.service.run(context).await.map_err(service_error) })
500 }
501 fn stop(&mut self) -> ServiceFuture<'_, ()> {
502 Box::pin(async move { self.service.stop().await.map_err(service_error) })
503 }
504 fn cleanup(&mut self) -> ServiceFuture<'_, ()> {
505 Box::pin(async move { self.service.cleanup().await.map_err(service_error) })
506 }
507}
508
509fn service_error(error: impl std::error::Error + 'static) -> ServingError {
514 ServingError::Handler(crate::Failure::originate(crate::operations::FailureKind::Permanent, &error, crate::failure::Origin::Plugin))
515}
516
517fn operation_error<O: Carrier>(error: O::Error) -> ServingError {
518 ServingError::Native(std::sync::Arc::new(error))
519}
520
521#[cfg(test)]
524mod tests {
525 use super::*;
526 use std::sync::{
527 Arc,
528 atomic::{AtomicUsize, Ordering},
529 };
530
531 struct Worker(Arc<AtomicUsize>);
532
533 struct ReadCatalog;
534
535 impl OperationCatalog for ReadCatalog {
536 const CATALOG: Catalog = Catalog::of::<Self>("ReadCatalog", &[], &[]);
537 fn batch() -> RegistrationBatch {
538 RegistrationBatch::new().operation::<Read>()
539 }
540 }
541
542 #[crate::operation(harmos_path = crate, resources = [u64], id = "fixture.read", response = u64,
543 policy = crate::OperationPolicy {
544 timeout: std::time::Duration::from_secs(1),
545 attempt_timeout: std::time::Duration::from_secs(1),
546 retries: crate::RetryPolicy::Never,
547 },
548 key = |_: &Read| Some(super::super::CacheKey::new(vec![42]).unwrap()),
549 )]
550 struct Read;
551
552 impl crate::Operation for Read {
553 type Error = std::convert::Infallible;
554
555 async fn run(self, ctx: crate::Context<'_, Self>) -> Result<crate::Response<u64>, Self::Error> {
556 Ok(crate::Response::from_owner(ctx.resource::<u64>()))
557 }
558 }
559
560 fn ready<T>(mut future: ServiceFuture<'_, T>) -> T {
561 let mut task = std::task::Context::from_waker(std::task::Waker::noop());
562 match future.as_mut().poll(&mut task) {
563 std::task::Poll::Ready(Ok(value)) => value,
564 _ => panic!("synchronous fixture must complete successfully"),
565 }
566 }
567
568 #[test]
569 fn carrier_assignments_are_immutable() {
570 use crate::operations::{AssignedOperation, OperationAssignments};
571 let first = AssignedOperation {
572 operation: format!("fixture.{}", Read::ID),
573 fingerprint: Read::definition().fingerprint.0,
574 generation: 1,
575 };
576 let assignments = OperationAssignments::default();
577 let bytes = AssignedOperation::encode(std::slice::from_ref(&first)).unwrap();
578 assignments.install(&bytes).unwrap();
579 assignments.clone().install(&bytes).unwrap();
580 assert_eq!(assignments.resolve::<Read>().unwrap(), &first);
581 let mut replacement = first.clone();
582 replacement.generation = 2;
583 assert!(assignments.install(&AssignedOperation::encode(&[replacement]).unwrap()).is_err());
584 assert_eq!(assignments.resolve::<Read>().unwrap().generation, 1);
585 let mut wrong = first.clone();
586 wrong.fingerprint[0] ^= 1;
587 let incompatible = OperationAssignments::default();
588 incompatible.install(&AssignedOperation::encode(&[wrong]).unwrap()).unwrap();
589 assert!(incompatible.resolve::<Read>().is_err());
590 let duplicate = AssignedOperation {
591 operation: format!("other.{}", Read::ID),
592 ..first.clone()
593 };
594 assert!(OperationAssignments::default().install(&AssignedOperation::encode(&[first, duplicate]).unwrap()).is_err());
595 }
596
597 #[test]
598 fn native_response_retains_owner_across_retries_and_cleanup() {
599 let value = Arc::new(42u64);
600 let retained = Arc::downgrade(&value);
601 let mut dependencies = ResourceSet::new();
602 dependencies.insert(value).unwrap();
603 let batch = RegistrationBatch::new().operation::<Read>();
604 let factory = &batch.operations[0];
605 assert!(factory.prepare_native(dependencies.clone(), Box::new(7u64), Box::new(())).is_err());
606 let mut invocation = factory.prepare_native(dependencies, Box::new(Read), Box::new(())).unwrap();
607 let context = ExecutionContext::new(ResourceSet::new(), super::super::CallContext::default());
608
609 ready(invocation.initialize(context.clone()));
610 let first = ready(invocation.run(context.clone())).restore::<u64>().unwrap();
611 let second = ready(invocation.run(context)).restore::<u64>().unwrap();
612 assert!(first.ptr_eq(&second));
613 assert_eq!(*first, 42);
614 ready(invocation.stop());
615 ready(invocation.cleanup());
616
617 drop(invocation);
618 drop(batch);
619 assert!(retained.upgrade().is_some());
620 drop(first);
621 drop(second);
622 assert!(retained.upgrade().is_none());
623 }
624
625 #[test]
626 fn encoded_factory_preserves_request_cache_key_and_response() {
627 use crate::Message;
628
629 let mut dependencies = ResourceSet::new();
630 dependencies.insert(Arc::new(42u64)).unwrap();
631 let batch = RegistrationBatch::new().operation::<Read>();
632 let mut prepared = batch.operations[0].prepare_encoded(dependencies, &Read.encode()).unwrap();
633 assert_eq!(prepared.key, Read::key(&Read));
634 let context = ExecutionContext::new(ResourceSet::new(), super::super::CallContext::default());
635 ready(prepared.instance.initialize(context.clone()));
636 let response = ready(prepared.instance.run(context)).restore::<Vec<u8>>().unwrap();
637 assert_eq!(u64::decode(&response).unwrap(), 42);
638 ready(prepared.instance.stop());
639 ready(prepared.instance.cleanup());
640 }
641
642 impl Service for Worker {
643 type Error = crate::ResourceError;
644
645 async fn initialize(context: ExecutionContext) -> Result<Self, Self::Error> {
646 let counter = context.resource::<AtomicUsize>()?;
647 counter.fetch_add(1, Ordering::SeqCst);
648 Ok(Self(counter))
649 }
650
651 async fn run(&mut self, _: ExecutionContext) -> Result<(), Self::Error> {
652 self.0.fetch_add(1, Ordering::SeqCst);
653 Ok(())
654 }
655 }
656
657 #[test]
658 fn factory_rejects_a_substituted_declaration() {
659 let mut batch = RegistrationBatch::new().operation::<Read>();
660 batch.operations[0].definition.id = crate::Identifier::new("different").unwrap();
661 assert!(matches!(batch.operations[0].validate(), Err(ServingError::Catalog)));
662 }
663
664 #[test]
665 fn wire_identity_fences_replacement_before_decoding() {
666 use crate::operations::{ServingError, WireTable};
667 use crate::{Message, QualifiedId};
668 let batch = RegistrationBatch::new().operation::<Read>();
669 let fingerprint = crate::OperationDefinition::of::<Read>().unwrap().fingerprint.0;
670 let mut table = WireTable::new("fixture").unwrap();
671 table.resource(42u64);
672 table.capacity(std::num::NonZeroUsize::MIN);
673 table.register(batch.clone()).unwrap();
674 table.validate_binding().unwrap();
675 assert!(matches!(table.prepare(1, "spoof.fixture.read", fingerprint, 1, Read.encode()), Err(ServingError::Unknown { .. })));
676 assert!(matches!(table.prepare(1, "fixture.fixture.read", [0; 32], 1, vec![255]), Err(ServingError::Identity { .. })));
677 assert!(matches!(table.prepare(1, "fixture.fixture.read", fingerprint, 0, vec![255]), Err(ServingError::Identity { .. })));
678 table.prepare(1, "fixture.fixture.read", fingerprint, 7, Read.encode()).unwrap();
679 ready(Box::pin(table.unregister(&QualifiedId::qualify("fixture", "fixture.read").unwrap())));
680 table.register(batch).unwrap();
681 assert!(matches!(table.prepare(2, "fixture.fixture.read", fingerprint, 7, vec![255]), Err(ServingError::Identity { .. })));
682 table.prepare(2, "fixture.fixture.read", fingerprint, 8, Read.encode()).unwrap();
683 assert!(matches!(table.prepare(3, "fixture.fixture.read", fingerprint, 9, vec![255]), Err(ServingError::Identity { .. })));
684 ready(Box::pin(table.settle()));
685 }
686
687 #[test]
688 fn service_factory_reconstructs_from_its_snapshot() {
689 let counter = Arc::new(AtomicUsize::new(0));
690 let mut dependencies = ResourceSet::new();
691 dependencies.insert(counter.clone()).unwrap();
692 let policy = ServicePolicy {
693 stop_timeout: std::time::Duration::from_secs(1),
694 restart: crate::RestartPolicy::Never,
695 };
696 let batch = RegistrationBatch::new().service::<Worker>("worker", policy);
697 let factory = &batch.services[0];
698 let context = ExecutionContext::new(dependencies, super::super::CallContext::default());
699 let mut first = factory.initialize(context.clone());
700 let mut second = factory.initialize(context);
701 let mut task = std::task::Context::from_waker(std::task::Waker::noop());
702 assert!(matches!(first.as_mut().poll(&mut task), std::task::Poll::Ready(Ok(_))));
703 assert!(matches!(second.as_mut().poll(&mut task), std::task::Poll::Ready(Ok(_))));
704 assert_eq!(counter.load(Ordering::SeqCst), 2);
705 }
706
707 #[test]
708 fn binding_errors_aggregate() {
709 use crate::StartupError;
710 let mut table = WireTable::new("fixture").unwrap();
711 table.resource(7u32).resource(8u32);
712 table.catalog(&ReadCatalog::CATALOG).catalog(&ReadCatalog::CATALOG);
713 assert!(table.definitions().is_empty());
714
715 let Err(ServingError::Startup(error)) = table.validate_binding() else {
716 panic!("invalid binding must be refused");
717 };
718 let StartupError::Multiple { errors } = *error else {
719 panic!("independent binding errors must be aggregated");
720 };
721 assert_eq!(errors.len(), 3);
722 assert!(errors.iter().any(|error| matches!(error, StartupError::DuplicateResource { .. })));
723 assert!(errors.iter().any(|error| matches!(error, StartupError::DuplicateCatalog { .. })));
724 assert!(errors.iter().any(|error| matches!(error, StartupError::MissingResource { .. })));
725 assert!(table.definitions().is_empty());
726 assert_eq!(*table.context().resource::<u32>().unwrap(), 7);
727 }
728
729 #[test]
730 fn frozen_dynamic_resources() {
731 let mut table = WireTable::new("fixture").unwrap();
732 let owner = Arc::new(41u32);
733 table.resource::<u32>(owner.clone());
734 table.validate_binding().unwrap();
735 assert!(Arc::ptr_eq(&owner, &table.context().resource::<u32>().unwrap()));
736
737 let error = table.register(ReadCatalog::batch()).unwrap_err();
738 assert!(matches!(error, ServingError::Resource { .. }));
739 table.resource(42u64);
740 assert!(table.validate_binding().is_err());
741 assert!(table.context().resource::<u64>().is_err());
742 assert!(table.definitions().is_empty());
743 }
744
745 #[test]
746 fn resource_order_independence() {
747 let mut table = WireTable::new("fixture").unwrap();
748 table.catalog(&ReadCatalog::CATALOG).resource(42u64);
749 assert!(table.definitions().is_empty());
750 table.validate_binding().unwrap();
751 assert_eq!(table.definitions().len(), 1);
752 assert_eq!(*table.context().resource::<u64>().unwrap(), 42);
753 }
754}