1#[cfg(test)]
7use crate::ParameterStorage;
8use crate::{
9 NodeKey, ParameterDescriptor, ParameterRange, ParameterServer, ParameterType,
10 SetParameterResult,
11};
12use heapless::String;
13
14pub use crate::ParameterVariant;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ParameterError {
20 TypeMismatch,
22 OutOfRange,
24 ReadOnly,
26 NotFound,
28 StorageFull,
30 StringConversion,
32 InvalidRange,
34 Rejected,
36}
37
38impl From<SetParameterResult> for ParameterError {
39 fn from(result: SetParameterResult) -> Self {
40 match result {
41 SetParameterResult::TypeMismatch => ParameterError::TypeMismatch,
42 SetParameterResult::OutOfRange => ParameterError::OutOfRange,
43 SetParameterResult::ReadOnly => ParameterError::ReadOnly,
44 SetParameterResult::NotFound => ParameterError::NotFound,
45 SetParameterResult::StorageFull => ParameterError::StorageFull,
46 SetParameterResult::InvalidRange => ParameterError::InvalidRange,
47 SetParameterResult::Rejected => ParameterError::Rejected,
48 SetParameterResult::Undeclared => ParameterError::NotFound,
51 SetParameterResult::Success => {
52 panic!("SetParameterResult::Success is not an error")
53 }
54 }
55 }
56}
57
58pub trait RangeConvertible: ParameterVariant + Clone {
60 fn to_parameter_range(
62 range: core::ops::RangeInclusive<Self>,
63 ) -> Result<ParameterRange, ParameterError>;
64}
65
66impl RangeConvertible for i64 {
67 fn to_parameter_range(
68 range: core::ops::RangeInclusive<Self>,
69 ) -> Result<ParameterRange, ParameterError> {
70 Ok(ParameterRange::Integer(crate::IntegerRange::new(
71 *range.start(),
72 *range.end(),
73 1, )))
75 }
76}
77
78impl RangeConvertible for f64 {
79 fn to_parameter_range(
80 range: core::ops::RangeInclusive<Self>,
81 ) -> Result<ParameterRange, ParameterError> {
82 Ok(ParameterRange::FloatingPoint(
83 crate::FloatingPointRange::new(
84 *range.start(),
85 *range.end(),
86 0.0, ),
88 ))
89 }
90}
91
92pub struct ParameterBuilder<'a, 's, T: ParameterVariant> {
94 server: &'a mut ParameterServer<'s>,
98 node: NodeKey,
102 name: &'a str,
104 default: Option<T>,
106 description: Option<&'a str>,
108 constraints: Option<&'a str>,
112 range: Option<ParameterRange>,
114 read_only: bool,
116 _phantom: core::marker::PhantomData<T>,
118}
119
120impl<'a, 's, T: ParameterVariant> ParameterBuilder<'a, 's, T> {
121 pub fn new(server: &'a mut ParameterServer<'s>, node: NodeKey, name: &'a str) -> Self {
123 Self {
124 server,
125 node,
126 name,
127 default: None,
128 description: None,
129 constraints: None,
130 range: None,
131 read_only: false,
132 _phantom: core::marker::PhantomData,
133 }
134 }
135
136 pub fn default(mut self, value: T) -> Self {
145 self.default = Some(value);
146 self
147 }
148
149 pub fn description(mut self, desc: &'a str) -> Self {
151 self.description = Some(desc);
152 self
153 }
154
155 pub fn constraints(mut self, text: &'a str) -> Self {
167 self.constraints = Some(text);
168 self
169 }
170
171 pub fn integer_range(mut self, min: i64, max: i64, step: i64) -> Result<Self, ParameterError> {
173 if T::parameter_type() != ParameterType::Integer {
174 return Err(ParameterError::InvalidRange);
175 }
176 let range = crate::IntegerRange::new(min, max, step);
177 if !range.is_valid() {
180 return Err(ParameterError::InvalidRange);
181 }
182 self.range = Some(ParameterRange::Integer(range));
183 Ok(self)
184 }
185
186 pub fn float_range(mut self, min: f64, max: f64, step: f64) -> Result<Self, ParameterError> {
188 if T::parameter_type() != ParameterType::Double {
189 return Err(ParameterError::InvalidRange);
190 }
191 let range = crate::FloatingPointRange::new(min, max, step);
192 if !range.is_valid() {
193 return Err(ParameterError::InvalidRange);
194 }
195 self.range = Some(ParameterRange::FloatingPoint(range));
196 Ok(self)
197 }
198
199 pub fn range(mut self, range: core::ops::RangeInclusive<T>) -> Result<Self, ParameterError>
207 where
208 T: RangeConvertible,
209 {
210 self.range = Some(T::to_parameter_range(range)?);
211 Ok(self)
212 }
213
214 fn build_descriptor(&self) -> Result<ParameterDescriptor, ParameterError> {
229 let mut descriptor = ParameterDescriptor::new(self.name, T::parameter_type())
230 .ok_or(ParameterError::StorageFull)?;
231 if let Some(desc) = self.description {
232 descriptor.set_description(desc);
233 }
234 if let Some(text) = self.constraints {
235 descriptor.set_additional_constraints(text);
236 }
237 descriptor.range = self.range.unwrap_or_default();
238 Ok(descriptor)
239 }
240
241 pub fn read_only(self) -> Result<ReadOnlyParameter<'a, 's, T>, ParameterError> {
246 let default_value = self
248 .default
249 .as_ref()
250 .ok_or(ParameterError::NotFound)?
251 .clone();
252
253 let mut descriptor = self.build_descriptor()?;
254 descriptor.read_only = true;
255
256 let param_value = default_value.to_parameter_value();
257
258 self.server
259 .declare_parameter(self.node, descriptor, Some(¶m_value))?;
260
261 Ok(ReadOnlyParameter::new(self.server, self.node, self.name))
262 }
263
264 pub fn mandatory(self) -> Result<MandatoryParameter<'a, 's, T>, ParameterError> {
268 let mut descriptor = self.build_descriptor()?;
269 descriptor.read_only = self.read_only;
270
271 let default_value = self.default.map(|v| v.to_parameter_value());
272
273 self.server
274 .declare_parameter(self.node, descriptor, default_value.as_ref())?;
275
276 Ok(MandatoryParameter::new(self.server, self.node, self.name))
277 }
278
279 pub fn optional(self) -> Result<OptionalParameter<'a, 's, T>, ParameterError> {
281 let mut descriptor = self.build_descriptor()?;
282 descriptor.read_only = self.read_only;
283
284 let default_value = self.default.map(|v| v.to_parameter_value());
285
286 self.server
287 .declare_parameter(self.node, descriptor, default_value.as_ref())?;
288
289 Ok(OptionalParameter::new(self.server, self.node, self.name))
290 }
291}
292
293pub struct MandatoryParameter<'a, 's, T: ParameterVariant> {
295 server: &'a mut ParameterServer<'s>,
296 node: NodeKey,
298 name: String<{ crate::MAX_PARAM_NAME_LEN }>,
299 _phantom: core::marker::PhantomData<T>,
300}
301
302impl<'a, 's, T: ParameterVariant> MandatoryParameter<'a, 's, T> {
303 pub(crate) fn new(server: &'a mut ParameterServer<'s>, node: NodeKey, name: &'a str) -> Self {
304 let mut n = String::new();
305 n.push_str(name).unwrap();
306 Self {
307 server,
308 node,
309 name: n,
310 _phantom: core::marker::PhantomData,
311 }
312 }
313
314 pub fn get(&self) -> T {
316 self.server
317 .get_parameter_value(self.node, self.name.as_str())
318 .and_then(|val| T::from_parameter_value(&val))
319 .expect("Mandatory parameter must have a value")
320 }
321
322 pub fn set(&mut self, value: T) -> Result<(), ParameterError> {
324 let converted = value
328 .try_to_parameter_value()
329 .map_err(|_| ParameterError::StringConversion)?;
330 let result = self
331 .server
332 .set_parameter_value(self.node, self.name.as_str(), converted);
333 if result == SetParameterResult::Success {
334 Ok(())
335 } else {
336 Err(ParameterError::from(result))
337 }
338 }
339}
340
341pub struct OptionalParameter<'a, 's, T: ParameterVariant> {
343 server: &'a mut ParameterServer<'s>,
344 node: NodeKey,
346 name: String<{ crate::MAX_PARAM_NAME_LEN }>,
347 _phantom: core::marker::PhantomData<T>,
348}
349
350impl<'a, 's, T: ParameterVariant> OptionalParameter<'a, 's, T> {
351 pub(crate) fn new(server: &'a mut ParameterServer<'s>, node: NodeKey, name: &'a str) -> Self {
352 let mut n = String::new();
353 n.push_str(name).unwrap();
354 Self {
355 server,
356 node,
357 name: n,
358 _phantom: core::marker::PhantomData,
359 }
360 }
361
362 pub fn get(&self) -> Option<T> {
364 self.server
365 .get_parameter_value(self.node, self.name.as_str())
366 .and_then(|val| T::from_parameter_value(&val))
367 }
368
369 pub fn set(&mut self, value: Option<T>) -> Result<(), ParameterError> {
371 let param_value = value.map(|v| v.to_parameter_value());
372 let result = self.server.set_parameter_value(
373 self.node,
374 self.name.as_str(),
375 param_value.unwrap_or_default(),
376 );
377 if result == SetParameterResult::Success {
378 Ok(())
379 } else {
380 Err(ParameterError::from(result))
381 }
382 }
383}
384
385pub struct ReadOnlyParameter<'a, 's, T: ParameterVariant> {
387 server: &'a mut ParameterServer<'s>,
388 node: NodeKey,
390 name: String<{ crate::MAX_PARAM_NAME_LEN }>,
391 _phantom: core::marker::PhantomData<T>,
392}
393
394impl<'a, 's, T: ParameterVariant> ReadOnlyParameter<'a, 's, T> {
395 pub(crate) fn new(server: &'a mut ParameterServer<'s>, node: NodeKey, name: &'a str) -> Self {
396 let mut n = String::new();
397 n.push_str(name).unwrap();
398 Self {
399 server,
400 node,
401 name: n,
402 _phantom: core::marker::PhantomData,
403 }
404 }
405
406 pub fn get(&self) -> T {
408 self.server
409 .get_parameter_value(self.node, self.name.as_str())
410 .and_then(|val| T::from_parameter_value(&val))
411 .expect("Read-only parameter must have a value")
412 }
413}
414
415pub struct UndeclaredParameters<'a, 's> {
424 server: &'a mut ParameterServer<'s>,
425 node: NodeKey,
428}
429
430impl<'a, 's> UndeclaredParameters<'a, 's> {
431 pub fn new(server: &'a mut ParameterServer<'s>, node: NodeKey) -> Self {
432 Self { server, node }
433 }
434
435 pub fn get_bool(&self, name: &str) -> Option<bool> {
437 self.server.get_bool(self.node, name)
438 }
439
440 pub fn get_integer(&self, name: &str) -> Option<i64> {
442 self.server.get_integer(self.node, name)
443 }
444
445 pub fn get_double(&self, name: &str) -> Option<f64> {
447 self.server.get_double(self.node, name)
448 }
449
450 pub fn get_string(&self, name: &str) -> Option<&str> {
452 self.server.get_string(self.node, name)
453 }
454
455 pub fn set_bool(&mut self, name: &str, value: bool) -> Result<(), ParameterError> {
467 self.set_value(name, crate::ParameterValue::Bool(value))
468 }
469
470 pub fn set_integer(&mut self, name: &str, value: i64) -> Result<(), ParameterError> {
472 self.set_value(name, crate::ParameterValue::Integer(value))
473 }
474
475 pub fn set_double(&mut self, name: &str, value: f64) -> Result<(), ParameterError> {
477 self.set_value(name, crate::ParameterValue::Double(value))
478 }
479
480 pub fn set_string(&mut self, name: &str, value: &str) -> Result<(), ParameterError> {
482 let value =
483 crate::ParameterValue::from_string(value).ok_or(ParameterError::StringConversion)?;
484 self.set_value(name, value)
485 }
486
487 fn set_value(
488 &mut self,
489 name: &str,
490 value: crate::ParameterValue,
491 ) -> Result<(), ParameterError> {
492 match self.server.set_or_declare(self.node, name, value) {
493 SetParameterResult::Success => Ok(()),
494 other => Err(other.into()),
495 }
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 const NODE: NodeKey = NodeKey::PRIMARY;
505
506 #[test]
507 fn test_mandatory_parameter_with_default() {
508 let mut storage: ParameterStorage = ParameterStorage::new();
509 let mut server = ParameterServer::new_in(storage.as_table());
510 let param = ParameterBuilder::<i64>::new(&mut server, NODE, "test_param")
511 .default(42)
512 .description("A test parameter")
513 .mandatory()
514 .expect("Failed to declare parameter");
515
516 assert_eq!(param.get(), 42);
517 }
518
519 #[test]
520 fn test_mandatory_parameter_set() {
521 let mut storage: ParameterStorage = ParameterStorage::new();
522 let mut server = ParameterServer::new_in(storage.as_table());
523 let mut param = ParameterBuilder::<i64>::new(&mut server, NODE, "test_param")
524 .default(0)
525 .mandatory()
526 .expect("Failed to declare parameter");
527
528 param.set(100).expect("Failed to set parameter");
529 assert_eq!(param.get(), 100);
530 }
531
532 #[test]
533 fn test_optional_parameter_none() {
534 let mut storage: ParameterStorage = ParameterStorage::new();
535 let mut server = ParameterServer::new_in(storage.as_table());
536 let param = ParameterBuilder::<i64>::new(&mut server, NODE, "test_param")
537 .optional()
538 .expect("Failed to declare parameter");
539
540 assert_eq!(param.get(), None);
541 }
542
543 #[test]
544 fn test_optional_parameter_with_default() {
545 let mut storage: ParameterStorage = ParameterStorage::new();
546 let mut server = ParameterServer::new_in(storage.as_table());
547 let param = ParameterBuilder::<i64>::new(&mut server, NODE, "test_param")
548 .default(42)
549 .optional()
550 .expect("Failed to declare parameter");
551
552 assert_eq!(param.get(), Some(42));
553 }
554
555 #[test]
556 fn test_optional_parameter_set() {
557 let mut storage: ParameterStorage = ParameterStorage::new();
558 let mut server = ParameterServer::new_in(storage.as_table());
559 let mut param = ParameterBuilder::<i64>::new(&mut server, NODE, "test_param")
560 .optional()
561 .expect("Failed to declare parameter");
562
563 param.set(Some(100)).expect("Failed to set parameter");
564 assert_eq!(param.get(), Some(100));
565 }
566
567 #[test]
568 fn test_read_only_parameter() {
569 let mut storage: ParameterStorage = ParameterStorage::new();
570 let mut server = ParameterServer::new_in(storage.as_table());
571 let param = ParameterBuilder::<i64>::new(&mut server, NODE, "readonly_param")
572 .default(42)
573 .description("A read-only parameter")
574 .read_only()
575 .expect("Failed to declare parameter");
576
577 assert_eq!(param.get(), 42);
578 }
579
580 #[test]
581 fn test_read_only_parameter_requires_default() {
582 let mut storage: ParameterStorage = ParameterStorage::new();
583 let mut server = ParameterServer::new_in(storage.as_table());
584 let result = ParameterBuilder::<i64>::new(&mut server, NODE, "readonly_param").read_only();
585
586 assert_eq!(result.err(), Some(ParameterError::NotFound));
587 }
588
589 #[test]
590 fn test_integer_range_constraint() {
591 let mut storage: ParameterStorage = ParameterStorage::new();
592 let mut server = ParameterServer::new_in(storage.as_table());
593 let mut param = ParameterBuilder::<i64>::new(&mut server, NODE, "ranged_param")
594 .default(50)
595 .integer_range(0, 100, 1)
596 .expect("Failed to set range")
597 .mandatory()
598 .expect("Failed to declare parameter");
599
600 param.set(75).expect("Failed to set valid value");
602 assert_eq!(param.get(), 75);
603 }
604
605 #[test]
606 fn test_float_range_constraint() {
607 let mut storage: ParameterStorage = ParameterStorage::new();
608 let mut server = ParameterServer::new_in(storage.as_table());
609 let mut param = ParameterBuilder::<f64>::new(&mut server, NODE, "float_param")
610 .default(0.5)
611 .float_range(0.0, 1.0, 0.0)
612 .expect("Failed to set range")
613 .mandatory()
614 .expect("Failed to declare parameter");
615
616 param.set(0.75).expect("Failed to set valid value");
618 assert_eq!(param.get(), 0.75);
619 }
620
621 #[test]
622 fn test_range_convenience_integer() {
623 let mut storage: ParameterStorage = ParameterStorage::new();
624 let mut server = ParameterServer::new_in(storage.as_table());
625 let param = ParameterBuilder::<i64>::new(&mut server, NODE, "ranged_param")
626 .default(50)
627 .range(0..=100)
628 .expect("Failed to set range")
629 .mandatory()
630 .expect("Failed to declare parameter");
631
632 assert_eq!(param.get(), 50);
633 }
634
635 #[test]
636 fn test_range_convenience_float() {
637 let mut storage: ParameterStorage = ParameterStorage::new();
638 let mut server = ParameterServer::new_in(storage.as_table());
639 let param = ParameterBuilder::<f64>::new(&mut server, NODE, "float_param")
640 .default(0.5)
641 .range(0.0..=1.0)
642 .expect("Failed to set range")
643 .mandatory()
644 .expect("Failed to declare parameter");
645
646 assert_eq!(param.get(), 0.5);
647 }
648
649 #[allow(clippy::absurd_extreme_comparisons, clippy::unnecessary_min_or_max)]
660 #[test]
661 fn builder_constraints_reach_the_descriptor_from_every_terminal() {
662 let text = "positive, hz";
663 let fits = text.len() <= crate::MAX_PARAM_CONSTRAINTS_LEN;
664 let stored = &text[..crate::MAX_PARAM_CONSTRAINTS_LEN.min(text.len())];
665
666 for which in 0..3u8 {
667 let mut storage: ParameterStorage = ParameterStorage::new();
668 let mut server = ParameterServer::new_in(storage.as_table());
669 {
670 let b = ParameterBuilder::<f64>::new(&mut server, NODE, "rate")
671 .default(1.0)
672 .description("publish rate")
673 .constraints(text);
674 match which {
675 0 => {
676 b.read_only().expect("read_only");
677 }
678 1 => {
679 b.mandatory().expect("mandatory");
680 }
681 _ => {
682 b.optional().expect("optional");
683 }
684 }
685 }
686 let desc = server.get_descriptor(NODE, "rate").expect("descriptor");
687 assert_eq!(desc.description.as_str(), "publish rate");
688 assert_eq!(
689 desc.additional_constraints.as_str(),
690 stored,
691 "terminal {which} lost the constraints text"
692 );
693 let mut reported = 0usize;
694 server.take_truncated_descriptions(|_, _| reported += 1);
695 assert_eq!(
696 reported,
697 usize::from(!fits),
698 "terminal {which}: a constraints string that did not fit went unreported"
699 );
700 }
701 }
702
703 #[test]
704 fn test_parameter_description() {
705 let mut storage: ParameterStorage = ParameterStorage::new();
706 let mut server = ParameterServer::new_in(storage.as_table());
707 let _param = ParameterBuilder::<i64>::new(&mut server, NODE, "described_param")
708 .default(42)
709 .description("This is a test description")
710 .mandatory()
711 .expect("Failed to declare parameter");
712
713 let desc = server.get_descriptor(NODE, "described_param");
715 assert!(desc.is_some());
716 assert_eq!(
717 desc.unwrap().description.as_str(),
718 "This is a test description"
719 );
720 }
721
722 #[test]
723 fn test_bool_parameter() {
724 let mut storage: ParameterStorage = ParameterStorage::new();
725 let mut server = ParameterServer::new_in(storage.as_table());
726 let mut param = ParameterBuilder::<bool>::new(&mut server, NODE, "bool_param")
727 .default(false)
728 .mandatory()
729 .expect("Failed to declare parameter");
730
731 assert!(!param.get());
732 param.set(true).expect("Failed to set parameter");
733 assert!(param.get());
734 }
735
736 #[test]
737 fn test_undeclared_parameters() {
738 use crate::ParameterValue;
739
740 let mut storage: ParameterStorage = ParameterStorage::new();
741 let mut server = ParameterServer::new_in(storage.as_table());
742
743 server.set_or_declare(NODE, "flag", ParameterValue::Bool(true));
745 server.set_or_declare(NODE, "count", ParameterValue::Integer(42));
746 server.set_or_declare(NODE, "ratio", ParameterValue::Double(0.5));
747
748 let undeclared = UndeclaredParameters::new(&mut server, NODE);
749
750 assert_eq!(undeclared.get_bool("flag"), Some(true));
751 assert_eq!(undeclared.get_integer("count"), Some(42));
752 assert_eq!(undeclared.get_double("ratio"), Some(0.5));
753 assert_eq!(undeclared.get_string("nonexistent"), None);
754 }
755}