rclrs/parameter/
value.rs

1use std::{ffi::CStr, sync::Arc};
2
3use crate::{
4    parameter::{ParameterRange, ParameterRanges},
5    rcl_bindings::*,
6    vendor::rcl_interfaces::msg::rmw::{ParameterType, ParameterValue as RmwParameterValue},
7    ParameterValueError,
8};
9
10/// A parameter value.
11///
12/// Such a value can be specified in YAML format on the command line, or in a parameter file.
13/// For instance `--param foo:='[1, 2, 3]'` specfies an `IntegerArray` value for the `foo` parameter.
14#[derive(Clone, Debug, PartialEq)]
15pub enum ParameterValue {
16    /// A boolean value.
17    ///
18    /// YAML examples: `true`, `True`, `yes`, `y`.
19    Bool(bool),
20    /// An i64 value.
21    ///
22    /// YAML examples: `1`, `-30`, `0x7C`.
23    Integer(i64),
24    /// An f64 value.
25    ///
26    /// YAML examples: `2.0`, `8e-3`.
27    Double(f64),
28    /// A string.
29    ///
30    /// YAML examples: `""`, `"björk"`, `"42"`.
31    ///
32    /// Unquoted strings are also possible, but not recommended,
33    /// because they may be interpreted as another data type.
34    String(Arc<str>),
35    /// An array of u8.
36    ///
37    /// YAML example: Not possible to specify as YAML.
38    ByteArray(Arc<[u8]>),
39    /// An array of booleans.
40    ///
41    /// YAML example: `[true, false, false]`.
42    BoolArray(Arc<[bool]>),
43    /// An array of i64.
44    ///
45    /// YAML example: `[3, 4]`.
46    IntegerArray(Arc<[i64]>),
47    /// An array of f64.
48    ///
49    /// YAML example: `[5.0, 6e2]`.
50    DoubleArray(Arc<[f64]>),
51    /// An array of strings.
52    ///
53    /// YAML example: `["abc", ""]`.
54    StringArray(Arc<[Arc<str>]>),
55}
56
57/// Describes the parameter's type. Similar to `ParameterValue` but also includes a `Dynamic`
58/// variant for dynamic parameters.
59#[derive(Clone, Debug, PartialEq)]
60pub enum ParameterKind {
61    /// A boolean parameter.
62    Bool,
63    /// An i64 value.
64    Integer,
65    /// An f64 value.
66    Double,
67    /// A string.
68    String,
69    /// An array of u8.
70    ByteArray,
71    /// An array of booleans.
72    BoolArray,
73    /// An array of i64.
74    IntegerArray,
75    /// An array of f64.
76    DoubleArray,
77    /// An array of strings.
78    StringArray,
79    /// A dynamic parameter that can change its type at runtime.
80    Dynamic,
81}
82
83impl From<bool> for ParameterValue {
84    fn from(value: bool) -> ParameterValue {
85        ParameterValue::Bool(value)
86    }
87}
88
89impl From<i64> for ParameterValue {
90    fn from(value: i64) -> ParameterValue {
91        ParameterValue::Integer(value)
92    }
93}
94
95impl From<f64> for ParameterValue {
96    fn from(value: f64) -> ParameterValue {
97        ParameterValue::Double(value)
98    }
99}
100
101impl From<Arc<str>> for ParameterValue {
102    fn from(value: Arc<str>) -> ParameterValue {
103        ParameterValue::String(value)
104    }
105}
106
107impl From<Arc<[u8]>> for ParameterValue {
108    fn from(value: Arc<[u8]>) -> ParameterValue {
109        ParameterValue::ByteArray(value)
110    }
111}
112
113impl From<Arc<[bool]>> for ParameterValue {
114    fn from(value: Arc<[bool]>) -> ParameterValue {
115        ParameterValue::BoolArray(value)
116    }
117}
118
119impl From<Arc<[i64]>> for ParameterValue {
120    fn from(value: Arc<[i64]>) -> ParameterValue {
121        ParameterValue::IntegerArray(value)
122    }
123}
124
125impl From<Arc<[f64]>> for ParameterValue {
126    fn from(value: Arc<[f64]>) -> ParameterValue {
127        ParameterValue::DoubleArray(value)
128    }
129}
130
131impl From<Arc<[Arc<str>]>> for ParameterValue {
132    fn from(value: Arc<[Arc<str>]>) -> ParameterValue {
133        ParameterValue::StringArray(value)
134    }
135}
136
137/// A trait that describes a value that can be converted into a parameter.
138pub trait ParameterVariant: Into<ParameterValue> + Clone + TryFrom<ParameterValue> {
139    /// The type used to describe the range of this parameter.
140    type Range: Into<ParameterRanges> + Default + Clone;
141
142    /// Returns the `ParameterKind` of the implemented type.
143    fn kind() -> ParameterKind;
144}
145
146impl TryFrom<ParameterValue> for bool {
147    type Error = ParameterValueError;
148
149    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
150        match value {
151            ParameterValue::Bool(v) => Ok(v),
152            _ => Err(ParameterValueError::TypeMismatch),
153        }
154    }
155}
156
157impl ParameterVariant for bool {
158    type Range = ();
159
160    fn kind() -> ParameterKind {
161        ParameterKind::Bool
162    }
163}
164
165impl TryFrom<ParameterValue> for i64 {
166    type Error = ParameterValueError;
167
168    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
169        match value {
170            ParameterValue::Integer(v) => Ok(v),
171            _ => Err(ParameterValueError::TypeMismatch),
172        }
173    }
174}
175
176impl ParameterVariant for i64 {
177    type Range = ParameterRange<i64>;
178
179    fn kind() -> ParameterKind {
180        ParameterKind::Integer
181    }
182}
183
184impl TryFrom<ParameterValue> for f64 {
185    type Error = ParameterValueError;
186
187    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
188        match value {
189            ParameterValue::Double(v) => Ok(v),
190            _ => Err(ParameterValueError::TypeMismatch),
191        }
192    }
193}
194
195impl ParameterVariant for f64 {
196    type Range = ParameterRange<f64>;
197
198    fn kind() -> ParameterKind {
199        ParameterKind::Double
200    }
201}
202
203impl TryFrom<ParameterValue> for Arc<str> {
204    type Error = ParameterValueError;
205
206    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
207        match value {
208            ParameterValue::String(v) => Ok(v),
209            _ => Err(ParameterValueError::TypeMismatch),
210        }
211    }
212}
213
214impl ParameterVariant for Arc<str> {
215    type Range = ();
216
217    fn kind() -> ParameterKind {
218        ParameterKind::String
219    }
220}
221
222impl TryFrom<ParameterValue> for Arc<[u8]> {
223    type Error = ParameterValueError;
224
225    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
226        match value {
227            ParameterValue::ByteArray(v) => Ok(v),
228            _ => Err(ParameterValueError::TypeMismatch),
229        }
230    }
231}
232
233impl ParameterVariant for Arc<[u8]> {
234    type Range = ();
235
236    fn kind() -> ParameterKind {
237        ParameterKind::ByteArray
238    }
239}
240
241impl TryFrom<ParameterValue> for Arc<[bool]> {
242    type Error = ParameterValueError;
243
244    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
245        match value {
246            ParameterValue::BoolArray(v) => Ok(v),
247            _ => Err(ParameterValueError::TypeMismatch),
248        }
249    }
250}
251
252impl ParameterVariant for Arc<[bool]> {
253    type Range = ();
254
255    fn kind() -> ParameterKind {
256        ParameterKind::BoolArray
257    }
258}
259
260impl TryFrom<ParameterValue> for Arc<[i64]> {
261    type Error = ParameterValueError;
262
263    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
264        match value {
265            ParameterValue::IntegerArray(v) => Ok(v),
266            _ => Err(ParameterValueError::TypeMismatch),
267        }
268    }
269}
270
271impl ParameterVariant for Arc<[i64]> {
272    type Range = ();
273
274    fn kind() -> ParameterKind {
275        ParameterKind::IntegerArray
276    }
277}
278
279impl TryFrom<ParameterValue> for Arc<[f64]> {
280    type Error = ParameterValueError;
281
282    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
283        match value {
284            ParameterValue::DoubleArray(v) => Ok(v),
285            _ => Err(ParameterValueError::TypeMismatch),
286        }
287    }
288}
289
290impl ParameterVariant for Arc<[f64]> {
291    type Range = ();
292
293    fn kind() -> ParameterKind {
294        ParameterKind::DoubleArray
295    }
296}
297
298impl TryFrom<ParameterValue> for Arc<[Arc<str>]> {
299    type Error = ParameterValueError;
300
301    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
302        match value {
303            ParameterValue::StringArray(v) => Ok(v),
304            _ => Err(ParameterValueError::TypeMismatch),
305        }
306    }
307}
308
309impl ParameterVariant for Arc<[Arc<str>]> {
310    type Range = ();
311
312    fn kind() -> ParameterKind {
313        ParameterKind::StringArray
314    }
315}
316
317impl ParameterVariant for ParameterValue {
318    type Range = ParameterRanges;
319
320    fn kind() -> ParameterKind {
321        ParameterKind::Dynamic
322    }
323}
324
325impl From<ParameterValue> for RmwParameterValue {
326    fn from(value: ParameterValue) -> Self {
327        match value {
328            ParameterValue::Bool(v) => RmwParameterValue {
329                type_: ParameterType::PARAMETER_BOOL,
330                bool_value: v,
331                ..Default::default()
332            },
333            ParameterValue::Integer(v) => RmwParameterValue {
334                type_: ParameterType::PARAMETER_INTEGER,
335                integer_value: v,
336                ..Default::default()
337            },
338            ParameterValue::Double(v) => RmwParameterValue {
339                type_: ParameterType::PARAMETER_DOUBLE,
340                double_value: v,
341                ..Default::default()
342            },
343            ParameterValue::String(v) => RmwParameterValue {
344                type_: ParameterType::PARAMETER_STRING,
345                string_value: v.into(),
346                ..Default::default()
347            },
348            ParameterValue::ByteArray(v) => RmwParameterValue {
349                type_: ParameterType::PARAMETER_BYTE_ARRAY,
350                byte_array_value: (*v).into(),
351                ..Default::default()
352            },
353            ParameterValue::BoolArray(v) => RmwParameterValue {
354                type_: ParameterType::PARAMETER_BOOL_ARRAY,
355                bool_array_value: (*v).into(),
356                ..Default::default()
357            },
358            ParameterValue::IntegerArray(v) => RmwParameterValue {
359                type_: ParameterType::PARAMETER_INTEGER_ARRAY,
360                integer_array_value: (*v).into(),
361                ..Default::default()
362            },
363            ParameterValue::DoubleArray(v) => RmwParameterValue {
364                type_: ParameterType::PARAMETER_DOUBLE_ARRAY,
365                double_array_value: (*v).into(),
366                ..Default::default()
367            },
368            ParameterValue::StringArray(v) => RmwParameterValue {
369                type_: ParameterType::PARAMETER_STRING_ARRAY,
370                string_array_value: v.iter().map(|v| v.clone().into()).collect(),
371                ..Default::default()
372            },
373        }
374    }
375}
376
377/// An error that occured when trying to convert a parameter from an
378/// `rcl_interfaces::msg::ParameterValue`
379#[derive(Debug)]
380pub enum RmwParameterConversionError {
381    /// The parameter type was not valid.
382    InvalidParameterType,
383}
384
385impl std::fmt::Display for RmwParameterConversionError {
386    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387        match self {
388            RmwParameterConversionError::InvalidParameterType => {
389                write!(f, "the parameter type was not valid")
390            }
391        }
392    }
393}
394
395impl std::error::Error for RmwParameterConversionError {}
396
397impl TryFrom<RmwParameterValue> for ParameterValue {
398    type Error = RmwParameterConversionError;
399
400    fn try_from(param: RmwParameterValue) -> Result<Self, Self::Error> {
401        // TODO(luca) how to deal with PARAMETER_NOT_SET? Should we allow service calls to unset
402        // parameters?
403        match param.type_ {
404            ParameterType::PARAMETER_BOOL => Ok(ParameterValue::Bool(param.bool_value)),
405            ParameterType::PARAMETER_INTEGER => Ok(ParameterValue::Integer(param.integer_value)),
406            ParameterType::PARAMETER_DOUBLE => Ok(ParameterValue::Double(param.double_value)),
407            ParameterType::PARAMETER_STRING => Ok(ParameterValue::String(
408                param.string_value.to_string().into(),
409            )),
410            ParameterType::PARAMETER_BYTE_ARRAY => {
411                Ok(ParameterValue::ByteArray((*param.byte_array_value).into()))
412            }
413            ParameterType::PARAMETER_BOOL_ARRAY => {
414                Ok(ParameterValue::BoolArray((*param.bool_array_value).into()))
415            }
416            ParameterType::PARAMETER_INTEGER_ARRAY => Ok(ParameterValue::IntegerArray(
417                (*param.integer_array_value).into(),
418            )),
419            ParameterType::PARAMETER_DOUBLE_ARRAY => Ok(ParameterValue::DoubleArray(
420                (*param.double_array_value).into(),
421            )),
422            ParameterType::PARAMETER_STRING_ARRAY => Ok(ParameterValue::StringArray(
423                param
424                    .string_array_value
425                    .iter()
426                    .map(|s| s.to_string().into())
427                    .collect::<Vec<_>>()
428                    .into(),
429            )),
430            _ => Err(RmwParameterConversionError::InvalidParameterType),
431        }
432    }
433}
434
435impl ParameterValue {
436    // Panics if the rcl_variant_t does not have exactly one field set.
437    //
438    // This function is unsafe because it is possible to pass in an rcl_variant_t
439    // containing dangling pointers, or incorrect array sizes.
440    pub(crate) unsafe fn from_rcl_variant(var: &rcl_variant_t) -> Self {
441        let num_active: u8 = [
442            !var.bool_value.is_null(),
443            !var.integer_value.is_null(),
444            !var.double_value.is_null(),
445            !var.string_value.is_null(),
446            !var.byte_array_value.is_null(),
447            !var.bool_array_value.is_null(),
448            !var.integer_array_value.is_null(),
449            !var.double_array_value.is_null(),
450            !var.string_array_value.is_null(),
451        ]
452        .into_iter()
453        .map(u8::from)
454        .sum();
455        assert_eq!(num_active, 1);
456        // Note: Unsafe blocks below are necessary to dereference raw pointers
457        // and call unsafe functions like CStr::from_ptr.
458        // In general, the following operations are as safe as they can be, because
459        // only non-null pointers are dereferenced, and strings and arrays are copied immediately,
460        // so there are no concerns about choosing the correct lifetime.
461        //
462        // Of course, a pointer being not null is not a guarantee that it points to a valid value.
463        // However, it cannot be checked that it points to a valid value. Similarly for array sizes.
464        // This is why this function must be unsafe itself.
465        if !var.bool_value.is_null() {
466            unsafe { ParameterValue::Bool(*var.bool_value) }
467        } else if !var.integer_value.is_null() {
468            unsafe { ParameterValue::Integer(*var.integer_value) }
469        } else if !var.double_value.is_null() {
470            unsafe { ParameterValue::Double(*var.double_value) }
471        } else if !var.string_value.is_null() {
472            unsafe {
473                let cstr = CStr::from_ptr(var.string_value);
474                let s = cstr.to_string_lossy().into_owned();
475                ParameterValue::String(s.into())
476            }
477        } else if !var.byte_array_value.is_null() {
478            unsafe {
479                let rcl_byte_array = &*var.byte_array_value;
480                let slice = rcl_from_raw_parts(rcl_byte_array.values, rcl_byte_array.size);
481                ParameterValue::ByteArray(slice.into())
482            }
483        } else if !var.bool_array_value.is_null() {
484            unsafe {
485                let rcl_bool_array = &*var.bool_array_value;
486                let slice = rcl_from_raw_parts(rcl_bool_array.values, rcl_bool_array.size);
487                ParameterValue::BoolArray(slice.into())
488            }
489        } else if !var.integer_array_value.is_null() {
490            unsafe {
491                let rcl_integer_array = &*var.integer_array_value;
492                let slice = rcl_from_raw_parts(rcl_integer_array.values, rcl_integer_array.size);
493                ParameterValue::IntegerArray(slice.into())
494            }
495        } else if !var.double_array_value.is_null() {
496            unsafe {
497                let rcl_double_array = &*var.double_array_value;
498                let slice = rcl_from_raw_parts(rcl_double_array.values, rcl_double_array.size);
499                ParameterValue::DoubleArray(slice.into())
500            }
501        } else if !var.string_array_value.is_null() {
502            unsafe {
503                let rcutils_string_array = &*var.string_array_value;
504                let slice =
505                    rcl_from_raw_parts(rcutils_string_array.data, rcutils_string_array.size);
506                let strings = slice
507                    .iter()
508                    .map(|&ptr| {
509                        debug_assert!(!ptr.is_null());
510                        let cstr = CStr::from_ptr(ptr);
511                        Arc::from(cstr.to_string_lossy())
512                    })
513                    .collect::<Vec<_>>();
514                ParameterValue::StringArray(strings.into())
515            }
516        } else {
517            unreachable!()
518        }
519    }
520
521    pub(crate) fn rcl_parameter_type(&self) -> u8 {
522        match self {
523            ParameterValue::Bool(_) => ParameterType::PARAMETER_BOOL,
524            ParameterValue::Integer(_) => ParameterType::PARAMETER_INTEGER,
525            ParameterValue::Double(_) => ParameterType::PARAMETER_DOUBLE,
526            ParameterValue::String(_) => ParameterType::PARAMETER_STRING,
527            ParameterValue::ByteArray(_) => ParameterType::PARAMETER_BYTE_ARRAY,
528            ParameterValue::BoolArray(_) => ParameterType::PARAMETER_BOOL_ARRAY,
529            ParameterValue::IntegerArray(_) => ParameterType::PARAMETER_INTEGER_ARRAY,
530            ParameterValue::DoubleArray(_) => ParameterType::PARAMETER_DOUBLE_ARRAY,
531            ParameterValue::StringArray(_) => ParameterType::PARAMETER_STRING_ARRAY,
532        }
533    }
534
535    /// Returns the `ParameterKind` for the parameter.
536    pub(crate) fn kind(&self) -> ParameterKind {
537        match self {
538            ParameterValue::Bool(_) => ParameterKind::Bool,
539            ParameterValue::Integer(_) => ParameterKind::Integer,
540            ParameterValue::Double(_) => ParameterKind::Double,
541            ParameterValue::String(_) => ParameterKind::String,
542            ParameterValue::ByteArray(_) => ParameterKind::ByteArray,
543            ParameterValue::BoolArray(_) => ParameterKind::BoolArray,
544            ParameterValue::IntegerArray(_) => ParameterKind::IntegerArray,
545            ParameterValue::DoubleArray(_) => ParameterKind::DoubleArray,
546            ParameterValue::StringArray(_) => ParameterKind::StringArray,
547        }
548    }
549}
550
551#[cfg(test)]
552mod tests {
553    use super::*;
554    use crate::{Context, InitOptions, RclrsError, ToResult};
555
556    // TODO(luca) tests for all from / to ParameterVariant functions
557
558    #[test]
559    fn test_parameter_value() -> Result<(), RclrsError> {
560        // This test is not a test of the YAML parser or argument parser, only a test that the
561        // correct ParameterValue variant is obtained from rcl_variants found in the wild.
562        let input_output_pairs = [
563            ("true", ParameterValue::Bool(true)),
564            ("1", ParameterValue::Integer(1)),
565            ("1.0", ParameterValue::Double(1.0)),
566            ("'1.0'", ParameterValue::String(Arc::from("1.0"))),
567            (
568                "[yes, no]",
569                ParameterValue::BoolArray(Arc::from([true, false])),
570            ),
571            ("[-3, 2]", ParameterValue::IntegerArray(Arc::from([-3, 2]))),
572            (
573                "[-3.0, 2.0]",
574                ParameterValue::DoubleArray(Arc::from([-3.0, 2.0])),
575            ),
576            (
577                "['yes']",
578                ParameterValue::StringArray(Arc::from([Arc::from("yes")])),
579            ),
580        ];
581        for pair in input_output_pairs {
582            let ctx = Context::new(
583                [
584                    String::from("--ros-args"),
585                    String::from("-p"),
586                    format!("foo:={}", pair.0),
587                ],
588                InitOptions::default(),
589            )?;
590            let mut rcl_params = std::ptr::null_mut();
591            unsafe {
592                rcl_arguments_get_param_overrides(
593                    &ctx.handle.rcl_context.lock().unwrap().global_arguments,
594                    &mut rcl_params,
595                )
596                .ok()?;
597            }
598            assert!(!rcl_params.is_null());
599            assert_eq!(unsafe { (*rcl_params).num_nodes }, 1);
600            let rcl_node_params = unsafe { &(*(*rcl_params).params) };
601            assert_eq!(rcl_node_params.num_params, 1);
602            let rcl_variant = unsafe { &(*rcl_node_params.parameter_values) };
603            let param_value = unsafe { ParameterValue::from_rcl_variant(rcl_variant) };
604            assert_eq!(param_value, pair.1);
605            unsafe { rcl_yaml_node_struct_fini(rcl_params) };
606        }
607        Ok(())
608    }
609}