Skip to main content

buffa_reflect/
pool.rs

1//! [`DescriptorPool`] — the root handle holding decoded descriptors.
2
3use std::sync::Arc;
4
5use buffa::Message as _;
6use buffa_descriptor::generated::descriptor::FileDescriptorSet;
7
8use crate::{
9    enumeration::EnumDescriptor, error::DescriptorError, file::FileDescriptor,
10    message::MessageDescriptor,
11};
12
13/// Index into [`PoolInner::messages`].
14pub(crate) type MessageIndex = u32;
15/// Index into [`PoolInner::enums`].
16pub(crate) type EnumIndex = u32;
17/// Index into [`PoolInner::files`].
18pub(crate) type FileIndex = u32;
19
20/// A pool of protobuf descriptors built from one or more
21/// `FileDescriptorSet`s.
22///
23/// Cloning is cheap (`Arc`-shared inner). All descriptor handles
24/// ([`MessageDescriptor`], [`FieldDescriptor`](crate::FieldDescriptor),
25/// [`EnumDescriptor`], …) own a `DescriptorPool` and are themselves cheap
26/// to clone.
27#[derive(Clone, Default)]
28pub struct DescriptorPool {
29    pub(crate) inner: Arc<PoolInner>,
30}
31
32impl std::fmt::Debug for DescriptorPool {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        f.debug_struct("DescriptorPool")
35            .field("files", &self.inner.files.len())
36            .field("messages", &self.inner.messages.len())
37            .field("enums", &self.inner.enums.len())
38            .finish()
39    }
40}
41
42#[derive(Clone, Default)]
43pub(crate) struct PoolInner {
44    pub(crate) names: hashbrown::HashMap<Box<str>, Definition>,
45    pub(crate) file_names: hashbrown::HashMap<Box<str>, FileIndex>,
46    pub(crate) files: Vec<FileEntry>,
47    pub(crate) messages: Vec<MessageEntry>,
48    pub(crate) enums: Vec<EnumEntry>,
49    pub(crate) services: Vec<crate::service::ServiceEntry>,
50    pub(crate) service_names: hashbrown::HashMap<Box<str>, crate::service::ServiceIndex>,
51}
52
53/// What a fully-qualified name refers to in the pool.
54#[derive(Debug, Clone, Copy)]
55pub(crate) enum Definition {
56    Message(MessageIndex),
57    Enum(EnumIndex),
58}
59
60#[derive(Clone)]
61pub(crate) struct FileEntry {
62    pub(crate) proto: buffa_descriptor::generated::descriptor::FileDescriptorProto,
63    /// Top-level message indices owned by this file (nested messages live
64    /// under their parent's `nested` list).
65    pub(crate) messages: Vec<MessageIndex>,
66    /// Top-level enum indices owned by this file.
67    pub(crate) enums: Vec<EnumIndex>,
68    /// Service indices declared in this file.
69    pub(crate) service_indices: Vec<crate::service::ServiceIndex>,
70}
71
72#[derive(Clone)]
73pub(crate) struct MessageEntry {
74    /// `<package>.<...>.<MessageName>` — no leading dot.
75    pub(crate) full_name: Box<str>,
76    /// The leaf name (the part after the last `.`).
77    pub(crate) name: Box<str>,
78    /// Index into `PoolInner::files`.
79    pub(crate) file: FileIndex,
80    /// Set when this message is nested inside another.
81    pub(crate) parent: Option<MessageIndex>,
82    /// Path used to locate the matching `DescriptorProto` inside the file.
83    /// Each step is an index into `message_type` / `nested_type` of the
84    /// previous level.
85    pub(crate) proto_path: Vec<u32>,
86    /// Resolved fields, in declaration order.
87    pub(crate) fields: Vec<FieldEntry>,
88    /// Resolved oneofs, in declaration order. Synthetic oneofs (proto3
89    /// optional) are included; consumers can filter with
90    /// [`crate::OneofDescriptor::is_synthetic`].
91    pub(crate) oneofs: Vec<OneofEntry>,
92    /// Nested message indices (recursive — children only, not grandchildren).
93    pub(crate) nested_messages: Vec<MessageIndex>,
94    /// Nested enum indices.
95    pub(crate) nested_enums: Vec<EnumIndex>,
96    /// Map from field number to position in `fields`.
97    pub(crate) by_number: hashbrown::HashMap<u32, u32>,
98    /// Map from proto field name to position in `fields`.
99    pub(crate) by_name: hashbrown::HashMap<Box<str>, u32>,
100    /// Map from JSON field name to position in `fields`.
101    pub(crate) by_json_name: hashbrown::HashMap<Box<str>, u32>,
102    /// True iff this message was synthesized as a `map<K, V>` entry by
103    /// protoc. Such messages are not user-facing types in generated code.
104    pub(crate) is_map_entry: bool,
105}
106
107#[derive(Clone)]
108pub(crate) struct FieldEntry {
109    pub(crate) name: Box<str>,
110    pub(crate) full_name: Box<str>,
111    pub(crate) json_name: Box<str>,
112    pub(crate) number: u32,
113    pub(crate) kind: KindRef,
114    pub(crate) cardinality: crate::field::Cardinality,
115    pub(crate) supports_presence: bool,
116    pub(crate) is_packed: bool,
117    /// When the field belongs to a oneof, the index into the parent
118    /// message's `oneofs` list.
119    pub(crate) oneof_index: Option<u32>,
120    /// Position of the descriptor inside the parent's
121    /// `DescriptorProto::field` list.
122    pub(crate) proto_field_index: u32,
123    /// Pre-parsed `[default = …]` literal, if any. Eagerly parsed at
124    /// pool-build time so malformed defaults surface as
125    /// [`crate::DescriptorError::InvalidDefaultValue`] rather than as
126    /// surprise crashes on first read.
127    #[cfg(feature = "dynamic")]
128    pub(crate) parsed_default: Option<crate::dynamic::Value>,
129}
130
131/// Internal, pool-relative version of [`crate::Kind`]. Avoids storing
132/// `MessageDescriptor`/`EnumDescriptor` (which would require an `Arc<Pool>`
133/// while the pool is still being built).
134#[derive(Clone, Copy, Debug)]
135pub(crate) enum KindRef {
136    Double,
137    Float,
138    Int32,
139    Int64,
140    Uint32,
141    Uint64,
142    Sint32,
143    Sint64,
144    Fixed32,
145    Fixed64,
146    Sfixed32,
147    Sfixed64,
148    Bool,
149    String,
150    Bytes,
151    Message(MessageIndex),
152    Enum(EnumIndex),
153}
154
155#[derive(Clone)]
156pub(crate) struct OneofEntry {
157    pub(crate) name: Box<str>,
158    pub(crate) full_name: Box<str>,
159    pub(crate) is_synthetic: bool,
160    /// Indices into the parent message's `fields` vector.
161    pub(crate) field_indices: Vec<u32>,
162    /// Position in the owning message's `oneof_decl` list.
163    pub(crate) proto_index: u32,
164}
165
166#[derive(Clone)]
167pub(crate) struct EnumEntry {
168    pub(crate) full_name: Box<str>,
169    pub(crate) name: Box<str>,
170    pub(crate) file: FileIndex,
171    pub(crate) parent: Option<MessageIndex>,
172    pub(crate) values: Vec<EnumValueEntry>,
173    /// Same role as [`MessageEntry::proto_path`], but pointing into
174    /// `enum_type` lists.
175    pub(crate) proto_path: Vec<u32>,
176    /// Map from variant name to position in `values`.
177    pub(crate) by_name: hashbrown::HashMap<Box<str>, u32>,
178    /// Map from variant number to position in `values`. (proto allows
179    /// multiple variants with the same number when `allow_alias` is set; we
180    /// keep the first.)
181    pub(crate) by_number: hashbrown::HashMap<i32, u32>,
182}
183
184#[derive(Clone)]
185pub(crate) struct EnumValueEntry {
186    pub(crate) name: Box<str>,
187    pub(crate) full_name: Box<str>,
188    pub(crate) number: i32,
189}
190
191// Static guarantee that the public handle types are usable across thread
192// boundaries — same property prost-reflect's docs promise. A future
193// refactor that swapped `Arc` for `Rc` would fail to compile here rather
194// than silently regress the API.
195const _: fn() = || {
196    fn assert_send_sync<T: Send + Sync>() {}
197    assert_send_sync::<DescriptorPool>();
198    assert_send_sync::<crate::message::MessageDescriptor>();
199    assert_send_sync::<crate::field::FieldDescriptor>();
200    assert_send_sync::<crate::enumeration::EnumDescriptor>();
201    assert_send_sync::<crate::oneof::OneofDescriptor>();
202    assert_send_sync::<crate::file::FileDescriptor>();
203    assert_send_sync::<crate::service::ServiceDescriptor>();
204    assert_send_sync::<crate::service::MethodDescriptor>();
205};
206
207impl DescriptorPool {
208    /// Construct an empty pool.
209    #[must_use]
210    pub fn new() -> Self {
211        Self::default()
212    }
213
214    /// Decode a serialized `google.protobuf.FileDescriptorSet` into a pool.
215    ///
216    /// # Errors
217    /// Returns [`DescriptorError::Decode`] if the bytes are not a valid
218    /// `FileDescriptorSet`, or any of the validation errors documented on
219    /// [`DescriptorError`].
220    pub fn decode(bytes: &[u8]) -> Result<Self, DescriptorError> {
221        let fds = FileDescriptorSet::decode_from_slice(bytes)?;
222        Self::from_file_descriptor_set(fds)
223    }
224
225    /// Build a pool from an already-decoded `FileDescriptorSet`.
226    ///
227    /// # Errors
228    /// See [`DescriptorError`].
229    pub fn from_file_descriptor_set(fds: FileDescriptorSet) -> Result<Self, DescriptorError> {
230        let mut pool = Self::new();
231        pool.add_file_descriptor_set(fds)?;
232        Ok(pool)
233    }
234
235    /// Merge another `FileDescriptorSet` into the pool.
236    ///
237    /// # Errors
238    /// See [`DescriptorError`].
239    pub fn add_file_descriptor_set(
240        &mut self,
241        fds: FileDescriptorSet,
242    ) -> Result<(), DescriptorError> {
243        let inner = Arc::make_mut(&mut self.inner);
244        crate::pool_build::ingest_file_descriptor_set(inner, fds)
245    }
246
247    /// Iterate over every file in the pool.
248    pub fn files(&self) -> impl ExactSizeIterator<Item = FileDescriptor> + '_ {
249        (0..self.inner.files.len() as u32).map(move |idx| FileDescriptor {
250            pool: self.clone(),
251            index: idx,
252        })
253    }
254
255    /// Iterate over every message in the pool, including nested messages
256    /// and synthesized map-entry messages.
257    pub fn all_messages(&self) -> impl ExactSizeIterator<Item = MessageDescriptor> + '_ {
258        (0..self.inner.messages.len() as u32).map(move |idx| MessageDescriptor {
259            pool: self.clone(),
260            index: idx,
261        })
262    }
263
264    /// Iterate over every enum in the pool, including nested enums.
265    pub fn all_enums(&self) -> impl ExactSizeIterator<Item = EnumDescriptor> + '_ {
266        (0..self.inner.enums.len() as u32).map(move |idx| EnumDescriptor {
267            pool: self.clone(),
268            index: idx,
269        })
270    }
271
272    /// Resolve a fully-qualified message name. Accepts either `pkg.Name` or
273    /// `.pkg.Name` (the leading dot is stripped).
274    #[must_use]
275    pub fn get_message_by_name(&self, full_name: &str) -> Option<MessageDescriptor> {
276        let key = full_name.strip_prefix('.').unwrap_or(full_name);
277        match self.inner.names.get(key)? {
278            Definition::Message(idx) => Some(MessageDescriptor {
279                pool: self.clone(),
280                index: *idx,
281            }),
282            Definition::Enum(_) => None,
283        }
284    }
285
286    /// Resolve a fully-qualified enum name (with or without leading dot).
287    #[must_use]
288    pub fn get_enum_by_name(&self, full_name: &str) -> Option<EnumDescriptor> {
289        let key = full_name.strip_prefix('.').unwrap_or(full_name);
290        match self.inner.names.get(key)? {
291            Definition::Enum(idx) => Some(EnumDescriptor {
292                pool: self.clone(),
293                index: *idx,
294            }),
295            Definition::Message(_) => None,
296        }
297    }
298
299    /// Resolve a file by its descriptor name (e.g. `acme/api/v1/user.proto`).
300    #[must_use]
301    pub fn get_file_by_name(&self, name: &str) -> Option<FileDescriptor> {
302        self.inner.file_names.get(name).map(|idx| FileDescriptor {
303            pool: self.clone(),
304            index: *idx,
305        })
306    }
307}