Skip to main content

zerodds_types/dynamic/
data.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! DynamicData (XTypes 1.3 §7.5.6).
4//!
5//! `DynamicData` is a data instance of a `DynamicType`. The API
6//! is spec-frozen (method names + signatures). Relative to the spec,
7//! two simplifications are made:
8//!
9//! 1. All 12 typed getters/setters go through a `DynamicValue`
10//!    discriminator enum, by which the `set_<T>_value`/`get_<T>_value`
11//!    methods do their type check. Type mismatch → `BadParameter`.
12//! 2. Loans are modeled via a reference-counted `DataLoan`
13//!    (Spec §7.5.6.1 — the full loan API follows with C4.7).
14
15use alloc::boxed::Box;
16use alloc::collections::BTreeMap;
17use alloc::string::String;
18use alloc::vec::Vec;
19
20use super::descriptor::{MemberId, TypeKind};
21use super::error::DynamicError;
22use super::type_::DynamicType;
23
24// ----------------------------------------------------------------------
25// DynamicValue — internal storage enum
26// ----------------------------------------------------------------------
27
28/// Sum type for all values storable in DynamicData. Spec §7.5.6
29/// speaks of 12 primitive types + composite + sequence — this enum
30/// encapsulates the storage.
31#[derive(Debug, Clone)]
32pub enum DynamicValue {
33    /// `bool`.
34    Bool(bool),
35    /// 8-bit unsigned (`octet`).
36    Byte(u8),
37    /// `int8`.
38    Int8(i8),
39    /// `uint8`.
40    UInt8(u8),
41    /// `int16`.
42    Int16(i16),
43    /// `uint16`.
44    UInt16(u16),
45    /// `int32`.
46    Int32(i32),
47    /// `uint32`.
48    UInt32(u32),
49    /// `int64`.
50    Int64(i64),
51    /// `uint64`.
52    UInt64(u64),
53    /// `float`.
54    Float32(f32),
55    /// `double`.
56    Float64(f64),
57    /// `char`.
58    Char8(u8),
59    /// `wchar`.
60    Char16(u16),
61    /// `string`.
62    String(String),
63    /// `wstring` (UTF-16 as Vec<u16>).
64    WString(Vec<u16>),
65    /// Composite or sequence — embedded `DynamicData`.
66    Complex(Box<DynamicData>),
67    /// Sequence of DynamicData (spec: the element type is known from the
68    /// containerizing TypeDescriptor).
69    Sequence(Vec<DynamicData>),
70    /// `map<K,V>` — ordered key→value entries. Each key/value is a `DynamicData`
71    /// (a scalar wrapped under member id 0, or a composite aggregate), matching
72    /// how sequence elements are stored. Entry order mirrors the wire order.
73    Map(Vec<(DynamicData, DynamicData)>),
74    /// Discard / not set (the default value is returned).
75    None,
76}
77
78impl PartialEq for DynamicValue {
79    fn eq(&self, other: &Self) -> bool {
80        match (self, other) {
81            (Self::Bool(a), Self::Bool(b)) => a == b,
82            (Self::Byte(a), Self::Byte(b)) => a == b,
83            (Self::Int8(a), Self::Int8(b)) => a == b,
84            (Self::UInt8(a), Self::UInt8(b)) => a == b,
85            (Self::Int16(a), Self::Int16(b)) => a == b,
86            (Self::UInt16(a), Self::UInt16(b)) => a == b,
87            (Self::Int32(a), Self::Int32(b)) => a == b,
88            (Self::UInt32(a), Self::UInt32(b)) => a == b,
89            (Self::Int64(a), Self::Int64(b)) => a == b,
90            (Self::UInt64(a), Self::UInt64(b)) => a == b,
91            (Self::Float32(a), Self::Float32(b)) => a.to_bits() == b.to_bits(),
92            (Self::Float64(a), Self::Float64(b)) => a.to_bits() == b.to_bits(),
93            (Self::Char8(a), Self::Char8(b)) => a == b,
94            (Self::Char16(a), Self::Char16(b)) => a == b,
95            (Self::String(a), Self::String(b)) => a == b,
96            (Self::WString(a), Self::WString(b)) => a == b,
97            (Self::Complex(a), Self::Complex(b)) => a.equals(b),
98            (Self::Sequence(a), Self::Sequence(b)) => {
99                a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.equals(y))
100            }
101            (Self::Map(a), Self::Map(b)) => {
102                a.len() == b.len()
103                    && a.iter()
104                        .zip(b.iter())
105                        .all(|((ka, va), (kb, vb))| ka.equals(kb) && va.equals(vb))
106            }
107            (Self::None, Self::None) => true,
108            _ => false,
109        }
110    }
111}
112
113impl DynamicValue {
114    fn kind_str(&self) -> &'static str {
115        match self {
116            Self::Bool(_) => "bool",
117            Self::Byte(_) => "byte",
118            Self::Int8(_) => "int8",
119            Self::UInt8(_) => "uint8",
120            Self::Int16(_) => "int16",
121            Self::UInt16(_) => "uint16",
122            Self::Int32(_) => "int32",
123            Self::UInt32(_) => "uint32",
124            Self::Int64(_) => "int64",
125            Self::UInt64(_) => "uint64",
126            Self::Float32(_) => "float32",
127            Self::Float64(_) => "float64",
128            Self::Char8(_) => "char8",
129            Self::Char16(_) => "char16",
130            Self::String(_) => "string",
131            Self::WString(_) => "wstring",
132            Self::Complex(_) => "complex",
133            Self::Sequence(_) => "sequence",
134            Self::Map(_) => "map",
135            Self::None => "none",
136        }
137    }
138}
139
140// ----------------------------------------------------------------------
141// DataLoan — simple RAII wrapper
142// ----------------------------------------------------------------------
143
144/// Returns a loaned `DynamicData` view onto a member.
145/// Lifecycle: `loan_value` → `return_loaned_value` (Spec §7.5.6.1).
146///
147/// Minimal in phase 4 — the full loan refcount comes with C4.7.
148#[derive(Debug)]
149pub struct DataLoan {
150    member_id: MemberId,
151    /// Index into the internal loans array — invalidated on `return`.
152    handle: u32,
153}
154
155impl DataLoan {
156    /// Member id for which the loan was created.
157    #[must_use]
158    pub fn member_id(&self) -> MemberId {
159        self.member_id
160    }
161}
162
163// ----------------------------------------------------------------------
164// DynamicData
165// ----------------------------------------------------------------------
166
167/// XTypes 1.3 §7.5.6 DynamicData.
168///
169/// Data instance of a `DynamicType`. The type must be passed at
170/// construction (`DynamicDataFactory::create_data`); afterwards
171/// members can be read/written via `set_<T>_value(member_id, value)` /
172/// `get_<T>_value`.
173#[derive(Debug, Clone)]
174pub struct DynamicData {
175    type_: DynamicType,
176    values: BTreeMap<MemberId, DynamicValue>,
177    /// Active loans — for lifecycle validation.
178    active_loans: Vec<u32>,
179    /// Monotonically growing loan counter.
180    next_loan_handle: u32,
181}
182
183impl DynamicData {
184    pub(super) fn new(type_: DynamicType) -> Self {
185        Self {
186            type_,
187            values: BTreeMap::new(),
188            active_loans: Vec::new(),
189            next_loan_handle: 1,
190        }
191    }
192
193    /// Spec §7.5.6.2.1 `type()`.
194    #[must_use]
195    pub fn dynamic_type(&self) -> &DynamicType {
196        &self.type_
197    }
198
199    /// Spec §7.5.6.2.6 `equals(other)` — Deep.
200    #[must_use]
201    pub fn equals(&self, other: &Self) -> bool {
202        self.type_.equals(&other.type_) && self.values == other.values
203    }
204
205    /// Spec §7.5.6.2.4 `get_item_count()`.
206    #[must_use]
207    pub fn item_count(&self) -> u32 {
208        u32::try_from(self.values.len()).unwrap_or(u32::MAX)
209    }
210
211    /// Checks whether `member_id` is defined in the type.
212    fn validate_member(&self, member_id: MemberId) -> Result<(), DynamicError> {
213        if self.type_.member_by_id(member_id).is_none() {
214            return Err(DynamicError::bad_parameter(alloc::format!(
215                "no member with id {member_id} in type {}",
216                self.type_.name()
217            )));
218        }
219        Ok(())
220    }
221
222    /// General setter — type-safe via `DynamicValue` with
223    /// Spec §7.5.4.1.2 TryConstruct apply semantics (C4.7): if the
224    /// member type defines a `bound` and the value violates the bound
225    /// (too long a string, too long a sequence, mismatched
226    /// array length), the `try_construct` strategy decides:
227    /// `Discard` leaves the member unset, `UseDefault` sets the
228    /// `default_value`, `Trim` truncates to the bound.
229    fn set(&mut self, member_id: MemberId, value: DynamicValue) -> Result<(), DynamicError> {
230        self.validate_member(member_id)?;
231        let member = self.type_.member_by_id(member_id).cloned();
232        if let Some(ref m) = member {
233            check_value_kind_matches_type(&value, m.dynamic_type().kind())?;
234        }
235        // TryConstruct apply (Spec §7.5.4.1.2). Members without bounds:
236        // `apply_try_construct` returns `Accept(value)` unchanged.
237        if let Some(m) = member {
238            match crate::dynamic::try_construct::apply_try_construct(&m, value) {
239                crate::dynamic::TryConstructOutcome::Accept(v)
240                | crate::dynamic::TryConstructOutcome::UseDefault(v)
241                | crate::dynamic::TryConstructOutcome::Trim(v) => {
242                    self.values.insert(member_id, v);
243                }
244                crate::dynamic::TryConstructOutcome::Discard => {
245                    // Member stays unset (Spec §7.5.4.1.2 Discard).
246                    self.values.remove(&member_id);
247                }
248            }
249        } else {
250            self.values.insert(member_id, value);
251        }
252        Ok(())
253    }
254
255    /// Lese-Helper.
256    fn get(&self, member_id: MemberId) -> Result<&DynamicValue, DynamicError> {
257        self.values.get(&member_id).ok_or_else(|| {
258            DynamicError::PreconditionNotMet(alloc::format!(
259                "member {member_id} not set in {}",
260                self.type_.name()
261            ))
262        })
263    }
264
265    /// Generic read of a member's raw [`DynamicValue`], or `None` if unset.
266    ///
267    /// The reflective codec ([`crate::dynamic::codec`]) uses this to walk a
268    /// fully-populated value without the per-kind typed getters.
269    #[must_use]
270    pub fn get_value(&self, member_id: MemberId) -> Option<&DynamicValue> {
271        self.values.get(&member_id)
272    }
273
274    /// Insert a raw [`DynamicValue`] for `member_id` WITHOUT TryConstruct
275    /// bound-application — a faithful set for the reflective decoder, which
276    /// reconstructs exactly what was on the wire. Unknown ids are accepted
277    /// (the decoder only ever passes ids from the type's own members).
278    pub fn set_value_raw(&mut self, member_id: MemberId, value: DynamicValue) {
279        self.values.insert(member_id, value);
280    }
281
282    /// The member ids that currently carry a value, in member (index) order of
283    /// the type. Used by the reflective encoder to emit members deterministically.
284    #[must_use]
285    pub fn set_member_ids(&self) -> alloc::vec::Vec<MemberId> {
286        let mut ids: alloc::vec::Vec<MemberId> = self.values.keys().copied().collect();
287        // Order by the type's declared member index so encode order is stable.
288        ids.sort_by_key(|id| self.type_.member_by_id(*id).map_or(u32::MAX, |m| m.index()));
289        ids
290    }
291}
292
293// ----------------------------------------------------------------------
294// 12 Primitive Getters/Setters (Spec §7.5.6.3)
295// ----------------------------------------------------------------------
296
297macro_rules! primitive_accessor {
298    ($get_name:ident, $set_name:ident, $rust_ty:ty, $variant:ident) => {
299        impl DynamicData {
300            #[doc = concat!("Spec §7.5.6.3 `get_", stringify!($variant), "_value`.")]
301            ///
302            /// # Errors
303            /// `BadParameter` on an unknown id, `Inconsistent` on a
304            /// type mismatch.
305            pub fn $get_name(&self, member_id: MemberId) -> Result<$rust_ty, DynamicError> {
306                let v = self.get(member_id)?;
307                if let DynamicValue::$variant(x) = v {
308                    Ok(x.clone())
309                } else {
310                    Err(DynamicError::bad_parameter(alloc::format!(
311                        "type mismatch: expected {} got {}",
312                        stringify!($variant),
313                        v.kind_str()
314                    )))
315                }
316            }
317
318            #[doc = concat!("Spec §7.5.6.3 `set_", stringify!($variant), "_value`.")]
319            ///
320            /// # Errors
321            /// Type mismatch / unknown id.
322            pub fn $set_name(
323                &mut self,
324                member_id: MemberId,
325                value: $rust_ty,
326            ) -> Result<(), DynamicError> {
327                self.set(member_id, DynamicValue::$variant(value))
328            }
329        }
330    };
331}
332
333primitive_accessor!(get_boolean_value, set_boolean_value, bool, Bool);
334primitive_accessor!(get_byte_value, set_byte_value, u8, Byte);
335primitive_accessor!(get_int8_value, set_int8_value, i8, Int8);
336primitive_accessor!(get_uint8_value, set_uint8_value, u8, UInt8);
337primitive_accessor!(get_int16_value, set_int16_value, i16, Int16);
338primitive_accessor!(get_uint16_value, set_uint16_value, u16, UInt16);
339primitive_accessor!(get_int32_value, set_int32_value, i32, Int32);
340primitive_accessor!(get_uint32_value, set_uint32_value, u32, UInt32);
341primitive_accessor!(get_int64_value, set_int64_value, i64, Int64);
342primitive_accessor!(get_uint64_value, set_uint64_value, u64, UInt64);
343primitive_accessor!(get_float32_value, set_float32_value, f32, Float32);
344primitive_accessor!(get_float64_value, set_float64_value, f64, Float64);
345primitive_accessor!(get_char8_value, set_char8_value, u8, Char8);
346primitive_accessor!(get_char16_value, set_char16_value, u16, Char16);
347
348// String + WString separat — Clone-Semantik vs. Move.
349impl DynamicData {
350    /// Spec §7.5.6.3 `get_string_value`.
351    ///
352    /// # Errors
353    /// Type-Mismatch.
354    pub fn get_string_value(&self, member_id: MemberId) -> Result<String, DynamicError> {
355        let v = self.get(member_id)?;
356        if let DynamicValue::String(s) = v {
357            Ok(s.clone())
358        } else {
359            Err(DynamicError::bad_parameter(alloc::format!(
360                "type mismatch: expected string got {}",
361                v.kind_str()
362            )))
363        }
364    }
365
366    /// Spec §7.5.6.3 `set_string_value`.
367    ///
368    /// # Errors
369    /// Type mismatch / unknown id.
370    pub fn set_string_value(
371        &mut self,
372        member_id: MemberId,
373        value: impl Into<String>,
374    ) -> Result<(), DynamicError> {
375        self.set(member_id, DynamicValue::String(value.into()))
376    }
377
378    /// Spec §7.5.6.3 `get_wstring_value`.
379    ///
380    /// # Errors
381    /// Type-Mismatch.
382    pub fn get_wstring_value(&self, member_id: MemberId) -> Result<Vec<u16>, DynamicError> {
383        let v = self.get(member_id)?;
384        if let DynamicValue::WString(s) = v {
385            Ok(s.clone())
386        } else {
387            Err(DynamicError::bad_parameter(alloc::format!(
388                "type mismatch: expected wstring got {}",
389                v.kind_str()
390            )))
391        }
392    }
393
394    /// Spec §7.5.6.3 `set_wstring_value`.
395    ///
396    /// # Errors
397    /// Type mismatch / unknown id.
398    pub fn set_wstring_value(
399        &mut self,
400        member_id: MemberId,
401        value: Vec<u16>,
402    ) -> Result<(), DynamicError> {
403        self.set(member_id, DynamicValue::WString(value))
404    }
405}
406
407// ----------------------------------------------------------------------
408// Composite + Sequence (Spec §7.5.6.5)
409// ----------------------------------------------------------------------
410
411impl DynamicData {
412    /// Spec §7.5.6.5.1 `get_complex_value(member_id)`.
413    ///
414    /// Returns a read view onto the composite member.
415    ///
416    /// # Errors
417    /// Type mismatch or member not set.
418    pub fn get_complex_value(&self, member_id: MemberId) -> Result<&DynamicData, DynamicError> {
419        let v = self.get(member_id)?;
420        if let DynamicValue::Complex(d) = v {
421            Ok(d)
422        } else {
423            Err(DynamicError::bad_parameter(alloc::format!(
424                "type mismatch: expected complex got {}",
425                v.kind_str()
426            )))
427        }
428    }
429
430    /// Spec §7.5.6.5.2 `set_complex_value(member_id, data)`.
431    ///
432    /// # Errors
433    /// Type mismatch or member does not exist.
434    pub fn set_complex_value(
435        &mut self,
436        member_id: MemberId,
437        value: DynamicData,
438    ) -> Result<(), DynamicError> {
439        self.set(member_id, DynamicValue::Complex(Box::new(value)))
440    }
441
442    /// Returns the number of elements of a sequence member.
443    ///
444    /// # Errors
445    /// Type mismatch or member not set.
446    pub fn get_sequence_length(&self, member_id: MemberId) -> Result<u32, DynamicError> {
447        let v = self.get(member_id)?;
448        if let DynamicValue::Sequence(seq) = v {
449            Ok(u32::try_from(seq.len()).unwrap_or(u32::MAX))
450        } else {
451            Err(DynamicError::bad_parameter(alloc::format!(
452                "type mismatch: expected sequence got {}",
453                v.kind_str()
454            )))
455        }
456    }
457
458    /// Sets a sequence completely.
459    ///
460    /// # Errors
461    /// Type mismatch.
462    pub fn set_sequence_value(
463        &mut self,
464        member_id: MemberId,
465        value: Vec<DynamicData>,
466    ) -> Result<(), DynamicError> {
467        self.set(member_id, DynamicValue::Sequence(value))
468    }
469
470    /// Sets a `map<K,V>` member completely, as ordered key→value entries.
471    ///
472    /// # Errors
473    /// Type mismatch.
474    pub fn set_map_value(
475        &mut self,
476        member_id: MemberId,
477        entries: Vec<(DynamicData, DynamicData)>,
478    ) -> Result<(), DynamicError> {
479        self.set(member_id, DynamicValue::Map(entries))
480    }
481
482    /// Returns an element of a sequence member.
483    ///
484    /// # Errors
485    /// Index out of bounds / type mismatch.
486    pub fn get_sequence_element(
487        &self,
488        member_id: MemberId,
489        index: u32,
490    ) -> Result<&DynamicData, DynamicError> {
491        let v = self.get(member_id)?;
492        if let DynamicValue::Sequence(seq) = v {
493            seq.get(index as usize).ok_or_else(|| {
494                DynamicError::bad_parameter(alloc::format!(
495                    "sequence index {index} out of range (len={})",
496                    seq.len()
497                ))
498            })
499        } else {
500            Err(DynamicError::bad_parameter(alloc::format!(
501                "type mismatch: expected sequence got {}",
502                v.kind_str()
503            )))
504        }
505    }
506}
507
508// ----------------------------------------------------------------------
509// Loans (Spec §7.5.6.1) — minimal, full lifecycle in C4.7
510// ----------------------------------------------------------------------
511
512impl DynamicData {
513    /// Spec §7.5.6.1 `loan_value(member_id)`.
514    ///
515    /// Creates a `DataLoan` handle. Until `return_loaned_value` is
516    /// called, another loan operation on the same
517    /// member is not allowed.
518    ///
519    /// # Errors
520    /// Member not found, or member already on loan.
521    pub fn loan_value(&mut self, member_id: MemberId) -> Result<DataLoan, DynamicError> {
522        self.validate_member(member_id)?;
523        if self.active_loans.contains(&member_id) {
524            return Err(DynamicError::loan(alloc::format!(
525                "member {member_id} is already on loan"
526            )));
527        }
528        self.active_loans.push(member_id);
529        let handle = self.next_loan_handle;
530        self.next_loan_handle = self.next_loan_handle.saturating_add(1);
531        Ok(DataLoan { member_id, handle })
532    }
533
534    /// Spec §7.5.6.1 `return_loaned_value(loan)`.
535    ///
536    /// # Errors
537    /// `LoanError` if the loan is unknown or already returned.
538    pub fn return_loaned_value(&mut self, loan: DataLoan) -> Result<(), DynamicError> {
539        let pos = self
540            .active_loans
541            .iter()
542            .position(|m| *m == loan.member_id)
543            .ok_or_else(|| {
544                DynamicError::loan(alloc::format!(
545                    "no active loan for member {}",
546                    loan.member_id
547                ))
548            })?;
549        self.active_loans.swap_remove(pos);
550        // The handle value is not used further — only kept as a sanity
551        // indicator that `return_loaned_value` receives a productive loan.
552        let _ = loan.handle;
553        Ok(())
554    }
555}
556
557/// Validates: does the value discriminator match the member type kind?
558fn check_value_kind_matches_type(value: &DynamicValue, kind: TypeKind) -> Result<(), DynamicError> {
559    let ok = match (value, kind) {
560        (DynamicValue::Bool(_), TypeKind::Boolean) => true,
561        (DynamicValue::Byte(_), TypeKind::Byte) => true,
562        (DynamicValue::Int8(_), TypeKind::Int8) => true,
563        (DynamicValue::UInt8(_), TypeKind::UInt8) => true,
564        (DynamicValue::Int16(_), TypeKind::Int16) => true,
565        (DynamicValue::UInt16(_), TypeKind::UInt16) => true,
566        (DynamicValue::Int32(_), TypeKind::Int32 | TypeKind::Enumeration) => true,
567        (DynamicValue::UInt32(_), TypeKind::UInt32) => true,
568        (DynamicValue::Int64(_), TypeKind::Int64) => true,
569        (DynamicValue::UInt64(_), TypeKind::UInt64) => true,
570        (DynamicValue::Float32(_), TypeKind::Float32) => true,
571        (DynamicValue::Float64(_), TypeKind::Float64) => true,
572        (DynamicValue::Char8(_), TypeKind::Char8) => true,
573        (DynamicValue::Char16(_), TypeKind::Char16) => true,
574        (DynamicValue::String(_), TypeKind::String8) => true,
575        (DynamicValue::WString(_), TypeKind::String16) => true,
576        (DynamicValue::Complex(_), k) if k.is_aggregable() => true,
577        (DynamicValue::Sequence(_), TypeKind::Sequence | TypeKind::Array) => true,
578        (DynamicValue::Map(_), TypeKind::Map) => true,
579        (DynamicValue::None, _) => true,
580        _ => false,
581    };
582    if !ok {
583        return Err(DynamicError::bad_parameter(alloc::format!(
584            "value kind {} cannot be assigned to member kind {kind:?}",
585            value.kind_str()
586        )));
587    }
588    Ok(())
589}
590
591#[cfg(test)]
592#[allow(clippy::unwrap_used)]
593mod tests {
594    use super::*;
595    use crate::dynamic::DynamicTypeBuilderFactory;
596    use crate::dynamic::descriptor::{MemberDescriptor, TypeDescriptor};
597
598    fn struct_with_int32_member() -> DynamicType {
599        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
600        b.add_struct_member("x", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
601            .unwrap();
602        b.build().unwrap()
603    }
604
605    #[test]
606    fn set_get_int32_roundtrips() {
607        let t = struct_with_int32_member();
608        let mut d = DynamicData::new(t);
609        d.set_int32_value(1, 42).unwrap();
610        assert_eq!(d.get_int32_value(1).unwrap(), 42);
611    }
612
613    #[test]
614    fn set_int32_into_int64_member_rejected() {
615        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
616        b.add_struct_member("x", 1, TypeDescriptor::primitive(TypeKind::Int64, "int64"))
617            .unwrap();
618        let t = b.build().unwrap();
619        let mut d = DynamicData::new(t);
620        let err = d.set_int32_value(1, 5).unwrap_err();
621        assert!(matches!(err, DynamicError::BadParameter(_)));
622    }
623
624    #[test]
625    fn unknown_member_id_rejected() {
626        let t = struct_with_int32_member();
627        let mut d = DynamicData::new(t);
628        let err = d.set_int32_value(999, 0).unwrap_err();
629        assert!(matches!(err, DynamicError::BadParameter(_)));
630    }
631
632    #[test]
633    fn loan_double_rejected() {
634        let t = struct_with_int32_member();
635        let mut d = DynamicData::new(t);
636        let _l = d.loan_value(1).unwrap();
637        let err = d.loan_value(1).unwrap_err();
638        assert!(matches!(err, DynamicError::LoanError(_)));
639    }
640
641    #[test]
642    fn loan_return_then_loan_again_works() {
643        let t = struct_with_int32_member();
644        let mut d = DynamicData::new(t);
645        let l = d.loan_value(1).unwrap();
646        d.return_loaned_value(l).unwrap();
647        let _l2 = d.loan_value(1).unwrap();
648    }
649
650    #[test]
651    fn return_unknown_loan_rejected() {
652        let t = struct_with_int32_member();
653        let mut d = DynamicData::new(t);
654        let fake = DataLoan {
655            member_id: 1,
656            handle: 99,
657        };
658        let err = d.return_loaned_value(fake).unwrap_err();
659        assert!(matches!(err, DynamicError::LoanError(_)));
660    }
661
662    #[test]
663    fn item_count_grows_with_sets() {
664        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
665        b.add_struct_member("a", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
666            .unwrap();
667        b.add_struct_member("b", 2, TypeDescriptor::primitive(TypeKind::Int64, "int64"))
668            .unwrap();
669        let t = b.build().unwrap();
670        let mut d = DynamicData::new(t);
671        assert_eq!(d.item_count(), 0);
672        d.set_int32_value(1, 10).unwrap();
673        assert_eq!(d.item_count(), 1);
674        d.set_int64_value(2, 20).unwrap();
675        assert_eq!(d.item_count(), 2);
676    }
677
678    #[test]
679    fn equals_distinguishes_value_diffs() {
680        let t = struct_with_int32_member();
681        let mut a = DynamicData::new(t.clone());
682        let mut b = DynamicData::new(t);
683        a.set_int32_value(1, 5).unwrap();
684        b.set_int32_value(1, 6).unwrap();
685        assert!(!a.equals(&b));
686        b.set_int32_value(1, 5).unwrap();
687        assert!(a.equals(&b));
688    }
689
690    #[test]
691    fn complex_value_set_get() {
692        // outer struct with a nested struct member.
693        let mut inner = DynamicTypeBuilderFactory::create_struct("::Inner");
694        inner
695            .add_struct_member("v", 1, TypeDescriptor::primitive(TypeKind::Int32, "int32"))
696            .unwrap();
697        let inner_t = inner.build().unwrap();
698
699        let mut outer = DynamicTypeBuilderFactory::create_struct("::Outer");
700        outer
701            .add_member(MemberDescriptor::new(
702                "nested",
703                10,
704                TypeDescriptor::structure("::Inner"),
705            ))
706            .unwrap();
707        let outer_t = outer.build().unwrap();
708
709        let mut inner_data = DynamicData::new(inner_t);
710        inner_data.set_int32_value(1, 99).unwrap();
711
712        let mut outer_data = DynamicData::new(outer_t);
713        outer_data.set_complex_value(10, inner_data).unwrap();
714
715        let got = outer_data.get_complex_value(10).unwrap();
716        assert_eq!(got.get_int32_value(1).unwrap(), 99);
717    }
718
719    #[test]
720    fn sequence_length_and_element_access() {
721        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
722        b.add_struct_member(
723            "items",
724            1,
725            TypeDescriptor::sequence(
726                "items",
727                TypeDescriptor::primitive(TypeKind::Int32, "int32"),
728                100,
729            ),
730        )
731        .unwrap();
732        let t = b.build().unwrap();
733
734        let elem_t = DynamicTypeBuilderFactory::get_primitive_type(TypeKind::Int32).unwrap();
735        let mut e0 = DynamicData::new(elem_t.clone());
736        e0.values.insert(0, DynamicValue::Int32(7));
737        let mut e1 = DynamicData::new(elem_t);
738        e1.values.insert(0, DynamicValue::Int32(8));
739        let mut d = DynamicData::new(t);
740        d.set_sequence_value(1, alloc::vec![e0, e1]).unwrap();
741        assert_eq!(d.get_sequence_length(1).unwrap(), 2);
742        let _ = d.get_sequence_element(1, 0).unwrap();
743        assert!(d.get_sequence_element(1, 9).is_err());
744    }
745
746    #[test]
747    fn all_twelve_primitive_setters_work() {
748        // This list is mandatory per Spec §7.5.6.3.
749        let cases: alloc::vec::Vec<(&str, MemberId, TypeKind)> = alloc::vec![
750            ("b", 1, TypeKind::Boolean),
751            ("by", 2, TypeKind::Byte),
752            ("i8", 3, TypeKind::Int8),
753            ("u8", 4, TypeKind::UInt8),
754            ("i16", 5, TypeKind::Int16),
755            ("u16", 6, TypeKind::UInt16),
756            ("i32", 7, TypeKind::Int32),
757            ("u32", 8, TypeKind::UInt32),
758            ("i64", 9, TypeKind::Int64),
759            ("u64", 10, TypeKind::UInt64),
760            ("f32", 11, TypeKind::Float32),
761            ("f64", 12, TypeKind::Float64),
762            ("c8", 13, TypeKind::Char8),
763            ("c16", 14, TypeKind::Char16),
764        ];
765        let mut b = DynamicTypeBuilderFactory::create_struct("::S");
766        for (n, id, k) in &cases {
767            b.add_struct_member(
768                *n,
769                *id,
770                TypeDescriptor::primitive(*k, super::super::type_::primitive_name(*k)),
771            )
772            .unwrap();
773        }
774        // Plus string and WString.
775        b.add_struct_member("s", 100, TypeDescriptor::string8(64))
776            .unwrap();
777        b.add_struct_member("w", 101, TypeDescriptor::string16(64))
778            .unwrap();
779        let t = b.build().unwrap();
780        let mut d = DynamicData::new(t);
781        d.set_boolean_value(1, true).unwrap();
782        d.set_byte_value(2, 0xFF).unwrap();
783        d.set_int8_value(3, -1).unwrap();
784        d.set_uint8_value(4, 255).unwrap();
785        d.set_int16_value(5, -2).unwrap();
786        d.set_uint16_value(6, 2).unwrap();
787        d.set_int32_value(7, -3).unwrap();
788        d.set_uint32_value(8, 3).unwrap();
789        d.set_int64_value(9, -4).unwrap();
790        d.set_uint64_value(10, 4).unwrap();
791        d.set_float32_value(11, 1.5).unwrap();
792        d.set_float64_value(12, 2.5).unwrap();
793        d.set_char8_value(13, b'A').unwrap();
794        d.set_char16_value(14, 0x42).unwrap();
795        d.set_string_value(100, "abc").unwrap();
796        d.set_wstring_value(101, alloc::vec![0x41, 0x42]).unwrap();
797
798        assert!(d.get_boolean_value(1).unwrap());
799        assert_eq!(d.get_byte_value(2).unwrap(), 0xFF);
800        assert_eq!(d.get_int8_value(3).unwrap(), -1);
801        assert_eq!(d.get_uint8_value(4).unwrap(), 255);
802        assert_eq!(d.get_int16_value(5).unwrap(), -2);
803        assert_eq!(d.get_uint16_value(6).unwrap(), 2);
804        assert_eq!(d.get_int32_value(7).unwrap(), -3);
805        assert_eq!(d.get_uint32_value(8).unwrap(), 3);
806        assert_eq!(d.get_int64_value(9).unwrap(), -4);
807        assert_eq!(d.get_uint64_value(10).unwrap(), 4);
808        assert!((d.get_float32_value(11).unwrap() - 1.5).abs() < 1e-6);
809        assert!((d.get_float64_value(12).unwrap() - 2.5).abs() < 1e-9);
810        assert_eq!(d.get_char8_value(13).unwrap(), b'A');
811        assert_eq!(d.get_char16_value(14).unwrap(), 0x42);
812        assert_eq!(d.get_string_value(100).unwrap(), "abc");
813        assert_eq!(d.get_wstring_value(101).unwrap(), alloc::vec![0x41, 0x42]);
814    }
815}