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}