Skip to main content

nros_params/
typed.rs

1//! Typed parameter API
2//!
3//! This module provides a fluent builder pattern for declaring typed parameters
4//! in a ROS 2 node, aligning with the rclrs API.
5
6use crate::{
7    ParameterDescriptor, ParameterRange, ParameterServer, ParameterType, SetParameterResult,
8};
9use heapless::String;
10
11/// Trait for types that can be used as typed parameters
12pub use crate::ParameterVariant;
13
14/// Error type for typed parameter operations
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ParameterError {
17    /// Parameter already declared with a different type
18    TypeMismatch,
19    /// Value is outside allowed range
20    OutOfRange,
21    /// Parameter is read-only
22    ReadOnly,
23    /// Parameter not found
24    NotFound,
25    /// Internal storage is full
26    StorageFull,
27    /// String conversion failed
28    StringConversion,
29    /// Invalid range for type
30    InvalidRange,
31}
32
33impl From<SetParameterResult> for ParameterError {
34    fn from(result: SetParameterResult) -> Self {
35        match result {
36            SetParameterResult::TypeMismatch => ParameterError::TypeMismatch,
37            SetParameterResult::OutOfRange => ParameterError::OutOfRange,
38            SetParameterResult::ReadOnly => ParameterError::ReadOnly,
39            SetParameterResult::NotFound => ParameterError::NotFound,
40            SetParameterResult::StorageFull => ParameterError::StorageFull,
41            _ => panic!("Unexpected SetParameterResult"), // Should not happen with valid results
42        }
43    }
44}
45
46/// Trait for types that can be converted to a parameter range from `RangeInclusive`
47pub trait RangeConvertible: ParameterVariant + Clone {
48    /// Convert a `RangeInclusive<Self>` to a `ParameterRange`
49    fn to_parameter_range(
50        range: core::ops::RangeInclusive<Self>,
51    ) -> Result<ParameterRange, ParameterError>;
52}
53
54impl RangeConvertible for i64 {
55    fn to_parameter_range(
56        range: core::ops::RangeInclusive<Self>,
57    ) -> Result<ParameterRange, ParameterError> {
58        Ok(ParameterRange::Integer(crate::IntegerRange::new(
59            *range.start(),
60            *range.end(),
61            1, // Default step of 1
62        )))
63    }
64}
65
66impl RangeConvertible for f64 {
67    fn to_parameter_range(
68        range: core::ops::RangeInclusive<Self>,
69    ) -> Result<ParameterRange, ParameterError> {
70        Ok(ParameterRange::FloatingPoint(
71            crate::FloatingPointRange::new(
72                *range.start(),
73                *range.end(),
74                0.0, // No step constraint (any value in range is valid)
75            ),
76        ))
77    }
78}
79
80/// Builder for declaring a typed parameter
81pub struct ParameterBuilder<'a, T: ParameterVariant> {
82    /// Reference to the parameter server
83    server: &'a mut ParameterServer,
84    /// Parameter name
85    name: &'a str,
86    /// Default value
87    default: Option<T>,
88    /// Human-readable description
89    description: Option<&'a str>,
90    /// Range constraints
91    range: Option<ParameterRange>,
92    /// Whether the parameter is read-only
93    read_only: bool,
94    /// Phantom data to hold the type parameter
95    _phantom: core::marker::PhantomData<T>,
96}
97
98impl<'a, T: ParameterVariant> ParameterBuilder<'a, T> {
99    /// Create a new parameter builder
100    pub fn new(server: &'a mut ParameterServer, name: &'a str) -> Self {
101        Self {
102            server,
103            name,
104            default: None,
105            description: None,
106            range: None,
107            read_only: false,
108            _phantom: core::marker::PhantomData,
109        }
110    }
111
112    /// Set a default value for the parameter
113    pub fn default(mut self, value: T) -> Self {
114        self.default = Some(value);
115        self
116    }
117
118    /// Set a human-readable description for the parameter
119    pub fn description(mut self, desc: &'a str) -> Self {
120        self.description = Some(desc);
121        self
122    }
123
124    /// Set integer range constraints for the parameter
125    pub fn integer_range(mut self, min: i64, max: i64, step: i64) -> Result<Self, ParameterError> {
126        if T::parameter_type() != ParameterType::Integer {
127            return Err(ParameterError::InvalidRange);
128        }
129        self.range = Some(ParameterRange::Integer(crate::IntegerRange::new(
130            min, max, step,
131        )));
132        Ok(self)
133    }
134
135    /// Set floating point range constraints for the parameter
136    pub fn float_range(mut self, min: f64, max: f64, step: f64) -> Result<Self, ParameterError> {
137        if T::parameter_type() != ParameterType::Double {
138            return Err(ParameterError::InvalidRange);
139        }
140        self.range = Some(ParameterRange::FloatingPoint(
141            crate::FloatingPointRange::new(min, max, step),
142        ));
143        Ok(self)
144    }
145
146    /// Set range constraints using an inclusive range
147    ///
148    /// This is a convenience method that works with `RangeInclusive`:
149    /// - For `i64` parameters: `range(0..=100)` sets an integer range with step 1
150    /// - For `f64` parameters: `range(0.0..=1.0)` sets a floating point range with step 0.0
151    ///
152    /// For more control (e.g., custom step), use `integer_range()` or `float_range()`.
153    pub fn range(mut self, range: core::ops::RangeInclusive<T>) -> Result<Self, ParameterError>
154    where
155        T: RangeConvertible,
156    {
157        self.range = Some(T::to_parameter_range(range)?);
158        Ok(self)
159    }
160
161    /// Declare a read-only parameter
162    ///
163    /// Read-only parameters cannot be changed after declaration.
164    /// A default value must be provided.
165    pub fn read_only(self) -> Result<ReadOnlyParameter<'a, T>, ParameterError> {
166        // Must have a default value for read-only parameters
167        let default_value = self
168            .default
169            .as_ref()
170            .ok_or(ParameterError::NotFound)?
171            .clone();
172
173        let mut descriptor = ParameterDescriptor::new(self.name, T::parameter_type())
174            .ok_or(ParameterError::StorageFull)?;
175        descriptor.description.clear();
176        if let Some(desc) = self.description {
177            descriptor
178                .description
179                .push_str(desc)
180                .map_err(|_| ParameterError::StringConversion)?;
181        }
182        descriptor.read_only = true;
183        descriptor.range = self.range.unwrap_or_default();
184
185        let param_value = default_value.to_parameter_value();
186
187        self.server
188            .declare_parameter(descriptor, Some(&param_value))?;
189
190        Ok(ReadOnlyParameter::new(self.server, self.name))
191    }
192
193    /// Declare a mandatory parameter
194    ///
195    /// If no default value is provided, it must be set externally before use.
196    pub fn mandatory(self) -> Result<MandatoryParameter<'a, T>, ParameterError> {
197        let mut descriptor = ParameterDescriptor::new(self.name, T::parameter_type())
198            .ok_or(ParameterError::StorageFull)?;
199        descriptor.description.clear();
200        if let Some(desc) = self.description {
201            descriptor
202                .description
203                .push_str(desc)
204                .map_err(|_| ParameterError::StringConversion)?;
205        }
206        descriptor.read_only = self.read_only;
207        descriptor.range = self.range.unwrap_or_default();
208
209        let default_value = self.default.map(|v| v.to_parameter_value());
210
211        self.server
212            .declare_parameter(descriptor, default_value.as_ref())?;
213
214        Ok(MandatoryParameter::new(self.server, self.name))
215    }
216
217    /// Declare an optional parameter
218    pub fn optional(self) -> Result<OptionalParameter<'a, T>, ParameterError> {
219        let mut descriptor = ParameterDescriptor::new(self.name, T::parameter_type())
220            .ok_or(ParameterError::StorageFull)?;
221        descriptor.description.clear();
222        if let Some(desc) = self.description {
223            descriptor
224                .description
225                .push_str(desc)
226                .map_err(|_| ParameterError::StringConversion)?;
227        }
228        descriptor.read_only = self.read_only;
229        descriptor.range = self.range.unwrap_or_default();
230
231        let default_value = self.default.map(|v| v.to_parameter_value());
232
233        self.server
234            .declare_parameter(descriptor, default_value.as_ref())?;
235
236        Ok(OptionalParameter::new(self.server, self.name))
237    }
238}
239
240/// A parameter that must always have a value
241pub struct MandatoryParameter<'a, T: ParameterVariant> {
242    server: &'a mut ParameterServer,
243    name: String<{ crate::MAX_PARAM_NAME_LEN }>,
244    _phantom: core::marker::PhantomData<T>,
245}
246
247impl<'a, T: ParameterVariant> MandatoryParameter<'a, T> {
248    pub(crate) fn new(server: &'a mut ParameterServer, name: &'a str) -> Self {
249        let mut n = String::new();
250        n.push_str(name).unwrap();
251        Self {
252            server,
253            name: n,
254            _phantom: core::marker::PhantomData,
255        }
256    }
257
258    /// Get the current value of the parameter
259    pub fn get(&self) -> T {
260        self.server
261            .get_parameter_value(self.name.as_str())
262            .and_then(|val| T::from_parameter_value(&val))
263            .expect("Mandatory parameter must have a value")
264    }
265
266    /// Set the value of the parameter
267    pub fn set(&mut self, value: T) -> Result<(), ParameterError> {
268        let result = self
269            .server
270            .set_parameter_value(self.name.as_str(), value.to_parameter_value());
271        if result == SetParameterResult::Success {
272            Ok(())
273        } else {
274            Err(ParameterError::from(result))
275        }
276    }
277}
278
279/// A parameter that may or may not have a value
280pub struct OptionalParameter<'a, T: ParameterVariant> {
281    server: &'a mut ParameterServer,
282    name: String<{ crate::MAX_PARAM_NAME_LEN }>,
283    _phantom: core::marker::PhantomData<T>,
284}
285
286impl<'a, T: ParameterVariant> OptionalParameter<'a, T> {
287    pub(crate) fn new(server: &'a mut ParameterServer, name: &'a str) -> Self {
288        let mut n = String::new();
289        n.push_str(name).unwrap();
290        Self {
291            server,
292            name: n,
293            _phantom: core::marker::PhantomData,
294        }
295    }
296
297    /// Get the current value of the parameter, if set
298    pub fn get(&self) -> Option<T> {
299        self.server
300            .get_parameter_value(self.name.as_str())
301            .and_then(|val| T::from_parameter_value(&val))
302    }
303
304    /// Set the value of the parameter
305    pub fn set(&mut self, value: Option<T>) -> Result<(), ParameterError> {
306        let param_value = value.map(|v| v.to_parameter_value());
307        let result = self
308            .server
309            .set_parameter_value(self.name.as_str(), param_value.unwrap_or_default());
310        if result == SetParameterResult::Success {
311            Ok(())
312        } else {
313            Err(ParameterError::from(result))
314        }
315    }
316}
317
318/// A parameter whose value cannot be changed after declaration
319pub struct ReadOnlyParameter<'a, T: ParameterVariant> {
320    server: &'a mut ParameterServer,
321    name: String<{ crate::MAX_PARAM_NAME_LEN }>,
322    _phantom: core::marker::PhantomData<T>,
323}
324
325impl<'a, T: ParameterVariant> ReadOnlyParameter<'a, T> {
326    pub(crate) fn new(server: &'a mut ParameterServer, name: &'a str) -> Self {
327        let mut n = String::new();
328        n.push_str(name).unwrap();
329        Self {
330            server,
331            name: n,
332            _phantom: core::marker::PhantomData,
333        }
334    }
335
336    /// Get the current value of the parameter
337    pub fn get(&self) -> T {
338        self.server
339            .get_parameter_value(self.name.as_str())
340            .and_then(|val| T::from_parameter_value(&val))
341            .expect("Read-only parameter must have a value")
342    }
343}
344
345/// Provides access to undeclared parameters in a ParameterServer
346///
347/// This struct is returned by `Node::use_undeclared_parameters()` and allows
348/// for dynamic retrieval of parameter values by name without explicit declaration.
349pub struct UndeclaredParameters<'a> {
350    server: &'a mut ParameterServer,
351}
352
353impl<'a> UndeclaredParameters<'a> {
354    pub fn new(server: &'a mut ParameterServer) -> Self {
355        Self { server }
356    }
357
358    /// Try to get the value of an undeclared boolean parameter
359    pub fn get_bool(&self, name: &str) -> Option<bool> {
360        self.server.get_bool(name)
361    }
362
363    /// Try to get the value of an undeclared integer parameter
364    pub fn get_integer(&self, name: &str) -> Option<i64> {
365        self.server.get_integer(name)
366    }
367
368    /// Try to get the value of an undeclared double parameter
369    pub fn get_double(&self, name: &str) -> Option<f64> {
370        self.server.get_double(name)
371    }
372
373    /// Try to get the value of an undeclared string parameter
374    pub fn get_string(&self, name: &str) -> Option<&str> {
375        self.server.get_string(name)
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_mandatory_parameter_with_default() {
385        let mut server = ParameterServer::new();
386        let param = ParameterBuilder::<i64>::new(&mut server, "test_param")
387            .default(42)
388            .description("A test parameter")
389            .mandatory()
390            .expect("Failed to declare parameter");
391
392        assert_eq!(param.get(), 42);
393    }
394
395    #[test]
396    fn test_mandatory_parameter_set() {
397        let mut server = ParameterServer::new();
398        let mut param = ParameterBuilder::<i64>::new(&mut server, "test_param")
399            .default(0)
400            .mandatory()
401            .expect("Failed to declare parameter");
402
403        param.set(100).expect("Failed to set parameter");
404        assert_eq!(param.get(), 100);
405    }
406
407    #[test]
408    fn test_optional_parameter_none() {
409        let mut server = ParameterServer::new();
410        let param = ParameterBuilder::<i64>::new(&mut server, "test_param")
411            .optional()
412            .expect("Failed to declare parameter");
413
414        assert_eq!(param.get(), None);
415    }
416
417    #[test]
418    fn test_optional_parameter_with_default() {
419        let mut server = ParameterServer::new();
420        let param = ParameterBuilder::<i64>::new(&mut server, "test_param")
421            .default(42)
422            .optional()
423            .expect("Failed to declare parameter");
424
425        assert_eq!(param.get(), Some(42));
426    }
427
428    #[test]
429    fn test_optional_parameter_set() {
430        let mut server = ParameterServer::new();
431        let mut param = ParameterBuilder::<i64>::new(&mut server, "test_param")
432            .optional()
433            .expect("Failed to declare parameter");
434
435        param.set(Some(100)).expect("Failed to set parameter");
436        assert_eq!(param.get(), Some(100));
437    }
438
439    #[test]
440    fn test_read_only_parameter() {
441        let mut server = ParameterServer::new();
442        let param = ParameterBuilder::<i64>::new(&mut server, "readonly_param")
443            .default(42)
444            .description("A read-only parameter")
445            .read_only()
446            .expect("Failed to declare parameter");
447
448        assert_eq!(param.get(), 42);
449    }
450
451    #[test]
452    fn test_read_only_parameter_requires_default() {
453        let mut server = ParameterServer::new();
454        let result = ParameterBuilder::<i64>::new(&mut server, "readonly_param").read_only();
455
456        assert_eq!(result.err(), Some(ParameterError::NotFound));
457    }
458
459    #[test]
460    fn test_integer_range_constraint() {
461        let mut server = ParameterServer::new();
462        let mut param = ParameterBuilder::<i64>::new(&mut server, "ranged_param")
463            .default(50)
464            .integer_range(0, 100, 1)
465            .expect("Failed to set range")
466            .mandatory()
467            .expect("Failed to declare parameter");
468
469        // Valid value within range
470        param.set(75).expect("Failed to set valid value");
471        assert_eq!(param.get(), 75);
472    }
473
474    #[test]
475    fn test_float_range_constraint() {
476        let mut server = ParameterServer::new();
477        let mut param = ParameterBuilder::<f64>::new(&mut server, "float_param")
478            .default(0.5)
479            .float_range(0.0, 1.0, 0.0)
480            .expect("Failed to set range")
481            .mandatory()
482            .expect("Failed to declare parameter");
483
484        // Valid value within range
485        param.set(0.75).expect("Failed to set valid value");
486        assert_eq!(param.get(), 0.75);
487    }
488
489    #[test]
490    fn test_range_convenience_integer() {
491        let mut server = ParameterServer::new();
492        let param = ParameterBuilder::<i64>::new(&mut server, "ranged_param")
493            .default(50)
494            .range(0..=100)
495            .expect("Failed to set range")
496            .mandatory()
497            .expect("Failed to declare parameter");
498
499        assert_eq!(param.get(), 50);
500    }
501
502    #[test]
503    fn test_range_convenience_float() {
504        let mut server = ParameterServer::new();
505        let param = ParameterBuilder::<f64>::new(&mut server, "float_param")
506            .default(0.5)
507            .range(0.0..=1.0)
508            .expect("Failed to set range")
509            .mandatory()
510            .expect("Failed to declare parameter");
511
512        assert_eq!(param.get(), 0.5);
513    }
514
515    #[test]
516    fn test_parameter_description() {
517        let mut server = ParameterServer::new();
518        let _param = ParameterBuilder::<i64>::new(&mut server, "described_param")
519            .default(42)
520            .description("This is a test description")
521            .mandatory()
522            .expect("Failed to declare parameter");
523
524        // Verify description was set in the server
525        let desc = server.get_descriptor("described_param");
526        assert!(desc.is_some());
527        assert_eq!(
528            desc.unwrap().description.as_str(),
529            "This is a test description"
530        );
531    }
532
533    #[test]
534    fn test_bool_parameter() {
535        let mut server = ParameterServer::new();
536        let mut param = ParameterBuilder::<bool>::new(&mut server, "bool_param")
537            .default(false)
538            .mandatory()
539            .expect("Failed to declare parameter");
540
541        assert!(!param.get());
542        param.set(true).expect("Failed to set parameter");
543        assert!(param.get());
544    }
545
546    #[test]
547    fn test_undeclared_parameters() {
548        use crate::ParameterValue;
549
550        let mut server = ParameterServer::new();
551
552        // Set some values using set_or_declare (simulating external parameter loading)
553        server.set_or_declare("flag", ParameterValue::Bool(true));
554        server.set_or_declare("count", ParameterValue::Integer(42));
555        server.set_or_declare("ratio", ParameterValue::Double(0.5));
556
557        let undeclared = UndeclaredParameters::new(&mut server);
558
559        assert_eq!(undeclared.get_bool("flag"), Some(true));
560        assert_eq!(undeclared.get_integer("count"), Some(42));
561        assert_eq!(undeclared.get_double("ratio"), Some(0.5));
562        assert_eq!(undeclared.get_string("nonexistent"), None);
563    }
564}