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}