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