Skip to main content

zerodds_types/
builder.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Programmatic builder for TypeObjects.
4//!
5//! Enables readable code like:
6//!
7//! ```no_run
8//! use zerodds_types::builder::{Extensibility, TypeObjectBuilder};
9//! use zerodds_types::{PrimitiveKind, TypeIdentifier};
10//!
11//! let chatter = TypeObjectBuilder::struct_type("::chat::Chatter")
12//!     .extensibility(Extensibility::Appendable)
13//!     .member(
14//!         "msg_id",
15//!         TypeIdentifier::Primitive(PrimitiveKind::Int64),
16//!         |m| m.key(),
17//!     )
18//!     .member(
19//!         "text",
20//!         TypeIdentifier::String8Small { bound: 255 },
21//!         |m| m,
22//!     )
23//!     .build_complete();
24//! ```
25//!
26//! Scope in T5: StructBuilder, EnumBuilder, AliasBuilder — the three
27//! most frequently used top-level types. Union, collections,
28//! bitmask, bitset, annotation follow on demand.
29
30use alloc::string::String;
31use alloc::vec::Vec;
32
33use crate::type_identifier::TypeIdentifier;
34use crate::type_object::common::{
35    AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CommonStructMember,
36    CompleteMemberDetail, CompleteTypeDetail, NameHash, OptionalAppliedAnnotationSeq,
37};
38use crate::type_object::complete::{
39    CompleteAliasBody, CompleteAliasHeader, CompleteAliasType, CompleteEnumeratedHeader,
40    CompleteEnumeratedLiteral, CompleteEnumeratedType, CompleteStructHeader, CompleteStructMember,
41    CompleteStructType,
42};
43use crate::type_object::flags::{
44    AliasMemberFlag, AliasTypeFlag, EnumLiteralFlag, EnumTypeFlag, StructMemberFlag, StructTypeFlag,
45};
46use crate::type_object::minimal::CommonAliasBody;
47use crate::type_object::minimal::{
48    CommonEnumeratedHeader, CommonEnumeratedLiteral, MinimalAliasBody, MinimalAliasType,
49    MinimalEnumeratedHeader, MinimalEnumeratedLiteral, MinimalEnumeratedType, MinimalStructHeader,
50    MinimalStructMember, MinimalStructType,
51};
52
53/// The `QualifiedTypeName` as serialized in a COMPLETE TypeObject
54/// (§7.3.4.5.4): the fully-qualified name WITHOUT its leading `::` scope token.
55/// CycloneDDS / FastDDS / RTI all omit it (byte-verified) — `::to::Plain`
56/// serializes as `to::Plain`.
57fn strip_leading_scope(name: &str) -> String {
58    name.strip_prefix("::").unwrap_or(name).into()
59}
60
61/// Extensibility kind (§7.2.2.4). Simpler representation than the
62/// flag bits: exactly one of three values.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum Extensibility {
65    /// `@final` — the type cannot be extended.
66    Final,
67    /// `@appendable` — new fields at the end (default).
68    Appendable,
69    /// `@mutable` — each field with an explicit @id; arbitrary evolution.
70    Mutable,
71}
72
73impl Default for Extensibility {
74    fn default() -> Self {
75        Self::Appendable
76    }
77}
78
79impl Extensibility {
80    const fn to_flag_bits(self) -> u16 {
81        match self {
82            Self::Final => StructTypeFlag::IS_FINAL,
83            Self::Appendable => StructTypeFlag::IS_APPENDABLE,
84            Self::Mutable => StructTypeFlag::IS_MUTABLE,
85        }
86    }
87}
88
89/// Einstiegspunkt.
90pub struct TypeObjectBuilder;
91
92impl TypeObjectBuilder {
93    /// Starts a struct builder with the given qualified
94    /// name (e.g. "::sensors::Chatter").
95    #[must_use]
96    pub fn struct_type(name: impl Into<String>) -> StructBuilder {
97        StructBuilder {
98            name: name.into(),
99            extensibility: Extensibility::default(),
100            nested: false,
101            autoid_hash: false,
102            base_type: TypeIdentifier::None,
103            members: Vec::new(),
104        }
105    }
106
107    /// Starts an enum builder.
108    #[must_use]
109    pub fn enum_type(name: impl Into<String>) -> EnumBuilder {
110        EnumBuilder {
111            name: name.into(),
112            bit_bound: 32,
113            literals: Vec::new(),
114        }
115    }
116
117    /// Starts an alias builder.
118    #[must_use]
119    pub fn alias(name: impl Into<String>, target: TypeIdentifier) -> AliasBuilder {
120        AliasBuilder {
121            name: name.into(),
122            related_type: target,
123        }
124    }
125}
126
127// ============================================================================
128// Struct
129// ============================================================================
130
131/// Builder for struct types.
132pub struct StructBuilder {
133    name: String,
134    extensibility: Extensibility,
135    nested: bool,
136    autoid_hash: bool,
137    base_type: TypeIdentifier,
138    members: Vec<StructMemberSpec>,
139}
140
141/// Inner state of a struct member — set via [`StructMemberBuilder`].
142pub struct StructMemberSpec {
143    name: String,
144    type_id: TypeIdentifier,
145    explicit_id: Option<u32>,
146    flags: u16,
147    unit: Option<String>,
148    min: Option<Vec<u8>>,
149    max: Option<Vec<u8>>,
150    hash_id: Option<String>,
151    default_value: Option<String>,
152}
153
154/// Fluent builder for member attributes.
155pub struct StructMemberBuilder<'a> {
156    spec: &'a mut StructMemberSpec,
157}
158
159impl StructMemberBuilder<'_> {
160    /// Marks a member as `@key`.
161    #[must_use]
162    pub fn key(self) -> Self {
163        self.spec.flags |= StructMemberFlag::IS_KEY;
164        self
165    }
166
167    /// `@optional`.
168    #[must_use]
169    pub fn optional(self) -> Self {
170        self.spec.flags |= StructMemberFlag::IS_OPTIONAL;
171        self
172    }
173
174    /// `@must_understand`.
175    #[must_use]
176    pub fn must_understand(self) -> Self {
177        self.spec.flags |= StructMemberFlag::IS_MUST_UNDERSTAND;
178        self
179    }
180
181    /// `@external` — indirect storage.
182    #[must_use]
183    pub fn external(self) -> Self {
184        self.spec.flags |= StructMemberFlag::IS_EXTERNAL;
185        self
186    }
187
188    /// Explizite `@id(n)`.
189    #[must_use]
190    pub fn id(self, id: u32) -> Self {
191        self.spec.explicit_id = Some(id);
192        self
193    }
194
195    /// `@unit("...")` — nur in Complete sichtbar.
196    #[must_use]
197    pub fn unit(self, unit: impl Into<String>) -> Self {
198        self.spec.unit = Some(unit.into());
199        self
200    }
201
202    /// `@min(val)` — opaque bytes.
203    #[must_use]
204    pub fn min_bytes(self, min: Vec<u8>) -> Self {
205        self.spec.min = Some(min);
206        self
207    }
208
209    /// `@max(val)` — opaque bytes.
210    #[must_use]
211    pub fn max_bytes(self, max: Vec<u8>) -> Self {
212        self.spec.max = Some(max);
213        self
214    }
215
216    /// `@hashid("name")`.
217    #[must_use]
218    pub fn hash_id(self, name: impl Into<String>) -> Self {
219        self.spec.hash_id = Some(name.into());
220        self
221    }
222
223    /// `@default(val)` — XTypes 1.3 §7.2.4.4.4.4.9. The value as a string
224    /// (encoder-converted on wire encode). Carried in the complete
225    /// TypeObject via `AppliedBuiltinMemberAnnotations.default_value`.
226    #[must_use]
227    pub fn set_member_default(self, value: impl Into<String>) -> Self {
228        self.spec.default_value = Some(value.into());
229        self
230    }
231}
232
233impl StructBuilder {
234    /// Sets the extensibility (default: Appendable).
235    #[must_use]
236    pub fn extensibility(mut self, ext: Extensibility) -> Self {
237        self.extensibility = ext;
238        self
239    }
240
241    /// Marks as `@nested`.
242    #[must_use]
243    pub fn nested(mut self) -> Self {
244        self.nested = true;
245        self
246    }
247
248    /// Aktiviert `@autoid(HASH)`.
249    #[must_use]
250    pub fn autoid_hash(mut self) -> Self {
251        self.autoid_hash = true;
252        self
253    }
254
255    /// Sets a base type for inheritance.
256    #[must_use]
257    pub fn base(mut self, base: TypeIdentifier) -> Self {
258        self.base_type = base;
259        self
260    }
261
262    /// Adds a member.
263    ///
264    /// The callback receives a `StructMemberBuilder` to set flags
265    /// and annotations.
266    #[must_use]
267    pub fn member<F>(mut self, name: impl Into<String>, ty: TypeIdentifier, f: F) -> Self
268    where
269        F: FnOnce(StructMemberBuilder<'_>) -> StructMemberBuilder<'_>,
270    {
271        let mut spec = StructMemberSpec {
272            name: name.into(),
273            type_id: ty,
274            explicit_id: None,
275            // Default TryConstructKind = DISCARD (XTypes 1.3 §7.2.2.4.4.4.4 /
276            // §7.3.1.2.1.1 bits[0..2]=01): every member carries this unless
277            // overridden. Matches the vendors' TypeObject member_flags; the old
278            // default of 0 (no TryConstruct bits) diverged from all three.
279            flags: StructMemberFlag::TRY_CONSTRUCT1,
280            unit: None,
281            min: None,
282            max: None,
283            hash_id: None,
284            default_value: None,
285        };
286        let _ = f(StructMemberBuilder { spec: &mut spec });
287        self.members.push(spec);
288        self
289    }
290
291    fn struct_flags(&self) -> StructTypeFlag {
292        let mut bits = self.extensibility.to_flag_bits();
293        if self.nested {
294            bits |= StructTypeFlag::IS_NESTED;
295        }
296        if self.autoid_hash {
297            bits |= StructTypeFlag::IS_AUTOID_HASH;
298        }
299        StructTypeFlag(bits)
300    }
301
302    /// Member-ID assignment:
303    /// - explicit_id, if set
304    /// - otherwise autoid-hash (first 4 bytes SHA-256 over the name — simplified)
305    /// - otherwise sequential from 1
306    fn resolve_member_ids(&self) -> Vec<u32> {
307        let mut ids = Vec::with_capacity(self.members.len());
308        // XTypes 1.3 §7.2.2.4.9: sequential `@autoid` assigns the FIRST member
309        // id 0, the next 1, … (NOT 1-based). This matches the vendors' TypeObject
310        // member ids AND ZeroDDS's own data-wire codegen (idl-rust struct_emit
311        // uses the 0-based positional index for @mutable EMHEADER ids) — the old
312        // 1-based start made the TypeObject inconsistent with the data wire.
313        let mut next_seq: u32 = 0;
314        for spec in &self.members {
315            let id = if let Some(explicit) = spec.explicit_id {
316                explicit
317            } else if self.autoid_hash {
318                // XTypes §7.2.2.4.9 + §7.3.1.2.1.1: for `@autoid(HASH)`
319                // the member ID is derived from bits [4..28) (=24 bits) of the
320                // first 4 MD5 bytes of the member name. Bits [0..4)
321                // are reserved (E-flag etc.) and not used.
322                let nh = NameHash::from_name(&spec.name);
323                (u32::from_le_bytes(nh.0) >> 4) & 0x00FF_FFFF
324            } else {
325                let v = next_seq;
326                next_seq += 1;
327                v
328            };
329            ids.push(id);
330        }
331        ids
332    }
333
334    fn member_common(spec: &StructMemberSpec, id: u32) -> CommonStructMember {
335        CommonStructMember {
336            member_id: id,
337            member_flags: StructMemberFlag(spec.flags),
338            member_type_id: spec.type_id.clone(),
339        }
340    }
341
342    fn member_detail(spec: &StructMemberSpec) -> CompleteMemberDetail {
343        CompleteMemberDetail {
344            name: spec.name.clone(),
345            ann_builtin: AppliedBuiltinMemberAnnotations {
346                unit: spec.unit.clone(),
347                min: spec.min.clone(),
348                max: spec.max.clone(),
349                hash_id: spec.hash_id.clone(),
350                default_value: spec.default_value.clone(),
351            },
352            ann_custom: OptionalAppliedAnnotationSeq::default(),
353        }
354    }
355
356    /// Builds a `MinimalStructType`.
357    #[must_use]
358    pub fn build_minimal(&self) -> MinimalStructType {
359        let ids = self.resolve_member_ids();
360        let member_seq = self
361            .members
362            .iter()
363            .zip(ids.iter())
364            .map(|(spec, id)| MinimalStructMember {
365                common: Self::member_common(spec, *id),
366                detail: NameHash::from_name(&spec.name),
367            })
368            .collect();
369        MinimalStructType {
370            struct_flags: self.struct_flags(),
371            header: MinimalStructHeader {
372                base_type: self.base_type.clone(),
373            },
374            member_seq,
375        }
376    }
377
378    /// Builds a `CompleteStructType`.
379    #[must_use]
380    pub fn build_complete(&self) -> CompleteStructType {
381        let ids = self.resolve_member_ids();
382        let member_seq = self
383            .members
384            .iter()
385            .zip(ids.iter())
386            .map(|(spec, id)| CompleteStructMember {
387                common: Self::member_common(spec, *id),
388                detail: Self::member_detail(spec),
389            })
390            .collect();
391        CompleteStructType {
392            struct_flags: self.struct_flags(),
393            header: CompleteStructHeader {
394                base_type: self.base_type.clone(),
395                detail: CompleteTypeDetail {
396                    ann_builtin: AppliedBuiltinTypeAnnotations::default(),
397                    ann_custom: OptionalAppliedAnnotationSeq::default(),
398                    type_name: strip_leading_scope(&self.name),
399                },
400            },
401            member_seq,
402        }
403    }
404}
405
406// ============================================================================
407// Enum
408// ============================================================================
409
410/// Builder for enumerated types.
411pub struct EnumBuilder {
412    name: String,
413    bit_bound: u16,
414    literals: Vec<EnumLiteralSpec>,
415}
416
417struct EnumLiteralSpec {
418    name: String,
419    value: i32,
420    is_default: bool,
421}
422
423impl EnumBuilder {
424    /// Sets the bit width (8/16/32, default 32).
425    #[must_use]
426    pub fn bit_bound(mut self, bits: u16) -> Self {
427        self.bit_bound = bits;
428        self
429    }
430
431    /// Adds a literal.
432    #[must_use]
433    pub fn literal(mut self, name: impl Into<String>, value: i32) -> Self {
434        self.literals.push(EnumLiteralSpec {
435            name: name.into(),
436            value,
437            is_default: false,
438        });
439        self
440    }
441
442    /// Adds the default literal (`@default_literal`).
443    #[must_use]
444    pub fn default_literal(mut self, name: impl Into<String>, value: i32) -> Self {
445        self.literals.push(EnumLiteralSpec {
446            name: name.into(),
447            value,
448            is_default: true,
449        });
450        self
451    }
452
453    /// Builds a `MinimalEnumeratedType`.
454    #[must_use]
455    pub fn build_minimal(&self) -> MinimalEnumeratedType {
456        MinimalEnumeratedType {
457            enum_flags: EnumTypeFlag::default(),
458            header: MinimalEnumeratedHeader {
459                common: CommonEnumeratedHeader {
460                    bit_bound: self.bit_bound,
461                },
462            },
463            literal_seq: self
464                .literals
465                .iter()
466                .map(|l| MinimalEnumeratedLiteral {
467                    common: CommonEnumeratedLiteral {
468                        value: l.value,
469                        flags: EnumLiteralFlag(if l.is_default {
470                            EnumLiteralFlag::IS_DEFAULT_LITERAL
471                        } else {
472                            0
473                        }),
474                    },
475                    detail: NameHash::from_name(&l.name),
476                })
477                .collect(),
478        }
479    }
480
481    /// Builds a `CompleteEnumeratedType`.
482    #[must_use]
483    pub fn build_complete(&self) -> CompleteEnumeratedType {
484        CompleteEnumeratedType {
485            enum_flags: EnumTypeFlag::default(),
486            header: CompleteEnumeratedHeader {
487                common: CommonEnumeratedHeader {
488                    bit_bound: self.bit_bound,
489                },
490                detail: CompleteTypeDetail {
491                    ann_builtin: AppliedBuiltinTypeAnnotations::default(),
492                    ann_custom: OptionalAppliedAnnotationSeq::default(),
493                    type_name: strip_leading_scope(&self.name),
494                },
495            },
496            literal_seq: self
497                .literals
498                .iter()
499                .map(|l| CompleteEnumeratedLiteral {
500                    common: CommonEnumeratedLiteral {
501                        value: l.value,
502                        flags: EnumLiteralFlag(if l.is_default {
503                            EnumLiteralFlag::IS_DEFAULT_LITERAL
504                        } else {
505                            0
506                        }),
507                    },
508                    detail: CompleteMemberDetail {
509                        name: l.name.clone(),
510                        ann_builtin: AppliedBuiltinMemberAnnotations::default(),
511                        ann_custom: OptionalAppliedAnnotationSeq::default(),
512                    },
513                })
514                .collect(),
515        }
516    }
517}
518
519// ============================================================================
520// Alias
521// ============================================================================
522
523/// Builder for alias/typedef.
524pub struct AliasBuilder {
525    name: String,
526    related_type: TypeIdentifier,
527}
528
529impl AliasBuilder {
530    /// Minimal-Alias.
531    #[must_use]
532    pub fn build_minimal(&self) -> MinimalAliasType {
533        MinimalAliasType {
534            alias_flags: AliasTypeFlag::default(),
535            body: MinimalAliasBody {
536                common: CommonAliasBody {
537                    related_flags: AliasMemberFlag::default(),
538                    related_type: self.related_type.clone(),
539                },
540            },
541        }
542    }
543
544    /// Complete-Alias.
545    #[must_use]
546    pub fn build_complete(&self) -> CompleteAliasType {
547        CompleteAliasType {
548            alias_flags: AliasTypeFlag::default(),
549            header: CompleteAliasHeader {
550                detail: CompleteTypeDetail {
551                    ann_builtin: AppliedBuiltinTypeAnnotations::default(),
552                    ann_custom: OptionalAppliedAnnotationSeq::default(),
553                    type_name: strip_leading_scope(&self.name),
554                },
555            },
556            body: CompleteAliasBody {
557                related_flags: AliasMemberFlag::default(),
558                related_type: self.related_type.clone(),
559                ann_builtin: AppliedBuiltinMemberAnnotations::default(),
560                ann_custom: OptionalAppliedAnnotationSeq::default(),
561            },
562        }
563    }
564}
565
566// ============================================================================
567// Union
568// ============================================================================
569
570/// Builder for union types.
571pub struct UnionBuilder {
572    name: String,
573    extensibility: Extensibility,
574    discriminator_type: TypeIdentifier,
575    cases: Vec<UnionCaseSpec>,
576}
577
578struct UnionCaseSpec {
579    name: String,
580    member_id: Option<u32>,
581    type_id: TypeIdentifier,
582    labels: Vec<i32>,
583    is_default: bool,
584}
585
586impl UnionBuilder {
587    /// Sets the extensibility (default Appendable).
588    #[must_use]
589    pub fn extensibility(mut self, ext: Extensibility) -> Self {
590        self.extensibility = ext;
591        self
592    }
593
594    /// Adds a case member.
595    #[must_use]
596    pub fn case(mut self, name: impl Into<String>, ty: TypeIdentifier, labels: Vec<i32>) -> Self {
597        self.cases.push(UnionCaseSpec {
598            name: name.into(),
599            member_id: None,
600            type_id: ty,
601            labels,
602            is_default: false,
603        });
604        self
605    }
606
607    /// Adds the default case.
608    #[must_use]
609    pub fn default_case(mut self, name: impl Into<String>, ty: TypeIdentifier) -> Self {
610        self.cases.push(UnionCaseSpec {
611            name: name.into(),
612            member_id: None,
613            type_id: ty,
614            labels: Vec::new(),
615            is_default: true,
616        });
617        self
618    }
619
620    fn union_flags(&self) -> crate::type_object::flags::UnionTypeFlag {
621        use crate::type_object::flags::UnionTypeFlag;
622        // UnionTypeFlag analogously uses the struct bits for
623        // extensibility (§7.3.4.5) — reuse the bit positions.
624        UnionTypeFlag(match self.extensibility {
625            Extensibility::Final => StructTypeFlag::IS_FINAL,
626            Extensibility::Appendable => StructTypeFlag::IS_APPENDABLE,
627            Extensibility::Mutable => StructTypeFlag::IS_MUTABLE,
628        })
629    }
630
631    fn resolve_case_ids(&self) -> Vec<u32> {
632        let mut ids = Vec::with_capacity(self.cases.len());
633        // Sequential @autoid is 0-based (§7.2.2.4.9) — byte-verified against
634        // Cyclone + FastDDS union goldens.
635        let mut next: u32 = 0;
636        for c in &self.cases {
637            ids.push(c.member_id.unwrap_or_else(|| {
638                let v = next;
639                next += 1;
640                v
641            }));
642        }
643        ids
644    }
645
646    /// Builds a `MinimalUnionType`.
647    #[must_use]
648    pub fn build_minimal(&self) -> crate::type_object::minimal::MinimalUnionType {
649        use crate::type_object::common::CommonUnionMember;
650        use crate::type_object::flags::{UnionDiscriminatorFlag, UnionMemberFlag};
651        use crate::type_object::minimal::{
652            CommonDiscriminatorMember, MinimalDiscriminatorMember, MinimalUnionMember,
653            MinimalUnionType,
654        };
655        let ids = self.resolve_case_ids();
656        let member_seq = self
657            .cases
658            .iter()
659            .zip(ids.iter())
660            .map(|(c, id)| MinimalUnionMember {
661                common: CommonUnionMember {
662                    member_id: *id,
663                    member_flags: UnionMemberFlag(
664                        UnionMemberFlag::TRY_CONSTRUCT1
665                            | if c.is_default {
666                                UnionMemberFlag::IS_DEFAULT
667                            } else {
668                                0
669                            },
670                    ),
671                    type_id: c.type_id.clone(),
672                    label_seq: c.labels.clone(),
673                },
674                detail: NameHash::from_name(&c.name),
675            })
676            .collect();
677        MinimalUnionType {
678            union_flags: self.union_flags(),
679            discriminator: MinimalDiscriminatorMember {
680                common: CommonDiscriminatorMember {
681                    member_flags: UnionDiscriminatorFlag::discriminator_default(),
682                    type_id: self.discriminator_type.clone(),
683                },
684            },
685            member_seq,
686        }
687    }
688
689    /// Builds a `CompleteUnionType`.
690    #[must_use]
691    pub fn build_complete(&self) -> crate::type_object::complete::CompleteUnionType {
692        use crate::type_object::common::CommonUnionMember;
693        use crate::type_object::complete::{
694            CompleteDiscriminatorMember, CompleteUnionHeader, CompleteUnionMember,
695            CompleteUnionType,
696        };
697        use crate::type_object::flags::{UnionDiscriminatorFlag, UnionMemberFlag};
698        use crate::type_object::minimal::CommonDiscriminatorMember;
699        let ids = self.resolve_case_ids();
700        let member_seq = self
701            .cases
702            .iter()
703            .zip(ids.iter())
704            .map(|(c, id)| CompleteUnionMember {
705                common: CommonUnionMember {
706                    member_id: *id,
707                    member_flags: UnionMemberFlag(
708                        UnionMemberFlag::TRY_CONSTRUCT1
709                            | if c.is_default {
710                                UnionMemberFlag::IS_DEFAULT
711                            } else {
712                                0
713                            },
714                    ),
715                    type_id: c.type_id.clone(),
716                    label_seq: c.labels.clone(),
717                },
718                detail: CompleteMemberDetail {
719                    name: c.name.clone(),
720                    ann_builtin: AppliedBuiltinMemberAnnotations::default(),
721                    ann_custom: OptionalAppliedAnnotationSeq::default(),
722                },
723            })
724            .collect();
725        CompleteUnionType {
726            union_flags: self.union_flags(),
727            header: CompleteUnionHeader {
728                detail: CompleteTypeDetail {
729                    ann_builtin: AppliedBuiltinTypeAnnotations::default(),
730                    ann_custom: OptionalAppliedAnnotationSeq::default(),
731                    type_name: strip_leading_scope(&self.name),
732                },
733            },
734            discriminator: CompleteDiscriminatorMember {
735                common: CommonDiscriminatorMember {
736                    member_flags: UnionDiscriminatorFlag::discriminator_default(),
737                    type_id: self.discriminator_type.clone(),
738                },
739                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
740                ann_custom: OptionalAppliedAnnotationSeq::default(),
741            },
742            member_seq,
743        }
744    }
745}
746
747// ============================================================================
748// Sequence / Array / Map
749// ============================================================================
750
751/// Builder for `sequence<T, N>`.
752pub struct SequenceBuilder {
753    element: TypeIdentifier,
754    bound: u32,
755}
756
757impl SequenceBuilder {
758    /// Builds a MinimalSequenceType.
759    #[must_use]
760    pub fn build_minimal(&self) -> crate::type_object::minimal::MinimalSequenceType {
761        use crate::type_object::flags::{CollectionElementFlag, CollectionTypeFlag};
762        use crate::type_object::minimal::{
763            CommonCollectionElement, MinimalCollectionElement, MinimalSequenceType,
764        };
765        MinimalSequenceType {
766            collection_flag: CollectionTypeFlag::default(),
767            bound: self.bound,
768            element: MinimalCollectionElement {
769                common: CommonCollectionElement {
770                    element_flags: CollectionElementFlag::default(),
771                    type_id: self.element.clone(),
772                },
773            },
774        }
775    }
776}
777
778/// Builder for `T[D1, D2, ...]`.
779pub struct ArrayBuilder {
780    element: TypeIdentifier,
781    dimensions: Vec<u32>,
782}
783
784impl ArrayBuilder {
785    /// Builds a MinimalArrayType.
786    #[must_use]
787    pub fn build_minimal(&self) -> crate::type_object::minimal::MinimalArrayType {
788        use crate::type_object::flags::{CollectionElementFlag, CollectionTypeFlag};
789        use crate::type_object::minimal::{
790            CommonCollectionElement, MinimalArrayType, MinimalCollectionElement,
791        };
792        MinimalArrayType {
793            collection_flag: CollectionTypeFlag::default(),
794            bound_seq: self.dimensions.clone(),
795            element: MinimalCollectionElement {
796                common: CommonCollectionElement {
797                    element_flags: CollectionElementFlag::default(),
798                    type_id: self.element.clone(),
799                },
800            },
801        }
802    }
803}
804
805/// Validation error when building collection types.
806#[derive(Debug, Clone, PartialEq, Eq)]
807pub enum BuilderError {
808    /// MUTABLE extensibility is not allowed on maps
809    /// (XTypes 1.3 §7.4.3.5.3 Rules 11-16). The spec allows only FINAL/
810    /// APPENDABLE; MUTABLE has historically been silently treated as
811    /// APPENDABLE. This error prevents the silent demotion.
812    MutableMapExtensibilityNotAllowed,
813}
814
815/// Builder for `map<K, V, N>`.
816pub struct MapBuilder {
817    key: TypeIdentifier,
818    value: TypeIdentifier,
819    bound: u32,
820}
821
822impl MapBuilder {
823    /// Validating builder: allows only FINAL/APPENDABLE extensibility.
824    /// MUTABLE wirft `BuilderError::MutableMapExtensibilityNotAllowed`
825    /// (XTypes 1.3 §7.4.3.5.3 Rules 11-16).
826    ///
827    /// # Errors
828    /// `MutableMapExtensibilityNotAllowed` if `ext == Mutable`.
829    pub fn add_map_member(
830        self,
831        ext: Extensibility,
832    ) -> Result<crate::type_object::minimal::MinimalMapType, BuilderError> {
833        if matches!(ext, Extensibility::Mutable) {
834            return Err(BuilderError::MutableMapExtensibilityNotAllowed);
835        }
836        Ok(self.build_minimal())
837    }
838
839    /// Builds a MinimalMapType.
840    #[must_use]
841    pub fn build_minimal(&self) -> crate::type_object::minimal::MinimalMapType {
842        use crate::type_object::flags::{CollectionElementFlag, CollectionTypeFlag};
843        use crate::type_object::minimal::{
844            CommonCollectionElement, MinimalCollectionElement, MinimalMapType,
845        };
846        let element = MinimalCollectionElement {
847            common: CommonCollectionElement {
848                element_flags: CollectionElementFlag::default(),
849                type_id: self.value.clone(),
850            },
851        };
852        let key = MinimalCollectionElement {
853            common: CommonCollectionElement {
854                element_flags: CollectionElementFlag::default(),
855                type_id: self.key.clone(),
856            },
857        };
858        MinimalMapType {
859            collection_flag: CollectionTypeFlag::default(),
860            bound: self.bound,
861            key,
862            element,
863        }
864    }
865}
866
867// ============================================================================
868// Bitmask / Bitset
869// ============================================================================
870
871/// Builder for `bitmask` types.
872pub struct BitmaskBuilder {
873    name: String,
874    bit_bound: u16,
875    flags: Vec<(String, u16)>,
876}
877
878impl BitmaskBuilder {
879    /// Bit width (default 32).
880    #[must_use]
881    pub fn bit_bound(mut self, bits: u16) -> Self {
882        self.bit_bound = bits;
883        self
884    }
885
886    /// Adds a bit flag.
887    #[must_use]
888    pub fn flag(mut self, name: impl Into<String>, position: u16) -> Self {
889        self.flags.push((name.into(), position));
890        self
891    }
892
893    /// Builds a MinimalBitmaskType.
894    #[must_use]
895    pub fn build_minimal(&self) -> crate::type_object::minimal::MinimalBitmaskType {
896        use crate::type_object::flags::{BitflagFlag, BitmaskTypeFlag};
897        use crate::type_object::minimal::{CommonBitflag, MinimalBitflag, MinimalBitmaskType};
898        MinimalBitmaskType {
899            bitmask_flags: BitmaskTypeFlag::default(),
900            bit_bound: self.bit_bound,
901            flag_seq: self
902                .flags
903                .iter()
904                .map(|(n, p)| MinimalBitflag {
905                    common: CommonBitflag {
906                        position: *p,
907                        flags: BitflagFlag::default(),
908                    },
909                    detail: NameHash::from_name(n),
910                })
911                .collect(),
912        }
913    }
914
915    /// Builds a `CompleteBitmaskType` (with names + annotation placeholders).
916    #[must_use]
917    pub fn build_complete(&self) -> crate::type_object::complete::CompleteBitmaskType {
918        use crate::type_object::common::{
919            AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteMemberDetail,
920            CompleteTypeDetail, OptionalAppliedAnnotationSeq,
921        };
922        use crate::type_object::complete::{CompleteBitflag, CompleteBitmaskType};
923        use crate::type_object::flags::{BitflagFlag, BitmaskTypeFlag};
924        CompleteBitmaskType {
925            bitmask_flags: BitmaskTypeFlag::default(),
926            bit_bound: self.bit_bound,
927            detail: CompleteTypeDetail {
928                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
929                ann_custom: OptionalAppliedAnnotationSeq::default(),
930                type_name: strip_leading_scope(&self.name),
931            },
932            flag_seq: self
933                .flags
934                .iter()
935                .map(|(n, p)| CompleteBitflag {
936                    common: crate::type_object::minimal::CommonBitflag {
937                        position: *p,
938                        flags: BitflagFlag::default(),
939                    },
940                    detail: CompleteMemberDetail {
941                        name: n.clone(),
942                        ann_builtin: AppliedBuiltinMemberAnnotations::default(),
943                        ann_custom: OptionalAppliedAnnotationSeq::default(),
944                    },
945                })
946                .collect(),
947        }
948    }
949
950    /// Getter — mostly used via [`build_complete`].
951    #[must_use]
952    pub fn name(&self) -> &str {
953        &self.name
954    }
955}
956
957/// Builder for `bitset` types.
958pub struct BitsetBuilder {
959    name: String,
960    fields: Vec<BitfieldSpec>,
961}
962
963struct BitfieldSpec {
964    name: String,
965    position: u16,
966    bitcount: u8,
967    holder_type: u8,
968}
969
970impl BitsetBuilder {
971    /// Adds a bitfield.
972    #[must_use]
973    pub fn field(
974        mut self,
975        name: impl Into<String>,
976        position: u16,
977        bitcount: u8,
978        holder_type: u8,
979    ) -> Self {
980        self.fields.push(BitfieldSpec {
981            name: name.into(),
982            position,
983            bitcount,
984            holder_type,
985        });
986        self
987    }
988
989    /// Builds a MinimalBitsetType.
990    #[must_use]
991    pub fn build_minimal(&self) -> crate::type_object::minimal::MinimalBitsetType {
992        use crate::type_object::flags::{BitfieldFlag, BitsetTypeFlag};
993        use crate::type_object::minimal::{CommonBitfield, MinimalBitfield, MinimalBitsetType};
994        MinimalBitsetType {
995            bitset_flags: BitsetTypeFlag::default(),
996            field_seq: self
997                .fields
998                .iter()
999                .map(|f| MinimalBitfield {
1000                    common: CommonBitfield {
1001                        position: f.position,
1002                        flags: BitfieldFlag::default(),
1003                        bitcount: f.bitcount,
1004                        holder_type: f.holder_type,
1005                    },
1006                    name_hash: NameHash::from_name(&f.name),
1007                })
1008                .collect(),
1009        }
1010    }
1011
1012    /// Builds a `CompleteBitsetType` (with names + annotation placeholders).
1013    #[must_use]
1014    pub fn build_complete(&self) -> crate::type_object::complete::CompleteBitsetType {
1015        use crate::type_object::common::{
1016            AppliedBuiltinMemberAnnotations, AppliedBuiltinTypeAnnotations, CompleteMemberDetail,
1017            CompleteTypeDetail, OptionalAppliedAnnotationSeq,
1018        };
1019        use crate::type_object::complete::{CompleteBitfield, CompleteBitsetType};
1020        use crate::type_object::flags::{BitfieldFlag, BitsetTypeFlag};
1021        CompleteBitsetType {
1022            bitset_flags: BitsetTypeFlag::default(),
1023            detail: CompleteTypeDetail {
1024                ann_builtin: AppliedBuiltinTypeAnnotations::default(),
1025                ann_custom: OptionalAppliedAnnotationSeq::default(),
1026                type_name: strip_leading_scope(&self.name),
1027            },
1028            field_seq: self
1029                .fields
1030                .iter()
1031                .map(|f| CompleteBitfield {
1032                    common: crate::type_object::minimal::CommonBitfield {
1033                        position: f.position,
1034                        flags: BitfieldFlag::default(),
1035                        bitcount: f.bitcount,
1036                        holder_type: f.holder_type,
1037                    },
1038                    detail: CompleteMemberDetail {
1039                        name: f.name.clone(),
1040                        ann_builtin: AppliedBuiltinMemberAnnotations::default(),
1041                        ann_custom: OptionalAppliedAnnotationSeq::default(),
1042                    },
1043                })
1044                .collect(),
1045        }
1046    }
1047
1048    /// Getter — mostly used via [`build_complete`].
1049    #[must_use]
1050    pub fn name(&self) -> &str {
1051        &self.name
1052    }
1053}
1054
1055// ============================================================================
1056// Entry points for the new builders
1057// ============================================================================
1058
1059impl TypeObjectBuilder {
1060    /// Starts a union builder. `discriminator_type` is typically
1061    /// an enum or an integer primitive.
1062    #[must_use]
1063    pub fn union_type(name: impl Into<String>, discriminator_type: TypeIdentifier) -> UnionBuilder {
1064        UnionBuilder {
1065            name: name.into(),
1066            extensibility: Extensibility::default(),
1067            discriminator_type,
1068            cases: Vec::new(),
1069        }
1070    }
1071
1072    /// Starts a sequence builder. `bound=0` = unbounded.
1073    #[must_use]
1074    pub fn sequence(element: TypeIdentifier, bound: u32) -> SequenceBuilder {
1075        SequenceBuilder { element, bound }
1076    }
1077
1078    /// Starts an array builder with the given list of dimensions.
1079    #[must_use]
1080    pub fn array(element: TypeIdentifier, dimensions: Vec<u32>) -> ArrayBuilder {
1081        ArrayBuilder {
1082            element,
1083            dimensions,
1084        }
1085    }
1086
1087    /// Starts a map builder.
1088    #[must_use]
1089    pub fn map(key: TypeIdentifier, value: TypeIdentifier, bound: u32) -> MapBuilder {
1090        MapBuilder { key, value, bound }
1091    }
1092
1093    /// Starts a bitmask builder.
1094    #[must_use]
1095    pub fn bitmask(name: impl Into<String>) -> BitmaskBuilder {
1096        BitmaskBuilder {
1097            name: name.into(),
1098            bit_bound: 32,
1099            flags: Vec::new(),
1100        }
1101    }
1102
1103    /// Starts a bitset builder.
1104    #[must_use]
1105    pub fn bitset(name: impl Into<String>) -> BitsetBuilder {
1106        BitsetBuilder {
1107            name: name.into(),
1108            fields: Vec::new(),
1109        }
1110    }
1111}
1112
1113// ============================================================================
1114// Tests
1115// ============================================================================
1116
1117#[cfg(test)]
1118#[allow(clippy::unwrap_used)]
1119mod tests {
1120    use super::*;
1121    use crate::type_identifier::PrimitiveKind;
1122    use crate::type_object::flags::StructTypeFlag;
1123
1124    #[test]
1125    fn struct_builder_basic_with_two_members() {
1126        let st = TypeObjectBuilder::struct_type("::chat::Chatter")
1127            .member(
1128                "sensor_id",
1129                TypeIdentifier::Primitive(PrimitiveKind::Int64),
1130                |m| m.key(),
1131            )
1132            .member("text", TypeIdentifier::String8Small { bound: 255 }, |m| m)
1133            .build_minimal();
1134
1135        assert_eq!(st.member_seq.len(), 2);
1136        // Sequential @autoid is 0-based (XTypes §7.2.2.4.9): first member = 0.
1137        assert_eq!(st.member_seq[0].common.member_id, 0);
1138        assert!(
1139            st.member_seq[0]
1140                .common
1141                .member_flags
1142                .has(StructMemberFlag::IS_KEY)
1143        );
1144        assert_eq!(st.member_seq[1].common.member_id, 1);
1145        // NameHash deterministisch: MD5("sensor_id")[0..4]
1146        assert_eq!(st.member_seq[0].detail, NameHash::from_name("sensor_id"));
1147        assert_eq!(st.member_seq[1].detail, NameHash::from_name("text"));
1148    }
1149
1150    #[test]
1151    fn struct_builder_explicit_ids_respected() {
1152        let st = TypeObjectBuilder::struct_type("::X")
1153            .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1154                m.id(100)
1155            })
1156            .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1157                m.id(200)
1158            })
1159            .build_minimal();
1160        assert_eq!(st.member_seq[0].common.member_id, 100);
1161        assert_eq!(st.member_seq[1].common.member_id, 200);
1162    }
1163
1164    #[test]
1165    fn mutable_map_extensibility_is_error() {
1166        // §7.4.3.5.3 Rules 11-16 — a MUTABLE map is forbidden (silent demotion).
1167        let res = TypeObjectBuilder::map(
1168            TypeIdentifier::Primitive(PrimitiveKind::Int32),
1169            TypeIdentifier::Primitive(PrimitiveKind::Int32),
1170            10,
1171        )
1172        .add_map_member(Extensibility::Mutable);
1173        assert!(matches!(
1174            res,
1175            Err(BuilderError::MutableMapExtensibilityNotAllowed)
1176        ));
1177    }
1178
1179    #[test]
1180    fn appendable_map_extensibility_is_ok() {
1181        let res = TypeObjectBuilder::map(
1182            TypeIdentifier::Primitive(PrimitiveKind::Int32),
1183            TypeIdentifier::Primitive(PrimitiveKind::Int32),
1184            10,
1185        )
1186        .add_map_member(Extensibility::Appendable);
1187        assert!(res.is_ok());
1188    }
1189
1190    #[test]
1191    fn final_map_extensibility_is_ok() {
1192        let res = TypeObjectBuilder::map(
1193            TypeIdentifier::Primitive(PrimitiveKind::Int32),
1194            TypeIdentifier::Primitive(PrimitiveKind::Int32),
1195            10,
1196        )
1197        .add_map_member(Extensibility::Final);
1198        assert!(res.is_ok());
1199    }
1200
1201    #[test]
1202    fn member_with_explicit_default_used_when_field_missing() {
1203        // §7.2.4.4.4.4.9 — `set_member_default` sets
1204        // `AppliedBuiltinMemberAnnotations.default_value`. The test ensures
1205        // that the value passes through the complete pipeline.
1206        let st = TypeObjectBuilder::struct_type("::S")
1207            .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1208                m.set_member_default("42")
1209            })
1210            .build_complete();
1211        assert_eq!(
1212            st.member_seq[0].detail.ann_builtin.default_value.as_deref(),
1213            Some("42")
1214        );
1215    }
1216
1217    #[test]
1218    fn default_overrides_implicit_zero() {
1219        // Encoder-side hint: an explicit `@default("99")` must appear in the
1220        // complete TypeObject — the decoder uses it
1221        // when the field is missing for optional-mutable members, instead of the
1222        // type-implicit zero.
1223        let st = TypeObjectBuilder::struct_type("::S")
1224            .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1225                m.set_member_default("99")
1226            })
1227            .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1228            .build_complete();
1229        assert_eq!(
1230            st.member_seq[0].detail.ann_builtin.default_value.as_deref(),
1231            Some("99")
1232        );
1233        assert_eq!(st.member_seq[1].detail.ann_builtin.default_value, None);
1234    }
1235
1236    #[test]
1237    fn default_value_roundtrips_via_encode_decode() {
1238        use crate::type_object::common::AppliedBuiltinMemberAnnotations;
1239        use alloc::vec;
1240        use zerodds_cdr::{BufferReader, BufferWriter, Endianness};
1241        let ann = AppliedBuiltinMemberAnnotations {
1242            unit: Some("meters".into()),
1243            min: Some(vec![0]),
1244            max: Some(vec![100]),
1245            hash_id: None,
1246            default_value: Some("17".into()),
1247        };
1248        let mut w = BufferWriter::new(Endianness::Little);
1249        ann.encode_into(&mut w).unwrap();
1250        let bytes = w.into_bytes();
1251        let mut r = BufferReader::new(&bytes, Endianness::Little);
1252        let decoded = AppliedBuiltinMemberAnnotations::decode_from(&mut r).unwrap();
1253        assert_eq!(decoded, ann);
1254    }
1255
1256    #[test]
1257    fn struct_builder_extensibility_mutable() {
1258        let st = TypeObjectBuilder::struct_type("::Y")
1259            .extensibility(Extensibility::Mutable)
1260            .build_minimal();
1261        assert!(st.struct_flags.has(StructTypeFlag::IS_MUTABLE));
1262        assert!(!st.struct_flags.has(StructTypeFlag::IS_APPENDABLE));
1263    }
1264
1265    #[test]
1266    fn complete_builder_preserves_names() {
1267        let st = TypeObjectBuilder::struct_type("::sensors::Chatter")
1268            .extensibility(Extensibility::Appendable)
1269            .member(
1270                "sensor_id",
1271                TypeIdentifier::Primitive(PrimitiveKind::Int64),
1272                |m| m.key().unit("celsius"),
1273            )
1274            .build_complete();
1275        assert_eq!(st.header.detail.type_name, "sensors::Chatter"); // §7.3.4.5.4: no leading ::
1276        assert_eq!(st.member_seq[0].detail.name, "sensor_id");
1277        assert_eq!(
1278            st.member_seq[0].detail.ann_builtin.unit.as_deref(),
1279            Some("celsius")
1280        );
1281    }
1282
1283    #[test]
1284    fn struct_builder_autoid_hash_collision_free_for_distinct_names() {
1285        let st = TypeObjectBuilder::struct_type("::H")
1286            .autoid_hash()
1287            .member(
1288                "alpha",
1289                TypeIdentifier::Primitive(PrimitiveKind::Int32),
1290                |m| m,
1291            )
1292            .member(
1293                "beta",
1294                TypeIdentifier::Primitive(PrimitiveKind::Int32),
1295                |m| m,
1296            )
1297            .build_minimal();
1298        assert!(st.struct_flags.has(StructTypeFlag::IS_AUTOID_HASH));
1299        assert_ne!(
1300            st.member_seq[0].common.member_id,
1301            st.member_seq[1].common.member_id
1302        );
1303    }
1304
1305    #[test]
1306    fn enum_builder_roundtrip_ready() {
1307        let e = TypeObjectBuilder::enum_type("::Color")
1308            .bit_bound(16)
1309            .default_literal("RED", 0)
1310            .literal("GREEN", 1)
1311            .literal("BLUE", 2)
1312            .build_minimal();
1313        assert_eq!(e.header.common.bit_bound, 16);
1314        assert_eq!(e.literal_seq.len(), 3);
1315        assert!(e.literal_seq[0].common.flags.0 & EnumLiteralFlag::IS_DEFAULT_LITERAL != 0);
1316    }
1317
1318    #[test]
1319    fn alias_builder_minimal_and_complete() {
1320        let a_min =
1321            TypeObjectBuilder::alias("::Count", TypeIdentifier::Primitive(PrimitiveKind::UInt64))
1322                .build_minimal();
1323        assert!(matches!(
1324            a_min.body.common.related_type,
1325            TypeIdentifier::Primitive(PrimitiveKind::UInt64)
1326        ));
1327
1328        let a_cmp =
1329            TypeObjectBuilder::alias("::Count", TypeIdentifier::Primitive(PrimitiveKind::UInt64))
1330                .build_complete();
1331        assert_eq!(a_cmp.header.detail.type_name, "Count");
1332    }
1333
1334    // ------------------------------------------------------------------
1335    // Neue Builder (T#2 — Union / Collections / Bitmask / Bitset)
1336    // ------------------------------------------------------------------
1337
1338    #[test]
1339    fn union_builder_minimal_with_two_cases_and_default() {
1340        let u = TypeObjectBuilder::union_type(
1341            "::Shape",
1342            TypeIdentifier::Primitive(PrimitiveKind::Int32),
1343        )
1344        .case(
1345            "circle",
1346            TypeIdentifier::Primitive(PrimitiveKind::Float64),
1347            alloc::vec![1, 2],
1348        )
1349        .default_case("other", TypeIdentifier::String8Small { bound: 64 })
1350        .build_minimal();
1351        assert_eq!(u.member_seq.len(), 2);
1352        assert_eq!(u.member_seq[0].common.label_seq, alloc::vec![1, 2]);
1353        // Default-Case hat IS_DEFAULT-Bit.
1354        assert!(
1355            u.member_seq[1].common.member_flags.0
1356                & crate::type_object::flags::UnionMemberFlag::IS_DEFAULT
1357                != 0
1358        );
1359    }
1360
1361    #[test]
1362    fn union_builder_complete_preserves_names_and_extensibility() {
1363        let u = TypeObjectBuilder::union_type(
1364            "::Shape",
1365            TypeIdentifier::Primitive(PrimitiveKind::Int32),
1366        )
1367        .extensibility(Extensibility::Mutable)
1368        .case(
1369            "a",
1370            TypeIdentifier::Primitive(PrimitiveKind::Int32),
1371            alloc::vec![1],
1372        )
1373        .build_complete();
1374        assert_eq!(u.header.detail.type_name, "Shape");
1375        assert_eq!(u.member_seq[0].detail.name, "a");
1376        assert_eq!(
1377            u.union_flags.0 & StructTypeFlag::IS_MUTABLE,
1378            StructTypeFlag::IS_MUTABLE
1379        );
1380    }
1381
1382    #[test]
1383    fn sequence_builder_minimal() {
1384        let s = TypeObjectBuilder::sequence(TypeIdentifier::Primitive(PrimitiveKind::Int16), 100)
1385            .build_minimal();
1386        assert_eq!(s.bound, 100);
1387        assert!(matches!(
1388            s.element.common.type_id,
1389            TypeIdentifier::Primitive(PrimitiveKind::Int16)
1390        ));
1391    }
1392
1393    #[test]
1394    fn array_builder_3d() {
1395        let a = TypeObjectBuilder::array(
1396            TypeIdentifier::Primitive(PrimitiveKind::Float32),
1397            alloc::vec![4, 4, 4],
1398        )
1399        .build_minimal();
1400        assert_eq!(a.bound_seq, alloc::vec![4, 4, 4]);
1401    }
1402
1403    #[test]
1404    fn map_builder_string_to_int() {
1405        let m = TypeObjectBuilder::map(
1406            TypeIdentifier::String8Small { bound: 64 },
1407            TypeIdentifier::Primitive(PrimitiveKind::Int64),
1408            1_000,
1409        )
1410        .build_minimal();
1411        assert_eq!(m.bound, 1_000);
1412        assert!(matches!(
1413            m.key.common.type_id,
1414            TypeIdentifier::String8Small { bound: 64 }
1415        ));
1416    }
1417
1418    #[test]
1419    fn bitmask_builder_three_flags() {
1420        let b = TypeObjectBuilder::bitmask("::Permissions")
1421            .bit_bound(32)
1422            .flag("READ", 0)
1423            .flag("WRITE", 1)
1424            .flag("EXEC", 2)
1425            .build_minimal();
1426        assert_eq!(b.bit_bound, 32);
1427        assert_eq!(b.flag_seq.len(), 3);
1428        assert_eq!(b.flag_seq[0].common.position, 0);
1429        assert_eq!(b.flag_seq[2].common.position, 2);
1430    }
1431
1432    #[test]
1433    fn bitset_builder_two_fields() {
1434        let b = TypeObjectBuilder::bitset("::Packed")
1435            .field("header", 0, 4, 0x07) // TK_UINT32
1436            .field("body", 4, 28, 0x07)
1437            .build_minimal();
1438        assert_eq!(b.field_seq.len(), 2);
1439        assert_eq!(b.field_seq[0].common.bitcount, 4);
1440        assert_eq!(b.field_seq[1].common.position, 4);
1441    }
1442
1443    #[test]
1444    fn bitmask_builder_complete_preserves_names() {
1445        let b = TypeObjectBuilder::bitmask("::Perm")
1446            .bit_bound(16)
1447            .flag("READ", 0)
1448            .flag("WRITE", 1)
1449            .build_complete();
1450        assert_eq!(b.detail.type_name, "Perm");
1451        assert_eq!(b.bit_bound, 16);
1452        assert_eq!(b.flag_seq.len(), 2);
1453        assert_eq!(b.flag_seq[0].detail.name, "READ");
1454        assert_eq!(b.flag_seq[1].detail.name, "WRITE");
1455    }
1456
1457    #[test]
1458    fn bitset_builder_complete_preserves_names() {
1459        let b = TypeObjectBuilder::bitset("::Packed")
1460            .field("header", 0, 4, 0x07)
1461            .field("body", 4, 28, 0x07)
1462            .build_complete();
1463        assert_eq!(b.detail.type_name, "Packed");
1464        assert_eq!(b.field_seq.len(), 2);
1465        assert_eq!(b.field_seq[0].detail.name, "header");
1466        assert_eq!(b.field_seq[1].detail.name, "body");
1467    }
1468}