1use alloc::string::ToString;
20use alloc::vec::Vec;
21
22use super::data::DynamicValue;
23use super::descriptor::{TryConstructKind, TypeKind};
24use super::type_::DynamicTypeMember;
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum TryConstructOutcome {
29 Accept(DynamicValue),
32 Discard,
34 UseDefault(DynamicValue),
36 Trim(DynamicValue),
38}
39
40#[must_use]
44pub fn apply_try_construct(member: &DynamicTypeMember, value: DynamicValue) -> TryConstructOutcome {
45 let descriptor = member.descriptor();
46 let bound_max = bound_max_length(member);
47 let target_kind = member.dynamic_type().kind();
48
49 let violation = detect_violation(&value, target_kind, bound_max);
50 if violation.is_none() {
51 return TryConstructOutcome::Accept(value);
52 }
53
54 match descriptor.try_construct {
55 TryConstructKind::Discard => TryConstructOutcome::Discard,
56 TryConstructKind::UseDefault => {
57 match parse_default(descriptor.default_value.as_deref(), target_kind) {
58 Some(default) => TryConstructOutcome::UseDefault(default),
59 None => TryConstructOutcome::Discard,
60 }
61 }
62 TryConstructKind::Trim => match trim_value(value, target_kind, bound_max) {
63 Some(trimmed) => TryConstructOutcome::Trim(trimmed),
64 None => TryConstructOutcome::Discard,
65 },
66 }
67}
68
69fn bound_max_length(member: &DynamicTypeMember) -> Option<usize> {
73 let descriptor = member.dynamic_type().descriptor();
74 match descriptor.kind {
75 TypeKind::String8 | TypeKind::String16 | TypeKind::Sequence | TypeKind::Map => descriptor
76 .bound
77 .first()
78 .copied()
79 .filter(|&b| b != 0)
80 .map(|b| b as usize),
81 TypeKind::Array => {
82 if descriptor.bound.is_empty() {
84 None
85 } else {
86 Some(descriptor.bound.iter().product::<u32>() as usize)
87 }
88 }
89 _ => None,
90 }
91}
92
93#[derive(Debug, PartialEq)]
94enum Violation {
95 StringTooLong,
96 SequenceTooLong,
97 ArrayLengthMismatch,
98}
99
100fn detect_violation(
101 value: &DynamicValue,
102 target_kind: TypeKind,
103 bound_max: Option<usize>,
104) -> Option<Violation> {
105 let max = bound_max?;
106 match (value, target_kind) {
107 (DynamicValue::String(s), TypeKind::String8) if s.len() > max => {
108 Some(Violation::StringTooLong)
109 }
110 (DynamicValue::WString(s), TypeKind::String16) if s.len() > max => {
111 Some(Violation::StringTooLong)
112 }
113 (DynamicValue::Sequence(s), TypeKind::Sequence) if s.len() > max => {
114 Some(Violation::SequenceTooLong)
115 }
116 (DynamicValue::Sequence(s), TypeKind::Array) if s.len() != max => {
117 Some(Violation::ArrayLengthMismatch)
118 }
119 _ => None,
120 }
121}
122
123fn trim_value(
124 value: DynamicValue,
125 target_kind: TypeKind,
126 bound_max: Option<usize>,
127) -> Option<DynamicValue> {
128 let max = bound_max?;
129 match (value, target_kind) {
130 (DynamicValue::String(mut s), TypeKind::String8) => {
131 if s.len() > max {
135 let mut cut = max;
136 while !s.is_char_boundary(cut) && cut > 0 {
137 cut -= 1;
138 }
139 s.truncate(cut);
140 }
141 Some(DynamicValue::String(s))
142 }
143 (DynamicValue::WString(mut s), TypeKind::String16) => {
144 if s.len() > max {
145 s.truncate(max);
146 }
147 Some(DynamicValue::WString(s))
148 }
149 (DynamicValue::Sequence(mut s), TypeKind::Sequence) => {
150 if s.len() > max {
151 s.truncate(max);
152 }
153 Some(DynamicValue::Sequence(s))
154 }
155 _ => None,
158 }
159}
160
161fn parse_default(default_str: Option<&str>, kind: TypeKind) -> Option<DynamicValue> {
162 let s = default_str?;
163 match kind {
164 TypeKind::Boolean => match s {
165 "TRUE" | "true" | "1" => Some(DynamicValue::Bool(true)),
166 "FALSE" | "false" | "0" => Some(DynamicValue::Bool(false)),
167 _ => None,
168 },
169 TypeKind::Byte | TypeKind::UInt8 => s.parse::<u8>().ok().map(DynamicValue::UInt8),
170 TypeKind::Int8 => s.parse::<i8>().ok().map(DynamicValue::Int8),
171 TypeKind::Int16 => s.parse::<i16>().ok().map(DynamicValue::Int16),
172 TypeKind::UInt16 => s.parse::<u16>().ok().map(DynamicValue::UInt16),
173 TypeKind::Int32 | TypeKind::Enumeration => s.parse::<i32>().ok().map(DynamicValue::Int32),
174 TypeKind::UInt32 => s.parse::<u32>().ok().map(DynamicValue::UInt32),
175 TypeKind::Int64 => s.parse::<i64>().ok().map(DynamicValue::Int64),
176 TypeKind::UInt64 => s.parse::<u64>().ok().map(DynamicValue::UInt64),
177 TypeKind::Float32 => s.parse::<f32>().ok().map(DynamicValue::Float32),
178 TypeKind::Float64 => s.parse::<f64>().ok().map(DynamicValue::Float64),
179 TypeKind::String8 => Some(DynamicValue::String(s.to_string())),
180 TypeKind::String16 => Some(DynamicValue::WString(s.encode_utf16().collect::<Vec<_>>())),
181 _ => None,
182 }
183}
184
185#[cfg(test)]
186#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
187mod tests {
188 use super::*;
189 use crate::dynamic::builder::{DynamicTypeBuilder, DynamicTypeBuilderFactory};
190 use crate::dynamic::descriptor::{MemberDescriptor, TypeDescriptor};
191 use alloc::boxed::Box;
192
193 fn make_struct_with_bounded_string(
194 max_len: u32,
195 try_construct: TryConstructKind,
196 default_value: Option<&str>,
197 ) -> crate::dynamic::DynamicType {
198 let mut builder = DynamicTypeBuilder::new(TypeDescriptor::structure("TestStruct"));
199 let mut string_desc = TypeDescriptor::primitive(TypeKind::String8, "string");
200 string_desc.bound = alloc::vec![max_len];
201 let mut member = MemberDescriptor::new("name", 1, string_desc);
202 member.try_construct = try_construct;
203 member.default_value = default_value.map(|s| s.to_string());
204 builder.add_member(member).unwrap();
205 builder.build().unwrap()
206 }
207
208 fn make_struct_with_bounded_seq(
209 max_len: u32,
210 try_construct: TryConstructKind,
211 ) -> crate::dynamic::DynamicType {
212 let mut builder = DynamicTypeBuilder::new(TypeDescriptor::structure("TestSeq"));
213 let mut seq_desc = TypeDescriptor::primitive(TypeKind::Sequence, "sequence");
214 seq_desc.bound = alloc::vec![max_len];
215 seq_desc.element_type = Some(Box::new(TypeDescriptor::primitive(TypeKind::Int32, "long")));
216 let mut member = MemberDescriptor::new("ids", 1, seq_desc);
217 member.try_construct = try_construct;
218 builder.add_member(member).unwrap();
219 builder.build().unwrap()
220 }
221
222 #[test]
223 fn discard_drops_too_long_string() {
224 let ty = make_struct_with_bounded_string(5, TryConstructKind::Discard, None);
225 let member = ty.member_by_id(1).unwrap();
226 let outcome = apply_try_construct(member, DynamicValue::String("toolong".into()));
227 assert_eq!(outcome, TryConstructOutcome::Discard);
228 }
229
230 #[test]
231 fn use_default_replaces_too_long_string() {
232 let ty = make_struct_with_bounded_string(5, TryConstructKind::UseDefault, Some("hello"));
233 let member = ty.member_by_id(1).unwrap();
234 let outcome = apply_try_construct(member, DynamicValue::String("toolong".into()));
235 match outcome {
236 TryConstructOutcome::UseDefault(DynamicValue::String(s)) => assert_eq!(s, "hello"),
237 other => panic!("expected UseDefault(\"hello\"), got {other:?}"),
238 }
239 }
240
241 #[test]
242 fn use_default_falls_back_to_discard_when_no_default() {
243 let ty = make_struct_with_bounded_string(5, TryConstructKind::UseDefault, None);
244 let member = ty.member_by_id(1).unwrap();
245 let outcome = apply_try_construct(member, DynamicValue::String("toolong".into()));
246 assert_eq!(outcome, TryConstructOutcome::Discard);
247 }
248
249 #[test]
250 fn trim_truncates_string_to_bound() {
251 let ty = make_struct_with_bounded_string(5, TryConstructKind::Trim, None);
252 let member = ty.member_by_id(1).unwrap();
253 let outcome = apply_try_construct(member, DynamicValue::String("hello world".into()));
254 match outcome {
255 TryConstructOutcome::Trim(DynamicValue::String(s)) => assert_eq!(s, "hello"),
256 other => panic!("expected Trim(\"hello\"), got {other:?}"),
257 }
258 }
259
260 #[test]
261 fn trim_respects_utf8_codepoint_boundaries() {
262 let ty = make_struct_with_bounded_string(3, TryConstructKind::Trim, None);
265 let member = ty.member_by_id(1).unwrap();
266 let outcome = apply_try_construct(member, DynamicValue::String("héllo".into()));
267 match outcome {
268 TryConstructOutcome::Trim(DynamicValue::String(s)) => {
269 assert!(s.is_char_boundary(s.len()));
270 assert!(s.len() <= 3);
271 assert_eq!(s, "hé");
273 }
274 other => panic!("expected Trim, got {other:?}"),
275 }
276 }
277
278 #[test]
279 fn accept_when_value_within_bound() {
280 let ty = make_struct_with_bounded_string(10, TryConstructKind::Discard, None);
281 let member = ty.member_by_id(1).unwrap();
282 let outcome = apply_try_construct(member, DynamicValue::String("ok".into()));
283 match outcome {
284 TryConstructOutcome::Accept(DynamicValue::String(s)) => assert_eq!(s, "ok"),
285 other => panic!("expected Accept, got {other:?}"),
286 }
287 }
288
289 #[test]
290 fn unbounded_string_always_accepts() {
291 let ty = make_struct_with_bounded_string(0, TryConstructKind::Discard, None);
292 let member = ty.member_by_id(1).unwrap();
293 let outcome =
295 apply_try_construct(member, DynamicValue::String("a-very-long-string".into()));
296 match outcome {
297 TryConstructOutcome::Accept(_) => {}
298 other => panic!("expected Accept (unbounded), got {other:?}"),
299 }
300 }
301
302 #[test]
303 fn discard_drops_too_long_sequence() {
304 let ty = make_struct_with_bounded_seq(3, TryConstructKind::Discard);
305 let member = ty.member_by_id(1).unwrap();
306 let elements: Vec<_> = (0..5)
307 .map(|i| {
308 let prim = DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Int32).unwrap();
309 let mut d = crate::dynamic::DynamicData::new(prim.clone());
310 d.set_int32_value(0, i).ok();
311 d
312 })
313 .collect();
314 let outcome = apply_try_construct(member, DynamicValue::Sequence(elements));
315 assert_eq!(outcome, TryConstructOutcome::Discard);
316 }
317
318 #[test]
319 fn trim_truncates_sequence_to_bound() {
320 let ty = make_struct_with_bounded_seq(3, TryConstructKind::Trim);
321 let member = ty.member_by_id(1).unwrap();
322 let elements: Vec<_> = (0..5)
323 .map(|i| {
324 let prim = DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Int32).unwrap();
325 let mut d = crate::dynamic::DynamicData::new(prim.clone());
326 d.set_int32_value(0, i).ok();
327 d
328 })
329 .collect();
330 let outcome = apply_try_construct(member, DynamicValue::Sequence(elements));
331 match outcome {
332 TryConstructOutcome::Trim(DynamicValue::Sequence(s)) => assert_eq!(s.len(), 3),
333 other => panic!("expected Trim(seq[3]), got {other:?}"),
334 }
335 }
336
337 #[test]
338 fn parse_default_int32_works() {
339 let v = parse_default(Some("42"), TypeKind::Int32);
340 assert_eq!(v, Some(DynamicValue::Int32(42)));
341 }
342
343 #[test]
344 fn parse_default_bool_accepts_canonical_forms() {
345 assert_eq!(
346 parse_default(Some("TRUE"), TypeKind::Boolean),
347 Some(DynamicValue::Bool(true))
348 );
349 assert_eq!(
350 parse_default(Some("false"), TypeKind::Boolean),
351 Some(DynamicValue::Bool(false))
352 );
353 }
354
355 #[test]
356 fn parse_default_invalid_returns_none() {
357 assert_eq!(parse_default(Some("not-a-number"), TypeKind::Int32), None);
358 }
359}