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