1use std::fmt;
2use std::sync::Arc;
3
4use super::{
5 execution, CoreCapabilityBundle, EngineId, EventDomainDriver, ExecutionContextIdentity,
6 HardwareClassId, InputSignatureEntry, ProviderDeviceIdentity, RuntimeCacheOwner,
7 RuntimeConfigError, StorageClass,
8};
9use tenferro_tensor::{AllocationDomainId, Placement, TensorBackend, TensorRead};
10
11#[derive(Debug)]
12pub(super) struct CandidateRegistrationToken;
13
14type InputPlacementPredicate = dyn Fn(&Placement, &StorageClass) -> bool + Send + Sync + 'static;
15type InputSignaturePredicate = dyn Fn(&Placement, Option<&'static str>, Option<AllocationDomainId>, &StorageClass) -> bool
16 + Send
17 + Sync
18 + 'static;
19type InputTensorPredicate =
20 dyn for<'a> Fn(&TensorRead<'a>, &StorageClass) -> bool + Send + Sync + 'static;
21
22#[derive(Clone)]
24pub struct InputPlacementContract(Arc<InputPlacementPredicate>);
25
26impl InputPlacementContract {
27 pub fn new(
45 predicate: impl Fn(&Placement, &StorageClass) -> bool + Send + Sync + 'static,
46 ) -> Self {
47 Self(Arc::new(predicate))
48 }
49
50 fn accepts(&self, placement: &Placement, storage_class: &StorageClass) -> bool {
51 (self.0)(placement, storage_class)
52 }
53}
54
55impl fmt::Debug for InputPlacementContract {
56 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
57 formatter.write_str("InputPlacementContract(..)")
58 }
59}
60
61#[derive(Clone)]
63pub struct InputSignatureContract(Arc<InputSignaturePredicate>);
64
65impl InputSignatureContract {
66 pub fn new(
88 predicate: impl Fn(&Placement, Option<&'static str>, Option<AllocationDomainId>, &StorageClass) -> bool
89 + Send
90 + Sync
91 + 'static,
92 ) -> Self {
93 Self(Arc::new(predicate))
94 }
95
96 fn accepts(&self, input: &InputSignatureEntry, storage_class: &StorageClass) -> bool {
97 (self.0)(
98 input.placement(),
99 input.backend_family(),
100 input.allocation_domain(),
101 storage_class,
102 )
103 }
104}
105
106impl fmt::Debug for InputSignatureContract {
107 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108 formatter.write_str("InputSignatureContract(..)")
109 }
110}
111
112#[derive(Clone)]
114pub struct RuntimeInputContract(Arc<InputTensorPredicate>);
115
116impl RuntimeInputContract {
117 pub fn new(
128 predicate: impl for<'a> Fn(&TensorRead<'a>, &StorageClass) -> bool + Send + Sync + 'static,
129 ) -> Self {
130 Self(Arc::new(predicate))
131 }
132
133 fn accepts(&self, input: &TensorRead<'_>, storage_class: &StorageClass) -> bool {
134 (self.0)(input, storage_class)
135 }
136}
137
138impl fmt::Debug for RuntimeInputContract {
139 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
140 formatter.write_str("RuntimeInputContract(..)")
141 }
142}
143
144#[derive(Clone)]
146pub struct ResidentOutputContract(Arc<InputTensorPredicate>);
147
148impl ResidentOutputContract {
149 pub fn new(
160 predicate: impl for<'a> Fn(&TensorRead<'a>, &StorageClass) -> bool + Send + Sync + 'static,
161 ) -> Self {
162 Self(Arc::new(predicate))
163 }
164
165 fn accepts(&self, input: &TensorRead<'_>, storage_class: &StorageClass) -> bool {
166 (self.0)(input, storage_class)
167 }
168}
169
170impl fmt::Debug for ResidentOutputContract {
171 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
172 formatter.write_str("ResidentOutputContract(..)")
173 }
174}
175
176#[derive(Clone, Debug)]
178pub struct InputIngressContract {
179 placement: InputPlacementContract,
180 signature: InputSignatureContract,
181 runtime_input: RuntimeInputContract,
182 resident_output: ResidentOutputContract,
183}
184
185impl InputIngressContract {
186 pub fn new(
205 placement: InputPlacementContract,
206 signature: InputSignatureContract,
207 runtime_input: RuntimeInputContract,
208 resident_output: ResidentOutputContract,
209 ) -> Self {
210 Self {
211 placement,
212 signature,
213 runtime_input,
214 resident_output,
215 }
216 }
217
218 fn accepts_placement(&self, placement: &Placement, storage_class: &StorageClass) -> bool {
219 self.placement.accepts(placement, storage_class)
220 }
221
222 fn accepts_signature(&self, input: &InputSignatureEntry, storage_class: &StorageClass) -> bool {
223 self.signature.accepts(input, storage_class)
224 }
225
226 fn accepts_runtime_input(&self, input: &TensorRead<'_>, storage_class: &StorageClass) -> bool {
227 self.runtime_input.accepts(input, storage_class)
228 }
229
230 fn owns_resident_output(&self, input: &TensorRead<'_>, storage_class: &StorageClass) -> bool {
231 self.resident_output.accepts(input, storage_class)
232 }
233}
234
235#[derive(Clone, Debug)]
270pub struct EngineRegistrationMetadata {
271 engine_id: EngineId,
272 provider_device_identity: ProviderDeviceIdentity,
273 hardware_class: HardwareClassId,
274 storage_classes: Arc<[StorageClass]>,
275 default_storage_class: StorageClass,
276 capabilities: CoreCapabilityBundle,
277}
278
279impl EngineRegistrationMetadata {
280 pub fn new(
283 engine_id: EngineId,
284 provider_device_identity: ProviderDeviceIdentity,
285 hardware_class: HardwareClassId,
286 storage_classes: Arc<[StorageClass]>,
287 default_storage_class: StorageClass,
288 capabilities: CoreCapabilityBundle,
289 ) -> Self {
290 Self {
291 engine_id,
292 provider_device_identity,
293 hardware_class,
294 storage_classes,
295 default_storage_class,
296 capabilities,
297 }
298 }
299}
300
301pub struct ExecutableEngineRegistrationConfig<B> {
357 metadata: EngineRegistrationMetadata,
358 backend: B,
359 event_domain_driver: Arc<dyn EventDomainDriver>,
360 ingress: InputIngressContract,
361 cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
362}
363
364impl<B> fmt::Debug for ExecutableEngineRegistrationConfig<B> {
365 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
366 formatter
367 .debug_struct("ExecutableEngineRegistrationConfig")
368 .field("metadata", &self.metadata)
369 .field("backend_type", &std::any::type_name::<B>())
370 .field("event_domain_driver_present", &true)
371 .field("ingress_present", &true)
372 .field("cache_owner_present", &self.cache_owner.is_some())
373 .finish_non_exhaustive()
374 }
375}
376
377impl<B> ExecutableEngineRegistrationConfig<B> {
378 pub fn new(
380 metadata: EngineRegistrationMetadata,
381 backend: B,
382 event_domain_driver: Arc<dyn EventDomainDriver>,
383 ingress: InputIngressContract,
384 cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
385 ) -> Self {
386 Self {
387 metadata,
388 backend,
389 event_domain_driver,
390 ingress,
391 cache_owner,
392 }
393 }
394}
395
396#[derive(Debug)]
435pub struct PreparationOnlyEngineRegistrationConfig {
436 metadata: EngineRegistrationMetadata,
437 context_identity: ExecutionContextIdentity,
438}
439
440impl PreparationOnlyEngineRegistrationConfig {
441 pub fn new(
443 metadata: EngineRegistrationMetadata,
444 context_identity: ExecutionContextIdentity,
445 ) -> Self {
446 Self {
447 metadata,
448 context_identity,
449 }
450 }
451}
452
453#[derive(Clone)]
455pub(crate) struct ExecutableEngineContract {
456 provider_device_identity: ProviderDeviceIdentity,
457 context_identity: ExecutionContextIdentity,
458 capabilities: CoreCapabilityBundle,
459 executor: Arc<dyn execution::ErasedTensorBackendExecutor>,
460 event_domain_driver: Arc<dyn EventDomainDriver>,
461 ingress: InputIngressContract,
462 cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
463}
464
465impl ExecutableEngineContract {
466 pub(super) fn new<B>(
470 provider_device_identity: ProviderDeviceIdentity,
471 capabilities: CoreCapabilityBundle,
472 backend: B,
473 event_domain_driver: Arc<dyn EventDomainDriver>,
474 ingress: InputIngressContract,
475 cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
476 ) -> Self
477 where
478 B: TensorBackend + Send + Sync + 'static,
479 {
480 Self {
481 provider_device_identity,
482 context_identity: ExecutionContextIdentity::of::<B>(),
483 capabilities,
484 executor: execution::erased_tensor_backend_executor(backend),
485 event_domain_driver,
486 ingress,
487 cache_owner,
488 }
489 }
490
491 #[cfg(test)]
492 pub(super) fn from_erased_for_test(
493 provider_device_identity: ProviderDeviceIdentity,
494 context_identity: ExecutionContextIdentity,
495 capabilities: CoreCapabilityBundle,
496 executor: Arc<dyn execution::ErasedTensorBackendExecutor>,
497 event_domain_driver: Arc<dyn EventDomainDriver>,
498 ingress: InputIngressContract,
499 cache_owner: Option<Arc<dyn RuntimeCacheOwner>>,
500 ) -> Self {
501 Self {
502 provider_device_identity,
503 context_identity,
504 capabilities,
505 executor,
506 event_domain_driver,
507 ingress,
508 cache_owner,
509 }
510 }
511
512 pub(super) fn capabilities(&self) -> &CoreCapabilityBundle {
513 &self.capabilities
514 }
515
516 pub(super) fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
517 &self.provider_device_identity
518 }
519
520 pub(super) fn context_identity(&self) -> ExecutionContextIdentity {
521 self.context_identity
522 }
523
524 pub(super) fn executor(&self) -> &Arc<dyn execution::ErasedTensorBackendExecutor> {
525 &self.executor
526 }
527
528 pub(super) fn event_domain_driver(&self) -> &Arc<dyn EventDomainDriver> {
529 &self.event_domain_driver
530 }
531
532 pub(super) fn cache_owner(&self) -> Option<&Arc<dyn RuntimeCacheOwner>> {
533 self.cache_owner.as_ref()
534 }
535
536 pub(super) fn accepts_input_placement(
537 &self,
538 placement: &Placement,
539 storage_class: &StorageClass,
540 ) -> bool {
541 self.ingress.accepts_placement(placement, storage_class)
542 }
543
544 pub(super) fn accepts_input_signature(
545 &self,
546 input: &InputSignatureEntry,
547 storage_class: &StorageClass,
548 ) -> bool {
549 self.ingress.accepts_signature(input, storage_class)
550 }
551
552 pub(super) fn accepts_runtime_input(
553 &self,
554 input: &TensorRead<'_>,
555 storage_class: &StorageClass,
556 ) -> bool {
557 self.ingress.accepts_runtime_input(input, storage_class)
558 }
559
560 pub(super) fn owns_resident_tensor(
561 &self,
562 input: &TensorRead<'_>,
563 storage_class: &StorageClass,
564 ) -> bool {
565 self.ingress.owns_resident_output(input, storage_class)
566 }
567}
568
569impl fmt::Debug for ExecutableEngineContract {
570 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
571 formatter
572 .debug_struct("ExecutableEngineContract")
573 .field("provider_device_identity", &self.provider_device_identity)
574 .field("context_identity", &self.context_identity)
575 .field("capabilities", &self.capabilities)
576 .field("executor", &self.executor.backend_type_name())
577 .field("event_domain_driver", &self.event_domain_driver)
578 .field("ingress", &self.ingress)
579 .field("cache_owner", &self.cache_owner.is_some())
580 .finish_non_exhaustive()
581 }
582}
583
584pub(crate) struct ProviderExecutableBinding {
586 engine_id: EngineId,
587 hardware_class: HardwareClassId,
588 storage_classes: Arc<[StorageClass]>,
589 default_storage_class: StorageClass,
590 contract: ExecutableEngineContract,
591}
592
593impl ProviderExecutableBinding {
594 pub(super) fn new(
597 engine_id: EngineId,
598 hardware_class: HardwareClassId,
599 storage_classes: Arc<[StorageClass]>,
600 default_storage_class: StorageClass,
601 contract: ExecutableEngineContract,
602 ) -> Result<Self, RuntimeConfigError> {
603 validate_storage_classes(&engine_id, &storage_classes, &default_storage_class)?;
604 Ok(Self {
605 engine_id,
606 hardware_class,
607 storage_classes,
608 default_storage_class,
609 contract,
610 })
611 }
612
613 pub(super) fn engine_id(&self) -> &EngineId {
614 &self.engine_id
615 }
616
617 pub(super) fn hardware_class(&self) -> &HardwareClassId {
618 &self.hardware_class
619 }
620
621 pub(super) fn storage_classes(&self) -> &[StorageClass] {
622 &self.storage_classes
623 }
624
625 pub(super) fn default_storage_class(&self) -> &StorageClass {
626 &self.default_storage_class
627 }
628
629 pub(super) fn contract(&self) -> &ExecutableEngineContract {
630 &self.contract
631 }
632}
633
634impl fmt::Debug for ProviderExecutableBinding {
635 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
636 formatter
637 .debug_struct("ProviderExecutableBinding")
638 .field("engine_id", &self.engine_id)
639 .field("hardware_class", &self.hardware_class)
640 .field("storage_class_count", &self.storage_classes.len())
641 .field("contract", &self.contract)
642 .finish_non_exhaustive()
643 }
644}
645
646impl Clone for ProviderExecutableBinding {
647 fn clone(&self) -> Self {
648 Self {
649 engine_id: self.engine_id.clone(),
650 hardware_class: self.hardware_class.clone(),
651 storage_classes: Arc::clone(&self.storage_classes),
652 default_storage_class: self.default_storage_class.clone(),
653 contract: self.contract.clone(),
654 }
655 }
656}
657
658pub(crate) struct ProviderPreparationBinding {
660 engine_id: EngineId,
661 provider_device_identity: ProviderDeviceIdentity,
662 context_identity: ExecutionContextIdentity,
663 hardware_class: HardwareClassId,
664 storage_classes: Arc<[StorageClass]>,
665 default_storage_class: StorageClass,
666 capabilities: CoreCapabilityBundle,
667}
668
669impl ProviderPreparationBinding {
670 pub(super) fn new(
673 engine_id: EngineId,
674 provider_device_identity: ProviderDeviceIdentity,
675 context_identity: ExecutionContextIdentity,
676 hardware_class: HardwareClassId,
677 storage_classes: Arc<[StorageClass]>,
678 default_storage_class: StorageClass,
679 capabilities: CoreCapabilityBundle,
680 ) -> Result<Self, RuntimeConfigError> {
681 validate_storage_classes(&engine_id, &storage_classes, &default_storage_class)?;
682 Ok(Self {
683 engine_id,
684 provider_device_identity,
685 context_identity,
686 hardware_class,
687 storage_classes,
688 default_storage_class,
689 capabilities,
690 })
691 }
692
693 pub(super) fn engine_id(&self) -> &EngineId {
694 &self.engine_id
695 }
696
697 pub(super) fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
698 &self.provider_device_identity
699 }
700
701 pub(super) fn context_identity(&self) -> ExecutionContextIdentity {
702 self.context_identity
703 }
704
705 pub(super) fn hardware_class(&self) -> &HardwareClassId {
706 &self.hardware_class
707 }
708
709 pub(super) fn storage_classes(&self) -> &[StorageClass] {
710 &self.storage_classes
711 }
712
713 pub(super) fn default_storage_class(&self) -> &StorageClass {
714 &self.default_storage_class
715 }
716
717 pub(super) fn capabilities(&self) -> &CoreCapabilityBundle {
718 &self.capabilities
719 }
720}
721
722impl Clone for ProviderPreparationBinding {
723 fn clone(&self) -> Self {
724 Self {
725 engine_id: self.engine_id.clone(),
726 provider_device_identity: self.provider_device_identity.clone(),
727 context_identity: self.context_identity,
728 hardware_class: self.hardware_class.clone(),
729 storage_classes: Arc::clone(&self.storage_classes),
730 default_storage_class: self.default_storage_class.clone(),
731 capabilities: self.capabilities.clone(),
732 }
733 }
734}
735
736impl fmt::Debug for ProviderPreparationBinding {
737 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
738 formatter
739 .debug_struct("ProviderPreparationBinding")
740 .field("engine_id", &self.engine_id)
741 .field("provider_device_identity", &self.provider_device_identity)
742 .field("context_identity", &self.context_identity)
743 .field("hardware_class", &self.hardware_class)
744 .field("storage_class_count", &self.storage_classes.len())
745 .field("capabilities", &self.capabilities)
746 .finish_non_exhaustive()
747 }
748}
749
750#[derive(Clone, Debug)]
752pub(crate) enum EngineRegistrationState {
753 PreparationOnly { binding: ProviderPreparationBinding },
756 Executable(ProviderExecutableBinding),
758}
759
760impl EngineRegistrationState {
761 pub(super) fn capabilities(&self) -> &CoreCapabilityBundle {
762 match self {
763 Self::PreparationOnly { binding } => binding.capabilities(),
764 Self::Executable(binding) => binding.contract().capabilities(),
765 }
766 }
767
768 pub(super) fn engine_id(&self) -> &EngineId {
769 match self {
770 Self::PreparationOnly { binding } => binding.engine_id(),
771 Self::Executable(binding) => binding.engine_id(),
772 }
773 }
774
775 pub(super) fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
776 match self {
777 Self::PreparationOnly { binding } => binding.provider_device_identity(),
778 Self::Executable(binding) => binding.contract().provider_device_identity(),
779 }
780 }
781
782 pub(super) fn context_identity(&self) -> ExecutionContextIdentity {
783 match self {
784 Self::PreparationOnly { binding } => binding.context_identity(),
785 Self::Executable(binding) => binding.contract().context_identity(),
786 }
787 }
788
789 pub(super) fn hardware_class(&self) -> &HardwareClassId {
790 match self {
791 Self::PreparationOnly { binding } => binding.hardware_class(),
792 Self::Executable(binding) => binding.hardware_class(),
793 }
794 }
795
796 pub(super) fn storage_classes(&self) -> &[StorageClass] {
797 match self {
798 Self::PreparationOnly { binding } => binding.storage_classes(),
799 Self::Executable(binding) => binding.storage_classes(),
800 }
801 }
802
803 pub(super) fn default_storage_class(&self) -> &StorageClass {
804 match self {
805 Self::PreparationOnly { binding } => binding.default_storage_class(),
806 Self::Executable(binding) => binding.default_storage_class(),
807 }
808 }
809}
810
811#[derive(Clone)]
813pub struct EngineRegistration {
814 state: EngineRegistrationState,
815 candidate_token: Arc<CandidateRegistrationToken>,
816}
817
818impl EngineRegistration {
819 pub(super) fn preparation_only(binding: ProviderPreparationBinding) -> Self {
821 Self {
822 state: EngineRegistrationState::PreparationOnly { binding },
823 candidate_token: Arc::new(CandidateRegistrationToken),
824 }
825 }
826
827 pub(super) fn executable(binding: ProviderExecutableBinding) -> Self {
829 Self {
830 state: EngineRegistrationState::Executable(binding),
831 candidate_token: Arc::new(CandidateRegistrationToken),
832 }
833 }
834
835 pub(super) fn from_state(state: EngineRegistrationState) -> Self {
836 Self {
837 state,
838 candidate_token: Arc::new(CandidateRegistrationToken),
839 }
840 }
841
842 #[cfg(test)]
844 pub(crate) fn execution_state(&self) -> &EngineRegistrationState {
845 &self.state
846 }
847
848 pub fn engine_id(&self) -> &EngineId {
850 self.state.engine_id()
851 }
852
853 pub fn provider_device_identity(&self) -> &ProviderDeviceIdentity {
855 self.state.provider_device_identity()
856 }
857
858 pub fn context_identity(&self) -> ExecutionContextIdentity {
860 self.state.context_identity()
861 }
862
863 pub fn hardware_class(&self) -> &HardwareClassId {
865 self.state.hardware_class()
866 }
867
868 pub fn storage_classes(&self) -> &[StorageClass] {
870 self.state.storage_classes()
871 }
872
873 pub(super) fn with_candidate_token(
874 mut self,
875 candidate_token: Arc<CandidateRegistrationToken>,
876 ) -> Self {
877 self.candidate_token = candidate_token;
878 self
879 }
880
881 pub(super) fn into_state_and_token(
882 self,
883 ) -> (EngineRegistrationState, Arc<CandidateRegistrationToken>) {
884 (self.state, self.candidate_token)
885 }
886
887 pub fn default_storage_class(&self) -> &StorageClass {
889 self.state.default_storage_class()
890 }
891
892 pub fn capabilities(&self) -> &CoreCapabilityBundle {
894 self.state.capabilities()
895 }
896
897 pub(super) fn candidate_identical(&self, other: &Self) -> bool {
898 self.engine_id() == other.engine_id()
899 && Arc::ptr_eq(&self.candidate_token, &other.candidate_token)
900 }
901}
902
903pub fn assemble_executable_engine_registration<B>(
916 config: ExecutableEngineRegistrationConfig<B>,
917) -> Result<EngineRegistration, RuntimeConfigError>
918where
919 B: TensorBackend + Send + Sync + 'static,
920{
921 let ExecutableEngineRegistrationConfig {
922 metadata:
923 EngineRegistrationMetadata {
924 engine_id,
925 provider_device_identity,
926 hardware_class,
927 storage_classes,
928 default_storage_class,
929 capabilities,
930 },
931 backend,
932 event_domain_driver,
933 ingress,
934 cache_owner,
935 } = config;
936 let contract = ExecutableEngineContract::new(
937 provider_device_identity,
938 capabilities,
939 backend,
940 event_domain_driver,
941 ingress,
942 cache_owner,
943 );
944 let binding = ProviderExecutableBinding::new(
945 engine_id,
946 hardware_class,
947 storage_classes,
948 default_storage_class,
949 contract,
950 )?;
951 Ok(EngineRegistration::executable(binding))
952}
953
954pub fn assemble_preparation_only_engine_registration(
966 config: PreparationOnlyEngineRegistrationConfig,
967) -> Result<EngineRegistration, RuntimeConfigError> {
968 let PreparationOnlyEngineRegistrationConfig {
969 metadata:
970 EngineRegistrationMetadata {
971 engine_id,
972 provider_device_identity,
973 hardware_class,
974 storage_classes,
975 default_storage_class,
976 capabilities,
977 },
978 context_identity,
979 } = config;
980 let binding = ProviderPreparationBinding::new(
981 engine_id,
982 provider_device_identity,
983 context_identity,
984 hardware_class,
985 storage_classes,
986 default_storage_class,
987 capabilities,
988 )?;
989 Ok(EngineRegistration::preparation_only(binding))
990}
991
992impl fmt::Debug for EngineRegistration {
993 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
994 formatter
995 .debug_struct("EngineRegistration")
996 .field("engine_id", self.engine_id())
997 .field("provider_device_identity", self.provider_device_identity())
998 .field("context_identity", &self.context_identity())
999 .field("hardware_class", self.hardware_class())
1000 .field("storage_class_count", &self.storage_classes().len())
1001 .field("default_storage_class", self.default_storage_class())
1002 .field("state", &self.state)
1003 .finish_non_exhaustive()
1004 }
1005}
1006
1007fn validate_storage_classes(
1008 engine_id: &EngineId,
1009 storage_classes: &[StorageClass],
1010 default_storage_class: &StorageClass,
1011) -> Result<(), RuntimeConfigError> {
1012 if storage_classes.is_empty() {
1013 return Err(RuntimeConfigError::EmptyStorageClasses {
1014 engine_id: engine_id.clone(),
1015 });
1016 }
1017 for duplicate_index in 0..storage_classes.len() {
1018 if let Some(first_index) = (0..duplicate_index)
1019 .find(|&first| storage_classes[first] == storage_classes[duplicate_index])
1020 {
1021 return Err(RuntimeConfigError::DuplicateStorageClass {
1022 engine_id: engine_id.clone(),
1023 storage_class: storage_classes[duplicate_index].clone(),
1024 first_index,
1025 duplicate_index,
1026 });
1027 }
1028 }
1029 if !storage_classes
1030 .iter()
1031 .any(|storage_class| storage_class == default_storage_class)
1032 {
1033 return Err(RuntimeConfigError::DefaultStorageClassNotListed {
1034 engine_id: engine_id.clone(),
1035 default_storage_class: default_storage_class.clone(),
1036 });
1037 }
1038 Ok(())
1039}