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                        "'{name}' is defined in '{dep_name}', which is not imported by '{root_name}'"
267                    )),
268                    name: name.into_owned(),
269                })
270            }
271            ResolveNameResult::NotFound => Err(DescriptorErrorKind::NameNotFound {
272                name: orig_name.into(),
273                found: Label::new(
274                    files,
275                    "found here",
276                    found_file,
277                    join_path(found_path1, found_path2),
278                ),
279                help: None,
280            }),
281            ResolveNameResult::Shadowed { name, shadowed_name } => Err(DescriptorErrorKind::NameShadowed {
282                found: Label::new(
283                    files,
284                    "found here",
285                    found_file,
286                    join_path(found_path1, found_path2),
287                ),
288                help: Some(format!(
289                    "The innermost scope is searched first in name resolution. Consider using a leading '.' (i.e., '.{name}') to start from the outermost scope.",
290                )),
291                name: name.into_owned(),
292                shadowed_name: shadowed_name.into_owned(),
293            }),
294        }
295    }
296}
297
298fn to_json_name(name: &str) -> String {
299    let mut result = String::with_capacity(name.len());
300    let mut uppercase_next = false;
301
302    for ch in name.chars() {
303        if ch == '_' {
304            uppercase_next = true
305        } else if uppercase_next {
306            result.push(ch.to_ascii_uppercase());
307            uppercase_next = false;
308        } else {
309            result.push(ch);
310        }
311    }
312
313    result
314}
315
316fn resolve_name<'a, 'b>(
317    dependencies: &HashSet<FileIndex>,
318    names: &'a HashMap<Box<str>, Definition>,
319    scope: &str,
320    name: &'b str,
321    filter: ResolveNameFilter,
322) -> ResolveNameResult<'a, 'b> {
323    match name.strip_prefix('.') {
324        Some(full_name) => ResolveNameResult::new(dependencies, names, full_name, filter),
325        None if scope.is_empty() => ResolveNameResult::new(dependencies, names, name, filter),
326        None => resolve_relative_name(dependencies, names, scope, name, filter),
327    }
328}
329
330fn resolve_relative_name<'a, 'b>(
331    dependencies: &HashSet<FileIndex>,
332    names: &'a HashMap<Box<str>, Definition>,
333    scope: &str,
334    relative_name: &'b str,
335    filter: ResolveNameFilter,
336) -> ResolveNameResult<'a, 'b> {
337    let mut err = ResolveNameResult::NotFound;
338    let relative_first_part = relative_name.split('.').next().unwrap_or_default();
339
340    for candidate_parent in resolve_relative_candidate_parents(scope) {
341        let candidate = match candidate_parent {
342            "" => Cow::Borrowed(relative_first_part),
343            _ => Cow::Owned(format!("{candidate_parent}.{relative_first_part}")),
344        };
345
346        if relative_first_part.len() == relative_name.len() {
347            // Looking up a simple name e.g. `Foo`
348            let res = ResolveNameResult::new(dependencies, names, candidate, filter);
349            if res.is_found() {
350                return res.into_owned();
351            } else if matches!(err, ResolveNameResult::NotFound) {
352                err = res;
353            }
354        } else {
355            // Looking up a name including a namespace e.g. `foo.Foo`. First determine the scope using the first component of the name.
356            match names.get(candidate.as_ref()) {
357                Some(def) if def.kind.is_parent() => {
358                    let candidate_full = match candidate_parent {
359                        "" => Cow::Borrowed(relative_name),
360                        _ => Cow::Owned(format!("{candidate_parent}.{relative_name}")),
361                    };
362
363                    let res =
364                        ResolveNameResult::new(dependencies, names, candidate_full.clone(), filter);
365                    if matches!(res, ResolveNameResult::NotFound) {
366                        return ResolveNameResult::Shadowed {
367                            name: Cow::Borrowed(relative_name),
368                            shadowed_name: candidate_full,
369                        };
370                    } else {
371                        return res;
372                    }
373                }
374                _ => continue,
375            }
376        }
377    }
378
379    err.into_owned()
380}
381
382fn resolve_relative_candidate_parents(scope: &str) -> impl Iterator<Item = &str> {
383    iter::once(scope)
384        .chain(scope.rmatch_indices('.').map(move |(i, _)| &scope[..i]))
385        .chain(iter::once(""))
386}
387
388fn join_path(path1: &[i32], path2: &[i32]) -> Box<[i32]> {
389    let mut path = Vec::with_capacity(path1.len() + path2.len());
390    path.extend_from_slice(path1);
391    path.extend_from_slice(path2);
392    path.into_boxed_slice()
393}