1#![deny(missing_docs)]
32
33use std::collections::{BTreeMap, HashMap, HashSet};
34use std::error::Error;
35use std::fmt::{self, Display, Formatter};
36use std::marker::PhantomData;
37use std::panic::{AssertUnwindSafe, catch_unwind};
38use std::sync::Arc;
39use std::sync::atomic::{AtomicBool, Ordering};
40use std::time::Instant;
41
42use wip_interface::DispatchError;
43pub use wip_interface::{
44 EffectGuarantee, HostError, HostErrorKind, InterfaceDefinition, InterfaceImplementation,
45};
46pub use wip_protocol;
47pub use wip_protocol::{
48 CallOperationRequest as CallRequest, CallOperationResponse as CallResult, InterfaceTarget,
49 Target,
50};
51use wip_protocol::{
52 FetchInterfaceRequest, FetchInterfaceResponse, InterfaceDescriptor, InterfaceReference, Object,
53 ProtocolError, ProtocolErrorCode, ValidationError, Value,
54};
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58pub struct Validator(Vec<u8>);
59
60impl Validator {
61 #[must_use]
63 pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
64 Self(bytes.into())
65 }
66
67 #[must_use]
69 pub fn as_bytes(&self) -> &[u8] {
70 &self.0
71 }
72}
73
74#[derive(Debug, Clone, Default)]
79pub struct CancellationToken(Arc<AtomicBool>);
80
81impl CancellationToken {
82 #[must_use]
84 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub fn cancel(&self) {
90 self.0.store(true, Ordering::Release);
91 }
92
93 #[must_use]
95 pub fn is_cancelled(&self) -> bool {
96 self.0.load(Ordering::Acquire)
97 }
98}
99
100#[derive(Debug, Clone)]
108pub struct ExecutionContext {
109 cancellation: CancellationToken,
110 deadline: Option<Instant>,
111 resource_budget: Option<u64>,
112}
113
114impl Default for ExecutionContext {
115 fn default() -> Self {
116 Self {
117 cancellation: CancellationToken::new(),
118 deadline: None,
119 resource_budget: None,
120 }
121 }
122}
123
124impl ExecutionContext {
125 #[must_use]
127 pub fn new() -> Self {
128 Self::default()
129 }
130
131 #[must_use]
133 pub fn with_cancellation(mut self, cancellation: CancellationToken) -> Self {
134 self.cancellation = cancellation;
135 self
136 }
137
138 #[must_use]
140 pub fn with_deadline(mut self, deadline: Instant) -> Self {
141 self.deadline = Some(deadline);
142 self
143 }
144
145 #[must_use]
147 pub fn with_resource_budget(mut self, resource_budget: u64) -> Self {
148 self.resource_budget = Some(resource_budget);
149 self
150 }
151
152 #[must_use]
154 pub fn cancellation(&self) -> &CancellationToken {
155 &self.cancellation
156 }
157
158 #[must_use]
160 pub fn deadline(&self) -> Option<Instant> {
161 self.deadline
162 }
163
164 #[must_use]
166 pub fn resource_budget(&self) -> Option<u64> {
167 self.resource_budget
168 }
169
170 fn preflight(&self) -> Result<(), CallError> {
171 if self.cancellation.is_cancelled() {
172 return Err(CallError::new(
173 CallErrorKind::ResourceLimitExceeded,
174 "operation was cancelled before execution",
175 ));
176 }
177 if self
178 .deadline
179 .is_some_and(|deadline| Instant::now() >= deadline)
180 {
181 return Err(CallError::new(
182 CallErrorKind::ResourceLimitExceeded,
183 "operation deadline elapsed before execution",
184 ));
185 }
186 Ok(())
187 }
188}
189
190#[derive(Debug, Clone)]
196pub struct Scope {
197 path: String,
198 lifetime: Arc<AtomicBool>,
199}
200
201impl Scope {
202 pub fn new(path: impl Into<String>) -> Result<Self, ValidationError> {
204 let path = path.into();
205 wip_protocol::validate_path(&path)?;
206 Ok(Self {
207 path,
208 lifetime: Arc::new(AtomicBool::new(true)),
209 })
210 }
211
212 #[must_use]
214 pub fn path(&self) -> &str {
215 &self.path
216 }
217
218 fn same_lifetime(&self, other: &Self) -> bool {
219 self.path == other.path && Arc::ptr_eq(&self.lifetime, &other.lifetime)
220 }
221
222 fn active(&self) -> bool {
223 self.lifetime.load(Ordering::Acquire)
224 }
225}
226
227#[derive(Debug, Clone)]
233pub struct ScopeObservation {
234 scope: Scope,
235 reference: Option<String>,
236}
237
238impl ScopeObservation {
239 #[must_use]
241 pub fn new(scope: &Scope, object: &impl WipObject) -> Self {
242 Self {
243 scope: scope.clone(),
244 reference: object.reference().map(str::to_owned),
245 }
246 }
247
248 #[must_use]
250 pub fn reference(&self) -> Option<&str> {
251 self.reference.as_deref()
252 }
253
254 #[must_use]
256 pub fn scope(&self) -> &Scope {
257 &self.scope
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
263pub struct InterfaceRef(InterfaceReference);
264
265impl InterfaceRef {
266 #[must_use]
268 pub fn as_str(&self) -> &str {
269 &self.0.name
270 }
271
272 #[must_use]
274 pub fn protocol_reference(&self) -> InterfaceReference {
275 self.0.clone()
276 }
277}
278
279#[derive(Debug, Clone, PartialEq)]
281pub struct InterfaceObservation {
282 pub reference: InterfaceRef,
284 pub descriptor: InterfaceDescriptor,
286 pub scope_ref: Option<String>,
288 pub validator: Option<Validator>,
290}
291
292impl InterfaceObservation {
293 #[must_use]
299 pub fn project_operations(mut self, visible: impl Fn(&str) -> bool) -> Self {
300 restrict_descriptor(&mut self.descriptor, visible);
301 self
302 }
303
304 pub fn protocol_response(&self) -> Result<FetchInterfaceResponse, ProtocolError> {
310 let response = FetchInterfaceResponse {
311 interface: self.reference.protocol_reference(),
312 descriptor: self.descriptor.clone(),
313 scope_ref: self.scope_ref.clone(),
314 validator: self.validator.as_ref().map(|validator| validator.0.clone()),
315 };
316 response
317 .validate_for(&FetchInterfaceRequest {
318 interface: self.reference.protocol_reference(),
319 })
320 .map_err(|_| ProtocolError {
321 code: ProtocolErrorCode::Internal,
322 message: "Host produced an invalid interface response".into(),
323 })?;
324 Ok(response)
325 }
326}
327
328#[derive(Debug)]
329struct InterfaceArtifact {
330 scope: Scope,
331 reference: InterfaceRef,
332 descriptor: InterfaceDescriptor,
333 validator: Option<Validator>,
334}
335
336pub struct RegisteredInterface<D> {
341 artifact: Arc<InterfaceArtifact>,
342 definition: PhantomData<fn() -> D>,
343}
344
345impl<D> Clone for RegisteredInterface<D> {
346 fn clone(&self) -> Self {
347 Self {
348 artifact: Arc::clone(&self.artifact),
349 definition: PhantomData,
350 }
351 }
352}
353
354impl<D> RegisteredInterface<D> {
355 #[must_use]
357 pub fn reference(&self) -> &InterfaceRef {
358 &self.artifact.reference
359 }
360}
361
362#[derive(Debug, Default)]
369pub struct InterfaceRegistry {
370 artifacts: HashMap<InterfaceRef, Arc<InterfaceArtifact>>,
371}
372
373impl InterfaceRegistry {
374 #[must_use]
376 pub fn new() -> Self {
377 Self::default()
378 }
379
380 pub fn register<D>(
382 &mut self,
383 scope: &Scope,
384 name: impl Into<String>,
385 _definition: D,
386 validator: Option<Validator>,
387 ) -> Result<RegisteredInterface<D>, RegistrationError>
388 where
389 D: InterfaceDefinition + 'static,
390 {
391 let reference = InterfaceRef(InterfaceReference {
392 scope: scope.path.clone(),
393 name: name.into(),
394 });
395 reference
396 .0
397 .validate()
398 .map_err(RegistrationError::InvalidReference)?;
399 if !scope.active() {
400 return Err(RegistrationError::RetiredScope);
401 }
402 if self.artifacts.contains_key(&reference) {
403 return Err(RegistrationError::AlreadyRegistered);
404 }
405 let descriptor = D::descriptor();
406 descriptor
407 .validate()
408 .map_err(RegistrationError::InvalidDescriptor)?;
409 let artifact = Arc::new(InterfaceArtifact {
410 scope: scope.clone(),
411 reference: reference.clone(),
412 descriptor,
413 validator,
414 });
415 self.artifacts.insert(reference, Arc::clone(&artifact));
416 Ok(RegisteredInterface {
417 artifact,
418 definition: PhantomData,
419 })
420 }
421
422 pub fn remove_scope(&mut self, scope: &Scope) {
427 scope.lifetime.store(false, Ordering::Release);
428 self.artifacts
429 .retain(|_, artifact| !artifact.scope.same_lifetime(scope));
430 }
431
432 #[must_use]
434 pub fn resolve(&self, reference: &InterfaceReference) -> Option<InterfaceRef> {
435 let reference = InterfaceRef(reference.clone());
436 self.artifacts.contains_key(&reference).then_some(reference)
437 }
438
439 pub fn scopes(&self) -> impl Iterator<Item = &Scope> {
441 self.artifacts.values().map(|artifact| &artifact.scope)
442 }
443
444 pub fn fetch_interface(
446 &self,
447 reference: &InterfaceReference,
448 current: &ScopeObservation,
449 ) -> Result<InterfaceObservation, ProtocolError> {
450 reference.validate().map_err(|error| ProtocolError {
451 code: ProtocolErrorCode::InvalidRequest,
452 message: error.to_string(),
453 })?;
454 self.artifacts
455 .get(&InterfaceRef(reference.clone()))
456 .filter(|artifact| {
457 artifact.scope.active() && artifact.scope.same_lifetime(¤t.scope)
458 })
459 .map(|artifact| InterfaceObservation {
460 reference: artifact.reference.clone(),
461 descriptor: artifact.descriptor.clone(),
462 scope_ref: current.reference.clone(),
463 validator: artifact.validator.clone(),
464 })
465 .ok_or_else(interface_not_found)
466 }
467}
468
469fn interface_not_found() -> ProtocolError {
470 ProtocolError {
471 code: ProtocolErrorCode::InterfaceNotFound,
472 message: "interface is not currently published".into(),
473 }
474}
475
476#[derive(Debug)]
478pub enum RegistrationError {
479 AlreadyRegistered,
481 InvalidDescriptor(ValidationError),
483 InvalidReference(ValidationError),
485 RetiredScope,
487}
488
489impl Display for RegistrationError {
490 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
491 match self {
492 Self::AlreadyRegistered => {
493 formatter.write_str("interface scope/name already registered")
494 }
495 Self::InvalidDescriptor(error) => {
496 write!(formatter, "invalid interface descriptor: {error}")
497 }
498 Self::InvalidReference(error) => {
499 write!(formatter, "invalid interface reference: {error}")
500 }
501 Self::RetiredScope => formatter.write_str("scope publication lifetime has ended"),
502 }
503 }
504}
505impl Error for RegistrationError {
506 fn source(&self) -> Option<&(dyn Error + 'static)> {
507 match self {
508 Self::InvalidDescriptor(error) | Self::InvalidReference(error) => Some(error),
509 Self::AlreadyRegistered | Self::RetiredScope => None,
510 }
511 }
512}
513
514pub trait WipObject {
520 fn name(&self) -> &str;
522
523 fn description(&self) -> Option<&str> {
525 None
526 }
527
528 fn reference(&self) -> Option<&str> {
530 None
531 }
532
533 fn validator(&self) -> Option<&Validator> {
535 None
536 }
537
538 fn requires_validator(&self, _interface: &InterfaceRef, _operation: &str) -> bool {
543 false
544 }
545}
546
547fn restrict_descriptor(descriptor: &mut InterfaceDescriptor, visible: impl Fn(&str) -> bool) {
548 let count = descriptor.operations.len();
549 descriptor
550 .operations
551 .retain(|operation| visible(&operation.name));
552 if descriptor.operations.len() == count {
553 return;
554 }
555 descriptor.documentation = None;
556 let mut used = HashSet::new();
557 fn references(expression: &wip_protocol::TypeExpr, used: &mut HashSet<String>) {
558 use wip_protocol::TypeExpr;
559 match expression {
560 TypeExpr::Named { name } => {
561 used.insert(name.clone());
562 }
563 TypeExpr::Record { fields } => {
564 for field in fields {
565 references(&field.r#type, used);
566 }
567 }
568 TypeExpr::List { items } => references(items, used),
569 TypeExpr::Union { cases } => {
570 for case in cases {
571 if let Some(payload) = &case.payload {
572 references(payload, used);
573 }
574 }
575 }
576 _ => {}
577 }
578 }
579 for operation in &descriptor.operations {
580 for parameter in &operation.parameters {
581 references(¶meter.r#type, &mut used);
582 }
583 references(&operation.returns.r#type, &mut used);
584 }
585 loop {
586 let before = used.len();
587 for declaration in &descriptor.types {
588 if used.contains(&declaration.name) {
589 references(&declaration.definition, &mut used);
590 }
591 }
592 if before == used.len() {
593 break;
594 }
595 }
596 descriptor
597 .types
598 .retain(|declaration| used.contains(&declaration.name));
599}
600
601type DispatchFn<T> = fn(&T, &str, BTreeMap<String, Value>) -> Result<Value, DispatchError>;
602
603struct BoundInterface<T> {
604 artifact: Arc<InterfaceArtifact>,
605 dispatch: DispatchFn<T>,
606}
607
608pub struct PublishedObject<T: WipObject> {
614 implementation: T,
615 interfaces: Vec<BoundInterface<T>>,
616 scopes: Vec<ScopeObservation>,
617}
618
619impl<T: WipObject> PublishedObject<T> {
620 #[must_use]
622 pub fn new(implementation: T) -> Self {
623 Self {
624 implementation,
625 interfaces: Vec::new(),
626 scopes: Vec::new(),
627 }
628 }
629
630 #[must_use]
636 pub fn bind_scope(mut self, current: ScopeObservation) -> Self {
637 self.scopes
638 .retain(|old| old.scope.path != current.scope.path);
639 self.scopes.push(current);
640 self
641 }
642
643 fn current_scope(&self, scope: &Scope) -> Option<&ScopeObservation> {
644 self.scopes
645 .iter()
646 .find(|current| scope.active() && scope.same_lifetime(¤t.scope))
647 }
648
649 fn scopes_valid_at(&self, path: &str) -> bool {
650 self.interfaces.iter().all(|bound| {
651 bound.artifact.reference.0.validate_for_path(path).is_ok()
652 && self
653 .current_scope(&bound.artifact.scope)
654 .is_some_and(|current| {
655 current.scope.path != path
656 || current.reference() == self.implementation.reference()
657 })
658 })
659 }
660
661 pub fn expose<D>(mut self, interface: &RegisteredInterface<D>) -> Result<Self, PublicationError>
667 where
668 D: InterfaceImplementation<T>,
669 {
670 if self
671 .interfaces
672 .iter()
673 .any(|bound| bound.artifact.reference == interface.artifact.reference)
674 {
675 return Err(PublicationError::DuplicateInterface(
676 interface.artifact.reference.clone(),
677 ));
678 }
679
680 self.interfaces.push(BoundInterface {
681 artifact: Arc::clone(&interface.artifact),
682 dispatch: D::dispatch,
683 });
684 Ok(self)
685 }
686
687 pub fn project(
694 mut self,
695 interfaces: &[InterfaceRef],
696 visible: impl Fn(&InterfaceRef, &str) -> bool,
697 ) -> Result<Self, PublicationError> {
698 let mut projected = Vec::with_capacity(interfaces.len());
699 for reference in interfaces {
700 if projected
701 .iter()
702 .any(|bound: &BoundInterface<T>| &bound.artifact.reference == reference)
703 {
704 return Err(PublicationError::DuplicateInterface(reference.clone()));
705 }
706 let Some(index) = self
707 .interfaces
708 .iter()
709 .position(|bound| &bound.artifact.reference == reference)
710 else {
711 return Err(PublicationError::UnexposedInterface(reference.clone()));
712 };
713 let mut bound = self.interfaces.remove(index);
714 let mut descriptor = bound.artifact.descriptor.clone();
715 restrict_descriptor(&mut descriptor, |operation| visible(reference, operation));
716 bound.artifact = Arc::new(InterfaceArtifact {
717 scope: bound.artifact.scope.clone(),
718 reference: reference.clone(),
719 descriptor,
720 validator: bound.artifact.validator.clone(),
721 });
722 projected.push(bound);
723 }
724 self.interfaces = projected;
725 Ok(self)
726 }
727
728 #[must_use]
730 pub fn implementation(&self) -> &T {
731 &self.implementation
732 }
733
734 #[must_use]
743 pub fn protocol_object(&self) -> Object {
744 Object {
745 name: self.implementation.name().to_owned(),
746 description: self.implementation.description().map(str::to_owned),
747 interfaces: self
748 .interfaces
749 .iter()
750 .map(|bound| bound.artifact.reference.protocol_reference())
751 .collect(),
752 r#ref: self.implementation.reference().map(str::to_owned),
753 validator: self
754 .implementation
755 .validator()
756 .map(|validator| validator.0.clone()),
757 }
758 }
759
760 pub fn protocol_object_at(&self, path: &str) -> Result<Object, CallError> {
766 wip_protocol::validate_path(path)
767 .map_err(|error| CallError::new(CallErrorKind::InvalidRequest, error.to_string()))?;
768 if !self.scopes_valid_at(path) {
769 return Err(CallError::new(
770 CallErrorKind::Internal,
771 "Host published interfaces without current compatible scope Objects",
772 ));
773 }
774 let object = self.protocol_object();
775 object.validate_at_path(path).map_err(|_| {
776 CallError::new(
777 CallErrorKind::Internal,
778 "Host produced an invalid object response",
779 )
780 })?;
781 Ok(object)
782 }
783
784 fn dispatch_resolved(
785 &self,
786 context: &ExecutionContext,
787 request: CallRequest,
788 authorize: impl FnOnce(&T, &InterfaceRef, &str) -> bool,
789 ) -> OperationAttempt {
790 let CallRequest {
791 target,
792 interface,
793 operation,
794 arguments,
795 } = request;
796 self.dispatch_resolved_with_decoder(
797 context,
798 target,
799 interface,
800 operation,
801 |_| Ok::<_, std::convert::Infallible>(arguments),
802 authorize,
803 )
804 }
805
806 fn dispatch_resolved_with_decoder<E>(
807 &self,
808 context: &ExecutionContext,
809 target: Target,
810 interface: InterfaceTarget,
811 operation: String,
812 decode_arguments: impl FnOnce(&InterfaceDescriptor) -> Result<BTreeMap<String, Value>, E>,
813 authorize: impl FnOnce(&T, &InterfaceRef, &str) -> bool,
814 ) -> OperationAttempt {
815 let pre_dispatch_failure =
816 |kind, message: &str| OperationAttempt::NotStarted(CallError::new(kind, message));
817 let current_object_validator = self.implementation.validator();
818 if let Some(supplied) = target.validator.as_deref()
819 && current_object_validator.map(Validator::as_bytes) != Some(supplied)
820 {
821 return pre_dispatch_failure(
822 CallErrorKind::ValidatorMismatch,
823 "object validator does not match current object state",
824 );
825 }
826
827 if interface.reference.validate_for_path(&target.path).is_err() {
828 return pre_dispatch_failure(
829 CallErrorKind::InterfaceMismatch,
830 "interface scope is not an ancestor of the target path",
831 );
832 }
833
834 let Some(bound) = self
835 .interfaces
836 .iter()
837 .find(|bound| bound.artifact.reference.protocol_reference() == interface.reference)
838 else {
839 return pre_dispatch_failure(
840 CallErrorKind::InterfaceMismatch,
841 "interface is not a member of the current object publication",
842 );
843 };
844
845 let Some(current_scope) = self.current_scope(&bound.artifact.scope) else {
846 return pre_dispatch_failure(
847 CallErrorKind::InterfaceMismatch,
848 "interface scope Object is not currently associated with this publication",
849 );
850 };
851 if current_scope.scope.path == target.path
852 && current_scope.reference() != self.implementation.reference()
853 {
854 return pre_dispatch_failure(
855 CallErrorKind::Internal,
856 "provider produced inconsistent self-scope metadata",
857 );
858 }
859 if interface
860 .scope_ref
861 .as_deref()
862 .is_some_and(|supplied| current_scope.reference() != Some(supplied))
863 {
864 return pre_dispatch_failure(
865 CallErrorKind::InterfaceMismatch,
866 "scope Object ref does not match the current association",
867 );
868 }
869
870 match (
871 bound.artifact.validator.as_ref(),
872 interface.validator.as_deref(),
873 ) {
874 (Some(_), None) => {
875 return pre_dispatch_failure(
876 CallErrorKind::InterfaceValidatorRequired,
877 "registered interface requires a validator",
878 );
879 }
880 (expected, Some(actual)) if expected.map(Validator::as_bytes) != Some(actual) => {
881 return pre_dispatch_failure(
882 CallErrorKind::InterfaceValidatorMismatch,
883 "interface validator does not match the registered descriptor",
884 );
885 }
886 _ => {}
887 }
888
889 if !bound
890 .artifact
891 .descriptor
892 .operations
893 .iter()
894 .any(|declared| declared.name == operation)
895 {
896 return pre_dispatch_failure(
897 CallErrorKind::OperationNotFound,
898 &format!("operation `{operation}` is not declared"),
899 );
900 }
901
902 if target.validator.is_none()
903 && self
904 .implementation
905 .requires_validator(&bound.artifact.reference, &operation)
906 {
907 return pre_dispatch_failure(
908 CallErrorKind::ValidatorRequired,
909 "this operation requires an object validator",
910 );
911 }
912
913 let arguments = match decode_arguments(&bound.artifact.descriptor) {
914 Ok(arguments) => arguments,
915 Err(_) => {
916 return pre_dispatch_failure(
917 CallErrorKind::InvalidArguments,
918 "operation arguments could not be decoded",
919 );
920 }
921 };
922 if let Err(error) = bound
923 .artifact
924 .descriptor
925 .validate_arguments(&operation, &arguments)
926 {
927 return pre_dispatch_failure(CallErrorKind::InvalidArguments, &error.to_string());
928 }
929
930 if !authorize(&self.implementation, &bound.artifact.reference, &operation) {
931 return pre_dispatch_failure(
932 CallErrorKind::PermissionDenied,
933 "operation is not permitted for this caller",
934 );
935 }
936
937 if let Err(error) = context.preflight() {
938 return OperationAttempt::NotStarted(error);
939 }
940
941 let value = match (bound.dispatch)(&self.implementation, &operation, arguments) {
942 Ok(value) => value,
943 Err(
944 error @ (DispatchError::UnknownOperation(_)
945 | DispatchError::InvalidArguments(_)
946 | DispatchError::Decode(_)),
947 ) => return OperationAttempt::NotStarted(CallError::from_pre_dispatch(error)),
948 Err(error) => {
949 return OperationAttempt::Started(Err(OperationFailure::from_dispatch(error)));
950 }
951 };
952
953 if bound
954 .artifact
955 .descriptor
956 .validate_result(&operation, &value)
957 .is_err()
958 {
959 return OperationAttempt::Started(Err(OperationFailure::internal(
960 EffectGuarantee::Unknown,
961 "operation implementation produced an invalid result",
962 )));
963 }
964
965 OperationAttempt::Started(Ok(CallResult {
966 result: value,
967 validator: self
968 .implementation
969 .validator()
970 .map(|validator| validator.0.clone()),
971 }))
972 }
973
974 #[must_use]
976 pub fn into_inner(self) -> T {
977 self.implementation
978 }
979}
980
981#[derive(Debug, Clone, PartialEq, Eq)]
987pub struct OperationFailure {
988 kind: HostErrorKind,
989 effect_guarantee: EffectGuarantee,
990 message: String,
991}
992
993impl OperationFailure {
994 fn internal(effect_guarantee: EffectGuarantee, message: impl Into<String>) -> Self {
995 Self {
996 kind: HostErrorKind::Internal,
997 effect_guarantee,
998 message: bounded_message(message.into()),
999 }
1000 }
1001
1002 fn from_dispatch(error: DispatchError) -> Self {
1003 match error {
1004 DispatchError::Host(error) => Self {
1005 kind: error.kind(),
1006 effect_guarantee: error.effect_guarantee(),
1007 message: match error.kind() {
1008 HostErrorKind::Internal => "operation implementation failed".into(),
1009 HostErrorKind::ResourceLimitExceeded => {
1010 "operation resource limit was exceeded".into()
1011 }
1012 },
1013 },
1014 DispatchError::Panic => Self::internal(
1015 EffectGuarantee::Unknown,
1016 "operation implementation panicked",
1017 ),
1018 DispatchError::Encode(_) => Self::internal(
1019 EffectGuarantee::Unknown,
1020 "operation result could not be encoded",
1021 ),
1022 DispatchError::InvalidResult(_) => Self::internal(
1023 EffectGuarantee::Unknown,
1024 "operation implementation produced an invalid result",
1025 ),
1026 DispatchError::UnknownOperation(_)
1027 | DispatchError::InvalidArguments(_)
1028 | DispatchError::Decode(_) => {
1029 unreachable!("pre-dispatch failures are classified before OperationFailure")
1030 }
1031 }
1032 }
1033
1034 #[must_use]
1036 pub fn kind(&self) -> HostErrorKind {
1037 self.kind
1038 }
1039
1040 #[must_use]
1042 pub fn effect_guarantee(&self) -> EffectGuarantee {
1043 self.effect_guarantee
1044 }
1045
1046 #[must_use]
1051 pub fn confirm_rollback(mut self) -> Self {
1052 self.effect_guarantee = EffectGuarantee::NotCommitted;
1053 self
1054 }
1055}
1056
1057#[derive(Debug)]
1059pub enum OperationAttempt {
1060 NotStarted(CallError),
1062 Started(Result<CallResult, OperationFailure>),
1065}
1066
1067impl OperationAttempt {
1068 #[must_use]
1073 pub fn reject(kind: CallErrorKind) -> Self {
1074 Self::NotStarted(CallError::new(kind, "provider precondition failed"))
1075 }
1076
1077 #[must_use]
1081 pub fn confirm_rollback(self) -> Self {
1082 match self {
1083 Self::Started(Err(error)) => Self::Started(Err(error.confirm_rollback())),
1084 other => other,
1085 }
1086 }
1087}
1088
1089#[derive(Debug)]
1091pub enum ProviderResult<T> {
1092 Found(T),
1094 NotFound,
1096 Failure(HostError),
1102}
1103
1104pub trait ObjectProvider {
1122 type Object: WipObject;
1124
1125 fn with_object<R>(
1127 &self,
1128 context: &ExecutionContext,
1129 path: &str,
1130 operation: impl FnOnce(&PublishedObject<Self::Object>) -> R,
1131 ) -> ProviderResult<R>;
1132
1133 fn execute_operation(
1140 &self,
1141 context: &ExecutionContext,
1142 path: &str,
1143 operation: impl FnOnce(&PublishedObject<Self::Object>) -> OperationAttempt,
1144 ) -> ProviderResult<OperationAttempt> {
1145 self.with_object(context, path, operation)
1146 }
1147
1148 fn authorize(
1154 &self,
1155 _path: &str,
1156 _object: &Self::Object,
1157 _interface: &InterfaceRef,
1158 _operation: &str,
1159 ) -> bool {
1160 true
1161 }
1162
1163 fn observe_object(&self, path: &str) -> Result<Object, CallError> {
1165 self.observe_object_with_context(&ExecutionContext::default(), path)
1166 }
1167
1168 fn observe_object_with_context(
1170 &self,
1171 context: &ExecutionContext,
1172 path: &str,
1173 ) -> Result<Object, CallError> {
1174 wip_protocol::validate_path(path)
1175 .map_err(|error| CallError::new(CallErrorKind::InvalidRequest, error.to_string()))?;
1176 let resolved = catch_unwind(AssertUnwindSafe(|| {
1177 self.with_object(context, path, |publication| {
1178 publication.protocol_object_at(path)
1179 })
1180 }))
1181 .map_err(|_| {
1182 CallError::new(
1183 CallErrorKind::Internal,
1184 "provider panicked while observing an object",
1185 )
1186 })?;
1187 let object = match resolved {
1188 ProviderResult::Found(object) => object?,
1189 ProviderResult::NotFound => {
1190 return Err(CallError::new(
1191 CallErrorKind::NotFound,
1192 "target path is not published",
1193 ));
1194 }
1195 ProviderResult::Failure(error) => return Err(CallError::from_read_failure(error)),
1196 };
1197 Ok(object)
1198 }
1199
1200 fn fetch_interface(
1205 &self,
1206 registry: &InterfaceRegistry,
1207 reference: &InterfaceReference,
1208 ) -> Result<InterfaceObservation, ProtocolError> {
1209 reference.validate().map_err(|error| ProtocolError {
1210 code: ProtocolErrorCode::InvalidRequest,
1211 message: error.to_string(),
1212 })?;
1213 let observed = catch_unwind(AssertUnwindSafe(|| {
1214 self.with_object(
1215 &ExecutionContext::default(),
1216 &reference.scope,
1217 |publication| {
1218 publication
1219 .scopes
1220 .iter()
1221 .find(|current| current.scope.path == reference.scope)
1222 .ok_or_else(interface_not_found)
1223 .and_then(|current| {
1224 if current.reference() != publication.implementation.reference() {
1225 return Err(ProtocolError {
1226 code: ProtocolErrorCode::Internal,
1227 message: "provider produced inconsistent scope Object metadata"
1228 .into(),
1229 });
1230 }
1231 registry.fetch_interface(reference, current)
1232 })
1233 },
1234 )
1235 }))
1236 .map_err(|_| ProtocolError {
1237 code: ProtocolErrorCode::Internal,
1238 message: "provider panicked while observing an interface".into(),
1239 })?;
1240 match observed {
1241 ProviderResult::Found(observation) => observation,
1242 ProviderResult::NotFound => Err(interface_not_found()),
1243 ProviderResult::Failure(error) => {
1244 Err(CallError::from_read_failure(error).protocol_error())
1245 }
1246 }
1247 }
1248
1249 fn call_operation(&self, request: CallRequest) -> Result<CallResult, CallError> {
1258 self.call_operation_with_context(&ExecutionContext::default(), request)
1259 }
1260
1261 fn call_operation_with_context(
1264 &self,
1265 context: &ExecutionContext,
1266 request: CallRequest,
1267 ) -> Result<CallResult, CallError> {
1268 wip_protocol::validate_path(&request.target.path)
1269 .map_err(|error| CallError::new(CallErrorKind::InvalidRequest, error.to_string()))?;
1270 request
1271 .interface
1272 .reference
1273 .validate()
1274 .map_err(|error| CallError::new(CallErrorKind::InvalidRequest, error.to_string()))?;
1275 let path = request.target.path.clone();
1276 let execution = catch_unwind(AssertUnwindSafe(|| {
1277 self.execute_operation(context, &path, |publication| {
1278 publication.dispatch_resolved(context, request, |object, interface, operation| {
1279 self.authorize(&path, object, interface, operation)
1280 })
1281 })
1282 }));
1283
1284 let execution = execution.map_err(|_| {
1285 CallError::from_operation_failure(OperationFailure::internal(
1286 EffectGuarantee::Unknown,
1287 "provider execution panicked",
1288 ))
1289 })?;
1290 match execution {
1291 ProviderResult::Found(OperationAttempt::NotStarted(error)) => Err(error),
1292 ProviderResult::Found(OperationAttempt::Started(Ok(result))) => Ok(result),
1293 ProviderResult::Found(OperationAttempt::Started(Err(error))) => {
1294 Err(CallError::from_operation_failure(error))
1295 }
1296 ProviderResult::NotFound => Err(CallError::new(
1297 CallErrorKind::NotFound,
1298 "target path is not published",
1299 )),
1300 ProviderResult::Failure(error) => Err(CallError::from_host_failure(error)),
1301 }
1302 }
1303
1304 fn call_operation_with_context_and_decoder<E>(
1315 &self,
1316 context: &ExecutionContext,
1317 target: Target,
1318 interface: InterfaceTarget,
1319 operation: String,
1320 decode_arguments: impl FnOnce(&InterfaceDescriptor) -> Result<BTreeMap<String, Value>, E>,
1321 ) -> Result<CallResult, CallError> {
1322 wip_protocol::validate_path(&target.path)
1323 .map_err(|error| CallError::new(CallErrorKind::InvalidRequest, error.to_string()))?;
1324 interface
1325 .reference
1326 .validate()
1327 .map_err(|error| CallError::new(CallErrorKind::InvalidRequest, error.to_string()))?;
1328 let path = target.path.clone();
1329 let execution = catch_unwind(AssertUnwindSafe(|| {
1330 self.execute_operation(context, &path, |publication| {
1331 publication.dispatch_resolved_with_decoder(
1332 context,
1333 target,
1334 interface,
1335 operation,
1336 decode_arguments,
1337 |object, interface, operation| {
1338 self.authorize(&path, object, interface, operation)
1339 },
1340 )
1341 })
1342 }));
1343
1344 let execution = execution.map_err(|_| {
1345 CallError::from_operation_failure(OperationFailure::internal(
1346 EffectGuarantee::Unknown,
1347 "provider execution panicked",
1348 ))
1349 })?;
1350 match execution {
1351 ProviderResult::Found(OperationAttempt::NotStarted(error)) => Err(error),
1352 ProviderResult::Found(OperationAttempt::Started(Ok(result))) => Ok(result),
1353 ProviderResult::Found(OperationAttempt::Started(Err(error))) => {
1354 Err(CallError::from_operation_failure(error))
1355 }
1356 ProviderResult::NotFound => Err(CallError::new(
1357 CallErrorKind::NotFound,
1358 "target path is not published",
1359 )),
1360 ProviderResult::Failure(error) => Err(CallError::from_host_failure(error)),
1361 }
1362 }
1363}
1364
1365#[derive(Debug, Clone, PartialEq, Eq)]
1367pub enum PublicationError {
1368 DuplicateInterface(InterfaceRef),
1370 UnexposedInterface(InterfaceRef),
1372}
1373
1374impl Display for PublicationError {
1375 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1376 match self {
1377 Self::UnexposedInterface(reference) => write!(
1378 formatter,
1379 "interface `{}` has no implementation",
1380 reference.as_str()
1381 ),
1382 Self::DuplicateInterface(reference) => write!(
1383 formatter,
1384 "interface `{}` is already exposed by this object",
1385 reference.as_str()
1386 ),
1387 }
1388 }
1389}
1390
1391impl Error for PublicationError {}
1392
1393#[cfg(test)]
1394mod projection_tests {
1395 use super::*;
1396 use wip_protocol::{
1397 Documentation, OperationDeclaration, ReturnDeclaration, TypeDeclaration, TypeExpr,
1398 };
1399
1400 #[test]
1401 fn operation_views_prune_hidden_types_and_documentation_without_mutating_registry() {
1402 let named = |name: &str| TypeExpr::Named { name: name.into() };
1403 let operation = |name: &str, returns| OperationDeclaration {
1404 name: name.into(),
1405 documentation: None,
1406 parameters: Vec::new(),
1407 returns: ReturnDeclaration {
1408 documentation: None,
1409 r#type: returns,
1410 },
1411 };
1412 let declaration = |name: &str, definition| TypeDeclaration {
1413 name: name.into(),
1414 documentation: None,
1415 definition,
1416 };
1417 let descriptor = InterfaceDescriptor {
1418 format: "wip/1".into(),
1419 documentation: Some(Documentation {
1420 summary: "contains hidden operation details".into(),
1421 details: None,
1422 }),
1423 operations: vec![
1424 operation("visible", named("Public")),
1425 operation("secret", named("Secret")),
1426 ],
1427 types: vec![
1428 declaration(
1429 "Public",
1430 TypeExpr::List {
1431 items: Box::new(named("Shared")),
1432 },
1433 ),
1434 declaration("Shared", TypeExpr::String),
1435 declaration("Secret", TypeExpr::Bytes),
1436 ],
1437 };
1438 let mut projected = descriptor.clone();
1439 restrict_descriptor(&mut projected, |operation| operation == "visible");
1440 assert_eq!(projected.operations.len(), 1);
1441 assert!(projected.documentation.is_none());
1442 assert_eq!(
1443 projected
1444 .types
1445 .iter()
1446 .map(|declaration| declaration.name.as_str())
1447 .collect::<Vec<_>>(),
1448 vec!["Public", "Shared"]
1449 );
1450 assert_eq!(descriptor.types.len(), 3);
1451 restrict_descriptor(&mut projected, |_| false);
1452 assert!(projected.types.is_empty());
1453 assert!(projected.operations.is_empty());
1454 }
1455}
1456
1457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1459pub enum CallErrorKind {
1460 InvalidRequest,
1462 NotFound,
1464 InterfaceMismatch,
1466 OperationNotFound,
1468 InvalidArguments,
1470 ResourceLimitExceeded,
1473 Internal,
1475 OperationOutcomeUnknown,
1477 ValidatorRequired,
1479 ValidatorMismatch,
1481 InterfaceValidatorRequired,
1483 InterfaceValidatorMismatch,
1485 PermissionDenied,
1487}
1488
1489#[derive(Debug, Clone, PartialEq, Eq)]
1491pub struct CallError {
1492 kind: CallErrorKind,
1493 message: String,
1494}
1495
1496impl CallError {
1497 fn new(kind: CallErrorKind, message: impl Into<String>) -> Self {
1498 Self {
1499 kind,
1500 message: bounded_message(message.into()),
1501 }
1502 }
1503
1504 fn from_pre_dispatch(error: DispatchError) -> Self {
1505 match error {
1506 DispatchError::UnknownOperation(operation) => Self::new(
1507 CallErrorKind::OperationNotFound,
1508 format!("operation `{operation}` is not declared"),
1509 ),
1510 DispatchError::InvalidArguments(error) => {
1511 Self::new(CallErrorKind::InvalidArguments, error.to_string())
1512 }
1513 DispatchError::Decode(_) => Self::new(
1514 CallErrorKind::InvalidArguments,
1515 "operation arguments could not be decoded",
1516 ),
1517 DispatchError::Host(_)
1518 | DispatchError::Panic
1519 | DispatchError::Encode(_)
1520 | DispatchError::InvalidResult(_) => {
1521 unreachable!("post-invocation failure used as a pre-dispatch failure")
1522 }
1523 }
1524 }
1525
1526 fn from_read_failure(error: HostError) -> Self {
1527 match error.kind() {
1528 HostErrorKind::Internal => Self::new(
1529 CallErrorKind::Internal,
1530 "provider failed while fetching data",
1531 ),
1532 HostErrorKind::ResourceLimitExceeded => Self::new(
1533 CallErrorKind::ResourceLimitExceeded,
1534 "provider resource limit was exceeded while fetching data",
1535 ),
1536 }
1537 }
1538
1539 fn from_host_failure(error: HostError) -> Self {
1540 let kind = classified_host_kind(error.kind(), error.effect_guarantee());
1541 let message = match error.kind() {
1542 HostErrorKind::Internal => "provider execution failed",
1543 HostErrorKind::ResourceLimitExceeded => {
1544 "provider execution resource limit was exceeded"
1545 }
1546 };
1547 Self::new(kind, message)
1548 }
1549
1550 fn from_operation_failure(error: OperationFailure) -> Self {
1551 Self::new(
1552 classified_host_kind(error.kind, error.effect_guarantee),
1553 error.message,
1554 )
1555 }
1556
1557 #[must_use]
1559 pub fn kind(&self) -> CallErrorKind {
1560 self.kind
1561 }
1562
1563 #[must_use]
1565 pub fn message(&self) -> &str {
1566 &self.message
1567 }
1568
1569 #[must_use]
1571 pub fn protocol_error(&self) -> ProtocolError {
1572 let code = match self.kind {
1573 CallErrorKind::InvalidRequest => ProtocolErrorCode::InvalidRequest,
1574 CallErrorKind::NotFound => ProtocolErrorCode::NotFound,
1575 CallErrorKind::InterfaceMismatch => ProtocolErrorCode::InterfaceMismatch,
1576 CallErrorKind::OperationNotFound => ProtocolErrorCode::OperationNotFound,
1577 CallErrorKind::InvalidArguments => ProtocolErrorCode::InvalidArguments,
1578 CallErrorKind::ResourceLimitExceeded => ProtocolErrorCode::ResourceLimitExceeded,
1579 CallErrorKind::Internal => ProtocolErrorCode::Internal,
1580 CallErrorKind::OperationOutcomeUnknown => ProtocolErrorCode::OperationOutcomeUnknown,
1581 CallErrorKind::ValidatorRequired => ProtocolErrorCode::ValidatorRequired,
1582 CallErrorKind::ValidatorMismatch => ProtocolErrorCode::ValidatorMismatch,
1583 CallErrorKind::InterfaceValidatorRequired => {
1584 ProtocolErrorCode::InterfaceValidatorRequired
1585 }
1586 CallErrorKind::InterfaceValidatorMismatch => {
1587 ProtocolErrorCode::InterfaceValidatorMismatch
1588 }
1589 CallErrorKind::PermissionDenied => ProtocolErrorCode::PermissionDenied,
1590 };
1591 ProtocolError {
1592 code,
1593 message: self.message.clone(),
1594 }
1595 }
1596}
1597
1598fn classified_host_kind(kind: HostErrorKind, effect_guarantee: EffectGuarantee) -> CallErrorKind {
1599 match effect_guarantee {
1600 EffectGuarantee::Unknown => CallErrorKind::OperationOutcomeUnknown,
1601 EffectGuarantee::NotCommitted => match kind {
1602 HostErrorKind::Internal => CallErrorKind::Internal,
1603 HostErrorKind::ResourceLimitExceeded => CallErrorKind::ResourceLimitExceeded,
1604 },
1605 }
1606}
1607
1608const MAX_PROTOCOL_MESSAGE_BYTES: usize = 512;
1609
1610fn bounded_message(mut message: String) -> String {
1611 if message.len() <= MAX_PROTOCOL_MESSAGE_BYTES {
1612 return message;
1613 }
1614 let mut end = MAX_PROTOCOL_MESSAGE_BYTES;
1615 while !message.is_char_boundary(end) {
1616 end -= 1;
1617 }
1618 message.truncate(end);
1619 message
1620}
1621
1622impl Display for CallError {
1623 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1624 write!(
1625 formatter,
1626 "host dispatch failed ({:?}): {}",
1627 self.kind, self.message
1628 )
1629 }
1630}
1631
1632impl Error for CallError {}