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}