Skip to main content

behavior_actors/discovery/
resolver.rs

1//! Read-only typed name resolution capability.
2
3use behavior::{
4    Actions, Address, Behavior, BehaviorActed, BehaviorBase, Never, NoBirths, Protocol, Recipient,
5    User,
6};
7use thiserror::Error;
8
9use crate::DeliveryRoute;
10
11/// Complete factual result returned by [`Resolver`].
12pub enum Resolution<K, D: Protocol> {
13    /// The key resolved to this typed recipient.
14    Found {
15        /// Queried key.
16        key: K,
17        /// Point-in-time recipient.
18        recipient: Recipient<D>,
19    },
20    /// No configured binding names the key.
21    Missing {
22        /// Queried key.
23        key: K,
24    },
25}
26
27/// The only operation exposed by a [`Resolver`] recipient.
28pub enum ResolverMessage<K, Route> {
29    /// Resolve one typed key without granting mutation authority.
30    Resolve {
31        /// Queried key.
32        key: K,
33        /// Typed result recipient.
34        reply_to: Route,
35    },
36}
37
38/// Rejected immutable resolver definition.
39#[derive(Debug, Error, Clone, PartialEq, Eq)]
40pub enum ResolverConfigError<K> {
41    /// Two source entries name the same key.
42    #[error("resolver definition contains a duplicate key")]
43    DuplicateKey {
44        /// Duplicate key cloned from the borrowed definition; the caller
45        /// retains ownership of the complete source slice.
46        key: K,
47    },
48}
49
50/// Immutable read-only typed resolver behavior.
51///
52/// Construction copies a borrowed unique binding definition, so rejection
53/// cannot consume caller ownership. The public protocol contains only
54/// [`ResolverMessage::Resolve`], making registry mutation authority
55/// unrepresentable for holders of a resolver recipient. Every lookup emits one
56/// factual [`Resolution`], including absence. Binding order is retained for
57/// deterministic inspection but does not affect lookup meaning. Initialization
58/// is empty, the behavior creates no actors, never terminates by policy, and
59/// requires only typed delivery. Capability separation and missing-as-fact are
60/// Bombay policy; endpoint validity is interpreted by Bombay Address. No
61/// method has a semantic panic condition.
62pub struct Resolver<
63    A: Address,
64    K,
65    D: Protocol<Addr = A>,
66    Route: DeliveryRoute<Protocol: behavior::Protocol<Addr = A, Msg = Resolution<K, D>>>,
67> {
68    bindings: Vec<(K, Recipient<D>)>,
69    marker: core::marker::PhantomData<fn() -> (A, Route)>,
70}
71
72impl<A, K, D, Route> Resolver<A, K, D, Route>
73where
74    A: Address,
75    K: Clone + Eq,
76    D: Protocol<Addr = A>,
77    Route: DeliveryRoute<Protocol: behavior::Protocol<Addr = A, Msg = Resolution<K, D>>>,
78{
79    /// Copy one borrowed immutable binding definition.
80    ///
81    /// # Errors
82    ///
83    /// Returns [`ResolverConfigError::DuplicateKey`] for the first duplicate;
84    /// the borrowed source remains owned by the caller.
85    pub fn from_bindings(bindings: &[(K, Recipient<D>)]) -> Result<Self, ResolverConfigError<K>> {
86        let mut accepted = Vec::with_capacity(bindings.len());
87        for (key, recipient) in bindings {
88            if accepted
89                .iter()
90                .any(|(accepted_key, _): &(K, Recipient<D>)| accepted_key == key)
91            {
92                return Err(ResolverConfigError::DuplicateKey { key: key.clone() });
93            }
94            accepted.push((key.clone(), *recipient));
95        }
96        Ok(Self {
97            bindings: accepted,
98            marker: core::marker::PhantomData,
99        })
100    }
101
102    /// Borrow the complete immutable binding definition.
103    #[must_use]
104    pub fn bindings(&self) -> &[(K, Recipient<D>)] {
105        &self.bindings
106    }
107}
108
109impl<A, K, D, Route> BehaviorBase for Resolver<A, K, D, Route>
110where
111    A: Address,
112    D: Protocol<Addr = A>,
113    Route: DeliveryRoute<Protocol: behavior::Protocol<Addr = A, Msg = Resolution<K, D>>>,
114{
115    type Base = Self;
116    fn base(&self) -> &Self {
117        self
118    }
119}
120
121impl<A, K, D, Route> behavior::Protocol for Resolver<A, K, D, Route>
122where
123    A: Address,
124    D: Protocol<Addr = A>,
125    Route: DeliveryRoute<Protocol: behavior::Protocol<Addr = A, Msg = Resolution<K, D>>>,
126{
127    type Addr = A;
128    type Msg = ResolverMessage<K, Route>;
129}
130
131impl<A, K, D, Route> Behavior for Resolver<A, K, D, Route>
132where
133    A: Address,
134    K: Clone + Eq,
135    D: Protocol<Addr = A>,
136    Route: DeliveryRoute<Protocol: behavior::Protocol<Addr = A, Msg = Resolution<K, D>>>,
137    Route::Sends: behavior::SendsFor<User<A, ResolverMessage<K, Route>>>,
138{
139    type Protocol = Self;
140    type Event = User<A, behavior::BehaviorMessage<Self>>;
141    type Sends = Route::Sends;
142    type Ph = Never;
143    type Error = Never;
144    type Birth = NoBirths;
145
146    fn transition(&mut self, _: behavior::ActiveTurn, event: Self::Event) -> BehaviorActed<Self> {
147        let ResolverMessage::Resolve { key, reply_to } = event.message;
148        let result = self
149            .bindings
150            .iter()
151            .find(|(bound, _)| bound == &key)
152            .map_or(
153                Resolution::Missing { key: key.clone() },
154                |(_, recipient)| Resolution::Found {
155                    key: key.clone(),
156                    recipient: *recipient,
157                },
158            );
159        Ok(Actions::send(reply_to.deliver(result)))
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use crate::Activate as _;
167    use behavior::MailAddr;
168    struct Destination;
169    struct Reply;
170    impl behavior::Protocol for Destination {
171        type Addr = MailAddr;
172        type Msg = u8;
173    }
174
175    impl Behavior for Destination {
176        type Protocol = Self;
177        type Event = User<MailAddr, u8>;
178        type Sends = Vec<Never>;
179        type Ph = Never;
180        type Error = Never;
181        type Birth = NoBirths;
182        fn transition(&mut self, _: behavior::ActiveTurn, _: Self::Event) -> BehaviorActed<Self> {
183            Ok(Actions::cont())
184        }
185    }
186    impl behavior::Protocol for Reply {
187        type Addr = MailAddr;
188        type Msg = Resolution<u8, Destination>;
189    }
190
191    impl Behavior for Reply {
192        type Protocol = Self;
193        type Event = User<MailAddr, behavior::BehaviorMessage<Self>>;
194        type Sends = Vec<Never>;
195        type Ph = Never;
196        type Error = Never;
197        type Birth = NoBirths;
198        fn transition(&mut self, _: behavior::ActiveTurn, _: Self::Event) -> BehaviorActed<Self> {
199            Ok(Actions::cont())
200        }
201    }
202    type Subject = Resolver<MailAddr, u8, Destination, Recipient<Reply>>;
203    #[test]
204    fn duplicate_definition_is_rejected_without_consuming_source() {
205        let destination = Recipient::global(MailAddr(1));
206        let source = vec![(1, destination), (1, destination)];
207        assert!(matches!(
208            Subject::from_bindings(&source),
209            Err(ResolverConfigError::DuplicateKey { key: 1 })
210        ));
211        assert_eq!(source.len(), 2);
212    }
213    #[test]
214    fn protocol_reports_found_and_missing_without_mutation_authority() {
215        let destination = Recipient::global(MailAddr(1));
216        let mut s = (Subject::from_bindings(&[(1, destination)]).unwrap())
217            .initialize()
218            .unwrap()
219            .behavior;
220        for key in [1, 2] {
221            let a = s
222                .receive(
223                    MailAddr(9),
224                    ResolverMessage::Resolve {
225                        key,
226                        reply_to: Recipient::global(MailAddr(2)),
227                    },
228                )
229                .unwrap();
230            assert!(matches!(
231                (&a.sends[0].message, key),
232                (Resolution::Found { .. }, 1) | (Resolution::Missing { .. }, 2)
233            ));
234        }
235        assert_eq!(s.bindings().len(), 1);
236    }
237}