Skip to main content

zerodds_proto/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3// Needs `std`: the mapping is built on the `dynamic` feature of zerodds-types, which
4// needs it.
5
6//! Crate `zerodds-proto`. Safety classification: **STANDARD**.
7//!
8//! Protocol Buffers **type reuse** for DDS (rc.7). A `FileDescriptorSet`
9//! (produced by `protoc -o out.pb --include_imports x.proto`) is mapped to an
10//! XTypes [`DynamicType`], so a `.proto` data model becomes a DDS topic type
11//! and rides the **DDS-native XCDR wire** through the reflective `DynamicData`
12//! codec — not a protobuf payload. proto **field numbers map directly to
13//! XTypes member IDs**, so the type is stable across additions.
14//!
15//! This is the scalar type mapping (plan checkpoint CP-A1). The
16//! `FileDescriptorSet` reader and the message / enum / repeated / map / oneof
17//! mapping (CP-A2..A5) build on it.
18//!
19//! [`DynamicType`]: zerodds_types::dynamic::DynamicType
20
21#[cfg(feature = "alloc")]
22extern crate alloc;
23
24pub mod reader;
25
26#[cfg(feature = "alloc")]
27pub mod descriptor;
28
29#[cfg(feature = "alloc")]
30pub mod mapper;
31
32#[cfg(feature = "alloc")]
33pub mod registry;
34
35use zerodds_types::dynamic::TypeKind;
36
37/// A Protocol Buffers field type, i.e. `FieldDescriptorProto.Type` in
38/// `descriptor.proto`. The discriminants are the on-the-wire enum values.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[repr(u8)]
41pub enum ProtoFieldType {
42    /// `double` -> `f64`.
43    Double = 1,
44    /// `float` -> `f32`.
45    Float = 2,
46    /// `int64` (varint) -> `i64`.
47    Int64 = 3,
48    /// `uint64` (varint) -> `u64`.
49    Uint64 = 4,
50    /// `int32` (varint) -> `i32`.
51    Int32 = 5,
52    /// `fixed64` (8 bytes) -> `u64`.
53    Fixed64 = 6,
54    /// `fixed32` (4 bytes) -> `u32`.
55    Fixed32 = 7,
56    /// `bool` -> boolean.
57    Bool = 8,
58    /// `string` (UTF-8) -> XTypes string.
59    String = 9,
60    /// Deprecated proto2 group, mapped like a nested message.
61    Group = 10,
62    /// Nested `message` -> nested struct (resolved from `type_name`).
63    Message = 11,
64    /// `bytes` -> `sequence<byte>`.
65    Bytes = 12,
66    /// `uint32` (varint) -> `u32`.
67    Uint32 = 13,
68    /// `enum` -> XTypes enum (resolved from `type_name`).
69    Enum = 14,
70    /// `sfixed32` (4 bytes, signed) -> `i32`.
71    Sfixed32 = 15,
72    /// `sfixed64` (8 bytes, signed) -> `i64`.
73    Sfixed64 = 16,
74    /// `sint32` (zigzag varint) -> `i32`.
75    Sint32 = 17,
76    /// `sint64` (zigzag varint) -> `i64`.
77    Sint64 = 18,
78}
79
80impl ProtoFieldType {
81    /// Decode the `FieldDescriptorProto.Type` enum value. Returns `None` for
82    /// out-of-range values (a malformed or newer descriptor).
83    #[must_use]
84    pub fn from_wire(v: i32) -> Option<Self> {
85        Some(match v {
86            1 => Self::Double,
87            2 => Self::Float,
88            3 => Self::Int64,
89            4 => Self::Uint64,
90            5 => Self::Int32,
91            6 => Self::Fixed64,
92            7 => Self::Fixed32,
93            8 => Self::Bool,
94            9 => Self::String,
95            10 => Self::Group,
96            11 => Self::Message,
97            12 => Self::Bytes,
98            13 => Self::Uint32,
99            14 => Self::Enum,
100            15 => Self::Sfixed32,
101            16 => Self::Sfixed64,
102            17 => Self::Sint32,
103            18 => Self::Sint64,
104            _ => return None,
105        })
106    }
107
108    /// The XTypes [`TypeKind`] for a **directly-representable** field type.
109    ///
110    /// `None` for the field types that do not map to a single primitive:
111    /// - [`ProtoFieldType::Bytes`] -> `sequence<byte>` (a collection),
112    /// - [`ProtoFieldType::Message`] / [`ProtoFieldType::Group`] -> a nested
113    ///   struct (resolved from the descriptor's `type_name`),
114    /// - [`ProtoFieldType::Enum`] -> an XTypes enum (resolved from `type_name`).
115    ///
116    /// These are handled by the descriptor mapper (CP-A3), not here.
117    #[must_use]
118    pub fn scalar_type_kind(self) -> Option<TypeKind> {
119        Some(match self {
120            Self::Double => TypeKind::Float64,
121            Self::Float => TypeKind::Float32,
122            Self::Int64 | Self::Sfixed64 | Self::Sint64 => TypeKind::Int64,
123            Self::Uint64 | Self::Fixed64 => TypeKind::UInt64,
124            Self::Int32 | Self::Sfixed32 | Self::Sint32 => TypeKind::Int32,
125            Self::Uint32 | Self::Fixed32 => TypeKind::UInt32,
126            Self::Bool => TypeKind::Boolean,
127            Self::String => TypeKind::String8,
128            Self::Bytes | Self::Message | Self::Group | Self::Enum => return None,
129        })
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn wire_values_round_trip_the_descriptor_enum() {
139        assert_eq!(ProtoFieldType::from_wire(1), Some(ProtoFieldType::Double));
140        assert_eq!(ProtoFieldType::from_wire(11), Some(ProtoFieldType::Message));
141        assert_eq!(ProtoFieldType::from_wire(18), Some(ProtoFieldType::Sint64));
142        assert_eq!(ProtoFieldType::from_wire(0), None);
143        assert_eq!(ProtoFieldType::from_wire(19), None);
144    }
145
146    #[test]
147    fn scalars_map_to_the_expected_type_kind() {
148        assert_eq!(
149            ProtoFieldType::Double.scalar_type_kind(),
150            Some(TypeKind::Float64)
151        );
152        assert_eq!(
153            ProtoFieldType::Float.scalar_type_kind(),
154            Some(TypeKind::Float32)
155        );
156        // signed/zigzag/fixed 32- and 64-bit all fold onto Int32/Int64.
157        for t in [
158            ProtoFieldType::Int64,
159            ProtoFieldType::Sfixed64,
160            ProtoFieldType::Sint64,
161        ] {
162            assert_eq!(t.scalar_type_kind(), Some(TypeKind::Int64));
163        }
164        for t in [
165            ProtoFieldType::Int32,
166            ProtoFieldType::Sfixed32,
167            ProtoFieldType::Sint32,
168        ] {
169            assert_eq!(t.scalar_type_kind(), Some(TypeKind::Int32));
170        }
171        assert_eq!(
172            ProtoFieldType::Uint64.scalar_type_kind(),
173            Some(TypeKind::UInt64)
174        );
175        assert_eq!(
176            ProtoFieldType::Fixed32.scalar_type_kind(),
177            Some(TypeKind::UInt32)
178        );
179        assert_eq!(
180            ProtoFieldType::Bool.scalar_type_kind(),
181            Some(TypeKind::Boolean)
182        );
183        assert_eq!(
184            ProtoFieldType::String.scalar_type_kind(),
185            Some(TypeKind::String8)
186        );
187    }
188
189    #[test]
190    fn non_scalar_field_types_have_no_direct_kind() {
191        for t in [
192            ProtoFieldType::Bytes,
193            ProtoFieldType::Message,
194            ProtoFieldType::Group,
195            ProtoFieldType::Enum,
196        ] {
197            assert_eq!(t.scalar_type_kind(), None);
198        }
199    }
200}