1use heapless::{String, Vec};
7
8#[cfg(feature = "alloc")]
12use alloc::string::ToString;
13
14pub use crate::config::*;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
34pub struct NodeKey(u8);
35
36impl NodeKey {
37 pub const PRIMARY: NodeKey = NodeKey(0);
39
40 pub const fn new(index: u8) -> Self {
42 NodeKey(index)
43 }
44
45 pub const fn index(self) -> usize {
47 self.0 as usize
48 }
49
50 pub const fn raw(self) -> u8 {
52 self.0
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
65pub struct NodeFlags {
66 bits: [u64; 4],
67}
68
69impl NodeFlags {
70 pub const fn new() -> Self {
72 Self { bits: [0; 4] }
73 }
74
75 pub const fn get(&self, node: NodeKey) -> bool {
77 let raw = node.raw() as usize;
78 self.bits[raw >> 6] & (1u64 << (raw & 63)) != 0
79 }
80
81 pub fn set(&mut self, node: NodeKey, on: bool) {
83 let raw = node.raw() as usize;
84 let mask = 1u64 << (raw & 63);
85 if on {
86 self.bits[raw >> 6] |= mask;
87 } else {
88 self.bits[raw >> 6] &= !mask;
89 }
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
97#[repr(u8)]
98pub enum ParameterType {
99 #[default]
101 NotSet = 0,
102 Bool = 1,
104 Integer = 2,
106 Double = 3,
108 String = 4,
110 ByteArray = 5,
112 BoolArray = 6,
114 IntegerArray = 7,
116 DoubleArray = 8,
118 StringArray = 9,
120}
121
122#[derive(Debug, Clone, Default)]
128#[allow(clippy::large_enum_variant)]
129pub enum ParameterValue {
130 #[default]
132 NotSet,
133 Bool(bool),
135 Integer(i64),
137 Double(f64),
139 String(String<MAX_STRING_VALUE_LEN>),
141 ByteArray(Vec<u8, MAX_BYTE_ARRAY_LEN>),
143 BoolArray(Vec<bool, MAX_ARRAY_LEN>),
145 IntegerArray(Vec<i64, MAX_ARRAY_LEN>),
147 DoubleArray(Vec<f64, MAX_ARRAY_LEN>),
149 StringArray(Vec<String<MAX_STRING_VALUE_LEN>, MAX_ARRAY_LEN>),
151}
152
153impl ParameterValue {
154 pub fn param_type(&self) -> ParameterType {
156 match self {
157 Self::NotSet => ParameterType::NotSet,
158 Self::Bool(_) => ParameterType::Bool,
159 Self::Integer(_) => ParameterType::Integer,
160 Self::Double(_) => ParameterType::Double,
161 Self::String(_) => ParameterType::String,
162 Self::ByteArray(_) => ParameterType::ByteArray,
163 Self::BoolArray(_) => ParameterType::BoolArray,
164 Self::IntegerArray(_) => ParameterType::IntegerArray,
165 Self::DoubleArray(_) => ParameterType::DoubleArray,
166 Self::StringArray(_) => ParameterType::StringArray,
167 }
168 }
169
170 pub fn is_set(&self) -> bool {
172 !matches!(self, Self::NotSet)
173 }
174
175 pub fn as_bool(&self) -> Option<bool> {
177 match self {
178 Self::Bool(v) => Some(*v),
179 _ => None,
180 }
181 }
182
183 pub fn as_integer(&self) -> Option<i64> {
185 match self {
186 Self::Integer(v) => Some(*v),
187 _ => None,
188 }
189 }
190
191 pub fn as_double(&self) -> Option<f64> {
193 match self {
194 Self::Double(v) => Some(*v),
195 _ => None,
196 }
197 }
198
199 pub fn as_string(&self) -> Option<&str> {
201 match self {
202 Self::String(v) => Some(v.as_str()),
203 _ => None,
204 }
205 }
206
207 pub fn as_byte_array(&self) -> Option<&[u8]> {
209 match self {
210 Self::ByteArray(v) => Some(v.as_slice()),
211 _ => None,
212 }
213 }
214
215 pub fn as_bool_array(&self) -> Option<&[bool]> {
217 match self {
218 Self::BoolArray(v) => Some(v.as_slice()),
219 _ => None,
220 }
221 }
222
223 pub fn as_integer_array(&self) -> Option<&[i64]> {
225 match self {
226 Self::IntegerArray(v) => Some(v.as_slice()),
227 _ => None,
228 }
229 }
230
231 pub fn as_double_array(&self) -> Option<&[f64]> {
233 match self {
234 Self::DoubleArray(v) => Some(v.as_slice()),
235 _ => None,
236 }
237 }
238
239 pub fn from_bool(value: bool) -> Self {
241 Self::Bool(value)
242 }
243
244 pub fn from_integer(value: i64) -> Self {
246 Self::Integer(value)
247 }
248
249 pub fn from_double(value: f64) -> Self {
251 Self::Double(value)
252 }
253
254 pub fn from_string(value: &str) -> Option<Self> {
256 let mut s = String::new();
257 s.push_str(value).ok()?;
258 Some(Self::String(s))
259 }
260
261 pub fn from_bool_array(values: &[bool]) -> Option<Self> {
274 Some(Self::BoolArray(Vec::from_slice(values).ok()?))
275 }
276
277 pub fn from_integer_array(values: &[i64]) -> Option<Self> {
280 Some(Self::IntegerArray(Vec::from_slice(values).ok()?))
281 }
282
283 pub fn from_double_array(values: &[f64]) -> Option<Self> {
286 Some(Self::DoubleArray(Vec::from_slice(values).ok()?))
287 }
288}
289
290#[derive(Debug, Clone, Copy, Default)]
292pub struct FloatingPointRange {
293 pub min: f64,
295 pub max: f64,
297 pub step: f64,
299}
300
301impl FloatingPointRange {
302 pub const fn new(min: f64, max: f64, step: f64) -> Self {
304 Self { min, max, step }
305 }
306
307 pub fn is_valid(&self) -> bool {
313 self.min <= self.max && self.step >= 0.0 && !self.step.is_nan()
314 }
315
316 pub fn contains(&self, value: f64) -> bool {
325 if are_close(value, self.min) || are_close(value, self.max) {
326 return true;
327 }
328 if !(value >= self.min && value <= self.max) {
329 return false;
330 }
331 if self.step == 0.0 {
332 return true;
333 }
334 let nearest = round_to_nearest((value - self.min) / self.step) * self.step + self.min;
335 are_close(nearest, value)
336 }
337}
338
339#[inline]
343fn are_close(a: f64, b: f64) -> bool {
344 const ULP_TOL: f64 = 100.0;
345 (a - b).abs() <= f64::EPSILON * (a + b).abs() * ULP_TOL
346}
347
348#[inline]
353fn round_to_nearest(q: f64) -> f64 {
354 if !q.is_finite() {
355 return q;
356 }
357 let shifted = if q < 0.0 { q - 0.5 } else { q + 0.5 };
358 (shifted as i64) as f64
359}
360
361#[derive(Debug, Clone, Copy, Default)]
363pub struct IntegerRange {
364 pub min: i64,
366 pub max: i64,
368 pub step: i64,
370}
371
372impl IntegerRange {
373 pub const fn new(min: i64, max: i64, step: i64) -> Self {
375 Self { min, max, step }
376 }
377
378 pub fn is_valid(&self) -> bool {
381 self.min <= self.max && self.step >= 0
382 }
383
384 pub fn contains(&self, value: i64) -> bool {
392 if !(value >= self.min && value <= self.max) {
393 return false;
394 }
395 if value == self.max || self.step == 0 {
396 return true;
397 }
398 match value
399 .checked_sub(self.min)
400 .and_then(|d| d.checked_rem(self.step))
401 {
402 Some(rem) => rem == 0,
403 None => false,
406 }
407 }
408}
409
410#[derive(Debug, Clone, Copy, Default)]
412pub enum ParameterRange {
413 #[default]
415 None,
416 FloatingPoint(FloatingPointRange),
418 Integer(IntegerRange),
420}
421
422impl ParameterRange {
423 pub fn is_valid(&self) -> bool {
425 match self {
426 Self::None => true,
427 Self::FloatingPoint(r) => r.is_valid(),
428 Self::Integer(r) => r.is_valid(),
429 }
430 }
431
432 pub fn contains(&self, value: &ParameterValue) -> bool {
441 match (self, value) {
442 (Self::None, _) => true,
443 (Self::Integer(range), ParameterValue::Integer(v)) => range.contains(*v),
444 (Self::FloatingPoint(range), ParameterValue::Double(v)) => range.contains(*v),
445 _ => true,
446 }
447 }
448}
449
450#[derive(Debug, Clone)]
454pub struct ParameterDescriptor {
455 pub name: String<MAX_PARAM_NAME_LEN>,
457 pub param_type: ParameterType,
459 pub description: String<MAX_PARAM_DESCRIPTION_LEN>,
467 pub additional_constraints: String<MAX_PARAM_CONSTRAINTS_LEN>,
482 pub read_only: bool,
484 pub dynamic_typing: bool,
486 pub range: ParameterRange,
488 description_truncated: bool,
495}
496
497pub fn fit_description<const N: usize>(dst: &mut String<N>, text: &str) -> bool {
505 dst.clear();
506 let mut cut = text.len().min(N);
507 while !text.is_char_boundary(cut) {
508 cut -= 1;
509 }
510 let stored = dst.push_str(&text[..cut]).is_ok();
512 debug_assert!(stored, "a prefix of at most N bytes fits String<N>");
513 cut < text.len()
514}
515
516impl ParameterDescriptor {
517 pub fn new(name: &str, param_type: ParameterType) -> Option<Self> {
519 let mut n = String::new();
520 n.push_str(name).ok()?;
521 Some(Self {
522 name: n,
523 param_type,
524 description: String::new(),
525 additional_constraints: String::new(),
526 read_only: false,
527 dynamic_typing: false,
528 range: ParameterRange::None,
529 description_truncated: false,
530 })
531 }
532
533 pub fn with_description(mut self, desc: &str) -> Self {
537 self.set_description(desc);
538 self
539 }
540
541 pub fn set_description(&mut self, desc: &str) -> bool {
546 self.description_truncated = fit_description(&mut self.description, desc);
547 self.description_truncated
548 }
549
550 pub fn description_truncated(&self) -> bool {
552 self.description_truncated
553 }
554
555 pub fn set_additional_constraints(&mut self, text: &str) -> bool {
559 let cut = fit_description(&mut self.additional_constraints, text);
560 self.description_truncated |= cut;
566 cut
567 }
568
569 pub fn with_additional_constraints(mut self, text: &str) -> Self {
571 self.set_additional_constraints(text);
572 self
573 }
574
575 pub(crate) fn clear_description_truncated(&mut self) {
577 self.description_truncated = false;
578 }
579
580 pub fn with_read_only(mut self, read_only: bool) -> Self {
582 self.read_only = read_only;
583 self
584 }
585
586 pub fn with_dynamic_typing(mut self, dynamic: bool) -> Self {
588 self.dynamic_typing = dynamic;
589 self
590 }
591
592 pub fn with_integer_range(mut self, min: i64, max: i64, step: i64) -> Self {
594 self.range = ParameterRange::Integer(IntegerRange::new(min, max, step));
595 self
596 }
597
598 pub fn with_float_range(mut self, min: f64, max: f64, step: f64) -> Self {
600 self.range = ParameterRange::FloatingPoint(FloatingPointRange::new(min, max, step));
601 self
602 }
603
604 pub fn validate_range(&self, value: &ParameterValue) -> bool {
608 self.range.contains(value)
609 }
610}
611
612#[derive(Debug, Clone)]
614pub struct Parameter {
615 pub name: String<MAX_PARAM_NAME_LEN>,
617 pub value: ParameterValue,
619}
620
621impl Parameter {
622 pub fn new(name: &str, value: ParameterValue) -> Option<Self> {
624 let mut n = String::new();
625 n.push_str(name).ok()?;
626 Some(Self { name: n, value })
627 }
628
629 pub fn bool(name: &str, value: bool) -> Option<Self> {
631 Self::new(name, ParameterValue::Bool(value))
632 }
633
634 pub fn integer(name: &str, value: i64) -> Option<Self> {
636 Self::new(name, ParameterValue::Integer(value))
637 }
638
639 pub fn double(name: &str, value: f64) -> Option<Self> {
641 Self::new(name, ParameterValue::Double(value))
642 }
643
644 pub fn string(name: &str, value: &str) -> Option<Self> {
646 Self::new(name, ParameterValue::from_string(value)?)
647 }
648
649 pub fn param_type(&self) -> ParameterType {
651 self.value.param_type()
652 }
653
654 pub fn name(&self) -> &str {
656 &self.name
657 }
658}
659
660#[derive(Debug, Clone, Copy, PartialEq, Eq)]
662pub enum SetParameterResult {
663 Success,
665 ReadOnly,
667 TypeMismatch,
669 OutOfRange,
671 NotFound,
673 StorageFull,
675 Undeclared,
683 InvalidRange,
687 Rejected,
695}
696
697impl SetParameterResult {
698 pub fn is_success(&self) -> bool {
700 matches!(self, Self::Success)
701 }
702}
703
704#[derive(Debug, Clone, Copy, PartialEq, Eq)]
710pub struct CapacityExceeded;
711
712pub trait ParameterVariant: Clone {
717 fn to_parameter_value(&self) -> ParameterValue;
719
720 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
732 Ok(self.to_parameter_value())
733 }
734
735 fn from_parameter_value(value: &ParameterValue) -> Option<Self>;
737
738 fn parameter_type() -> ParameterType;
740}
741
742impl ParameterVariant for bool {
745 fn to_parameter_value(&self) -> ParameterValue {
746 ParameterValue::Bool(*self)
747 }
748
749 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
750 value.as_bool()
751 }
752
753 fn parameter_type() -> ParameterType {
754 ParameterType::Bool
755 }
756}
757
758impl ParameterVariant for i64 {
759 fn to_parameter_value(&self) -> ParameterValue {
760 ParameterValue::Integer(*self)
761 }
762
763 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
764 value.as_integer()
765 }
766
767 fn parameter_type() -> ParameterType {
768 ParameterType::Integer
769 }
770}
771
772impl ParameterVariant for f64 {
773 fn to_parameter_value(&self) -> ParameterValue {
774 ParameterValue::Double(*self)
775 }
776
777 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
778 value.as_double()
779 }
780
781 fn parameter_type() -> ParameterType {
782 ParameterType::Double
783 }
784}
785
786impl ParameterVariant for String<MAX_STRING_VALUE_LEN> {
796 fn to_parameter_value(&self) -> ParameterValue {
797 ParameterValue::String(self.clone())
798 }
799
800 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
801 if let ParameterValue::String(s) = value {
802 Some(s.clone())
803 } else {
804 None
805 }
806 }
807
808 fn parameter_type() -> ParameterType {
809 ParameterType::String
810 }
811}
812
813#[cfg(feature = "alloc")]
815impl ParameterVariant for alloc::string::String {
816 fn to_parameter_value(&self) -> ParameterValue {
817 ParameterValue::from_string(self.as_str()).unwrap_or_default()
818 }
819
820 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
821 ParameterValue::from_string(self.as_str()).ok_or(CapacityExceeded)
822 }
823
824 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
825 value.as_string().map(|s| s.to_string())
826 }
827
828 fn parameter_type() -> ParameterType {
829 ParameterType::String
830 }
831}
832
833#[cfg(feature = "alloc")]
835impl ParameterVariant for alloc::vec::Vec<i64> {
836 fn to_parameter_value(&self) -> ParameterValue {
837 ParameterValue::IntegerArray(Vec::from_slice(self.as_slice()).unwrap_or_default())
838 }
839
840 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
841 Vec::from_slice(self.as_slice())
842 .map(ParameterValue::IntegerArray)
843 .map_err(|_| CapacityExceeded)
844 }
845
846 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
847 value.as_integer_array().map(|v| v.to_vec())
848 }
849
850 fn parameter_type() -> ParameterType {
851 ParameterType::IntegerArray
852 }
853}
854
855#[cfg(feature = "alloc")]
857impl ParameterVariant for alloc::vec::Vec<f64> {
858 fn to_parameter_value(&self) -> ParameterValue {
859 ParameterValue::DoubleArray(Vec::from_slice(self.as_slice()).unwrap_or_default())
860 }
861
862 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
863 Vec::from_slice(self.as_slice())
864 .map(ParameterValue::DoubleArray)
865 .map_err(|_| CapacityExceeded)
866 }
867
868 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
869 value.as_double_array().map(|v| v.to_vec())
870 }
871
872 fn parameter_type() -> ParameterType {
873 ParameterType::DoubleArray
874 }
875}
876
877#[cfg(feature = "alloc")]
879impl ParameterVariant for alloc::vec::Vec<bool> {
880 fn to_parameter_value(&self) -> ParameterValue {
881 ParameterValue::BoolArray(Vec::from_slice(self.as_slice()).unwrap_or_default())
882 }
883
884 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
885 Vec::from_slice(self.as_slice())
886 .map(ParameterValue::BoolArray)
887 .map_err(|_| CapacityExceeded)
888 }
889
890 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
891 value.as_bool_array().map(|v| v.to_vec())
892 }
893
894 fn parameter_type() -> ParameterType {
895 ParameterType::BoolArray
896 }
897}
898
899#[cfg(feature = "alloc")]
901impl ParameterVariant for alloc::vec::Vec<alloc::string::String> {
902 fn to_parameter_value(&self) -> ParameterValue {
903 let mut vec = Vec::new();
904 for s in self {
905 let mut h_string = String::new();
906 if h_string.push_str(s.as_str()).is_ok() {
907 let _ = vec.push(h_string);
908 }
909 }
910 ParameterValue::StringArray(vec)
911 }
912
913 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
914 let mut vec = Vec::new();
917 for s in self {
918 let mut h_string = String::new();
919 h_string
920 .push_str(s.as_str())
921 .map_err(|_| CapacityExceeded)?;
922 vec.push(h_string).map_err(|_| CapacityExceeded)?;
923 }
924 Ok(ParameterValue::StringArray(vec))
925 }
926
927 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
928 if let ParameterValue::StringArray(v) = value {
929 Some(v.iter().map(|s| s.as_str().to_string()).collect())
930 } else {
931 None
932 }
933 }
934
935 fn parameter_type() -> ParameterType {
936 ParameterType::StringArray
937 }
938}
939
940#[cfg(test)]
941mod tests {
942 use super::*;
943
944 #[test]
947 fn integer_step_accepts_lattice_points_and_rejects_the_rest() {
948 let r = IntegerRange::new(10, 20, 5);
949 assert!(r.contains(10));
950 assert!(r.contains(15));
951 assert!(r.contains(20));
952 assert!(!r.contains(11));
955 assert!(!r.contains(14));
956 assert!(!r.contains(19));
957 assert!(!r.contains(5));
959 assert!(!r.contains(25));
960 }
961
962 #[test]
963 fn integer_upper_bound_is_accepted_even_off_lattice() {
964 let r = IntegerRange::new(0, 7, 3);
966 assert!(r.contains(6));
967 assert!(!r.contains(5));
968 assert!(r.contains(7));
969 }
970
971 #[test]
972 fn integer_step_zero_means_unconstrained() {
973 let r = IntegerRange::new(0, 100, 0);
974 for v in [0, 1, 37, 99, 100] {
975 assert!(r.contains(v), "{v}");
976 }
977 assert!(!r.contains(101));
978 }
979
980 #[test]
981 fn integer_step_does_not_overflow_at_the_extremes() {
982 let r = IntegerRange::new(i64::MIN, i64::MAX, 2);
983 let _ = r.contains(1);
986 let _ = r.contains(i64::MAX - 1);
987 assert!(r.contains(i64::MIN));
988 assert!(r.contains(i64::MAX));
989 }
990
991 #[test]
992 fn float_step_accepts_lattice_points_within_tolerance() {
993 let r = FloatingPointRange::new(0.0, 1.0, 0.1);
994 for v in [0.0, 0.1, 0.3, 0.7, 1.0] {
997 assert!(r.contains(v), "{v}");
998 }
999 assert!(r.contains(0.1 + 0.2));
1000 assert!(!r.contains(0.05));
1002 assert!(!r.contains(0.35));
1003 assert!(!r.contains(0.999));
1004 assert!(!r.contains(-0.1));
1006 assert!(!r.contains(1.1));
1007 }
1008
1009 #[test]
1010 fn float_endpoints_are_accepted_even_off_lattice() {
1011 let r = FloatingPointRange::new(0.0, 1.0, 0.3);
1012 assert!(r.contains(0.9));
1013 assert!(!r.contains(0.95));
1014 assert!(r.contains(1.0));
1015 }
1016
1017 #[test]
1018 fn float_step_zero_means_unconstrained() {
1019 let r = FloatingPointRange::new(0.0, 10.0, 0.0);
1020 for v in [0.0, 0.001, 3.25, 9.999, 10.0] {
1021 assert!(r.contains(v), "{v}");
1022 }
1023 assert!(!r.contains(10.001));
1024 }
1025
1026 #[test]
1027 fn float_step_with_a_nonzero_min_is_relative_to_min() {
1028 let r = FloatingPointRange::new(0.5, 2.5, 0.5);
1029 assert!(r.contains(1.5));
1030 assert!(!r.contains(1.25));
1031 assert!(r.contains(0.5));
1032 }
1033
1034 #[test]
1035 fn range_validity_is_min_le_max_and_step_ge_zero() {
1036 assert!(IntegerRange::new(0, 10, 0).is_valid());
1037 assert!(IntegerRange::new(0, 10, 3).is_valid());
1038 assert!(IntegerRange::new(5, 5, 1).is_valid());
1039 assert!(!IntegerRange::new(10, 0, 1).is_valid());
1040 assert!(!IntegerRange::new(0, 10, -1).is_valid());
1041 assert!(FloatingPointRange::new(0.0, 1.0, 0.0).is_valid());
1042 assert!(!FloatingPointRange::new(1.0, 0.0, 0.1).is_valid());
1043 assert!(!FloatingPointRange::new(0.0, 1.0, -0.1).is_valid());
1044 assert!(!FloatingPointRange::new(0.0, 1.0, f64::NAN).is_valid());
1045 assert!(ParameterRange::None.is_valid());
1046 }
1047
1048 #[test]
1049 fn descriptor_validate_range_enforces_step_on_both_arms() {
1050 let int_desc = ParameterDescriptor::new("i", ParameterType::Integer)
1051 .unwrap()
1052 .with_integer_range(0, 100, 10);
1053 assert!(int_desc.validate_range(&ParameterValue::Integer(30)));
1054 assert!(!int_desc.validate_range(&ParameterValue::Integer(31)));
1055 assert!(int_desc.validate_range(&ParameterValue::Double(31.0)));
1057
1058 let f_desc = ParameterDescriptor::new("f", ParameterType::Double)
1059 .unwrap()
1060 .with_float_range(0.0, 10.0, 0.5);
1061 assert!(f_desc.validate_range(&ParameterValue::Double(2.5)));
1062 assert!(!f_desc.validate_range(&ParameterValue::Double(2.7)));
1063 assert!(f_desc.validate_range(&ParameterValue::Integer(3)));
1064 }
1065
1066 #[test]
1067 #[allow(clippy::approx_constant)]
1068 fn test_parameter_value_types() {
1069 let bool_val = ParameterValue::Bool(true);
1070 assert_eq!(bool_val.param_type(), ParameterType::Bool);
1071 assert_eq!(bool_val.as_bool(), Some(true));
1072
1073 let int_val = ParameterValue::Integer(42);
1074 assert_eq!(int_val.param_type(), ParameterType::Integer);
1075 assert_eq!(int_val.as_integer(), Some(42));
1076
1077 let double_val = ParameterValue::Double(3.14);
1078 assert_eq!(double_val.param_type(), ParameterType::Double);
1079 assert_eq!(double_val.as_double(), Some(3.14));
1080 }
1081
1082 #[test]
1083 fn test_parameter_value_string() {
1084 let string_val = ParameterValue::from_string("hello").unwrap();
1085 assert_eq!(string_val.param_type(), ParameterType::String);
1086 assert_eq!(string_val.as_string(), Some("hello"));
1087 }
1088
1089 #[test]
1090 fn test_parameter_creation() {
1091 let param = Parameter::bool("my_param", true).unwrap();
1092 assert_eq!(param.name(), "my_param");
1093 assert_eq!(param.param_type(), ParameterType::Bool);
1094 assert_eq!(param.value.as_bool(), Some(true));
1095 }
1096
1097 #[test]
1098 fn test_parameter_descriptor() {
1099 let desc = ParameterDescriptor::new("speed", ParameterType::Double)
1100 .unwrap()
1101 .with_description("Maximum speed in m/s")
1102 .with_float_range(0.0, 10.0, 0.1);
1103
1104 assert_eq!(desc.name.as_str(), "speed");
1105 assert_eq!(desc.param_type, ParameterType::Double);
1106 assert!(!desc.read_only);
1107
1108 assert!(desc.validate_range(&ParameterValue::Double(5.0)));
1110 assert!(!desc.validate_range(&ParameterValue::Double(15.0)));
1111 }
1112
1113 #[test]
1114 fn test_integer_range() {
1115 let range = IntegerRange::new(0, 100, 1);
1116 assert!(range.contains(50));
1117 assert!(range.contains(0));
1118 assert!(range.contains(100));
1119 assert!(!range.contains(-1));
1120 assert!(!range.contains(101));
1121 }
1122
1123 #[test]
1124 fn test_set_result() {
1125 assert!(SetParameterResult::Success.is_success());
1126 assert!(!SetParameterResult::ReadOnly.is_success());
1127 }
1128}
1129
1130#[cfg(test)]
1135mod ghost_checks {
1136 use super::*;
1137 use nros_ghost_types::ParameterValueGhost;
1138
1139 fn ghost_from_value(v: &ParameterValue) -> ParameterValueGhost {
1142 match v {
1143 ParameterValue::NotSet => ParameterValueGhost::NotSet,
1144 ParameterValue::Bool(b) => ParameterValueGhost::Bool(*b),
1145 ParameterValue::Integer(i) => ParameterValueGhost::Integer(*i),
1146 ParameterValue::Double(_) => ParameterValueGhost::Double,
1147 ParameterValue::String(_) => ParameterValueGhost::String,
1148 ParameterValue::ByteArray(_) => ParameterValueGhost::ByteArray,
1149 ParameterValue::BoolArray(_) => ParameterValueGhost::BoolArray,
1150 ParameterValue::IntegerArray(_) => ParameterValueGhost::IntegerArray,
1151 ParameterValue::DoubleArray(_) => ParameterValueGhost::DoubleArray,
1152 ParameterValue::StringArray(_) => ParameterValueGhost::StringArray,
1153 }
1154 }
1155
1156 #[test]
1157 fn ghost_variant_coverage() {
1158 let _ = ghost_from_value(&ParameterValue::NotSet);
1160 let _ = ghost_from_value(&ParameterValue::Bool(true));
1161 let _ = ghost_from_value(&ParameterValue::Integer(0));
1162 let _ = ghost_from_value(&ParameterValue::Double(0.0));
1163 let _ = ghost_from_value(&ParameterValue::String(String::new()));
1164 let _ = ghost_from_value(&ParameterValue::ByteArray(Vec::new()));
1165 let _ = ghost_from_value(&ParameterValue::BoolArray(Vec::new()));
1166 let _ = ghost_from_value(&ParameterValue::IntegerArray(Vec::new()));
1167 let _ = ghost_from_value(&ParameterValue::DoubleArray(Vec::new()));
1168 let _ = ghost_from_value(&ParameterValue::StringArray(Vec::new()));
1169 }
1170
1171 #[test]
1172 fn ghost_bool_preserves() {
1173 let ghost = ghost_from_value(&ParameterValue::Bool(true));
1174 assert!(matches!(ghost, ParameterValueGhost::Bool(true)));
1175 }
1176
1177 #[test]
1178 fn ghost_integer_preserves() {
1179 let ghost = ghost_from_value(&ParameterValue::Integer(42));
1180 assert!(matches!(ghost, ParameterValueGhost::Integer(42)));
1181 }
1182}
1183
1184#[cfg(kani)]
1189mod verification {
1190 use super::*;
1191
1192 #[kani::proof]
1195 fn parameter_i64_roundtrip() {
1196 let val: i64 = kani::any();
1197 let pv = ParameterValue::from_integer(val);
1198 assert_eq!(pv.as_integer(), Some(val));
1199 assert_eq!(pv.param_type(), ParameterType::Integer);
1200 assert!(pv.is_set());
1201 }
1202
1203 #[kani::proof]
1204 fn parameter_bool_roundtrip() {
1205 let val: bool = kani::any();
1206 let pv = ParameterValue::from_bool(val);
1207 assert_eq!(pv.as_bool(), Some(val));
1208 assert_eq!(pv.param_type(), ParameterType::Bool);
1209 assert!(pv.is_set());
1210 }
1211
1212 #[kani::proof]
1213 fn parameter_double_roundtrip() {
1214 let val: f64 = kani::any();
1215 let pv = ParameterValue::from_double(val);
1216 let result = pv.as_double();
1217 assert!(result.is_some());
1218 assert_eq!(result.unwrap().to_bits(), val.to_bits());
1219 assert_eq!(pv.param_type(), ParameterType::Double);
1220 }
1221
1222 #[kani::proof]
1223 fn parameter_not_set_default() {
1224 let pv = ParameterValue::default();
1225 assert!(!pv.is_set());
1226 assert_eq!(pv.param_type(), ParameterType::NotSet);
1227 assert!(pv.as_bool().is_none());
1228 assert!(pv.as_integer().is_none());
1229 assert!(pv.as_double().is_none());
1230 assert!(pv.as_string().is_none());
1231 }
1232
1233 #[kani::proof]
1236 fn parameter_type_mismatch_bool() {
1237 let val: i64 = kani::any();
1238 let pv = ParameterValue::from_integer(val);
1239 assert!(pv.as_bool().is_none());
1240 assert!(pv.as_double().is_none());
1241 assert!(pv.as_string().is_none());
1242 }
1243
1244 #[kani::proof]
1245 fn parameter_type_mismatch_integer() {
1246 let val: bool = kani::any();
1247 let pv = ParameterValue::from_bool(val);
1248 assert!(pv.as_integer().is_none());
1249 assert!(pv.as_double().is_none());
1250 assert!(pv.as_string().is_none());
1251 }
1252
1253 #[kani::proof]
1256 fn integer_range_contains_bounds() {
1257 let min: i64 = kani::any();
1258 let max: i64 = kani::any();
1259 kani::assume(min >= -1_000_000 && min <= 1_000_000);
1261 kani::assume(max >= -1_000_000 && max <= 1_000_000);
1262 kani::assume(min <= max);
1263 let range = IntegerRange::new(min, max, 1);
1264 assert!(range.contains(min));
1266 assert!(range.contains(max));
1267 }
1268
1269 #[kani::proof]
1270 fn integer_range_outside_bounds() {
1271 let min: i64 = kani::any();
1272 let max: i64 = kani::any();
1273 kani::assume(min <= max);
1274 kani::assume(min > i64::MIN); kani::assume(max < i64::MAX); let range = IntegerRange::new(min, max, 1);
1277 assert!(!range.contains(min - 1));
1278 assert!(!range.contains(max + 1));
1279 }
1280
1281 #[kani::proof]
1284 fn float_range_contains_bounds() {
1285 let min: f64 = kani::any();
1286 let max: f64 = kani::any();
1287 kani::assume(!min.is_nan() && !max.is_nan());
1288 kani::assume(min <= max);
1289 let range = FloatingPointRange::new(min, max, 0.0);
1290 assert!(range.contains(min));
1291 assert!(range.contains(max));
1292 }
1293
1294 #[kani::proof]
1297 fn set_result_success_only() {
1298 assert!(SetParameterResult::Success.is_success());
1300 assert!(!SetParameterResult::ReadOnly.is_success());
1301 assert!(!SetParameterResult::TypeMismatch.is_success());
1302 assert!(!SetParameterResult::OutOfRange.is_success());
1303 assert!(!SetParameterResult::NotFound.is_success());
1304 assert!(!SetParameterResult::StorageFull.is_success());
1305 }
1306}
1307
1308#[cfg(all(test, feature = "alloc"))]
1311mod issue_0323_tests {
1312 use super::*;
1313
1314 #[test]
1318 fn oversize_string_is_rejected_not_silently_notset() {
1319 let long = "x".repeat(MAX_STRING_VALUE_LEN + 1);
1320 assert!(
1321 matches!(long.to_parameter_value(), ParameterValue::NotSet),
1322 "documenting the legacy infallible behaviour"
1323 );
1324 assert!(
1325 long.try_to_parameter_value().is_err(),
1326 "the fallible path must reject it"
1327 );
1328 }
1329
1330 #[test]
1332 fn oversize_integer_array_is_rejected_not_silently_empty() {
1333 let big: alloc::vec::Vec<i64> = (0..(MAX_ARRAY_LEN as i64 + 1)).collect();
1334 match big.to_parameter_value() {
1335 ParameterValue::IntegerArray(v) => {
1336 assert!(v.is_empty(), "documenting the legacy empty-array behaviour")
1337 }
1338 other => panic!("unexpected {other:?}"),
1339 }
1340 assert!(big.try_to_parameter_value().is_err());
1341 }
1342
1343 #[test]
1345 fn oversize_string_array_element_is_rejected_not_skipped() {
1346 let big: alloc::vec::Vec<alloc::string::String> =
1347 (0..(MAX_ARRAY_LEN + 1)).map(|i| i.to_string()).collect();
1348 assert!(big.try_to_parameter_value().is_err());
1349 }
1350
1351 #[test]
1355 fn a_description_that_fits_is_stored_whole() {
1356 let mut s: String<16> = String::new();
1357 assert!(!fit_description(&mut s, "wheel speed"));
1358 assert_eq!(s.as_str(), "wheel speed");
1359 assert!(!fit_description(&mut s, "0123456789abcdef"));
1361 assert_eq!(s.len(), 16);
1362 }
1363
1364 #[test]
1367 fn a_long_description_keeps_its_prefix_and_reports_the_cut() {
1368 let mut s: String<8> = String::new();
1369 assert!(fit_description(&mut s, "0123456789"));
1370 assert_eq!(s.as_str(), "01234567");
1371 }
1372
1373 #[test]
1377 fn truncation_backs_off_to_a_character_boundary() {
1378 let mut s: String<5> = String::new();
1379 assert!(fit_description(&mut s, "abcd\u{e9}f"));
1381 assert_eq!(s.as_str(), "abcd");
1382 let mut s: String<6> = String::new();
1383 assert!(fit_description(&mut s, "ab\u{1f4cf}z"));
1385 assert_eq!(s.as_str(), "ab\u{1f4cf}");
1386 let mut s: String<5> = String::new();
1387 assert!(fit_description(&mut s, "ab\u{1f4cf}z"));
1388 assert_eq!(s.as_str(), "ab");
1389 }
1390
1391 #[test]
1394 fn zero_capacity_stores_no_description_and_reports_it() {
1395 let mut s: String<0> = String::new();
1396 assert!(fit_description(&mut s, "anything"));
1397 assert!(s.is_empty());
1398 assert!(
1399 !fit_description(&mut s, ""),
1400 "an empty description loses nothing, so there is nothing to report"
1401 );
1402 }
1403
1404 #[test]
1406 fn the_descriptor_remembers_a_truncated_description() {
1407 let long = "d".repeat(MAX_PARAM_DESCRIPTION_LEN + 3);
1408 let d = ParameterDescriptor::new("p", ParameterType::Bool)
1409 .unwrap()
1410 .with_description(&long);
1411 assert_eq!(d.description.len(), MAX_PARAM_DESCRIPTION_LEN);
1412 assert!(d.description_truncated());
1413 let mut d = d;
1414 assert!(!d.set_description(""));
1415 assert!(!d.description_truncated());
1416 }
1417}