Skip to main content

nros/
dispatch_tag.rs

1//! Phase 216.A.4 — tag types Node authors hold on `Self::State` to
2//! match against incoming [`Callback`] in
3//! [`ExecutableNode::on_callback`](crate::ExecutableNode::on_callback).
4//!
5//! Each tag is an opaque newtype around a `&'static str` (the stable
6//! callback identifier shape — see
7//! [`CallbackId`]). The three flavors
8//! ([`SubscriptionTag`], [`ServiceTag`], [`ActionTag`]) keep the kind
9//! distinct at the type level so a Node author can't accidentally match
10//! a subscription tag against a service callback.
11//!
12//! Tag types support:
13//!
14//! - [`Into<CallbackId<'static>>`] — convert a tag into a borrowable
15//!   `CallbackId` when handing off to the runtime.
16//! - [`PartialEq<Callback<'_>>`](crate::Callback) — match directly against the
17//!   [`Callback`] delivered to
18//!   [`ExecutableNode::on_callback`](crate::ExecutableNode::on_callback):
19//!
20//!   ```ignore
21//!   if state.sub_chatter == cb { /* … */ }
22//!   ```
23//!
24//! - [`placeholder`](SubscriptionTag::placeholder) — const-fn sentinel
25//!   used by macro-emitted `init()` bodies before the real tag is
26//!   resolved (the macro overwrites at register time).
27//!
28//! The companion `NodeContext::create_subscription_static` /
29//! `_service_static` / `_action_static` methods that consume these tags
30//! at register time land in a follow-up commit; this commit ships only
31//! the types so the 216.B.5 + 216.C.5 example carving can declare them.
32
33use crate::{node::Callback, node_metadata::CallbackId};
34
35/// Tag identifying a subscription callback registered on a Node.
36///
37/// Stored on `Self::State` by macro-emitted `init()` bodies (or hand-
38/// written equivalents) and matched against the [`Callback`] delivered to
39/// delivered to
40/// [`ExecutableNode::on_callback`](crate::ExecutableNode::on_callback).
41#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
42pub struct SubscriptionTag(&'static str);
43
44impl SubscriptionTag {
45    /// Sentinel used by macro-emitted `init()` bodies before the real
46    /// tag is resolved. The macro overwrites at register time.
47    pub const fn placeholder() -> Self {
48        Self("")
49    }
50
51    /// Construct a tag with an explicit stable identifier.
52    pub const fn new(id: &'static str) -> Self {
53        Self(id)
54    }
55
56    /// Borrow the underlying identifier string.
57    pub const fn as_str(&self) -> &'static str {
58        self.0
59    }
60}
61
62impl From<SubscriptionTag> for CallbackId<'static> {
63    fn from(tag: SubscriptionTag) -> Self {
64        CallbackId(tag.0)
65    }
66}
67
68impl PartialEq<CallbackId<'_>> for SubscriptionTag {
69    fn eq(&self, other: &CallbackId<'_>) -> bool {
70        self.0 == other.0
71    }
72}
73
74impl PartialEq<Callback<'_>> for SubscriptionTag {
75    fn eq(&self, other: &Callback<'_>) -> bool {
76        self.0 == other.as_str()
77    }
78}
79
80/// Tag identifying a service-server callback registered on a Node.
81///
82/// See [`SubscriptionTag`] for the usage pattern.
83#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
84pub struct ServiceTag(&'static str);
85
86impl ServiceTag {
87    /// Sentinel used by macro-emitted `init()` bodies before the real
88    /// tag is resolved.
89    pub const fn placeholder() -> Self {
90        Self("")
91    }
92
93    /// Construct a tag with an explicit stable identifier.
94    pub const fn new(id: &'static str) -> Self {
95        Self(id)
96    }
97
98    /// Borrow the underlying identifier string.
99    pub const fn as_str(&self) -> &'static str {
100        self.0
101    }
102}
103
104impl From<ServiceTag> for CallbackId<'static> {
105    fn from(tag: ServiceTag) -> Self {
106        CallbackId(tag.0)
107    }
108}
109
110impl PartialEq<CallbackId<'_>> for ServiceTag {
111    fn eq(&self, other: &CallbackId<'_>) -> bool {
112        self.0 == other.0
113    }
114}
115
116impl PartialEq<Callback<'_>> for ServiceTag {
117    fn eq(&self, other: &Callback<'_>) -> bool {
118        self.0 == other.as_str()
119    }
120}
121
122/// Tag identifying an action-server callback registered on a Node.
123///
124/// See [`SubscriptionTag`] for the usage pattern.
125#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
126pub struct ActionTag(&'static str);
127
128impl ActionTag {
129    /// Sentinel used by macro-emitted `init()` bodies before the real
130    /// tag is resolved.
131    pub const fn placeholder() -> Self {
132        Self("")
133    }
134
135    /// Construct a tag with an explicit stable identifier.
136    pub const fn new(id: &'static str) -> Self {
137        Self(id)
138    }
139
140    /// Borrow the underlying identifier string.
141    pub const fn as_str(&self) -> &'static str {
142        self.0
143    }
144}
145
146impl From<ActionTag> for CallbackId<'static> {
147    fn from(tag: ActionTag) -> Self {
148        CallbackId(tag.0)
149    }
150}
151
152impl PartialEq<CallbackId<'_>> for ActionTag {
153    fn eq(&self, other: &CallbackId<'_>) -> bool {
154        self.0 == other.0
155    }
156}
157
158impl PartialEq<Callback<'_>> for ActionTag {
159    fn eq(&self, other: &Callback<'_>) -> bool {
160        self.0 == other.as_str()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn placeholder_returns_empty_str() {
170        assert_eq!(SubscriptionTag::placeholder().as_str(), "");
171        assert_eq!(ServiceTag::placeholder().as_str(), "");
172        assert_eq!(ActionTag::placeholder().as_str(), "");
173    }
174
175    #[test]
176    fn tag_into_callback_id_round_trips() {
177        let sub = SubscriptionTag::new("sub_chatter");
178        let svc = ServiceTag::new("svc_add_two_ints");
179        let act = ActionTag::new("act_fibonacci");
180
181        let sub_cb: CallbackId<'static> = sub.into();
182        let svc_cb: CallbackId<'static> = svc.into();
183        let act_cb: CallbackId<'static> = act.into();
184
185        assert_eq!(sub_cb.as_str(), "sub_chatter");
186        assert_eq!(svc_cb.as_str(), "svc_add_two_ints");
187        assert_eq!(act_cb.as_str(), "act_fibonacci");
188    }
189
190    #[test]
191    fn tag_eq_callback_id_matches() {
192        let sub = SubscriptionTag::new("sub_chatter");
193        let other = CallbackId::new("sub_chatter");
194        let mismatch = CallbackId::new("sub_other");
195
196        assert!(sub == other);
197        assert!(!(sub == mismatch));
198
199        let svc = ServiceTag::new("svc_add_two_ints");
200        assert!(svc == CallbackId::new("svc_add_two_ints"));
201        assert!(!(svc == CallbackId::new("svc_other")));
202
203        let act = ActionTag::new("act_fibonacci");
204        assert!(act == CallbackId::new("act_fibonacci"));
205        assert!(!(act == CallbackId::new("act_other")));
206
207        // Placeholder shouldn't match a real callback id.
208        assert!(!(SubscriptionTag::placeholder() == CallbackId::new("sub_chatter")));
209    }
210
211    #[test]
212    fn tag_eq_callback_event_matches() {
213        let sub = SubscriptionTag::new("sub_chatter");
214        let event = Callback::__from_id(CallbackId::new("sub_chatter"));
215        let mismatch = Callback::__from_id(CallbackId::new("sub_other"));
216
217        assert!(sub == event);
218        assert!(!(sub == mismatch));
219        assert!(ServiceTag::new("svc") == Callback::__from_id(CallbackId::new("svc")));
220        assert!(ActionTag::new("act") == Callback::__from_id(CallbackId::new("act")));
221    }
222}