protobuf/reflect/file/
mod.rs

1use std::collections::HashSet;
2use std::hash::Hash;
3use std::hash::Hasher;
4use std::sync::Arc;
5
6use crate::descriptor::DescriptorProto;
7use crate::descriptor::FileDescriptorProto;
8use crate::reflect::file::dynamic::DynamicFileDescriptor;
9use crate::reflect::file::fds::build_fds;
10use crate::reflect::file::index::EnumIndices;
11use crate::reflect::file::index::FileDescriptorCommon;
12use crate::reflect::file::index::MessageIndices;
13use crate::reflect::name::protobuf_name_starts_with_package;
14use crate::reflect::service::ServiceDescriptor;
15use crate::reflect::EnumDescriptor;
16use crate::reflect::FieldDescriptor;
17use crate::reflect::GeneratedFileDescriptor;
18use crate::reflect::MessageDescriptor;
19use crate::reflect::Syntax;
20
21pub(crate) mod building;
22pub(crate) mod dynamic;
23pub(crate) mod fds;
24pub(crate) mod generated;
25pub(crate) mod index;
26pub(crate) mod syntax;
27
28#[derive(Clone, Debug)]
29pub(crate) enum FileDescriptorImpl {
30    Generated(&'static GeneratedFileDescriptor),
31    Dynamic(Arc<DynamicFileDescriptor>),
32}
33
34impl PartialEq for FileDescriptorImpl {
35    fn eq(&self, other: &Self) -> bool {
36        match (self, other) {
37            (FileDescriptorImpl::Generated(a), FileDescriptorImpl::Generated(b)) => {
38                *a as *const GeneratedFileDescriptor == *b as *const GeneratedFileDescriptor
39            }
40            (FileDescriptorImpl::Dynamic(a), FileDescriptorImpl::Dynamic(b)) => Arc::ptr_eq(a, b),
41            _ => false,
42        }
43    }
44}
45
46impl Hash for FileDescriptorImpl {
47    fn hash<H: Hasher>(&self, state: &mut H) {
48        match self {
49            FileDescriptorImpl::Generated(g) => {
50                Hash::hash(&(*g as *const GeneratedFileDescriptor), state)
51            }
52            FileDescriptorImpl::Dynamic(a) => {
53                Hash::hash(&(&**a as *const DynamicFileDescriptor), state)
54            }
55        }
56    }
57}
58
59impl Eq for FileDescriptorImpl {}
60
61/// Reflection for objects defined in `.proto` file (messages, enums, etc).
62///
63/// The object is refcounted: clone is shallow.
64///
65/// The equality performs pointer comparison: two clones of the same `FileDescriptor`
66/// objects are equal, but two `FileDescriptor` objects created from the same `FileDescriptorProto`
67/// objects are **not** equal.
68#[derive(Clone, PartialEq, Eq, Hash, Debug)]
69pub struct FileDescriptor {
70    pub(crate) imp: FileDescriptorImpl,
71}
72
73impl FileDescriptor {
74    pub(crate) fn common(&self) -> &FileDescriptorCommon {
75        match &self.imp {
76            FileDescriptorImpl::Generated(g) => &g.common,
77            FileDescriptorImpl::Dynamic(d) => &d.common,
78        }
79    }
80
81    /// Same as `common`, but returns `&'static`.
82    pub(crate) fn common_for_generated_descriptor(&self) -> &'static FileDescriptorCommon {
83        match &self.imp {
84            FileDescriptorImpl::Generated(g) => &g.common,
85            FileDescriptorImpl::Dynamic(..) => panic!("not generated"),
86        }
87    }
88
89    pub(crate) fn message_indices(&self, index: usize) -> &MessageIndices {
90        &self.common().messages[index]
91    }
92
93    pub(crate) fn message_by_index(&self, index: usize) -> MessageDescriptor {
94        MessageDescriptor {
95            file_descriptor: self.clone(),
96            index,
97        }
98    }
99
100    pub(crate) fn message_proto_by_index(&self, index: usize) -> &DescriptorProto {
101        &self.common().messages[index].proto
102    }
103
104    pub(crate) fn enum_indices(&self, index: usize) -> &EnumIndices {
105        &self.common().enums[index]
106    }
107
108    /// The file name.
109    pub fn name(&self) -> &str {
110        self.proto().name()
111    }
112
113    /// Protobuf package.
114    pub fn package(&self) -> &str {
115        self.proto().package()
116    }
117
118    /// Syntax of current file.
119    pub fn syntax(&self) -> Syntax {
120        Syntax::parse(self.proto().syntax()).unwrap_or(Syntax::Proto2)
121    }
122
123    /// Top-level messages.
124    pub fn messages(&self) -> impl Iterator<Item = MessageDescriptor> + '_ {
125        self.common()
126            .top_level_messages
127            .iter()
128            .map(|i| MessageDescriptor::new(self.clone(), *i))
129    }
130
131    /// Get top-level enums.
132    pub fn enums(&self) -> impl Iterator<Item = EnumDescriptor> + '_ {
133        self.proto()
134            .enum_type
135            .iter()
136            .enumerate()
137            .map(|(i, _)| EnumDescriptor::new(self.clone(), i))
138    }
139
140    /// Get services defined in `.proto` file.
141    pub fn services(&self) -> impl Iterator<Item = ServiceDescriptor> + '_ {
142        self.proto()
143            .service
144            .iter()
145            .enumerate()
146            .map(|(i, _)| ServiceDescriptor::new(self.clone(), i))
147    }
148
149    /// Extension fields.
150    pub fn extensions(&self) -> impl Iterator<Item = FieldDescriptor> + '_ {
151        self.common()
152            .extension_field_range()
153            .map(move |index| FieldDescriptor {
154                file_descriptor: self.clone(),
155                index,
156            })
157    }
158
159    /// Find message by name relative to the package.
160    ///
161    /// Only search in the current file, not in any dependencies.
162    pub fn message_by_package_relative_name(&self, name: &str) -> Option<MessageDescriptor> {
163        self.common()
164            .message_by_name_to_package
165            .get(name)
166            .map(|&index| MessageDescriptor::new(self.clone(), index))
167    }
168
169    /// Find message by name relative to the package.
170    ///
171    /// Only search in the current file, not in any dependencies.
172    pub fn enum_by_package_relative_name(&self, name: &str) -> Option<EnumDescriptor> {
173        self.common()
174            .enums_by_name_to_package
175            .get(name)
176            .map(|&index| EnumDescriptor::new(self.clone(), index))
177    }
178
179    /// Find message by fully-qualified name.
180    ///
181    /// Only search in the current file, not in any dependencies.
182    pub fn message_by_full_name(&self, name: &str) -> Option<MessageDescriptor> {
183        if let Some(name_to_package) =
184            protobuf_name_starts_with_package(name, self.proto().package())
185        {
186            self.message_by_package_relative_name(name_to_package)
187        } else {
188            None
189        }
190    }
191
192    /// Find enum by name fully-qualified name.
193    ///
194    /// Only search in the current file, not in any dependencies.
195    pub fn enum_by_full_name(&self, name: &str) -> Option<EnumDescriptor> {
196        if let Some(name_to_package) =
197            protobuf_name_starts_with_package(name, self.proto().package())
198        {
199            self.enum_by_package_relative_name(name_to_package)
200        } else {
201            None
202        }
203    }
204
205    /// This function is called from generated code, it is not stable, and should not be called.
206    #[doc(hidden)]
207    pub fn new_generated_2(generated: &'static GeneratedFileDescriptor) -> FileDescriptor {
208        FileDescriptor {
209            imp: FileDescriptorImpl::Generated(generated),
210        }
211    }
212
213    /// Dynamic message created from [`FileDescriptorProto`] without generated files.
214    pub fn new_dynamic(
215        proto: FileDescriptorProto,
216        dependencies: &[FileDescriptor],
217    ) -> crate::Result<FileDescriptor> {
218        Ok(FileDescriptor {
219            imp: FileDescriptorImpl::Dynamic(Arc::new(DynamicFileDescriptor::new(
220                proto,
221                dependencies,
222            )?)),
223        })
224    }
225
226    /// Create a set of file descriptors from individual file descriptors.
227    pub fn new_dynamic_fds(
228        protos: Vec<FileDescriptorProto>,
229        dependencies: &[FileDescriptor],
230    ) -> crate::Result<Vec<FileDescriptor>> {
231        build_fds(protos, dependencies)
232    }
233
234    /// `.proto` data for this file.
235    pub fn proto(&self) -> &FileDescriptorProto {
236        match &self.imp {
237            FileDescriptorImpl::Generated(g) => &g.proto,
238            FileDescriptorImpl::Dynamic(d) => &d.proto,
239        }
240    }
241
242    /// Direct dependencies of this file.
243    pub fn deps(&self) -> &[FileDescriptor] {
244        &self.common().dependencies
245    }
246
247    /// Subset of dependencies which are public
248    pub fn public_deps(&self) -> impl Iterator<Item = FileDescriptor> + '_ {
249        self.proto()
250            .public_dependency
251            .iter()
252            .map(|&i| self.deps()[i as usize].clone())
253    }
254
255    fn _all_files(&self) -> Vec<&FileDescriptor> {
256        let mut r = Vec::new();
257        let mut visited = HashSet::new();
258
259        let mut stack = Vec::new();
260        stack.push(self);
261        while let Some(file) = stack.pop() {
262            if !visited.insert(file) {
263                continue;
264            }
265
266            r.push(file);
267            stack.extend(file.deps());
268        }
269
270        r
271    }
272}
273
274#[cfg(test)]
275mod test {
276    use crate::descriptor;
277
278    #[test]
279    #[cfg_attr(miri, ignore)]
280    fn eq() {
281        assert!(descriptor::file_descriptor() == &descriptor::file_descriptor().clone());
282    }
283}