use std::cell::{Cell, RefCell}; use std::collections::BTreeMap; use std::rc::Rc; use wip_host::{ CallErrorKind, CallRequest, ExecutionContext, InterfaceRegistry, InterfaceTarget, ObjectProvider, ProviderResult, PublishedObject, RegisteredInterface, RegistrationError, Scope, ScopeObservation, Target, Validator, WipObject, }; use wip_interface::{OperationResult, interface}; use wip_protocol::{InterfaceReference, ProtocolErrorCode, Value}; #[interface] pub trait Read { fn read(&self, value: String) -> OperationResult; } #[interface] pub trait Count { fn read(&self, value: i64) -> OperationResult; } #[derive(Clone)] struct Metadata { name: String, reference: Option, validator: Option, } impl WipObject for Metadata { fn name(&self) -> &str { &self.name } fn reference(&self) -> Option<&str> { self.reference.as_deref() } fn validator(&self) -> Option<&Validator> { self.validator.as_ref() } } type OnStart = Rc>>>; struct Implementation { metadata: Metadata, calls: Rc>, on_start: OnStart, } impl WipObject for Implementation { fn name(&self) -> &str { self.metadata.name() } fn reference(&self) -> Option<&str> { self.metadata.reference() } fn validator(&self) -> Option<&Validator> { self.metadata.validator() } } impl Read for Implementation { fn read(&self, value: String) -> OperationResult { self.calls.set(self.calls.get() + 1); if let Some(action) = self.on_start.borrow_mut().take() { action(); } Ok(value) } } struct Provider { interface: RegisteredInterface, current: Rc>>, target_path: String, target_exists: Cell, member: Cell, calls: Rc>, resolutions: Cell, on_start: OnStart, } impl Provider { fn new( scope: &Scope, interface: RegisteredInterface, target: &str, reference: Option<&str>, ) -> Self { Self { interface, current: Rc::new(RefCell::new(Some(( scope.clone(), Metadata { name: scope.path().rsplit('/').next().unwrap().into(), reference: reference.map(str::to_owned), validator: Some(Validator::new(b"scope-state-v1")), }, )))), target_path: target.into(), target_exists: Cell::new(true), member: Cell::new(true), calls: Rc::new(Cell::new(0)), resolutions: Cell::new(0), on_start: Rc::new(RefCell::new(None)), } } fn request(&self, scope_ref: Option<&str>) -> CallRequest { CallRequest { target: Target { path: self.target_path.clone(), validator: None, }, interface: InterfaceTarget { reference: self.interface.reference().protocol_reference(), scope_ref: scope_ref.map(str::to_owned), validator: Some(b"descriptor-v1".to_vec()), }, operation: "read".into(), arguments: BTreeMap::from([("value".into(), Value::String("original backend".into()))]), } } } impl ObjectProvider for Provider { type Object = Implementation; fn with_object( &self, _: &ExecutionContext, path: &str, callback: impl FnOnce(&PublishedObject) -> R, ) -> ProviderResult { self.resolutions.set(self.resolutions.get() + 1); // A single boundary captures both the target and the live scope association. let current = self.current.borrow().clone(); let target = path == self.target_path; let metadata = if target { if !self.target_exists.get() { return ProviderResult::NotFound; } if let Some((scope, object)) = ¤t && scope.path() == path { object.clone() } else { Metadata { name: path.rsplit('/').next().unwrap().into(), reference: None, validator: Some(Validator::new(b"target-v1")), } } } else if let Some((scope, object)) = ¤t { if path != scope.path() { return ProviderResult::NotFound; } object.clone() } else { return ProviderResult::NotFound; }; let mut publication = PublishedObject::new(Implementation { metadata, calls: self.calls.clone(), on_start: self.on_start.clone(), }); if target && self.member.get() { publication = publication.expose(&self.interface).unwrap(); } if let Some((scope, object)) = current { publication = publication.bind_scope(ScopeObservation::new(&scope, &object)); } ProviderResult::Found(callback(&publication)) } } fn setup( scope: &str, target: &str, reference: Option<&str>, ) -> (InterfaceRegistry, Scope, Provider) { let scope = Scope::new(scope).unwrap(); let mut registry = InterfaceRegistry::new(); let interface = registry .register( &scope, "read::#日本語", ReadDefinition, Some(Validator::new(b"descriptor-v1")), ) .unwrap(); let provider = Provider::new(&scope, interface, target, reference); (registry, scope, provider) } #[test] fn registrations_use_scope_name_not_definition_type_or_delimiters() { let mut registry = InterfaceRegistry::new(); let left = Scope::new("/left").unwrap(); let right = Scope::new("/right").unwrap(); let a = registry .register(&left, "read::#日本語", ReadDefinition, None) .unwrap(); let b = registry .register(&right, "read::#日本語", ReadDefinition, None) .unwrap(); assert_ne!(a.reference(), b.reference()); assert_eq!( a.reference().protocol_reference(), InterfaceReference { scope: "/left".into(), name: "read::#日本語".into(), } ); assert!(matches!( registry.register(&left, "read::#日本語", CountDefinition, None), Err(RegistrationError::AlreadyRegistered) )); assert!(matches!( registry.register(&left, "", ReadDefinition, None), Err(RegistrationError::InvalidReference(_)) )); assert!(Scope::new("/invalid/").is_err()); registry .register(&left, "another", ReadDefinition, None) .unwrap(); } #[test] fn self_root_ancestor_and_membership_are_independent() { for (scope, target) in [ ("/", "/"), ("/", "/target"), ("/scope", "/scope"), ("/scope", "/scope/target"), ] { let (registry, _, provider) = setup(scope, target, Some("scope-id")); assert_eq!( provider .call_operation(provider.request(Some("scope-id"))) .unwrap() .result, Value::String("original backend".into()) ); assert_eq!( provider .fetch_interface(®istry, &provider.request(None).interface.reference) .unwrap() .scope_ref .as_deref(), Some("scope-id") ); provider.member.set(false); assert_eq!( provider .call_operation(provider.request(None)) .unwrap_err() .kind(), CallErrorKind::InterfaceMismatch ); } // Non-self scope Objects need not implement or expose any Interface. let (registry, _, provider) = setup("/scope", "/scope/target", None); assert!( provider .observe_object("/scope") .unwrap() .interfaces .is_empty() ); provider .fetch_interface(®istry, &provider.request(None).interface.reference) .unwrap(); for target in ["/sibling/target", "/scopelong", "/"] { let (_, _, provider) = setup("/scope", target, None); assert_eq!( provider .call_operation(provider.request(None)) .unwrap_err() .kind(), CallErrorKind::InterfaceMismatch ); assert_eq!(provider.calls.get(), 0); assert_eq!( provider.observe_object(target).unwrap_err().kind(), CallErrorKind::Internal ); } } #[test] fn optional_scope_ref_and_state_validator_have_distinct_responsibilities() { for current in [Some("scope-id"), None, Some("")] { let (registry, _, provider) = setup("/scope", "/scope/target", current); let observation = provider .fetch_interface(®istry, &provider.request(None).interface.reference) .unwrap(); assert_eq!( observation .protocol_response() .unwrap() .scope_ref .as_deref(), current ); assert_eq!( observation.validator, Some(Validator::new(b"descriptor-v1")) ); provider.call_operation(provider.request(None)).unwrap(); if let Some(reference) = current { provider .call_operation(provider.request(Some(reference))) .unwrap(); } let before = provider.calls.get(); for supplied in ["other", "missing"] { let mut call = provider.request(Some(supplied)); call.interface.validator = None; call.operation = "missing".into(); assert_eq!( provider.call_operation(call).unwrap_err().kind(), CallErrorKind::InterfaceMismatch ); } assert_eq!(provider.calls.get(), before); provider.current.borrow_mut().as_mut().unwrap().1.validator = Some(Validator::new(b"scope-state-v2")); provider.call_operation(provider.request(current)).unwrap(); assert_eq!( provider .fetch_interface(®istry, &provider.request(None).interface.reference) .unwrap(), observation ); } } #[test] fn scope_precondition_is_membership_stage_in_both_dispatch_paths() { let (_, _, provider) = setup("/scope", "/scope/target", Some("current")); let mut call = provider.request(Some("stale")); call.target.validator = Some(b"wrong".to_vec()); assert_eq!( provider.call_operation(call.clone()).unwrap_err().kind(), CallErrorKind::ValidatorMismatch ); provider.target_exists.set(false); assert_eq!( provider.call_operation(call).unwrap_err().kind(), CallErrorKind::NotFound ); provider.target_exists.set(true); let call = provider.request(Some("stale")); assert_eq!( provider .call_operation_with_context_and_decoder( &ExecutionContext::new(), call.target, call.interface, call.operation, |_| -> Result, ()> { panic!("decoder must not run") } ) .unwrap_err() .kind(), CallErrorKind::InterfaceMismatch ); assert_eq!(provider.calls.get(), 0); } #[test] fn deletion_republication_and_movement_never_inherit_retained_artifacts() { let (mut registry, scope, provider) = setup("/scope", "/scope/target", Some("old")); let reference = provider.request(None).interface.reference; provider.fetch_interface(®istry, &reference).unwrap(); *provider.current.borrow_mut() = None; provider.target_exists.set(false); assert_eq!( provider .fetch_interface(®istry, &reference) .unwrap_err() .code, ProtocolErrorCode::InterfaceNotFound ); assert_eq!( provider .call_operation(provider.request(None)) .unwrap_err() .kind(), CallErrorKind::NotFound ); provider.target_exists.set(true); // Even the same ref is not proof that a replacement inherits registration. let replacement = Scope::new("/scope").unwrap(); *provider.current.borrow_mut() = Some(( replacement.clone(), Metadata { name: "scope".into(), reference: Some("old".into()), validator: None, }, )); assert_eq!( provider .fetch_interface(®istry, &reference) .unwrap_err() .code, ProtocolErrorCode::InterfaceNotFound ); assert_eq!( provider .call_operation(provider.request(Some("old"))) .unwrap_err() .kind(), CallErrorKind::InterfaceMismatch ); registry.remove_scope(&scope); assert!(matches!( registry.register(&scope, "new", ReadDefinition, None), Err(RegistrationError::RetiredScope) )); let fresh = registry .register(&replacement, reference.name.clone(), ReadDefinition, None) .unwrap(); provider.fetch_interface(®istry, &reference).unwrap(); // Old target membership still binds the old lifetime, even after new publication. assert_eq!( provider .call_operation(provider.request(None)) .unwrap_err() .kind(), CallErrorKind::InterfaceMismatch ); let new_provider = Provider::new(&replacement, fresh, "/scope/target", Some("new")); let mut new_call = new_provider.request(Some("new")); new_call.interface.validator = None; new_provider.call_operation(new_call).unwrap(); let moved = Scope::new("/moved").unwrap(); new_provider.target_exists.set(false); *new_provider.current.borrow_mut() = Some(( moved.clone(), Metadata { name: "moved".into(), reference: Some("new".into()), validator: None, }, )); assert_eq!( new_provider .fetch_interface(®istry, &reference) .unwrap_err() .code, ProtocolErrorCode::InterfaceNotFound ); assert_eq!( new_provider .call_operation(new_provider.request(None)) .unwrap_err() .kind(), CallErrorKind::NotFound ); let moved_interface = registry .register(&moved, reference.name, ReadDefinition, None) .unwrap(); assert_eq!( moved_interface.reference().protocol_reference().scope, "/moved" ); assert_eq!(provider.calls.get(), 0); } #[test] fn replacement_after_fetch_rejects_old_ref_but_omission_and_refless_are_allowed() { for previous_ref in [Some("old"), None] { let (mut registry, old_scope, old_provider) = setup("/scope", "/scope/target", previous_ref); let observed = old_provider .fetch_interface(®istry, &old_provider.request(None).interface.reference) .unwrap(); registry.remove_scope(&old_scope); let new_scope = Scope::new("/scope").unwrap(); let token = registry .register( &new_scope, "read::#日本語", ReadDefinition, Some(Validator::new(b"descriptor-v1")), ) .unwrap(); let new_provider = Provider::new(&new_scope, token, "/scope/target", None); if let Some(reference) = observed.scope_ref { assert_eq!( new_provider .call_operation(new_provider.request(Some(&reference))) .unwrap_err() .kind(), CallErrorKind::InterfaceMismatch ); assert_eq!(new_provider.calls.get(), 0); } new_provider .call_operation(new_provider.request(None)) .unwrap(); } } #[test] fn started_execution_retains_target_and_same_descriptor_through_replacement() { let (registry, old_scope, provider) = setup("/scope", "/scope/target", Some("old")); let registry = Rc::new(RefCell::new(registry)); let current = provider.current.clone(); let replacement = Scope::new("/scope").unwrap(); let registry_for_action = registry.clone(); let replacement_for_action = replacement.clone(); *provider.on_start.borrow_mut() = Some(Box::new(move || { let mut registry = registry_for_action.borrow_mut(); registry.remove_scope(&old_scope); registry .register( &replacement_for_action, "read::#日本語", CountDefinition, Some(Validator::new(b"descriptor-v2")), ) .unwrap(); *current.borrow_mut() = Some(( replacement_for_action.clone(), Metadata { name: "scope".into(), reference: Some("new".into()), validator: None, }, )); })); let call = provider.request(Some("old")); let result = provider .call_operation_with_context_and_decoder( &ExecutionContext::new(), call.target, call.interface, call.operation, |descriptor| { assert_eq!(descriptor, &ReadDefinition::descriptor()); Ok::<_, ()>(call.arguments) }, ) .unwrap(); assert_eq!(result.result, Value::String("original backend".into())); assert_eq!(provider.calls.get(), 1); assert_eq!(provider.resolutions.get(), 1); let new = provider .fetch_interface( ®istry.borrow(), &provider.request(None).interface.reference, ) .unwrap(); assert_eq!(new.descriptor, CountDefinition::descriptor()); assert_eq!(new.scope_ref.as_deref(), Some("new")); // The old bound target can no longer start a call against the new descriptor. assert_eq!( provider .call_operation(provider.request(None)) .unwrap_err() .kind(), CallErrorKind::InterfaceMismatch ); assert_eq!(provider.calls.get(), 1); }