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 first(&self) -> Option<NodeKey> {
83 for (word, bits) in self.bits.iter().enumerate() {
84 if *bits != 0 {
85 return Some(NodeKey::new(
86 (word * 64 + bits.trailing_zeros() as usize) as u8,
87 ));
88 }
89 }
90 None
91 }
92
93 pub fn set(&mut self, node: NodeKey, on: bool) {
95 let raw = node.raw() as usize;
96 let mask = 1u64 << (raw & 63);
97 if on {
98 self.bits[raw >> 6] |= mask;
99 } else {
100 self.bits[raw >> 6] &= !mask;
101 }
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
109#[repr(u8)]
110pub enum ParameterType {
111 #[default]
113 NotSet = 0,
114 Bool = 1,
116 Integer = 2,
118 Double = 3,
120 String = 4,
122 ByteArray = 5,
124 BoolArray = 6,
126 IntegerArray = 7,
128 DoubleArray = 8,
130 StringArray = 9,
132}
133
134#[derive(Debug, Clone, Default)]
140#[allow(clippy::large_enum_variant)]
141pub enum ParameterValue {
142 #[default]
144 NotSet,
145 Bool(bool),
147 Integer(i64),
149 Double(f64),
151 String(String<MAX_STRING_VALUE_LEN>),
153 ByteArray(Vec<u8, MAX_BYTE_ARRAY_LEN>),
155 BoolArray(Vec<bool, MAX_ARRAY_LEN>),
157 IntegerArray(Vec<i64, MAX_ARRAY_LEN>),
159 DoubleArray(Vec<f64, MAX_ARRAY_LEN>),
161 StringArray(Vec<String<MAX_STRING_VALUE_LEN>, MAX_ARRAY_LEN>),
163}
164
165impl ParameterValue {
166 pub fn assign_from(&mut self, src: &ParameterValue) {
175 macro_rules! fill {
176 ($variant:ident, $dst:ident, $body:expr) => {{
177 if !matches!(self, Self::$variant(_)) {
178 *self = Self::$variant(Default::default());
179 }
180 if let Self::$variant($dst) = self {
181 $dst.clear();
182 $body;
183 }
184 }};
185 }
186 match src {
187 Self::NotSet => *self = Self::NotSet,
188 Self::Bool(v) => *self = Self::Bool(*v),
189 Self::Integer(v) => *self = Self::Integer(*v),
190 Self::Double(v) => *self = Self::Double(*v),
191 Self::String(v) => fill!(String, d, {
192 let _ = d.push_str(v);
193 }),
194 Self::ByteArray(v) => fill!(ByteArray, d, {
195 let _ = d.extend_from_slice(v);
196 }),
197 Self::BoolArray(v) => fill!(BoolArray, d, {
198 let _ = d.extend_from_slice(v);
199 }),
200 Self::IntegerArray(v) => fill!(IntegerArray, d, {
201 let _ = d.extend_from_slice(v);
202 }),
203 Self::DoubleArray(v) => fill!(DoubleArray, d, {
204 let _ = d.extend_from_slice(v);
205 }),
206 Self::StringArray(v) => fill!(StringArray, d, {
207 for e in v {
208 let mut s = String::new();
209 let _ = s.push_str(e);
210 let _ = d.push(s);
211 }
212 }),
213 }
214 }
215
216 pub fn param_type(&self) -> ParameterType {
218 match self {
219 Self::NotSet => ParameterType::NotSet,
220 Self::Bool(_) => ParameterType::Bool,
221 Self::Integer(_) => ParameterType::Integer,
222 Self::Double(_) => ParameterType::Double,
223 Self::String(_) => ParameterType::String,
224 Self::ByteArray(_) => ParameterType::ByteArray,
225 Self::BoolArray(_) => ParameterType::BoolArray,
226 Self::IntegerArray(_) => ParameterType::IntegerArray,
227 Self::DoubleArray(_) => ParameterType::DoubleArray,
228 Self::StringArray(_) => ParameterType::StringArray,
229 }
230 }
231
232 pub fn is_set(&self) -> bool {
234 !matches!(self, Self::NotSet)
235 }
236
237 pub fn as_bool(&self) -> Option<bool> {
239 match self {
240 Self::Bool(v) => Some(*v),
241 _ => None,
242 }
243 }
244
245 pub fn as_integer(&self) -> Option<i64> {
247 match self {
248 Self::Integer(v) => Some(*v),
249 _ => None,
250 }
251 }
252
253 pub fn as_double(&self) -> Option<f64> {
255 match self {
256 Self::Double(v) => Some(*v),
257 _ => None,
258 }
259 }
260
261 pub fn as_string(&self) -> Option<&str> {
263 match self {
264 Self::String(v) => Some(v.as_str()),
265 _ => None,
266 }
267 }
268
269 pub fn as_byte_array(&self) -> Option<&[u8]> {
271 match self {
272 Self::ByteArray(v) => Some(v.as_slice()),
273 _ => None,
274 }
275 }
276
277 pub fn as_bool_array(&self) -> Option<&[bool]> {
279 match self {
280 Self::BoolArray(v) => Some(v.as_slice()),
281 _ => None,
282 }
283 }
284
285 pub fn as_integer_array(&self) -> Option<&[i64]> {
287 match self {
288 Self::IntegerArray(v) => Some(v.as_slice()),
289 _ => None,
290 }
291 }
292
293 pub fn as_double_array(&self) -> Option<&[f64]> {
295 match self {
296 Self::DoubleArray(v) => Some(v.as_slice()),
297 _ => None,
298 }
299 }
300
301 pub fn from_bool(value: bool) -> Self {
303 Self::Bool(value)
304 }
305
306 pub fn from_integer(value: i64) -> Self {
308 Self::Integer(value)
309 }
310
311 pub fn from_double(value: f64) -> Self {
313 Self::Double(value)
314 }
315
316 pub fn from_string(value: &str) -> Option<Self> {
318 let mut s = String::new();
319 s.push_str(value).ok()?;
320 Some(Self::String(s))
321 }
322
323 pub fn from_bool_array(values: &[bool]) -> Option<Self> {
336 Some(Self::BoolArray(Vec::from_slice(values).ok()?))
337 }
338
339 pub fn from_integer_array(values: &[i64]) -> Option<Self> {
342 Some(Self::IntegerArray(Vec::from_slice(values).ok()?))
343 }
344
345 pub fn from_double_array(values: &[f64]) -> Option<Self> {
348 Some(Self::DoubleArray(Vec::from_slice(values).ok()?))
349 }
350
351 pub fn from_byte_array(values: &[u8]) -> Option<Self> {
354 Some(Self::ByteArray(Vec::from_slice(values).ok()?))
355 }
356
357 pub fn assign_string(&mut self, value: &str) -> bool {
364 if !matches!(self, Self::String(_)) {
365 *self = Self::String(String::new());
366 }
367 match self {
368 Self::String(s) => {
369 s.clear();
370 s.push_str(value).is_ok()
371 }
372 _ => false,
373 }
374 }
375
376 pub fn assign_bool_array(&mut self, values: &[bool]) -> bool {
379 assign_slice!(self, BoolArray, values)
380 }
381
382 pub fn assign_integer_array(&mut self, values: &[i64]) -> bool {
385 assign_slice!(self, IntegerArray, values)
386 }
387
388 pub fn assign_double_array(&mut self, values: &[f64]) -> bool {
391 assign_slice!(self, DoubleArray, values)
392 }
393
394 pub fn assign_byte_array(&mut self, values: &[u8]) -> bool {
397 assign_slice!(self, ByteArray, values)
398 }
399}
400
401macro_rules! assign_slice {
404 ($v:expr, $variant:ident, $values:expr) => {{
405 if !matches!($v, Self::$variant(_)) {
406 *$v = Self::$variant(Vec::new());
407 }
408 match $v {
409 Self::$variant(d) => {
410 d.clear();
411 d.extend_from_slice($values).is_ok()
412 }
413 _ => false,
414 }
415 }};
416}
417use assign_slice;
418
419#[derive(Debug, Clone, Copy, Default)]
421pub struct FloatingPointRange {
422 pub min: f64,
424 pub max: f64,
426 pub step: f64,
428}
429
430impl FloatingPointRange {
431 pub const fn new(min: f64, max: f64, step: f64) -> Self {
433 Self { min, max, step }
434 }
435
436 pub fn is_valid(&self) -> bool {
442 self.min <= self.max && self.step >= 0.0 && !self.step.is_nan()
443 }
444
445 pub fn contains(&self, value: f64) -> bool {
454 if are_close(value, self.min) || are_close(value, self.max) {
455 return true;
456 }
457 if !(value >= self.min && value <= self.max) {
458 return false;
459 }
460 if self.step == 0.0 {
461 return true;
462 }
463 let nearest = round_to_nearest((value - self.min) / self.step) * self.step + self.min;
464 are_close(nearest, value)
465 }
466}
467
468#[inline]
472fn are_close(a: f64, b: f64) -> bool {
473 const ULP_TOL: f64 = 100.0;
474 (a - b).abs() <= f64::EPSILON * (a + b).abs() * ULP_TOL
475}
476
477#[inline]
482fn round_to_nearest(q: f64) -> f64 {
483 if !q.is_finite() {
484 return q;
485 }
486 let shifted = if q < 0.0 { q - 0.5 } else { q + 0.5 };
487 (shifted as i64) as f64
488}
489
490#[derive(Debug, Clone, Copy, Default)]
492pub struct IntegerRange {
493 pub min: i64,
495 pub max: i64,
497 pub step: i64,
499}
500
501impl IntegerRange {
502 pub const fn new(min: i64, max: i64, step: i64) -> Self {
504 Self { min, max, step }
505 }
506
507 pub fn is_valid(&self) -> bool {
510 self.min <= self.max && self.step >= 0
511 }
512
513 pub fn contains(&self, value: i64) -> bool {
521 if !(value >= self.min && value <= self.max) {
522 return false;
523 }
524 if value == self.max || self.step == 0 {
525 return true;
526 }
527 match value
528 .checked_sub(self.min)
529 .and_then(|d| d.checked_rem(self.step))
530 {
531 Some(rem) => rem == 0,
532 None => false,
535 }
536 }
537}
538
539#[derive(Debug, Clone, Copy, Default)]
541pub enum ParameterRange {
542 #[default]
544 None,
545 FloatingPoint(FloatingPointRange),
547 Integer(IntegerRange),
549}
550
551impl ParameterRange {
552 pub fn is_valid(&self) -> bool {
554 match self {
555 Self::None => true,
556 Self::FloatingPoint(r) => r.is_valid(),
557 Self::Integer(r) => r.is_valid(),
558 }
559 }
560
561 pub fn contains(&self, value: &ParameterValue) -> bool {
570 match (self, value) {
571 (Self::None, _) => true,
572 (Self::Integer(range), ParameterValue::Integer(v)) => range.contains(*v),
573 (Self::FloatingPoint(range), ParameterValue::Double(v)) => range.contains(*v),
574 _ => true,
575 }
576 }
577}
578
579#[derive(Debug, Clone)]
583pub struct ParameterDescriptor {
584 pub name: String<MAX_PARAM_NAME_LEN>,
586 pub param_type: ParameterType,
588 pub description: String<MAX_PARAM_DESCRIPTION_LEN>,
596 pub additional_constraints: String<MAX_PARAM_CONSTRAINTS_LEN>,
611 pub read_only: bool,
613 pub dynamic_typing: bool,
615 pub range: ParameterRange,
617 description_truncated: bool,
624}
625
626pub fn fit_description<const N: usize>(dst: &mut String<N>, text: &str) -> bool {
634 dst.clear();
635 let mut cut = text.len().min(N);
636 while !text.is_char_boundary(cut) {
637 cut -= 1;
638 }
639 let stored = dst.push_str(&text[..cut]).is_ok();
641 debug_assert!(stored, "a prefix of at most N bytes fits String<N>");
642 cut < text.len()
643}
644
645impl ParameterDescriptor {
646 pub fn new(name: &str, param_type: ParameterType) -> Option<Self> {
648 let mut n = String::new();
649 n.push_str(name).ok()?;
650 Some(Self {
651 name: n,
652 param_type,
653 description: String::new(),
654 additional_constraints: String::new(),
655 read_only: false,
656 dynamic_typing: false,
657 range: ParameterRange::None,
658 description_truncated: false,
659 })
660 }
661
662 pub fn with_description(mut self, desc: &str) -> Self {
666 self.set_description(desc);
667 self
668 }
669
670 pub fn set_description(&mut self, desc: &str) -> bool {
675 self.description_truncated = fit_description(&mut self.description, desc);
676 self.description_truncated
677 }
678
679 pub fn description_truncated(&self) -> bool {
681 self.description_truncated
682 }
683
684 pub fn set_additional_constraints(&mut self, text: &str) -> bool {
688 let cut = fit_description(&mut self.additional_constraints, text);
689 self.description_truncated |= cut;
695 cut
696 }
697
698 pub fn with_additional_constraints(mut self, text: &str) -> Self {
700 self.set_additional_constraints(text);
701 self
702 }
703
704 pub(crate) fn clear_description_truncated(&mut self) {
706 self.description_truncated = false;
707 }
708
709 pub fn with_read_only(mut self, read_only: bool) -> Self {
711 self.read_only = read_only;
712 self
713 }
714
715 pub fn with_dynamic_typing(mut self, dynamic: bool) -> Self {
717 self.dynamic_typing = dynamic;
718 self
719 }
720
721 pub fn with_integer_range(mut self, min: i64, max: i64, step: i64) -> Self {
723 self.range = ParameterRange::Integer(IntegerRange::new(min, max, step));
724 self
725 }
726
727 pub fn with_float_range(mut self, min: f64, max: f64, step: f64) -> Self {
729 self.range = ParameterRange::FloatingPoint(FloatingPointRange::new(min, max, step));
730 self
731 }
732
733 pub fn validate_range(&self, value: &ParameterValue) -> bool {
737 self.range.contains(value)
738 }
739}
740
741#[derive(Debug, Clone)]
743pub struct Parameter {
744 pub name: String<MAX_PARAM_NAME_LEN>,
746 pub value: ParameterValue,
748}
749
750impl Parameter {
751 pub fn new(name: &str, value: ParameterValue) -> Option<Self> {
753 let mut n = String::new();
754 n.push_str(name).ok()?;
755 Some(Self { name: n, value })
756 }
757
758 pub fn bool(name: &str, value: bool) -> Option<Self> {
760 Self::new(name, ParameterValue::Bool(value))
761 }
762
763 pub fn integer(name: &str, value: i64) -> Option<Self> {
765 Self::new(name, ParameterValue::Integer(value))
766 }
767
768 pub fn double(name: &str, value: f64) -> Option<Self> {
770 Self::new(name, ParameterValue::Double(value))
771 }
772
773 pub fn string(name: &str, value: &str) -> Option<Self> {
775 Self::new(name, ParameterValue::from_string(value)?)
776 }
777
778 pub fn param_type(&self) -> ParameterType {
780 self.value.param_type()
781 }
782
783 pub fn name(&self) -> &str {
785 &self.name
786 }
787}
788
789#[deprecated(
796 since = "0.6.0",
797 note = "the parameter store returns `Result<(), ParameterError>`; `Success` is `Ok(())`"
798)]
799#[derive(Debug, Clone, Copy, PartialEq, Eq)]
800pub enum SetParameterResult {
801 Success,
803 ReadOnly,
805 TypeMismatch,
807 OutOfRange,
809 NotFound,
811 StorageFull,
813 Undeclared,
821 InvalidRange,
825 Rejected,
833}
834
835#[allow(deprecated)]
836impl SetParameterResult {
837 pub fn is_success(&self) -> bool {
839 matches!(self, Self::Success)
840 }
841}
842
843#[derive(Debug, Clone, Copy, PartialEq, Eq)]
849pub struct CapacityExceeded;
850
851pub trait ParameterVariant: Clone {
856 fn to_parameter_value(&self) -> ParameterValue;
858
859 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
871 Ok(self.to_parameter_value())
872 }
873
874 fn from_parameter_value(value: &ParameterValue) -> Option<Self>;
876
877 fn parameter_type() -> ParameterType;
879}
880
881impl ParameterVariant for bool {
884 fn to_parameter_value(&self) -> ParameterValue {
885 ParameterValue::Bool(*self)
886 }
887
888 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
889 value.as_bool()
890 }
891
892 fn parameter_type() -> ParameterType {
893 ParameterType::Bool
894 }
895}
896
897impl ParameterVariant for i64 {
898 fn to_parameter_value(&self) -> ParameterValue {
899 ParameterValue::Integer(*self)
900 }
901
902 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
903 value.as_integer()
904 }
905
906 fn parameter_type() -> ParameterType {
907 ParameterType::Integer
908 }
909}
910
911impl ParameterVariant for f64 {
912 fn to_parameter_value(&self) -> ParameterValue {
913 ParameterValue::Double(*self)
914 }
915
916 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
917 value.as_double()
918 }
919
920 fn parameter_type() -> ParameterType {
921 ParameterType::Double
922 }
923}
924
925impl ParameterVariant for String<MAX_STRING_VALUE_LEN> {
935 fn to_parameter_value(&self) -> ParameterValue {
936 ParameterValue::String(self.clone())
937 }
938
939 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
940 if let ParameterValue::String(s) = value {
941 Some(s.clone())
942 } else {
943 None
944 }
945 }
946
947 fn parameter_type() -> ParameterType {
948 ParameterType::String
949 }
950}
951
952#[cfg(feature = "alloc")]
954impl ParameterVariant for alloc::string::String {
955 fn to_parameter_value(&self) -> ParameterValue {
956 ParameterValue::from_string(self.as_str()).unwrap_or_default()
957 }
958
959 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
960 ParameterValue::from_string(self.as_str()).ok_or(CapacityExceeded)
961 }
962
963 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
964 value.as_string().map(|s| s.to_string())
965 }
966
967 fn parameter_type() -> ParameterType {
968 ParameterType::String
969 }
970}
971
972#[cfg(feature = "alloc")]
974impl ParameterVariant for alloc::vec::Vec<i64> {
975 fn to_parameter_value(&self) -> ParameterValue {
976 ParameterValue::IntegerArray(Vec::from_slice(self.as_slice()).unwrap_or_default())
977 }
978
979 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
980 Vec::from_slice(self.as_slice())
981 .map(ParameterValue::IntegerArray)
982 .map_err(|_| CapacityExceeded)
983 }
984
985 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
986 value.as_integer_array().map(|v| v.to_vec())
987 }
988
989 fn parameter_type() -> ParameterType {
990 ParameterType::IntegerArray
991 }
992}
993
994#[cfg(feature = "alloc")]
996impl ParameterVariant for alloc::vec::Vec<f64> {
997 fn to_parameter_value(&self) -> ParameterValue {
998 ParameterValue::DoubleArray(Vec::from_slice(self.as_slice()).unwrap_or_default())
999 }
1000
1001 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
1002 Vec::from_slice(self.as_slice())
1003 .map(ParameterValue::DoubleArray)
1004 .map_err(|_| CapacityExceeded)
1005 }
1006
1007 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
1008 value.as_double_array().map(|v| v.to_vec())
1009 }
1010
1011 fn parameter_type() -> ParameterType {
1012 ParameterType::DoubleArray
1013 }
1014}
1015
1016#[cfg(feature = "alloc")]
1018impl ParameterVariant for alloc::vec::Vec<bool> {
1019 fn to_parameter_value(&self) -> ParameterValue {
1020 ParameterValue::BoolArray(Vec::from_slice(self.as_slice()).unwrap_or_default())
1021 }
1022
1023 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
1024 Vec::from_slice(self.as_slice())
1025 .map(ParameterValue::BoolArray)
1026 .map_err(|_| CapacityExceeded)
1027 }
1028
1029 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
1030 value.as_bool_array().map(|v| v.to_vec())
1031 }
1032
1033 fn parameter_type() -> ParameterType {
1034 ParameterType::BoolArray
1035 }
1036}
1037
1038#[cfg(feature = "alloc")]
1040impl ParameterVariant for alloc::vec::Vec<alloc::string::String> {
1041 fn to_parameter_value(&self) -> ParameterValue {
1042 let mut vec = Vec::new();
1043 for s in self {
1044 let mut h_string = String::new();
1045 if h_string.push_str(s.as_str()).is_ok() {
1046 let _ = vec.push(h_string);
1047 }
1048 }
1049 ParameterValue::StringArray(vec)
1050 }
1051
1052 fn try_to_parameter_value(&self) -> Result<ParameterValue, CapacityExceeded> {
1053 let mut vec = Vec::new();
1056 for s in self {
1057 let mut h_string = String::new();
1058 h_string
1059 .push_str(s.as_str())
1060 .map_err(|_| CapacityExceeded)?;
1061 vec.push(h_string).map_err(|_| CapacityExceeded)?;
1062 }
1063 Ok(ParameterValue::StringArray(vec))
1064 }
1065
1066 fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
1067 if let ParameterValue::StringArray(v) = value {
1068 Some(v.iter().map(|s| s.as_str().to_string()).collect())
1069 } else {
1070 None
1071 }
1072 }
1073
1074 fn parameter_type() -> ParameterType {
1075 ParameterType::StringArray
1076 }
1077}
1078
1079#[cfg(test)]
1080#[allow(deprecated)] mod tests {
1082 use super::*;
1083
1084 #[test]
1087 fn integer_step_accepts_lattice_points_and_rejects_the_rest() {
1088 let r = IntegerRange::new(10, 20, 5);
1089 assert!(r.contains(10));
1090 assert!(r.contains(15));
1091 assert!(r.contains(20));
1092 assert!(!r.contains(11));
1095 assert!(!r.contains(14));
1096 assert!(!r.contains(19));
1097 assert!(!r.contains(5));
1099 assert!(!r.contains(25));
1100 }
1101
1102 #[test]
1103 fn integer_upper_bound_is_accepted_even_off_lattice() {
1104 let r = IntegerRange::new(0, 7, 3);
1106 assert!(r.contains(6));
1107 assert!(!r.contains(5));
1108 assert!(r.contains(7));
1109 }
1110
1111 #[test]
1112 fn integer_step_zero_means_unconstrained() {
1113 let r = IntegerRange::new(0, 100, 0);
1114 for v in [0, 1, 37, 99, 100] {
1115 assert!(r.contains(v), "{v}");
1116 }
1117 assert!(!r.contains(101));
1118 }
1119
1120 #[test]
1121 fn integer_step_does_not_overflow_at_the_extremes() {
1122 let r = IntegerRange::new(i64::MIN, i64::MAX, 2);
1123 let _ = r.contains(1);
1126 let _ = r.contains(i64::MAX - 1);
1127 assert!(r.contains(i64::MIN));
1128 assert!(r.contains(i64::MAX));
1129 }
1130
1131 #[test]
1132 fn float_step_accepts_lattice_points_within_tolerance() {
1133 let r = FloatingPointRange::new(0.0, 1.0, 0.1);
1134 for v in [0.0, 0.1, 0.3, 0.7, 1.0] {
1137 assert!(r.contains(v), "{v}");
1138 }
1139 assert!(r.contains(0.1 + 0.2));
1140 assert!(!r.contains(0.05));
1142 assert!(!r.contains(0.35));
1143 assert!(!r.contains(0.999));
1144 assert!(!r.contains(-0.1));
1146 assert!(!r.contains(1.1));
1147 }
1148
1149 #[test]
1150 fn float_endpoints_are_accepted_even_off_lattice() {
1151 let r = FloatingPointRange::new(0.0, 1.0, 0.3);
1152 assert!(r.contains(0.9));
1153 assert!(!r.contains(0.95));
1154 assert!(r.contains(1.0));
1155 }
1156
1157 #[test]
1158 fn float_step_zero_means_unconstrained() {
1159 let r = FloatingPointRange::new(0.0, 10.0, 0.0);
1160 for v in [0.0, 0.001, 3.25, 9.999, 10.0] {
1161 assert!(r.contains(v), "{v}");
1162 }
1163 assert!(!r.contains(10.001));
1164 }
1165
1166 #[test]
1167 fn float_step_with_a_nonzero_min_is_relative_to_min() {
1168 let r = FloatingPointRange::new(0.5, 2.5, 0.5);
1169 assert!(r.contains(1.5));
1170 assert!(!r.contains(1.25));
1171 assert!(r.contains(0.5));
1172 }
1173
1174 #[test]
1175 fn range_validity_is_min_le_max_and_step_ge_zero() {
1176 assert!(IntegerRange::new(0, 10, 0).is_valid());
1177 assert!(IntegerRange::new(0, 10, 3).is_valid());
1178 assert!(IntegerRange::new(5, 5, 1).is_valid());
1179 assert!(!IntegerRange::new(10, 0, 1).is_valid());
1180 assert!(!IntegerRange::new(0, 10, -1).is_valid());
1181 assert!(FloatingPointRange::new(0.0, 1.0, 0.0).is_valid());
1182 assert!(!FloatingPointRange::new(1.0, 0.0, 0.1).is_valid());
1183 assert!(!FloatingPointRange::new(0.0, 1.0, -0.1).is_valid());
1184 assert!(!FloatingPointRange::new(0.0, 1.0, f64::NAN).is_valid());
1185 assert!(ParameterRange::None.is_valid());
1186 }
1187
1188 #[test]
1189 fn descriptor_validate_range_enforces_step_on_both_arms() {
1190 let int_desc = ParameterDescriptor::new("i", ParameterType::Integer)
1191 .unwrap()
1192 .with_integer_range(0, 100, 10);
1193 assert!(int_desc.validate_range(&ParameterValue::Integer(30)));
1194 assert!(!int_desc.validate_range(&ParameterValue::Integer(31)));
1195 assert!(int_desc.validate_range(&ParameterValue::Double(31.0)));
1197
1198 let f_desc = ParameterDescriptor::new("f", ParameterType::Double)
1199 .unwrap()
1200 .with_float_range(0.0, 10.0, 0.5);
1201 assert!(f_desc.validate_range(&ParameterValue::Double(2.5)));
1202 assert!(!f_desc.validate_range(&ParameterValue::Double(2.7)));
1203 assert!(f_desc.validate_range(&ParameterValue::Integer(3)));
1204 }
1205
1206 #[test]
1207 #[allow(clippy::approx_constant)]
1208 fn test_parameter_value_types() {
1209 let bool_val = ParameterValue::Bool(true);
1210 assert_eq!(bool_val.param_type(), ParameterType::Bool);
1211 assert_eq!(bool_val.as_bool(), Some(true));
1212
1213 let int_val = ParameterValue::Integer(42);
1214 assert_eq!(int_val.param_type(), ParameterType::Integer);
1215 assert_eq!(int_val.as_integer(), Some(42));
1216
1217 let double_val = ParameterValue::Double(3.14);
1218 assert_eq!(double_val.param_type(), ParameterType::Double);
1219 assert_eq!(double_val.as_double(), Some(3.14));
1220 }
1221
1222 #[test]
1223 fn test_parameter_value_string() {
1224 let string_val = ParameterValue::from_string("hello").unwrap();
1225 assert_eq!(string_val.param_type(), ParameterType::String);
1226 assert_eq!(string_val.as_string(), Some("hello"));
1227 }
1228
1229 #[test]
1230 fn test_parameter_creation() {
1231 let param = Parameter::bool("my_param", true).unwrap();
1232 assert_eq!(param.name(), "my_param");
1233 assert_eq!(param.param_type(), ParameterType::Bool);
1234 assert_eq!(param.value.as_bool(), Some(true));
1235 }
1236
1237 #[test]
1238 fn test_parameter_descriptor() {
1239 let desc = ParameterDescriptor::new("speed", ParameterType::Double)
1240 .unwrap()
1241 .with_description("Maximum speed in m/s")
1242 .with_float_range(0.0, 10.0, 0.1);
1243
1244 assert_eq!(desc.name.as_str(), "speed");
1245 assert_eq!(desc.param_type, ParameterType::Double);
1246 assert!(!desc.read_only);
1247
1248 assert!(desc.validate_range(&ParameterValue::Double(5.0)));
1250 assert!(!desc.validate_range(&ParameterValue::Double(15.0)));
1251 }
1252
1253 #[test]
1254 fn test_integer_range() {
1255 let range = IntegerRange::new(0, 100, 1);
1256 assert!(range.contains(50));
1257 assert!(range.contains(0));
1258 assert!(range.contains(100));
1259 assert!(!range.contains(-1));
1260 assert!(!range.contains(101));
1261 }
1262
1263 #[test]
1264 fn test_set_result() {
1265 assert!(SetParameterResult::Success.is_success());
1266 assert!(!SetParameterResult::ReadOnly.is_success());
1267 }
1268}
1269
1270#[cfg(test)]
1275mod ghost_checks {
1276 use super::*;
1277 use nros_ghost_types::ParameterValueGhost;
1278
1279 fn ghost_from_value(v: &ParameterValue) -> ParameterValueGhost {
1282 match v {
1283 ParameterValue::NotSet => ParameterValueGhost::NotSet,
1284 ParameterValue::Bool(b) => ParameterValueGhost::Bool(*b),
1285 ParameterValue::Integer(i) => ParameterValueGhost::Integer(*i),
1286 ParameterValue::Double(_) => ParameterValueGhost::Double,
1287 ParameterValue::String(_) => ParameterValueGhost::String,
1288 ParameterValue::ByteArray(_) => ParameterValueGhost::ByteArray,
1289 ParameterValue::BoolArray(_) => ParameterValueGhost::BoolArray,
1290 ParameterValue::IntegerArray(_) => ParameterValueGhost::IntegerArray,
1291 ParameterValue::DoubleArray(_) => ParameterValueGhost::DoubleArray,
1292 ParameterValue::StringArray(_) => ParameterValueGhost::StringArray,
1293 }
1294 }
1295
1296 #[test]
1297 fn ghost_variant_coverage() {
1298 let _ = ghost_from_value(&ParameterValue::NotSet);
1300 let _ = ghost_from_value(&ParameterValue::Bool(true));
1301 let _ = ghost_from_value(&ParameterValue::Integer(0));
1302 let _ = ghost_from_value(&ParameterValue::Double(0.0));
1303 let _ = ghost_from_value(&ParameterValue::String(String::new()));
1304 let _ = ghost_from_value(&ParameterValue::ByteArray(Vec::new()));
1305 let _ = ghost_from_value(&ParameterValue::BoolArray(Vec::new()));
1306 let _ = ghost_from_value(&ParameterValue::IntegerArray(Vec::new()));
1307 let _ = ghost_from_value(&ParameterValue::DoubleArray(Vec::new()));
1308 let _ = ghost_from_value(&ParameterValue::StringArray(Vec::new()));
1309 }
1310
1311 #[test]
1312 fn ghost_bool_preserves() {
1313 let ghost = ghost_from_value(&ParameterValue::Bool(true));
1314 assert!(matches!(ghost, ParameterValueGhost::Bool(true)));
1315 }
1316
1317 #[test]
1318 fn ghost_integer_preserves() {
1319 let ghost = ghost_from_value(&ParameterValue::Integer(42));
1320 assert!(matches!(ghost, ParameterValueGhost::Integer(42)));
1321 }
1322}
1323
1324#[cfg(kani)]
1329mod verification {
1330 use super::*;
1331
1332 #[kani::proof]
1335 fn parameter_i64_roundtrip() {
1336 let val: i64 = kani::any();
1337 let pv = ParameterValue::from_integer(val);
1338 assert_eq!(pv.as_integer(), Some(val));
1339 assert_eq!(pv.param_type(), ParameterType::Integer);
1340 assert!(pv.is_set());
1341 }
1342
1343 #[kani::proof]
1344 fn parameter_bool_roundtrip() {
1345 let val: bool = kani::any();
1346 let pv = ParameterValue::from_bool(val);
1347 assert_eq!(pv.as_bool(), Some(val));
1348 assert_eq!(pv.param_type(), ParameterType::Bool);
1349 assert!(pv.is_set());
1350 }
1351
1352 #[kani::proof]
1353 fn parameter_double_roundtrip() {
1354 let val: f64 = kani::any();
1355 let pv = ParameterValue::from_double(val);
1356 let result = pv.as_double();
1357 assert!(result.is_some());
1358 assert_eq!(result.unwrap().to_bits(), val.to_bits());
1359 assert_eq!(pv.param_type(), ParameterType::Double);
1360 }
1361
1362 #[kani::proof]
1363 fn parameter_not_set_default() {
1364 let pv = ParameterValue::default();
1365 assert!(!pv.is_set());
1366 assert_eq!(pv.param_type(), ParameterType::NotSet);
1367 assert!(pv.as_bool().is_none());
1368 assert!(pv.as_integer().is_none());
1369 assert!(pv.as_double().is_none());
1370 assert!(pv.as_string().is_none());
1371 }
1372
1373 #[kani::proof]
1376 fn parameter_type_mismatch_bool() {
1377 let val: i64 = kani::any();
1378 let pv = ParameterValue::from_integer(val);
1379 assert!(pv.as_bool().is_none());
1380 assert!(pv.as_double().is_none());
1381 assert!(pv.as_string().is_none());
1382 }
1383
1384 #[kani::proof]
1385 fn parameter_type_mismatch_integer() {
1386 let val: bool = kani::any();
1387 let pv = ParameterValue::from_bool(val);
1388 assert!(pv.as_integer().is_none());
1389 assert!(pv.as_double().is_none());
1390 assert!(pv.as_string().is_none());
1391 }
1392
1393 #[kani::proof]
1396 fn integer_range_contains_bounds() {
1397 let min: i64 = kani::any();
1398 let max: i64 = kani::any();
1399 kani::assume(min >= -1_000_000 && min <= 1_000_000);
1401 kani::assume(max >= -1_000_000 && max <= 1_000_000);
1402 kani::assume(min <= max);
1403 let range = IntegerRange::new(min, max, 1);
1404 assert!(range.contains(min));
1406 assert!(range.contains(max));
1407 }
1408
1409 #[kani::proof]
1410 fn integer_range_outside_bounds() {
1411 let min: i64 = kani::any();
1412 let max: i64 = kani::any();
1413 kani::assume(min <= max);
1414 kani::assume(min > i64::MIN); kani::assume(max < i64::MAX); let range = IntegerRange::new(min, max, 1);
1417 assert!(!range.contains(min - 1));
1418 assert!(!range.contains(max + 1));
1419 }
1420
1421 #[kani::proof]
1424 fn float_range_contains_bounds() {
1425 let min: f64 = kani::any();
1426 let max: f64 = kani::any();
1427 kani::assume(!min.is_nan() && !max.is_nan());
1428 kani::assume(min <= max);
1429 let range = FloatingPointRange::new(min, max, 0.0);
1430 assert!(range.contains(min));
1431 assert!(range.contains(max));
1432 }
1433
1434 #[kani::proof]
1437 fn set_result_success_only() {
1438 assert!(SetParameterResult::Success.is_success());
1440 assert!(!SetParameterResult::ReadOnly.is_success());
1441 assert!(!SetParameterResult::TypeMismatch.is_success());
1442 assert!(!SetParameterResult::OutOfRange.is_success());
1443 assert!(!SetParameterResult::NotFound.is_success());
1444 assert!(!SetParameterResult::StorageFull.is_success());
1445 }
1446}
1447
1448#[cfg(all(test, feature = "alloc"))]
1451mod issue_0323_tests {
1452 use super::*;
1453
1454 #[test]
1458 fn oversize_string_is_rejected_not_silently_notset() {
1459 let long = "x".repeat(MAX_STRING_VALUE_LEN + 1);
1460 assert!(
1461 matches!(long.to_parameter_value(), ParameterValue::NotSet),
1462 "documenting the legacy infallible behaviour"
1463 );
1464 assert!(
1465 long.try_to_parameter_value().is_err(),
1466 "the fallible path must reject it"
1467 );
1468 }
1469
1470 #[test]
1472 fn oversize_integer_array_is_rejected_not_silently_empty() {
1473 let big: alloc::vec::Vec<i64> = (0..(MAX_ARRAY_LEN as i64 + 1)).collect();
1474 match big.to_parameter_value() {
1475 ParameterValue::IntegerArray(v) => {
1476 assert!(v.is_empty(), "documenting the legacy empty-array behaviour")
1477 }
1478 other => panic!("unexpected {other:?}"),
1479 }
1480 assert!(big.try_to_parameter_value().is_err());
1481 }
1482
1483 #[test]
1485 fn oversize_string_array_element_is_rejected_not_skipped() {
1486 let big: alloc::vec::Vec<alloc::string::String> =
1487 (0..(MAX_ARRAY_LEN + 1)).map(|i| i.to_string()).collect();
1488 assert!(big.try_to_parameter_value().is_err());
1489 }
1490
1491 #[test]
1495 fn a_description_that_fits_is_stored_whole() {
1496 let mut s: String<16> = String::new();
1497 assert!(!fit_description(&mut s, "wheel speed"));
1498 assert_eq!(s.as_str(), "wheel speed");
1499 assert!(!fit_description(&mut s, "0123456789abcdef"));
1501 assert_eq!(s.len(), 16);
1502 }
1503
1504 #[test]
1507 fn a_long_description_keeps_its_prefix_and_reports_the_cut() {
1508 let mut s: String<8> = String::new();
1509 assert!(fit_description(&mut s, "0123456789"));
1510 assert_eq!(s.as_str(), "01234567");
1511 }
1512
1513 #[test]
1517 fn truncation_backs_off_to_a_character_boundary() {
1518 let mut s: String<5> = String::new();
1519 assert!(fit_description(&mut s, "abcd\u{e9}f"));
1521 assert_eq!(s.as_str(), "abcd");
1522 let mut s: String<6> = String::new();
1523 assert!(fit_description(&mut s, "ab\u{1f4cf}z"));
1525 assert_eq!(s.as_str(), "ab\u{1f4cf}");
1526 let mut s: String<5> = String::new();
1527 assert!(fit_description(&mut s, "ab\u{1f4cf}z"));
1528 assert_eq!(s.as_str(), "ab");
1529 }
1530
1531 #[test]
1534 fn zero_capacity_stores_no_description_and_reports_it() {
1535 let mut s: String<0> = String::new();
1536 assert!(fit_description(&mut s, "anything"));
1537 assert!(s.is_empty());
1538 assert!(
1539 !fit_description(&mut s, ""),
1540 "an empty description loses nothing, so there is nothing to report"
1541 );
1542 }
1543
1544 #[test]
1546 fn the_descriptor_remembers_a_truncated_description() {
1547 let long = "d".repeat(MAX_PARAM_DESCRIPTION_LEN + 3);
1548 let d = ParameterDescriptor::new("p", ParameterType::Bool)
1549 .unwrap()
1550 .with_description(&long);
1551 assert_eq!(d.description.len(), MAX_PARAM_DESCRIPTION_LEN);
1552 assert!(d.description_truncated());
1553 let mut d = d;
1554 assert!(!d.set_description(""));
1555 assert!(!d.description_truncated());
1556 }
1557}