protox_parse/
lib.rs

1//! Parsing of protobuf source files.
2//!
3//! See the documentation for [`parse()`] for details.
4#![warn(missing_debug_implementations, missing_docs)]
5#![deny(unsafe_code)]
6#![doc(html_root_url = "https://docs.rs/protox-parse/0.7.0/")]
7
8use logos::Span;
9use prost_types::FileDescriptorProto;
10
11pub use self::error::ParseError;
12
13mod ast;
14mod case;
15mod error;
16mod generate;
17mod lex;
18mod parse;
19mod tag;
20#[cfg(test)]
21mod tests;
22
23const MAX_MESSAGE_FIELD_NUMBER: i32 = 536_870_911;
24
25/// Parses a single protobuf source file into a [`FileDescriptorProto`].
26///
27/// This function only looks at the syntax of the file, without resolving type names or reading
28/// imported files.
29///
30/// # Examples
31///
32/// ```
33/// # use protox_parse::parse;
34/// # use prost_types::{DescriptorProto, FieldDescriptorProto, FileDescriptorProto, SourceCodeInfo, source_code_info::Location, field_descriptor_proto::Label};
35/// #
36/// let source = r#"
37///     syntax = "proto3";
38///     import "dep.proto";
39///
40///     message Foo {
41///         Bar bar = 1;
42///     }
43/// "#;
44/// let file_descriptor = parse("foo.proto", source).unwrap();
45/// assert_eq!(file_descriptor, FileDescriptorProto {
46///     name: Some("foo.proto".to_owned()),
47///     syntax: Some("proto3".to_owned()),
48///     dependency: vec!["dep.proto".to_owned()],
49///     message_type: vec![DescriptorProto {
50///         name: Some("Foo".to_owned()),
51///         field: vec![FieldDescriptorProto {
52///             label: Some(Label::Optional as _),
53///             name: Some("bar".to_owned()),
54///             number: Some(1),
55///             type_name: Some("Bar".to_owned()),
56///             ..Default::default()
57///         }],
58///         ..Default::default()
59///     }],
60///     source_code_info: Some(SourceCodeInfo {
61///         location: vec![
62///             Location { path: vec![], span: vec![1, 4, 6, 5], ..Default::default() },
63///             Location { path: vec![3, 0], span: vec![2, 4, 23], ..Default::default() },
64///             Location { path: vec![4, 0], span: vec![4, 4, 6, 5], ..Default::default() },
65///             Location { path: vec![4, 0, 1], span: vec![4, 12, 15], ..Default::default() },
66///             Location { path: vec![4, 0, 2, 0], span: vec![5, 8, 20], ..Default::default() },
67///             Location { path: vec![4, 0, 2, 0, 1], span: vec![5, 12, 15], ..Default::default() },
68///             Location { path: vec![4, 0, 2, 0, 3], span: vec![5, 18, 19], ..Default::default() },
69///             Location { path: vec![4, 0, 2, 0, 6], span: vec![5, 8, 11], ..Default::default() },
70///             Location { path: vec![12], span: vec![1, 4, 22], ..Default::default() },
71///         ],
72///     }),
73///     ..Default::default()
74/// })
75/// ```
76pub fn parse(name: &str, source: &str) -> Result<FileDescriptorProto, ParseError> {
77    if source.len() > MAX_FILE_LEN {
78        return Err(ParseError::new(
79            vec![error::ParseErrorKind::FileTooLarge],
80            name,
81            String::default(),
82        ));
83    }
84
85    let ast = parse::parse_file(source)
86        .map_err(|errors| ParseError::new(errors, name, source.to_owned()))?;
87
88    generate::generate_file(ast, name, source)
89        .map_err(|errors| ParseError::new(errors, name, source.to_owned()))
90}
91
92const MAX_FILE_LEN: usize = i32::MAX as usize;
93
94fn index_to_i32(index: usize) -> i32 {
95    // We enforce that all files parsed are at most i32::MAX bytes long. Therefore the indices of any
96    // definitions in a single file must fit into an i32.
97    index.try_into().unwrap()
98}
99
100fn join_span(start: Span, end: Span) -> Span {
101    start.start..end.end
102}