prost_reflect/descriptor/build/
mod.rs

1mod names;
2mod options;
3mod resolve;
4mod visit;
5
6use core::fmt;
7use std::{
8    borrow::Cow,
9    collections::{HashMap, HashSet},
10    iter,
11    sync::Arc,
12};
13
14use crate::{
15    descriptor::{
16        error::{DescriptorErrorKind, Label},
17        to_index,
18        types::FileDescriptorProto,
19        Definition, DefinitionKind, DescriptorPoolInner, EnumIndex, ExtensionIndex,
20        FileDescriptorInner, FileIndex, MessageIndex, ServiceIndex,
21    },
22    DescriptorError, DescriptorPool,
23};
24
25#[derive(Clone, Copy)]
26struct DescriptorPoolOffsets {
27    file: FileIndex,
28    message: MessageIndex,
29    enum_: EnumIndex,
30    service: ServiceIndex,
31    extension: ExtensionIndex,
32}
33
34#[derive(Copy, Clone, Debug)]
35enum ResolveNameFilter {
36    Message,
37    Extension,
38    FieldType,
39}
40
41enum ResolveNameResult<'a, 'b> {
42    Found {
43        name: Cow<'b, str>,
44        def: &'a Definition,
45    },
46    InvalidType {
47        name: Cow<'b, str>,
48        def: &'a Definition,
49        filter: ResolveNameFilter,
50    },
51    NotImported {
52        name: Cow<'b, str>,
53        file: FileIndex,
54    },
55    Shadowed {
56        name: Cow<'b, str>,
57        shadowed_name: Cow<'b, str>,
58    },
59    NotFound,
60}
61
62impl DescriptorPoolOffsets {
63    fn new(pool: &DescriptorPoolInner) -> Self {
64        DescriptorPoolOffsets {
65            file: to_index(pool.files.len()),
66            message: to_index(pool.messages.len()),
67            enum_: to_index(pool.enums.len()),
68            service: to_index(pool.services.len()),
69            extension: to_index(pool.extensions.len()),
70        }
71    }
72
73    fn rollback(&self, pool: &mut DescriptorPoolInner) {
74        pool.files.truncate(self.file as usize);
75        pool.messages.truncate(self.message as usize);
76        pool.enums.truncate(self.enum_ as usize);
77        pool.extensions.truncate(self.extension as usize);
78        pool.services.truncate(self.service as usize);
79        pool.names.retain(|name, definition| match definition.kind {
80            DefinitionKind::Package => pool.files.iter().any(|f| {
81                f.prost.package().starts_with(name.as_ref())
82                    && matches!(
83                        f.prost.package().as_bytes().get(name.len()),
84                        None | Some(&b'.')
85                    )
86            }),
87            DefinitionKind::Message(message)
88            | DefinitionKind::Field(message)
89            | DefinitionKind::Oneof(message) => message < self.message,
90            DefinitionKind::Service(service) | DefinitionKind::Method(service) => {
91                service < self.service
92            }
93            DefinitionKind::Enum(enum_) | DefinitionKind::EnumValue(enum_) => enum_ < self.enum_,
94            DefinitionKind::Extension(extension) => extension < self.extension,
95        });
96        pool.file_names.retain(|_, &mut file| file < self.file);
97        for message in &mut pool.messages {
98            message.extensions.retain(|&message| message < self.message);
99        }
100    }
101}
102
103impl DescriptorPool {
104    pub(crate) fn build_files<I>(&mut self, files: I) -> Result<(), DescriptorError>
105    where
106        I: IntoIterator<Item = FileDescriptorProto>,
107    {
108        let offsets = DescriptorPoolOffsets::new(&self.inner);
109
110        let deduped_files: Vec<_> = files
111            .into_iter()
112            .filter(|f| !self.inner.file_names.contains_key(f.name()))
113            .collect();
114
115        let result = self.build_files_deduped(offsets, &deduped_files);
116        if result.is_err() {
117            debug_assert_eq!(Arc::strong_count(&self.inner), 1);
118            offsets.rollback(Arc::get_mut(&mut self.inner).unwrap());
119        }
120
121        result
122    }
123
124    fn build_files_deduped(
125        &mut self,
126        offsets: DescriptorPoolOffsets,
127        deduped_files: &[FileDescriptorProto],
128    ) -> Result<(), DescriptorError> {
129        if deduped_files.is_empty() {
130            return Ok(());
131        }
132
133        let inner = Arc::make_mut(&mut self.inner);
134
135        inner.collect_names(offsets, deduped_files)?;
136
137        inner.resolve_names(offsets, deduped_files)?;
138
139        self.resolve_options(offsets, deduped_files)?;
140
141        debug_assert_eq!(Arc::strong_count(&self.inner), 1);
142        let inner = Arc::get_mut(&mut self.inner).unwrap();
143        for file in &mut inner.files[offsets.file as usize..] {
144            file.prost = file.raw.to_prost();
145        }
146
147        Ok(())
148    }
149}
150
151impl ResolveNameFilter {
152    fn is_match(&self, def: &DefinitionKind) -> bool {
153        matches!(
154            (self, def),
155            (ResolveNameFilter::Message, DefinitionKind::Message(_))
156                | (ResolveNameFilter::Extension, DefinitionKind::Extension(_))
157                | (
158                    ResolveNameFilter::FieldType,
159                    DefinitionKind::Message(_) | DefinitionKind::Enum(_),
160                )
161        )
162    }
163}
164
165impl fmt::Display for ResolveNameFilter {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        match self {
168            ResolveNameFilter::Message => f.write_str("a message type"),
169            ResolveNameFilter::Extension => f.write_str("an extension"),
170            ResolveNameFilter::FieldType => f.write_str("a message or enum type"),
171        }
172    }
173}
174
175impl<'a, 'b> ResolveNameResult<'a, 'b> {
176    fn new(
177        dependencies: &HashSet<FileIndex>,
178        names: &'a HashMap<Box<str>, Definition>,
179        name: impl Into<Cow<'b, str>>,
180        filter: ResolveNameFilter,
181    ) -> Self {
182        let name = name.into();
183        if let Some(def) = names.get(name.as_ref()) {
184            if !dependencies.contains(&def.file) {
185                ResolveNameResult::NotImported {
186                    name,
187                    file: def.file,
188                }
189            } else if !filter.is_match(&def.kind) {
190                ResolveNameResult::InvalidType { name, def, filter }
191            } else {
192                ResolveNameResult::Found { name, def }
193            }
194        } else {
195            ResolveNameResult::NotFound
196        }
197    }
198
199    fn into_owned(self) -> ResolveNameResult<'a, 'static> {
200        match self {
201            ResolveNameResult::Found { name, def } => ResolveNameResult::Found {
202                name: Cow::Owned(name.into_owned()),
203                def,
204            },
205            ResolveNameResult::InvalidType { name, def, filter } => {
206                ResolveNameResult::InvalidType {
207                    name: Cow::Owned(name.into_owned()),
208                    def,
209                    filter,
210                }
211            }
212            ResolveNameResult::NotImported { name, file } => ResolveNameResult::NotImported {
213                name: Cow::Owned(name.into_owned()),
214                file,
215            },
216            ResolveNameResult::Shadowed {
217                name,
218                shadowed_name,
219            } => ResolveNameResult::Shadowed {
220                name: Cow::Owned(name.into_owned()),
221                shadowed_name: Cow::Owned(shadowed_name.into_owned()),
222            },
223            ResolveNameResult::NotFound => ResolveNameResult::NotFound,
224        }
225    }
226
227    fn is_found(&self) -> bool {
228        matches!(self, ResolveNameResult::Found { .. })
229    }
230
231    #[allow(clippy::result_large_err)]
232    fn into_result(
233        self,
234        orig_name: impl Into<String>,
235        files: &[FileDescriptorInner],
236        found_file: FileIndex,
237        found_path1: &[i32],
238        found_path2: &[i32],
239    ) -> Result<(Cow<'b, str>, &'a Definition), DescriptorErrorKind> {
240        match self {
241            ResolveNameResult::Found { name, def } => Ok((name, def)),
242            ResolveNameResult::InvalidType { name, def, filter } => {
243                Err(DescriptorErrorKind::InvalidType {
244                    name: name.into_owned(),
245                    expected: filter.to_string(),
246                    found: Label::new(
247                        files,
248                        "found here",
249                        found_file,
250                        join_path(found_path1, found_path2),
251                    ),
252                    defined: Label::new(files, "defined here", def.file, def.path.clone()),
253                })
254            }
255            ResolveNameResult::NotImported { name, file } => {
256                let root_name = files[found_file as usize].raw.name();
257                let dep_name = files[file as usize].raw.name();
258                Err(DescriptorErrorKind::NameNotFound {
259                    found: Label::new(
260                        files,
261                        "found here",
262                        found_file,
263                        join_path(found_path1, found_path2),
264                    ),
265                    help: Some(format!(
266                        "'{}' is defined in '{}', which is not imported by '{}'",
267                        name, dep_name, root_name
268                    )),
269                    name: name.into_owned(),
270                })
271            }
272            ResolveNameResult::NotFound => Err(DescriptorErrorKind::NameNotFound {
273                name: orig_name.into(),
274                found: Label::new(
275                    files,
276                    "found here",
277                    found_file,
278                    join_path(found_path1, found_path2),
279                ),
280                help: None,
281            }),
282            ResolveNameResult::Shadowed { name, shadowed_name } => Err(DescriptorErrorKind::NameShadowed {
283                found: Label::new(
284                    files,
285                    "found here",
286                    found_file,
287                    join_path(found_path1, found_path2),
288                ),
289                help: Some(format!(
290                    "The innermost scope is searched first in name resolution. Consider using a leading '.' (i.e., '.{name}') to start from the outermost scope.",
291                )),
292                name: name.into_owned(),
293                shadowed_name: shadowed_name.into_owned(),
294            }),
295        }
296    }
297}
298
299fn to_json_name(name: &str) -> String {
300    let mut result = String::with_capacity(name.len());
301    let mut uppercase_next = false;
302
303    for ch in name.chars() {
304        if ch == '_' {
305            uppercase_next = true
306        } else if uppercase_next {
307            result.push(ch.to_ascii_uppercase());
308            uppercase_next = false;
309        } else {
310            result.push(ch);
311        }
312    }
313
314    result
315}
316
317fn resolve_name<'a, 'b>(
318    dependencies: &HashSet<FileIndex>,
319    names: &'a HashMap<Box<str>, Definition>,
320    scope: &str,
321    name: &'b str,
322    filter: ResolveNameFilter,
323) -> ResolveNameResult<'a, 'b> {
324    match name.strip_prefix('.') {
325        Some(full_name) => ResolveNameResult::new(dependencies, names, full_name, filter),
326        None if scope.is_empty() => ResolveNameResult::new(dependencies, names, name, filter),
327        None => resolve_relative_name(dependencies, names, scope, name, filter),
328    }
329}
330
331fn resolve_relative_name<'a, 'b>(
332    dependencies: &HashSet<FileIndex>,
333    names: &'a HashMap<Box<str>, Definition>,
334    scope: &str,
335    relative_name: &'b str,
336    filter: ResolveNameFilter,
337) -> ResolveNameResult<'a, 'b> {
338    let mut err = ResolveNameResult::NotFound;
339    let relative_first_part = relative_name.split('.').next().unwrap_or_default();
340
341    for candidate_parent in resolve_relative_candidate_parents(scope) {
342        let candidate = match candidate_parent {
343            "" => Cow::Borrowed(relative_first_part),
344            _ => Cow::Owned(format!("{}.{}", candidate_parent, relative_first_part)),
345        };
346
347        if relative_first_part.len() == relative_name.len() {
348            // Looking up a simple name e.g. `Foo`
349            let res = ResolveNameResult::new(dependencies, names, candidate, filter);
350            if res.is_found() {
351                return res.into_owned();
352            } else if matches!(err, ResolveNameResult::NotFound) {
353                err = res;
354            }
355        } else {
356            // Looking up a name including a namespace e.g. `foo.Foo`. First determine the scope using the first component of the name.
357            match names.get(candidate.as_ref()) {
358                Some(def) if def.kind.is_parent() => {
359                    let candidate_full = match candidate_parent {
360                        "" => Cow::Borrowed(relative_name),
361                        _ => Cow::Owned(format!("{}.{}", candidate_parent, relative_name)),
362                    };
363
364                    let res =
365                        ResolveNameResult::new(dependencies, names, candidate_full.clone(), filter);
366                    if matches!(res, ResolveNameResult::NotFound) {
367                        return ResolveNameResult::Shadowed {
368                            name: Cow::Borrowed(relative_name),
369                            shadowed_name: candidate_full,
370                        };
371                    } else {
372                        return res;
373                    }
374                }
375                _ => continue,
376            }
377        }
378    }
379
380    err.into_owned()
381}
382
383fn resolve_relative_candidate_parents(scope: &str) -> impl Iterator<Item = &str> {
384    iter::once(scope)
385        .chain(scope.rmatch_indices('.').map(move |(i, _)| &scope[..i]))
386        .chain(iter::once(""))
387}
388
389fn join_path(path1: &[i32], path2: &[i32]) -> Box<[i32]> {
390    let mut path = Vec::with_capacity(path1.len() + path2.len());
391    path.extend_from_slice(path1);
392    path.extend_from_slice(path2);
393    path.into_boxed_slice()
394}