do_not_use_testing_rclrs/parameter/
value.rs

1use std::ffi::CStr;
2use std::sync::Arc;
3
4use crate::rcl_bindings::*;
5use crate::{ParameterRange, ParameterRanges, ParameterValueError};
6
7/// A parameter value.
8///
9/// Such a value can be specified in YAML format on the command line, or in a parameter file.
10/// For instance `--param foo:='[1, 2, 3]'` specfies an `IntegerArray` value for the `foo` parameter.
11#[derive(Clone, Debug, PartialEq)]
12pub enum ParameterValue {
13    /// A boolean value.
14    ///
15    /// YAML examples: `true`, `True`, `yes`, `y`.
16    Bool(bool),
17    /// An i64 value.
18    ///
19    /// YAML examples: `1`, `-30`, `0x7C`.
20    Integer(i64),
21    /// An f64 value.
22    ///
23    /// YAML examples: `2.0`, `8e-3`.
24    Double(f64),
25    /// A string.
26    ///
27    /// YAML examples: `""`, `"björk"`, `"42"`.
28    ///
29    /// Unquoted strings are also possible, but not recommended,
30    /// because they may be interpreted as another data type.
31    String(Arc<str>),
32    /// An array of u8.
33    ///
34    /// YAML example: Not possible to specify as YAML.
35    ByteArray(Arc<[u8]>),
36    /// An array of booleans.
37    ///
38    /// YAML example: `[true, false, false]`.
39    BoolArray(Arc<[bool]>),
40    /// An array of i64.
41    ///
42    /// YAML example: `[3, 4]`.
43    IntegerArray(Arc<[i64]>),
44    /// An array of f64.
45    ///
46    /// YAML example: `[5.0, 6e2]`.
47    DoubleArray(Arc<[f64]>),
48    /// An array of strings.
49    ///
50    /// YAML example: `["abc", ""]`.
51    StringArray(Arc<[Arc<str>]>),
52}
53
54/// Describes the parameter's type. Similar to `ParameterValue` but also includes a `Dynamic`
55/// variant for dynamic parameters.
56#[derive(Clone, Debug, PartialEq)]
57pub enum ParameterKind {
58    /// A boolean parameter.
59    Bool,
60    /// An i64 value.
61    Integer,
62    /// An f64 value.
63    Double,
64    /// A string.
65    String,
66    /// An array of u8.
67    ByteArray,
68    /// An array of booleans.
69    BoolArray,
70    /// An array of i64.
71    IntegerArray,
72    /// An array of f64.
73    DoubleArray,
74    /// An array of strings.
75    StringArray,
76    /// A dynamic parameter that can change its type at runtime.
77    Dynamic,
78}
79
80impl From<bool> for ParameterValue {
81    fn from(value: bool) -> ParameterValue {
82        ParameterValue::Bool(value)
83    }
84}
85
86impl From<i64> for ParameterValue {
87    fn from(value: i64) -> ParameterValue {
88        ParameterValue::Integer(value)
89    }
90}
91
92impl From<f64> for ParameterValue {
93    fn from(value: f64) -> ParameterValue {
94        ParameterValue::Double(value)
95    }
96}
97
98impl From<Arc<str>> for ParameterValue {
99    fn from(value: Arc<str>) -> ParameterValue {
100        ParameterValue::String(value)
101    }
102}
103
104impl From<Arc<[u8]>> for ParameterValue {
105    fn from(value: Arc<[u8]>) -> ParameterValue {
106        ParameterValue::ByteArray(value)
107    }
108}
109
110impl From<Arc<[bool]>> for ParameterValue {
111    fn from(value: Arc<[bool]>) -> ParameterValue {
112        ParameterValue::BoolArray(value)
113    }
114}
115
116impl From<Arc<[i64]>> for ParameterValue {
117    fn from(value: Arc<[i64]>) -> ParameterValue {
118        ParameterValue::IntegerArray(value)
119    }
120}
121
122impl From<Arc<[f64]>> for ParameterValue {
123    fn from(value: Arc<[f64]>) -> ParameterValue {
124        ParameterValue::DoubleArray(value)
125    }
126}
127
128impl From<Arc<[Arc<str>]>> for ParameterValue {
129    fn from(value: Arc<[Arc<str>]>) -> ParameterValue {
130        ParameterValue::StringArray(value)
131    }
132}
133
134/// A trait that describes a value that can be converted into a parameter.
135pub trait ParameterVariant: Into<ParameterValue> + Clone + TryFrom<ParameterValue> {
136    /// The type used to describe the range of this parameter.
137    type Range: Into<ParameterRanges> + Default + Clone;
138
139    /// Returns the `ParameterKind` of the implemented type.
140    fn kind() -> ParameterKind;
141}
142
143impl TryFrom<ParameterValue> for bool {
144    type Error = ParameterValueError;
145
146    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
147        match value {
148            ParameterValue::Bool(v) => Ok(v),
149            _ => Err(ParameterValueError::TypeMismatch),
150        }
151    }
152}
153
154impl ParameterVariant for bool {
155    type Range = ();
156
157    fn kind() -> ParameterKind {
158        ParameterKind::Bool
159    }
160}
161
162impl TryFrom<ParameterValue> for i64 {
163    type Error = ParameterValueError;
164
165    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
166        match value {
167            ParameterValue::Integer(v) => Ok(v),
168            _ => Err(ParameterValueError::TypeMismatch),
169        }
170    }
171}
172
173impl ParameterVariant for i64 {
174    type Range = ParameterRange<i64>;
175
176    fn kind() -> ParameterKind {
177        ParameterKind::Integer
178    }
179}
180
181impl TryFrom<ParameterValue> for f64 {
182    type Error = ParameterValueError;
183
184    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
185        match value {
186            ParameterValue::Double(v) => Ok(v),
187            _ => Err(ParameterValueError::TypeMismatch),
188        }
189    }
190}
191
192impl ParameterVariant for f64 {
193    type Range = ParameterRange<f64>;
194
195    fn kind() -> ParameterKind {
196        ParameterKind::Double
197    }
198}
199
200impl TryFrom<ParameterValue> for Arc<str> {
201    type Error = ParameterValueError;
202
203    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
204        match value {
205            ParameterValue::String(v) => Ok(v),
206            _ => Err(ParameterValueError::TypeMismatch),
207        }
208    }
209}
210
211impl ParameterVariant for Arc<str> {
212    type Range = ();
213
214    fn kind() -> ParameterKind {
215        ParameterKind::String
216    }
217}
218
219impl TryFrom<ParameterValue> for Arc<[u8]> {
220    type Error = ParameterValueError;
221
222    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
223        match value {
224            ParameterValue::ByteArray(v) => Ok(v),
225            _ => Err(ParameterValueError::TypeMismatch),
226        }
227    }
228}
229
230impl ParameterVariant for Arc<[u8]> {
231    type Range = ();
232
233    fn kind() -> ParameterKind {
234        ParameterKind::ByteArray
235    }
236}
237
238impl TryFrom<ParameterValue> for Arc<[bool]> {
239    type Error = ParameterValueError;
240
241    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
242        match value {
243            ParameterValue::BoolArray(v) => Ok(v),
244            _ => Err(ParameterValueError::TypeMismatch),
245        }
246    }
247}
248
249impl ParameterVariant for Arc<[bool]> {
250    type Range = ();
251
252    fn kind() -> ParameterKind {
253        ParameterKind::BoolArray
254    }
255}
256
257impl TryFrom<ParameterValue> for Arc<[i64]> {
258    type Error = ParameterValueError;
259
260    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
261        match value {
262            ParameterValue::IntegerArray(v) => Ok(v),
263            _ => Err(ParameterValueError::TypeMismatch),
264        }
265    }
266}
267
268impl ParameterVariant for Arc<[i64]> {
269    type Range = ();
270
271    fn kind() -> ParameterKind {
272        ParameterKind::IntegerArray
273    }
274}
275
276impl TryFrom<ParameterValue> for Arc<[f64]> {
277    type Error = ParameterValueError;
278
279    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
280        match value {
281            ParameterValue::DoubleArray(v) => Ok(v),
282            _ => Err(ParameterValueError::TypeMismatch),
283        }
284    }
285}
286
287impl ParameterVariant for Arc<[f64]> {
288    type Range = ();
289
290    fn kind() -> ParameterKind {
291        ParameterKind::DoubleArray
292    }
293}
294
295impl TryFrom<ParameterValue> for Arc<[Arc<str>]> {
296    type Error = ParameterValueError;
297
298    fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
299        match value {
300            ParameterValue::StringArray(v) => Ok(v),
301            _ => Err(ParameterValueError::TypeMismatch),
302        }
303    }
304}
305
306impl ParameterVariant for Arc<[Arc<str>]> {
307    type Range = ();
308
309    fn kind() -> ParameterKind {
310        ParameterKind::StringArray
311    }
312}
313
314impl ParameterVariant for ParameterValue {
315    type Range = ParameterRanges;
316
317    fn kind() -> ParameterKind {
318        ParameterKind::Dynamic
319    }
320}
321
322impl ParameterValue {
323    // Panics if the rcl_variant_t does not have exactly one field set.
324    //
325    // This function is unsafe because it is possible to pass in an rcl_variant_t
326    // containing dangling pointers, or incorrect array sizes.
327    pub(crate) unsafe fn from_rcl_variant(var: &rcl_variant_t) -> Self {
328        let num_active: u8 = [
329            !var.bool_value.is_null(),
330            !var.integer_value.is_null(),
331            !var.double_value.is_null(),
332            !var.string_value.is_null(),
333            !var.byte_array_value.is_null(),
334            !var.bool_array_value.is_null(),
335            !var.integer_array_value.is_null(),
336            !var.double_array_value.is_null(),
337            !var.string_array_value.is_null(),
338        ]
339        .into_iter()
340        .map(u8::from)
341        .sum();
342        assert_eq!(num_active, 1);
343        // Note: This code has no unsafe blocks because it is inside an unsafe function.
344        // In general, the following operations are as safe as they can be, because
345        // only non-null pointers are dereferenced, and strings and arrays are copied immediately,
346        // so there are no concerns about choosing the correct lifetime.
347        //
348        // Of course, a pointer being not null is not a guarantee that it points to a valid value.
349        // However, it cannot be checked that it points to a valid value. Similarly for array sizes.
350        // This is why this function must be unsafe itself.
351        if !var.bool_value.is_null() {
352            ParameterValue::Bool(*var.bool_value)
353        } else if !var.integer_value.is_null() {
354            ParameterValue::Integer(*var.integer_value)
355        } else if !var.double_value.is_null() {
356            ParameterValue::Double(*var.double_value)
357        } else if !var.string_value.is_null() {
358            let cstr = CStr::from_ptr(var.string_value);
359            let s = cstr.to_string_lossy().into_owned();
360            ParameterValue::String(s.into())
361        } else if !var.byte_array_value.is_null() {
362            let rcl_byte_array = &*var.byte_array_value;
363            let slice = std::slice::from_raw_parts(rcl_byte_array.values, rcl_byte_array.size);
364            ParameterValue::ByteArray(slice.into())
365        } else if !var.bool_array_value.is_null() {
366            let rcl_bool_array = &*var.bool_array_value;
367            let slice = std::slice::from_raw_parts(rcl_bool_array.values, rcl_bool_array.size);
368            ParameterValue::BoolArray(slice.into())
369        } else if !var.integer_array_value.is_null() {
370            let rcl_integer_array = &*var.integer_array_value;
371            let slice =
372                std::slice::from_raw_parts(rcl_integer_array.values, rcl_integer_array.size);
373            ParameterValue::IntegerArray(slice.into())
374        } else if !var.double_array_value.is_null() {
375            let rcl_double_array = &*var.double_array_value;
376            let slice = std::slice::from_raw_parts(rcl_double_array.values, rcl_double_array.size);
377            ParameterValue::DoubleArray(slice.into())
378        } else if !var.string_array_value.is_null() {
379            let rcutils_string_array = &*var.string_array_value;
380            let slice =
381                std::slice::from_raw_parts(rcutils_string_array.data, rcutils_string_array.size);
382            let strings = slice
383                .iter()
384                .map(|&ptr| {
385                    debug_assert!(!ptr.is_null());
386                    let cstr = CStr::from_ptr(ptr);
387                    Arc::from(cstr.to_string_lossy())
388                })
389                .collect::<Vec<_>>();
390            ParameterValue::StringArray(strings.into())
391        } else {
392            unreachable!()
393        }
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400    use crate::{Context, RclrsError, ToResult};
401
402    // TODO(luca) tests for all from / to ParameterVariant functions
403
404    #[test]
405    fn test_parameter_value() -> Result<(), RclrsError> {
406        // This test is not a test of the YAML parser or argument parser, only a test that the
407        // correct ParameterValue variant is obtained from rcl_variants found in the wild.
408        let input_output_pairs = [
409            ("true", ParameterValue::Bool(true)),
410            ("1", ParameterValue::Integer(1)),
411            ("1.0", ParameterValue::Double(1.0)),
412            ("'1.0'", ParameterValue::String(Arc::from("1.0"))),
413            (
414                "[yes, no]",
415                ParameterValue::BoolArray(Arc::from([true, false])),
416            ),
417            ("[-3, 2]", ParameterValue::IntegerArray(Arc::from([-3, 2]))),
418            (
419                "[-3.0, 2.0]",
420                ParameterValue::DoubleArray(Arc::from([-3.0, 2.0])),
421            ),
422            (
423                "['yes']",
424                ParameterValue::StringArray(Arc::from([Arc::from("yes")])),
425            ),
426        ];
427        for pair in input_output_pairs {
428            let ctx = Context::new([
429                String::from("--ros-args"),
430                String::from("-p"),
431                format!("foo:={}", pair.0),
432            ])?;
433            let mut rcl_params = std::ptr::null_mut();
434            unsafe {
435                rcl_arguments_get_param_overrides(
436                    &ctx.rcl_context_mtx.lock().unwrap().global_arguments,
437                    &mut rcl_params,
438                )
439                .ok()?;
440            }
441            assert!(!rcl_params.is_null());
442            assert_eq!(unsafe { (*rcl_params).num_nodes }, 1);
443            let rcl_node_params = unsafe { &(*(*rcl_params).params) };
444            assert_eq!(rcl_node_params.num_params, 1);
445            let rcl_variant = unsafe { &(*rcl_node_params.parameter_values) };
446            let param_value = unsafe { ParameterValue::from_rcl_variant(rcl_variant) };
447            assert_eq!(param_value, pair.1);
448            unsafe { rcl_yaml_node_struct_fini(rcl_params) };
449        }
450        Ok(())
451    }
452}