prost_reflect/descriptor/build/
names.rs

1use std::collections::{hash_map, BTreeMap, HashMap, HashSet};
2
3use crate::{
4    descriptor::{
5        build::{
6            join_path,
7            options::option_to_bool,
8            visit::{visit, Visitor},
9            DescriptorPoolOffsets,
10        },
11        error::{DescriptorError, DescriptorErrorKind, Label},
12        tag, to_index,
13        types::{
14            DescriptorProto, EnumDescriptorProto, EnumValueDescriptorProto, FieldDescriptorProto,
15            FileDescriptorProto, MethodDescriptorProto, OneofDescriptorProto,
16            ServiceDescriptorProto,
17        },
18        Definition, DefinitionKind, DescriptorPoolInner, EnumDescriptorInner, EnumIndex,
19        EnumValueDescriptorInner, EnumValueIndex, ExtensionIndex, FieldIndex, FileDescriptorInner,
20        FileIndex, Identity, MessageDescriptorInner, MessageIndex, MethodIndex,
21        OneofDescriptorInner, OneofIndex, ServiceIndex,
22    },
23    Syntax,
24};
25
26impl DescriptorPoolInner {
27    pub(super) fn collect_names(
28        &mut self,
29        offsets: DescriptorPoolOffsets,
30        files: &[FileDescriptorProto],
31    ) -> Result<(), DescriptorError> {
32        let mut visitor = NameVisitor {
33            pool: self,
34            errors: vec![],
35        };
36        visit(offsets, files, &mut visitor);
37        if visitor.errors.is_empty() {
38            Ok(())
39        } else {
40            Err(DescriptorError::new(visitor.errors))
41        }
42    }
43}
44
45struct NameVisitor<'a> {
46    pool: &'a mut DescriptorPoolInner,
47    errors: Vec<DescriptorErrorKind>,
48}
49
50impl Visitor for NameVisitor<'_> {
51    fn visit_file(&mut self, path: &[i32], index: FileIndex, file: &FileDescriptorProto) {
52        debug_assert_eq!(to_index(self.pool.files.len()), index);
53
54        let syntax = match file.syntax.as_deref() {
55            None | Some("proto2") => Syntax::Proto2,
56            Some("proto3") => Syntax::Proto3,
57            Some(syntax) => {
58                self.errors.push(DescriptorErrorKind::UnknownSyntax {
59                    syntax: syntax.to_owned(),
60                    found: Label::new(
61                        &self.pool.files,
62                        "found here",
63                        index,
64                        join_path(path, &[tag::file::SYNTAX]),
65                    ),
66                });
67                return;
68            }
69        };
70
71        if self
72            .pool
73            .file_names
74            .insert(file.name().into(), index)
75            .is_some()
76        {
77            self.errors.push(DescriptorErrorKind::DuplicateFileName {
78                name: file.name().to_owned(),
79            });
80        }
81        self.pool.files.push(FileDescriptorInner {
82            syntax,
83            raw: file.clone(),
84            prost: Default::default(), // the prost descriptor is initialized from the internal descriptor once resolution is complete, to avoid needing to duplicate all modifications
85            dependencies: Vec::with_capacity(file.dependency.len()),
86            transitive_dependencies: HashSet::default(),
87        });
88
89        if !file.package().is_empty() {
90            for (i, _) in file.package().match_indices('.') {
91                self.add_name(
92                    index,
93                    &file.package()[..i],
94                    path,
95                    &[tag::file::PACKAGE],
96                    DefinitionKind::Package,
97                );
98            }
99            self.add_name(
100                index,
101                file.package(),
102                path,
103                &[tag::file::PACKAGE],
104                DefinitionKind::Package,
105            );
106        }
107    }
108
109    fn visit_message(
110        &mut self,
111        path: &[i32],
112        full_name: &str,
113        file: FileIndex,
114        parent: Option<MessageIndex>,
115        index: MessageIndex,
116        message: &DescriptorProto,
117    ) {
118        self.add_name(
119            file,
120            full_name,
121            path,
122            &[tag::message::NAME],
123            DefinitionKind::Message(index),
124        );
125
126        debug_assert_eq!(to_index(self.pool.messages.len()), index);
127        self.pool.messages.push(MessageDescriptorInner {
128            id: Identity::new(file, path, full_name, message.name()),
129            fields: Vec::with_capacity(message.field.len()),
130            field_numbers: BTreeMap::new(),
131            field_names: HashMap::with_capacity(message.field.len()),
132            field_json_names: HashMap::with_capacity(message.field.len()),
133            oneofs: Vec::with_capacity(message.oneof_decl.len()),
134            extensions: Vec::new(),
135            parent,
136        });
137
138        if self.pool.files[file as usize].syntax != Syntax::Proto2 {
139            self.check_message_field_camel_case_names(file, path, message);
140        }
141    }
142
143    fn visit_field(
144        &mut self,
145        path: &[i32],
146        full_name: &str,
147        file: FileIndex,
148        message: MessageIndex,
149        _: FieldIndex,
150        _: &FieldDescriptorProto,
151    ) {
152        self.add_name(
153            file,
154            full_name,
155            path,
156            &[tag::field::NAME],
157            DefinitionKind::Field(message),
158        );
159    }
160
161    fn visit_oneof(
162        &mut self,
163        path: &[i32],
164        full_name: &str,
165        file: FileIndex,
166        message: MessageIndex,
167        index: OneofIndex,
168        oneof: &OneofDescriptorProto,
169    ) {
170        self.add_name(
171            file,
172            full_name,
173            path,
174            &[tag::oneof::NAME],
175            DefinitionKind::Oneof(message),
176        );
177
178        debug_assert_eq!(
179            to_index(self.pool.messages[message as usize].oneofs.len()),
180            index
181        );
182        self.pool.messages[message as usize]
183            .oneofs
184            .push(OneofDescriptorInner {
185                id: Identity::new(file, path, full_name, oneof.name()),
186                fields: Vec::new(),
187            });
188    }
189
190    fn visit_service(
191        &mut self,
192        path: &[i32],
193        full_name: &str,
194        file: FileIndex,
195        index: ServiceIndex,
196        _: &ServiceDescriptorProto,
197    ) {
198        self.add_name(
199            file,
200            full_name,
201            path,
202            &[tag::service::NAME],
203            DefinitionKind::Service(index),
204        );
205    }
206
207    fn visit_method(
208        &mut self,
209        path: &[i32],
210        full_name: &str,
211        file: FileIndex,
212        service: ServiceIndex,
213        _: MethodIndex,
214        _: &MethodDescriptorProto,
215    ) {
216        self.add_name(
217            file,
218            full_name,
219            path,
220            &[tag::service::NAME],
221            DefinitionKind::Method(service),
222        );
223    }
224
225    fn visit_enum(
226        &mut self,
227        path: &[i32],
228        full_name: &str,
229        file: FileIndex,
230        parent: Option<MessageIndex>,
231        index: EnumIndex,
232        enum_: &EnumDescriptorProto,
233    ) {
234        self.add_name(
235            file,
236            full_name,
237            path,
238            &[tag::enum_::NAME],
239            DefinitionKind::Enum(index),
240        );
241
242        if enum_.value.is_empty() {
243            self.errors.push(DescriptorErrorKind::EmptyEnum {
244                found: Label::new(&self.pool.files, "enum defined here", file, path.into()),
245            });
246        } else if self.pool.files[file as usize].syntax != Syntax::Proto2
247            && enum_.value[0].number() != 0
248        {
249            self.errors
250                .push(DescriptorErrorKind::InvalidProto3EnumDefault {
251                    found: Label::new(
252                        &self.pool.files,
253                        "defined here",
254                        file,
255                        join_path(path, &[tag::enum_::VALUE, 0, tag::enum_value::NUMBER]),
256                    ),
257                });
258        }
259
260        let allow_alias = enum_.options.as_ref().is_some_and(|o| {
261            o.value.allow_alias()
262                || o.value.uninterpreted_option.iter().any(|u| {
263                    u.name.len() == 1
264                        && u.name[0].name_part == "allow_alias"
265                        && !u.name[0].is_extension
266                        && option_to_bool(u).unwrap_or(false)
267                })
268        });
269
270        debug_assert_eq!(to_index(self.pool.enums.len()), index);
271        self.pool.enums.push(EnumDescriptorInner {
272            id: Identity::new(file, path, full_name, enum_.name()),
273            parent,
274            values: Vec::with_capacity(enum_.value.len()),
275            value_numbers: Vec::with_capacity(enum_.value.len()),
276            value_names: HashMap::with_capacity(enum_.value.len()),
277            allow_alias,
278        });
279    }
280
281    fn visit_enum_value(
282        &mut self,
283        path: &[i32],
284        full_name: &str,
285        file: FileIndex,
286        enum_: EnumIndex,
287        index: EnumValueIndex,
288        value: &EnumValueDescriptorProto,
289    ) {
290        self.add_name(
291            file,
292            full_name,
293            path,
294            &[tag::enum_value::NAME],
295            DefinitionKind::EnumValue(enum_),
296        );
297
298        debug_assert_eq!(
299            to_index(self.pool.enums[enum_ as usize].values.len()),
300            index
301        );
302        self.pool.enums[enum_ as usize]
303            .values
304            .push(EnumValueDescriptorInner {
305                id: Identity::new(file, path, full_name, value.name()),
306                number: value.number(),
307            });
308    }
309
310    fn visit_extension(
311        &mut self,
312        path: &[i32],
313        full_name: &str,
314        file: FileIndex,
315        _: Option<MessageIndex>,
316        index: ExtensionIndex,
317        _: &FieldDescriptorProto,
318    ) {
319        self.add_name(
320            file,
321            full_name,
322            path,
323            &[tag::field::NAME],
324            DefinitionKind::Extension(index),
325        );
326    }
327}
328
329impl NameVisitor<'_> {
330    fn add_name(
331        &mut self,
332        file: FileIndex,
333        name: &str,
334        path1: &[i32],
335        path2: &[i32],
336        kind: DefinitionKind,
337    ) {
338        let path = join_path(path1, path2);
339
340        match self.pool.names.entry(name.into()) {
341            hash_map::Entry::Vacant(entry) => {
342                entry.insert(Definition { file, kind, path });
343            }
344            hash_map::Entry::Occupied(_) => {
345                let entry = &self.pool.names[name];
346
347                if matches!(kind, DefinitionKind::Package)
348                    && matches!(entry.kind, DefinitionKind::Package)
349                {
350                    return;
351                }
352
353                self.errors.push(DescriptorErrorKind::DuplicateName {
354                    name: name.to_owned(),
355                    first: Label::new(
356                        &self.pool.files,
357                        "first defined here",
358                        entry.file,
359                        entry.path.clone(),
360                    ),
361                    second: Label::new(&self.pool.files, "defined again here", file, path),
362                })
363            }
364        }
365    }
366
367    fn check_message_field_camel_case_names(
368        &mut self,
369        file: FileIndex,
370        path: &[i32],
371        message: &DescriptorProto,
372    ) {
373        let mut names: HashMap<String, (&str, i32)> = HashMap::new();
374        for (index, field) in message.field.iter().enumerate() {
375            let name = field.name();
376            let index = index as i32;
377
378            match names.entry(to_lower_without_underscores(name)) {
379                hash_map::Entry::Occupied(entry) => {
380                    self.errors
381                        .push(DescriptorErrorKind::DuplicateFieldCamelCaseName {
382                            first_name: entry.get().0.to_owned(),
383                            first: Label::new(
384                                &self.pool.files,
385                                "first defined here",
386                                file,
387                                join_path(
388                                    path,
389                                    &[tag::message::FIELD, entry.get().1, tag::field::NAME],
390                                ),
391                            ),
392                            second_name: name.to_owned(),
393                            second: Label::new(
394                                &self.pool.files,
395                                "defined again here",
396                                file,
397                                join_path(path, &[tag::message::FIELD, index, tag::field::NAME]),
398                            ),
399                        })
400                }
401                hash_map::Entry::Vacant(entry) => {
402                    entry.insert((name, index));
403                }
404            }
405        }
406    }
407}
408
409fn to_lower_without_underscores(name: &str) -> String {
410    name.chars()
411        .filter_map(|ch| match ch {
412            '_' => None,
413            _ => Some(ch.to_ascii_lowercase()),
414        })
415        .collect()
416}