nros_node/node_options.rs
1//! Node construction options — rclrs's `NodeOptions` / `IntoNodeOptions`
2//! (phase-515 R2).
3//!
4//! Lives here rather than in `nros` so the executor's own
5//! [`create_node`](crate::Executor::create_node) can take it; `nros`
6//! re-exports both names unchanged.
7
8/// Runtime-neutral node construction options.
9///
10/// ADOPT-BOUNDED against rclrs's `NodeOptions` / `IntoNodeOptions` builder: three
11/// public fields (`name`, `namespace`, `domain_id`) and nothing else. There are no
12/// node ARGUMENTS and no rosout toggle — both are fixed at build time here
13/// (RFC-0045), so an rclrs builder call for either is a compile error.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct NodeOptions<'a> {
16 /// Source node name. Launch planning may remap/namespace later.
17 pub name: &'a str,
18 /// Source namespace. Defaults to `/`.
19 pub namespace: &'a str,
20 /// ROS domain ID hint. Defaults to `0`.
21 pub domain_id: u32,
22}
23
24impl<'a> NodeOptions<'a> {
25 /// Create node options with default namespace and domain.
26 pub const fn new(name: &'a str) -> Self {
27 Self {
28 name,
29 namespace: "/",
30 domain_id: 0,
31 }
32 }
33
34 /// Set source namespace.
35 pub const fn namespace(mut self, namespace: &'a str) -> Self {
36 self.namespace = namespace;
37 self
38 }
39
40 /// Set ROS domain ID hint.
41 pub const fn domain_id(mut self, domain_id: u32) -> Self {
42 self.domain_id = domain_id;
43 self
44 }
45}
46
47/// rclrs's `IntoNodeOptions`: anything a node can be created from — a bare
48/// name (`"talker"`) or a full [`NodeOptions`] — so
49/// `executor.create_node("talker")` and
50/// `executor.create_node("talker".namespace("/robot"))` read as they do in
51/// rclrs.
52///
53/// ADOPT-BOUNDED: rclrs's other builder steps (`arguments`,
54/// `use_global_arguments`, `enable_rosout`, `start_parameter_services`,
55/// `clock_type`, `clock_qos`) configure what is fixed at build time here
56/// (RFC-0045), so they are absent and a call to one is a compile error.
57pub trait IntoNodeOptions<'a>: Sized {
58 /// Convert to the full options value.
59 fn into_node_options(self) -> NodeOptions<'a>;
60
61 /// Set the namespace — rclrs's `"name".namespace("/ns")`.
62 fn namespace(self, namespace: &'a str) -> NodeOptions<'a> {
63 self.into_node_options().namespace(namespace)
64 }
65}
66
67impl<'a> IntoNodeOptions<'a> for &'a str {
68 fn into_node_options(self) -> NodeOptions<'a> {
69 NodeOptions::new(self)
70 }
71}
72
73impl<'a> IntoNodeOptions<'a> for NodeOptions<'a> {
74 fn into_node_options(self) -> NodeOptions<'a> {
75 self
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn a_bare_name_takes_the_defaults() {
85 assert_eq!("talker".into_node_options(), NodeOptions::new("talker"));
86 }
87
88 #[test]
89 fn rclrs_namespace_step_on_a_bare_name() {
90 let o = "talker".namespace("/robot");
91 assert_eq!(o.name, "talker");
92 assert_eq!(o.namespace, "/robot");
93 assert_eq!(o.domain_id, 0);
94 }
95
96 #[test]
97 fn full_options_pass_through() {
98 let o = NodeOptions::new("talker").domain_id(7);
99 assert_eq!(o.into_node_options(), o);
100 }
101}