proto_file_parser/lib.rs
1use pest::Parser;
2use pest_derive::Parser;
3use serde::Serialize;
4use thiserror::Error;
5
6/// Parser implementation using pest grammar rules.
7/// This struct is used to parse Protocol Buffer files according to the grammar defined in proto.pest.
8#[derive(Parser)]
9#[grammar = "proto.pest"]
10pub struct ProtoParser;
11
12/// Represents all possible errors that can occur during parsing and processing of proto files.
13#[derive(Error, Debug)]
14pub enum ParserError {
15 /// Indicates an error in the proto syntax structure
16 #[error("Syntax error: {0}")]
17 SyntaxError(String),
18
19 /// Indicates an error during the parsing process
20 #[error("Parse error: {0}")]
21 ParseError(#[from] pest::error::Error<Rule>),
22
23 /// Indicates an error during file operations
24 #[error("IO error: {0}")]
25 IoError(#[from] std::io::Error),
26
27 /// Indicates an error during JSON serialization
28 #[error("JSON serialization error: {0}")]
29 SerializationError(#[from] serde_json::Error),
30}
31
32/// Main structure representing a complete Protocol Buffer file.
33/// Contains all the elements that can be defined in a proto file.
34#[derive(Debug, Serialize)]
35pub struct Proto {
36 /// The syntax version specified in the proto file (e.g., "proto3")
37 syntax: String,
38 /// Optional package name that scopes the proto definitions
39 package: Option<String>,
40 /// List of other proto files that are imported
41 imports: Vec<String>,
42 /// List of message type definitions
43 messages: Vec<Message>,
44 /// List of enum type definitions
45 enums: Vec<EnumDef>,
46 /// List of service definitions
47 services: Vec<Service>,
48}
49
50/// Represents a message definition in the proto file.
51/// Messages are user-defined composite types.
52#[derive(Debug, Serialize)]
53pub struct Message {
54 /// Name of the message type
55 name: String,
56 /// List of fields contained in the message
57 fields: Vec<Field>,
58 /// List of message types defined within this message
59 nested_messages: Vec<Message>,
60 /// List of enum types defined within this message
61 nested_enums: Vec<EnumDef>,
62}
63
64/// Represents a field within a message.
65/// Fields are the basic components of a message.
66#[derive(Debug, Serialize)]
67pub struct Field {
68 /// Name of the field
69 name: String,
70 /// Type of the field (can be primitive type or another message type)
71 type_name: String,
72 /// Unique numerical tag that identifies the field in the message
73 tag: i32,
74 /// Indicates if the field is a repeated field (array/list)
75 repeated: bool,
76}
77
78/// Represents an enumeration definition.
79/// Enums are a type that can have one of a predefined set of values.
80#[derive(Debug, Serialize)]
81pub struct EnumDef {
82 /// Name of the enum type
83 name: String,
84 /// List of possible values for this enum
85 values: Vec<EnumValue>,
86}
87
88/// Represents a single value in an enum definition.
89#[derive(Debug, Serialize)]
90pub struct EnumValue {
91 /// Name of the enum value (should be UPPERCASE_WITH_UNDERSCORES by convention)
92 name: String,
93 /// Integer value associated with this enum value
94 number: i32,
95}
96
97/// Represents a service definition.
98/// Services define methods that can be called remotely.
99#[derive(Debug, Serialize)]
100pub struct Service {
101 /// Name of the service
102 name: String,
103 /// List of methods provided by this service
104 methods: Vec<Method>,
105}
106
107/// Represents an RPC method in a service definition.
108#[derive(Debug, Serialize)]
109pub struct Method {
110 /// Name of the method
111 name: String,
112 /// Type of the input message
113 input_type: String,
114 /// Type of the output message
115 output_type: String,
116}
117
118impl Proto {
119 /// Parses a proto file from the filesystem and returns its JSON representation.
120 ///
121 /// # Arguments
122 ///
123 /// * `path` - Path to the proto file to be parsed
124 ///
125 /// # Returns
126 ///
127 /// A Result containing either the JSON string representation of the parsed proto file
128 /// or a ParserError if any error occurs during parsing or processing.
129 ///
130 /// # Examples
131 ///
132 /// ```no_run
133 /// use proto_file_parser::Proto;
134 ///
135 /// let json = Proto::parse_file("example.proto").unwrap();
136 /// println!("{}", json);
137 /// ```
138 pub fn parse_file(path: &str) -> Result<String, ParserError> {
139 let content = std::fs::read_to_string(path)?;
140 Self::parse(&content)
141 }
142
143 /// Parses a proto definition from a string and returns its JSON representation.
144 ///
145 /// # Arguments
146 ///
147 /// * `input` - String containing the proto definition to be parsed
148 ///
149 /// # Returns
150 ///
151 /// A Result containing either the JSON string representation of the parsed proto definition
152 /// or a ParserError if any error occurs during parsing or processing.
153 ///
154 /// # Examples
155 ///
156 /// ```
157 /// use proto_file_parser::Proto;
158 ///
159 /// let input = r#"
160 /// syntax = "proto3";
161 /// message Test {
162 /// string name = 1;
163 /// }
164 /// "#;
165 ///
166 /// let json = Proto::parse(input).unwrap();
167 /// println!("{}", json);
168 /// ```
169 pub fn parse(input: &str) -> Result<String, ParserError> {
170 let pairs = ProtoParser::parse(Rule::proto_file, input)?;
171
172 let mut proto = Proto {
173 syntax: "proto3".to_string(),
174 package: None,
175 imports: Vec::new(),
176 messages: Vec::new(),
177 enums: Vec::new(),
178 services: Vec::new(),
179 };
180
181 for pair in pairs {
182 match pair.as_rule() {
183 Rule::proto_file => {
184 for inner_pair in pair.into_inner() {
185 match inner_pair.as_rule() {
186 Rule::syntax => {
187 proto.syntax = inner_pair
188 .into_inner()
189 .next()
190 .unwrap()
191 .as_str()
192 .trim_matches('"')
193 .to_string();
194 }
195 Rule::package => {
196 proto.package = Some(
197 inner_pair
198 .into_inner()
199 .next()
200 .unwrap()
201 .as_str()
202 .to_string(),
203 );
204 }
205 Rule::import => {
206 proto.imports.push(
207 inner_pair
208 .into_inner()
209 .next()
210 .unwrap()
211 .as_str()
212 .trim_matches('"')
213 .to_string(),
214 );
215 }
216 Rule::message_def => {
217 proto.messages.push(Self::parse_message(inner_pair)?);
218 }
219 Rule::enum_def => {
220 proto.enums.push(Self::parse_enum(inner_pair)?);
221 }
222 Rule::service_def => {
223 proto.services.push(Self::parse_service(inner_pair)?);
224 }
225 Rule::EOI => {}
226 _ => {}
227 }
228 }
229 }
230 _ => {}
231 }
232 }
233
234 let json = serde_json::to_string_pretty(&proto)?;
235 Ok(json)
236 }
237
238 /// Parses a message definition from a pest Pair.
239 fn parse_message(pair: pest::iterators::Pair<Rule>) -> Result<Message, ParserError> {
240 let mut message = Message {
241 name: String::new(),
242 fields: Vec::new(),
243 nested_messages: Vec::new(),
244 nested_enums: Vec::new(),
245 };
246
247 let mut pairs = pair.into_inner();
248
249 if let Some(name_pair) = pairs.next() {
250 if name_pair.as_rule() == Rule::ident {
251 message.name = name_pair.as_str().to_string();
252 }
253 }
254
255 for pair in pairs {
256 match pair.as_rule() {
257 Rule::field => {
258 message.fields.push(Self::parse_field(pair)?);
259 }
260 Rule::message_def => {
261 message.nested_messages.push(Self::parse_message(pair)?);
262 }
263 Rule::enum_def => {
264 message.nested_enums.push(Self::parse_enum(pair)?);
265 }
266 _ => {}
267 }
268 }
269
270 Ok(message)
271 }
272
273 /// Parses a field definition from a pest Pair.
274 fn parse_field(pair: pest::iterators::Pair<Rule>) -> Result<Field, ParserError> {
275 let mut field = Field {
276 name: String::new(),
277 type_name: String::new(),
278 tag: 0,
279 repeated: false,
280 };
281
282 let mut pairs = pair.into_inner().peekable();
283
284 if let Some(first_pair) = pairs.peek() {
285 if first_pair.as_rule() == Rule::field_rule {
286 field.repeated = first_pair.as_str() == "repeated";
287 pairs.next();
288 }
289 }
290
291 if let Some(type_pair) = pairs.next() {
292 field.type_name = type_pair.as_str().to_string();
293 }
294
295 if let Some(name_pair) = pairs.next() {
296 field.name = name_pair.as_str().to_string();
297 }
298
299 if let Some(tag_pair) = pairs.next() {
300 field.tag = tag_pair.as_str().parse().unwrap_or(0);
301 }
302
303 Ok(field)
304 }
305
306 /// Parses an enum definition from a pest Pair.
307 fn parse_enum(pair: pest::iterators::Pair<Rule>) -> Result<EnumDef, ParserError> {
308 let mut enum_def = EnumDef {
309 name: String::new(),
310 values: Vec::new(),
311 };
312
313 let mut pairs = pair.into_inner();
314
315 if let Some(name_pair) = pairs.next() {
316 if name_pair.as_rule() == Rule::ident {
317 enum_def.name = name_pair.as_str().to_string();
318 }
319 }
320
321 for pair in pairs {
322 if pair.as_rule() == Rule::enum_value {
323 let mut value_pairs = pair.into_inner();
324 let mut enum_value = EnumValue {
325 name: String::new(),
326 number: 0,
327 };
328
329 if let Some(name_pair) = value_pairs.next() {
330 enum_value.name = name_pair.as_str().to_string();
331 }
332 if let Some(number_pair) = value_pairs.next() {
333 enum_value.number = number_pair.as_str().parse().unwrap_or(0);
334 }
335
336 enum_def.values.push(enum_value);
337 }
338 }
339
340 Ok(enum_def)
341 }
342
343 /// Parses a service definition from a pest Pair.
344 fn parse_service(pair: pest::iterators::Pair<Rule>) -> Result<Service, ParserError> {
345 let mut service = Service {
346 name: String::new(),
347 methods: Vec::new(),
348 };
349
350 let mut pairs = pair.into_inner();
351
352 if let Some(name_pair) = pairs.next() {
353 if name_pair.as_rule() == Rule::ident {
354 service.name = name_pair.as_str().to_string();
355 }
356 }
357
358 for pair in pairs {
359 if pair.as_rule() == Rule::rpc_def {
360 let mut method = Method {
361 name: String::new(),
362 input_type: String::new(),
363 output_type: String::new(),
364 };
365
366 let mut rpc_pairs = pair.into_inner();
367
368 if let Some(name_pair) = rpc_pairs.next() {
369 method.name = name_pair.as_str().to_string();
370 }
371 if let Some(input_pair) = rpc_pairs.next() {
372 method.input_type = input_pair.into_inner().next().unwrap().as_str().to_string();
373 }
374 if let Some(output_pair) = rpc_pairs.next() {
375 method.output_type = output_pair.into_inner().next().unwrap().as_str().to_string();
376 }
377
378 service.methods.push(method);
379 }
380 }
381
382 Ok(service)
383 }
384}