├── .gitignore ├── Cargo.toml ├── Changelog.md ├── LICENSE-MIT.md ├── README.md └── src ├── lib.rs └── parser.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protobuf-parser" 3 | version = "0.1.3" 4 | description = "A nom-based parser for .proto files" 5 | authors = ["Johann Tuffe "] 6 | keywords = ["protobuf", "parser"] 7 | license = "MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/tafia/protobuf-parser" 10 | 11 | [dependencies] 12 | nom = "3.2.1" 13 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | > Legend: 2 | - feat: A new feature 3 | - fix: A bug fix 4 | - docs: Documentation only changes 5 | - style: White-space, formatting, missing semi-colons, etc 6 | - refactor: A code change that neither fixes a bug nor adds a feature 7 | - perf: A code change that improves performance 8 | - test: Adding missing tests 9 | - chore: Changes to the build process or auxiliary tools/libraries/documentation 10 | 11 | ## 0.1.3 12 | - feat: add extension parsing 13 | 14 | ## 0.1.2 15 | - fix: return an error if file ends with an invalid entry 16 | 17 | ## 0.1.1 18 | - feat: support group parsing 19 | -------------------------------------------------------------------------------- /LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Johann Tuffe and Stepan Koltsov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # protobuf-parser 2 | 3 | A nom-based protobuf parser. 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A nom-based protobuf file parser 2 | //! 3 | //! This crate can be seen as a rust transcription of the 4 | //! [descriptor.proto](https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto) file 5 | 6 | #[macro_use] 7 | extern crate nom; 8 | 9 | mod parser; 10 | 11 | use std::ops::Range; 12 | use parser::file_descriptor; 13 | 14 | /// Protobox syntax 15 | #[derive(Debug, Clone, Copy)] 16 | pub enum Syntax { 17 | /// Protobuf syntax [2](https://developers.google.com/protocol-buffers/docs/proto) (default) 18 | Proto2, 19 | /// Protobuf syntax [3](https://developers.google.com/protocol-buffers/docs/proto3) 20 | Proto3, 21 | } 22 | 23 | impl Default for Syntax { 24 | fn default() -> Syntax { 25 | Syntax::Proto2 26 | } 27 | } 28 | 29 | /// A field rule 30 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 31 | pub enum Rule { 32 | /// A well-formed message can have zero or one of this field (but not more than one). 33 | Optional, 34 | /// This field can be repeated any number of times (including zero) in a well-formed message. 35 | /// The order of the repeated values will be preserved. 36 | Repeated, 37 | /// A well-formed message must have exactly one of this field. 38 | Required, 39 | } 40 | 41 | /// Protobuf supported field types 42 | /// 43 | /// TODO: Groups (even if deprecated) 44 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 45 | pub enum FieldType { 46 | /// Protobuf int32 47 | /// 48 | /// # Remarks 49 | /// 50 | /// Uses variable-length encoding. Inefficient for encoding negative numbers – if 51 | /// your field is likely to have negative values, use sint32 instead. 52 | Int32, 53 | /// Protobuf int64 54 | /// 55 | /// # Remarks 56 | /// 57 | /// Uses variable-length encoding. Inefficient for encoding negative numbers – if 58 | /// your field is likely to have negative values, use sint64 instead. 59 | Int64, 60 | /// Protobuf uint32 61 | /// 62 | /// # Remarks 63 | /// 64 | /// Uses variable-length encoding. 65 | Uint32, 66 | /// Protobuf uint64 67 | /// 68 | /// # Remarks 69 | /// 70 | /// Uses variable-length encoding. 71 | Uint64, 72 | /// Protobuf sint32 73 | /// 74 | /// # Remarks 75 | /// 76 | /// Uses ZigZag variable-length encoding. Signed int value. These more efficiently 77 | /// encode negative numbers than regular int32s. 78 | Sint32, 79 | /// Protobuf sint64 80 | /// 81 | /// # Remarks 82 | /// 83 | /// Uses ZigZag variable-length encoding. Signed int value. These more efficiently 84 | /// encode negative numbers than regular int32s. 85 | Sint64, 86 | /// Protobuf bool 87 | Bool, 88 | /// Protobuf fixed64 89 | /// 90 | /// # Remarks 91 | /// 92 | /// Always eight bytes. More efficient than uint64 if values are often greater than 2^56. 93 | Fixed64, 94 | /// Protobuf sfixed64 95 | /// 96 | /// # Remarks 97 | /// 98 | /// Always eight bytes. 99 | Sfixed64, 100 | /// Protobuf double 101 | Double, 102 | /// Protobuf string 103 | /// 104 | /// # Remarks 105 | /// 106 | /// A string must always contain UTF-8 encoded or 7-bit ASCII text. 107 | String, 108 | /// Protobuf bytes 109 | /// 110 | /// # Remarks 111 | /// 112 | /// May contain any arbitrary sequence of bytes. 113 | Bytes, 114 | /// Protobut fixed32 115 | /// 116 | /// # Remarks 117 | /// 118 | /// Always four bytes. More efficient than uint32 if values are often greater than 2^28. 119 | Fixed32, 120 | /// Protobut sfixed32 121 | /// 122 | /// # Remarks 123 | /// 124 | /// Always four bytes. 125 | Sfixed32, 126 | /// Protobut float 127 | Float, 128 | /// Protobuf message or enum (holds the name) 129 | MessageOrEnum(String), 130 | /// Protobut map 131 | Map(Box<(FieldType, FieldType)>), 132 | /// Protobuf group (deprecated) 133 | Group(Vec), 134 | } 135 | 136 | /// A Protobuf Field 137 | #[derive(Debug, Clone, Hash, Eq, PartialEq)] 138 | pub struct Field { 139 | /// Field name 140 | pub name: String, 141 | /// Field `Rule` 142 | pub rule: Rule, 143 | /// Field type 144 | pub typ: FieldType, 145 | /// Tag number 146 | pub number: i32, 147 | /// Default value for the field 148 | pub default: Option, 149 | /// Packed property for repeated fields 150 | pub packed: Option, 151 | /// Is the field deprecated 152 | pub deprecated: bool, 153 | } 154 | 155 | /// A protobuf message 156 | #[derive(Debug, Clone, Default)] 157 | pub struct Message { 158 | /// Message name 159 | pub name: String, 160 | /// Message `Field`s 161 | pub fields: Vec, 162 | /// Message `OneOf`s 163 | pub oneofs: Vec, 164 | /// Message reserved numbers 165 | /// 166 | /// TODO: use RangeInclusive once stable 167 | pub reserved_nums: Vec>, 168 | /// Message reserved names 169 | pub reserved_names: Vec, 170 | /// Nested messages 171 | pub messages: Vec, 172 | /// Nested enums 173 | pub enums: Vec, 174 | } 175 | 176 | /// A protobuf enumeration field 177 | #[derive(Debug, Clone)] 178 | pub struct EnumValue { 179 | /// enum value name 180 | pub name: String, 181 | /// enum value number 182 | pub number: i32, 183 | } 184 | 185 | /// A protobuf enumerator 186 | #[derive(Debug, Clone)] 187 | pub struct Enumeration { 188 | /// enum name 189 | pub name: String, 190 | /// enum values 191 | pub values: Vec, 192 | } 193 | 194 | /// A OneOf 195 | #[derive(Debug, Clone, Default)] 196 | pub struct OneOf { 197 | /// OneOf name 198 | pub name: String, 199 | /// OneOf fields 200 | pub fields: Vec, 201 | } 202 | 203 | #[derive(Debug, Clone)] 204 | pub struct Extension { 205 | /// Extend this type with field 206 | pub extendee: String, 207 | /// Extension field 208 | pub field: Field, 209 | } 210 | 211 | /// A File descriptor representing a whole .proto file 212 | #[derive(Debug, Default, Clone)] 213 | pub struct FileDescriptor { 214 | /// Imports 215 | pub import_paths: Vec, 216 | /// Package 217 | pub package: String, 218 | /// Protobuf Syntax 219 | pub syntax: Syntax, 220 | /// Top level messages 221 | pub messages: Vec, 222 | /// Enums 223 | pub enums: Vec, 224 | /// Extensions 225 | pub extensions: Vec, 226 | } 227 | 228 | impl FileDescriptor { 229 | /// Parses a .proto file content into a `FileDescriptor` 230 | pub fn parse>(file: S) -> Result { 231 | let file = file.as_ref(); 232 | match file_descriptor(file) { 233 | ::nom::IResult::Done(unparsed, r) => { 234 | if !unparsed.is_empty() { 235 | // TODO: make error detection part of parser and report position 236 | Err(::nom::IError::Error(::nom::ErrorKind::NoneOf)) 237 | } else { 238 | Ok(r) 239 | } 240 | } 241 | o => o.to_full_result(), 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | use std::ops::Range; 3 | 4 | use super::{EnumValue, Enumeration, Extension, Field, FieldType, FileDescriptor, Message, OneOf, 5 | Rule, Syntax}; 6 | use nom::{digit, hex_digit, multispace}; 7 | 8 | fn is_word(b: u8) -> bool { 9 | match b { 10 | b'a'...b'z' | b'A'...b'Z' | b'0'...b'9' | b'_' | b'.' => true, 11 | _ => false, 12 | } 13 | } 14 | 15 | named!( 16 | word, 17 | map_res!(take_while!(is_word), |b: &[u8]| String::from_utf8( 18 | b.to_vec() 19 | )) 20 | ); 21 | named!( 22 | word_ref<&str>, 23 | map_res!(take_while!(is_word), str::from_utf8) 24 | ); 25 | 26 | named!( 27 | hex_integer, 28 | do_parse!( 29 | tag!("0x") >> num: map_res!(map_res!(hex_digit, str::from_utf8), |s| { 30 | i32::from_str_radix(s, 16) 31 | }) >> (num) 32 | ) 33 | ); 34 | 35 | named!( 36 | integer, 37 | map_res!(map_res!(digit, str::from_utf8), str::FromStr::from_str) 38 | ); 39 | 40 | named!( 41 | comment<()>, 42 | do_parse!(tag!("//") >> take_until_and_consume!("\n") >> ()) 43 | ); 44 | named!( 45 | block_comment<()>, 46 | do_parse!(tag!("/*") >> take_until_and_consume!("*/") >> ()) 47 | ); 48 | 49 | /// word break: multispace or comment 50 | named!( 51 | br<()>, 52 | alt!(map!(multispace, |_| ()) | comment | block_comment) 53 | ); 54 | 55 | named!( 56 | syntax, 57 | do_parse!( 58 | tag!("syntax") >> many0!(br) >> tag!("=") >> many0!(br) 59 | >> proto: 60 | alt!(tag!("\"proto2\"") => { |_| Syntax::Proto2 } | 61 | tag!("\"proto3\"") => { |_| Syntax::Proto3 }) >> many0!(br) 62 | >> tag!(";") >> (proto) 63 | ) 64 | ); 65 | 66 | named!( 67 | import, 68 | do_parse!( 69 | tag!("import") >> many1!(br) >> tag!("\"") 70 | >> path: map_res!(take_until!("\""), |b: &[u8]| String::from_utf8(b.to_vec())) 71 | >> tag!("\"") >> many0!(br) >> tag!(";") >> (path) 72 | ) 73 | ); 74 | 75 | named!( 76 | package, 77 | do_parse!( 78 | tag!("package") >> many1!(br) >> package: word >> many0!(br) >> tag!(";") >> (package) 79 | ) 80 | ); 81 | 82 | named!( 83 | num_range>, 84 | do_parse!( 85 | from_: integer >> many1!(br) >> tag!("to") >> many1!(br) >> to_: integer 86 | >> (from_..to_.saturating_add(1)) 87 | ) 88 | ); 89 | 90 | named!( 91 | reserved_nums>>, 92 | do_parse!( 93 | tag!("reserved") >> many1!(br) 94 | >> nums: 95 | separated_list!( 96 | do_parse!(many0!(br) >> tag!(",") >> many0!(br) >> (())), 97 | alt!(num_range | integer => { |i: i32| i..i.saturating_add(1) }) 98 | ) >> many0!(br) >> tag!(";") >> (nums) 99 | ) 100 | ); 101 | 102 | named!( 103 | reserved_names>, 104 | do_parse!( 105 | tag!("reserved") >> many1!(br) 106 | >> names: 107 | many1!(do_parse!( 108 | tag!("\"") >> name: word >> tag!("\"") 109 | >> many0!(alt!(br | tag!(",") => { |_| () })) >> (name) 110 | )) >> many0!(br) >> tag!(";") >> (names) 111 | ) 112 | ); 113 | 114 | named!( 115 | key_val<(&str, &str)>, 116 | do_parse!( 117 | tag!("[") >> many0!(br) >> key: word_ref >> many0!(br) >> tag!("=") >> many0!(br) 118 | >> value: map_res!(is_not!("]"), str::from_utf8) >> tag!("]") >> many0!(br) 119 | >> ((key, value.trim())) 120 | ) 121 | ); 122 | 123 | named!( 124 | rule, 125 | alt!(tag!("optional") => { |_| Rule::Optional } | 126 | tag!("repeated") => { |_| Rule::Repeated } | 127 | tag!("required") => { |_| Rule::Required } ) 128 | ); 129 | 130 | named!( 131 | field_type, 132 | alt!(tag!("int32") => { |_| FieldType::Int32 } | 133 | tag!("int64") => { |_| FieldType::Int64 } | 134 | tag!("uint32") => { |_| FieldType::Uint32 } | 135 | tag!("uint64") => { |_| FieldType::Uint64 } | 136 | tag!("sint32") => { |_| FieldType::Sint32 } | 137 | tag!("sint64") => { |_| FieldType::Sint64 } | 138 | tag!("fixed32") => { |_| FieldType::Fixed32 } | 139 | tag!("sfixed32") => { |_| FieldType::Sfixed32 } | 140 | tag!("fixed64") => { |_| FieldType::Fixed64 } | 141 | tag!("sfixed64") => { |_| FieldType::Sfixed64 } | 142 | tag!("bool") => { |_| FieldType::Bool } | 143 | tag!("string") => { |_| FieldType::String } | 144 | tag!("bytes") => { |_| FieldType::Bytes } | 145 | tag!("float") => { |_| FieldType::Float } | 146 | tag!("double") => { |_| FieldType::Double } | 147 | tag!("group") => { |_| FieldType::Group(Vec::new()) } | 148 | map_field => { |(k, v)| FieldType::Map(Box::new((k, v))) } | 149 | word => { |w| FieldType::MessageOrEnum(w) }) 150 | ); 151 | 152 | named!( 153 | map_field<(FieldType, FieldType)>, 154 | do_parse!( 155 | tag!("map") >> many0!(br) >> tag!("<") >> many0!(br) >> key: field_type >> many0!(br) 156 | >> tag!(",") >> many0!(br) >> value: field_type >> tag!(">") >> ((key, value)) 157 | ) 158 | ); 159 | 160 | named!( 161 | fields_in_braces>, 162 | do_parse!( 163 | tag!("{") >> many0!(br) 164 | >> fields: separated_list!(br, message_field) 165 | >> many0!(br) >> tag!("}") >> (fields) 166 | ) 167 | ); 168 | 169 | named!( 170 | one_of, 171 | do_parse!( 172 | tag!("oneof") >> many1!(br) >> name: word >> many0!(br) 173 | >> fields: fields_in_braces >> many0!(br) 174 | >> (OneOf { 175 | name: name, 176 | fields: fields, 177 | }) 178 | ) 179 | ); 180 | 181 | named!( 182 | group_fields_or_semicolon>>, 183 | alt!( 184 | tag!(";") => { |_| None } | 185 | fields_in_braces => { Some }) 186 | ); 187 | 188 | named!( 189 | message_field, 190 | do_parse!( 191 | rule: opt!(rule) >> many0!(br) >> typ: field_type >> many1!(br) >> name: word >> many0!(br) 192 | >> tag!("=") >> many0!(br) >> number: integer >> many0!(br) 193 | >> key_vals: many0!(key_val) >> many0!(br) 194 | >> group_fields: group_fields_or_semicolon >> ({ 195 | 196 | let typ = match (typ, group_fields) { 197 | (FieldType::Group(..), Some(group_fields)) => { 198 | FieldType::Group(group_fields) 199 | } 200 | // TODO: produce error if semicolon is after group or group is without fields 201 | (typ, _) => typ 202 | }; 203 | 204 | Field { 205 | name: name, 206 | rule: rule.unwrap_or(Rule::Optional), 207 | typ: typ, 208 | number: number, 209 | default: key_vals 210 | .iter() 211 | .find(|&&(k, _)| k == "default") 212 | .map(|&(_, v)| v.to_string()), 213 | packed: key_vals 214 | .iter() 215 | .find(|&&(k, _)| k == "packed") 216 | .map(|&(_, v)| str::FromStr::from_str(v).expect("Cannot parse Packed value")), 217 | deprecated: key_vals 218 | .iter() 219 | .find(|&&(k, _)| k == "deprecated") 220 | .map_or(false, |&(_, v)| str::FromStr::from_str(v) 221 | .expect("Cannot parse Deprecated value")), 222 | }}) 223 | ) 224 | ); 225 | 226 | enum MessageEvent { 227 | Message(Message), 228 | Enumeration(Enumeration), 229 | Field(Field), 230 | ReservedNums(Vec>), 231 | ReservedNames(Vec), 232 | OneOf(OneOf), 233 | Ignore, 234 | } 235 | 236 | named!( 237 | message_event, 238 | alt!(reserved_nums => { |r| MessageEvent::ReservedNums(r) } | 239 | reserved_names => { |r| MessageEvent::ReservedNames(r) } | 240 | message_field => { |f| MessageEvent::Field(f) } | 241 | message => { |m| MessageEvent::Message(m) } | 242 | enumerator => { |e| MessageEvent::Enumeration(e) } | 243 | one_of => { |o| MessageEvent::OneOf(o) } | 244 | br => { |_| MessageEvent::Ignore }) 245 | ); 246 | 247 | named!( 248 | message_events<(String, Vec)>, 249 | do_parse!( 250 | tag!("message") >> many1!(br) >> name: word >> many0!(br) >> tag!("{") >> many0!(br) 251 | >> events: many0!(message_event) >> many0!(br) >> tag!("}") >> many0!(br) 252 | >> many0!(tag!(";")) >> ((name, events)) 253 | ) 254 | ); 255 | 256 | named!( 257 | message, 258 | map!( 259 | message_events, 260 | |(name, events): (String, Vec)| { 261 | let mut msg = Message { 262 | name: name.clone(), 263 | ..Message::default() 264 | }; 265 | for e in events { 266 | match e { 267 | MessageEvent::Field(f) => msg.fields.push(f), 268 | MessageEvent::ReservedNums(r) => msg.reserved_nums = r, 269 | MessageEvent::ReservedNames(r) => msg.reserved_names = r, 270 | MessageEvent::Message(m) => msg.messages.push(m), 271 | MessageEvent::Enumeration(e) => msg.enums.push(e), 272 | MessageEvent::OneOf(o) => msg.oneofs.push(o), 273 | MessageEvent::Ignore => (), 274 | } 275 | } 276 | msg 277 | } 278 | ) 279 | ); 280 | 281 | named!( 282 | extensions>, 283 | do_parse!( 284 | tag!("extend") >> many1!(br) >> extendee: word >> many0!(br) >> 285 | fields: fields_in_braces >> ( 286 | fields.into_iter().map(|field| Extension { 287 | extendee: extendee.clone(), 288 | field 289 | }).collect() 290 | ) 291 | ) 292 | ); 293 | 294 | named!( 295 | enum_value, 296 | do_parse!( 297 | name: word >> many0!(br) >> tag!("=") >> many0!(br) >> number: alt!(hex_integer | integer) 298 | >> many0!(br) >> tag!(";") >> many0!(br) >> (EnumValue { 299 | name: name, 300 | number: number, 301 | }) 302 | ) 303 | ); 304 | 305 | named!( 306 | enumerator, 307 | do_parse!( 308 | tag!("enum") >> many1!(br) >> name: word >> many0!(br) >> tag!("{") >> many0!(br) 309 | >> values: many0!(enum_value) >> many0!(br) >> tag!("}") >> many0!(br) 310 | >> many0!(tag!(";")) >> (Enumeration { 311 | name: name, 312 | values: values, 313 | }) 314 | ) 315 | ); 316 | 317 | named!( 318 | option_ignore<()>, 319 | do_parse!(tag!("option") >> many1!(br) >> take_until_and_consume!(";") >> ()) 320 | ); 321 | 322 | named!( 323 | service_ignore<()>, 324 | do_parse!( 325 | tag!("service") >> many1!(br) >> word >> many0!(br) >> tag!("{") 326 | >> take_until_and_consume!("}") >> () 327 | ) 328 | ); 329 | 330 | enum Event { 331 | Syntax(Syntax), 332 | Import(String), 333 | Package(String), 334 | Message(Message), 335 | Enum(Enumeration), 336 | Extensions(Vec), 337 | Ignore, 338 | } 339 | 340 | named!( 341 | event, 342 | alt!(syntax => { |s| Event::Syntax(s) } | 343 | import => { |i| Event::Import(i) } | 344 | package => { |p| Event::Package(p) } | 345 | message => { |m| Event::Message(m) } | 346 | enumerator => { |e| Event::Enum(e) } | 347 | extensions => { |e| Event::Extensions(e) } | 348 | option_ignore => { |_| Event::Ignore } | 349 | service_ignore => { |_| Event::Ignore } | 350 | br => { |_| Event::Ignore }) 351 | ); 352 | 353 | named!(pub file_descriptor, 354 | map!(many0!(event), |events: Vec| { 355 | let mut desc = FileDescriptor::default(); 356 | for event in events { 357 | match event { 358 | Event::Syntax(s) => desc.syntax = s, 359 | Event::Import(i) => desc.import_paths.push(i), 360 | Event::Package(p) => desc.package = p, 361 | Event::Message(m) => desc.messages.push(m), 362 | Event::Enum(e) => desc.enums.push(e), 363 | Event::Extensions(e) => desc.extensions.extend(e), 364 | Event::Ignore => (), 365 | } 366 | } 367 | desc 368 | })); 369 | 370 | #[cfg(test)] 371 | mod test { 372 | use super::*; 373 | 374 | #[test] 375 | fn test_message() { 376 | let msg = r#"message ReferenceData 377 | { 378 | repeated ScenarioInfo scenarioSet = 1; 379 | repeated CalculatedObjectInfo calculatedObjectSet = 2; 380 | repeated RiskFactorList riskFactorListSet = 3; 381 | repeated RiskMaturityInfo riskMaturitySet = 4; 382 | repeated IndicatorInfo indicatorSet = 5; 383 | repeated RiskStrikeInfo riskStrikeSet = 6; 384 | repeated FreeProjectionList freeProjectionListSet = 7; 385 | repeated ValidationProperty ValidationSet = 8; 386 | repeated CalcProperties calcPropertiesSet = 9; 387 | repeated MaturityInfo maturitySet = 10; 388 | }"#; 389 | 390 | let mess = message(msg.as_bytes()); 391 | if let ::nom::IResult::Done(_, mess) = mess { 392 | assert_eq!(10, mess.fields.len()); 393 | } 394 | } 395 | 396 | #[test] 397 | fn test_enum() { 398 | let msg = r#"enum PairingStatus { 399 | DEALPAIRED = 0; 400 | INVENTORYORPHAN = 1; 401 | CALCULATEDORPHAN = 2; 402 | CANCELED = 3; 403 | }"#; 404 | 405 | let enumeration = enumerator(msg.as_bytes()); 406 | if let ::nom::IResult::Done(_, mess) = enumeration { 407 | assert_eq!(4, mess.values.len()); 408 | } 409 | } 410 | 411 | #[test] 412 | fn test_ignore() { 413 | let msg = r#"option optimize_for = SPEED;"#; 414 | 415 | match option_ignore(msg.as_bytes()) { 416 | ::nom::IResult::Done(_, _) => (), 417 | e => panic!("Expecting done {:?}", e), 418 | } 419 | } 420 | 421 | #[test] 422 | fn test_import() { 423 | let msg = r#"syntax = "proto3"; 424 | 425 | import "test_import_nested_imported_pb.proto"; 426 | 427 | message ContainsImportedNested { 428 | optional ContainerForNested.NestedMessage m = 1; 429 | optional ContainerForNested.NestedEnum e = 2; 430 | } 431 | "#; 432 | let desc = file_descriptor(msg.as_bytes()).to_full_result().unwrap(); 433 | assert_eq!( 434 | vec!["test_import_nested_imported_pb.proto"], 435 | desc.import_paths 436 | ); 437 | } 438 | 439 | #[test] 440 | fn test_package() { 441 | let msg = r#" 442 | package foo.bar; 443 | 444 | message ContainsImportedNested { 445 | optional ContainerForNested.NestedMessage m = 1; 446 | optional ContainerForNested.NestedEnum e = 2; 447 | } 448 | "#; 449 | let desc = file_descriptor(msg.as_bytes()).to_full_result().unwrap(); 450 | assert_eq!("foo.bar".to_string(), desc.package); 451 | } 452 | 453 | #[test] 454 | fn test_nested_message() { 455 | let msg = r#"message A 456 | { 457 | message B { 458 | repeated int32 a = 1; 459 | optional string b = 2; 460 | } 461 | optional b = 1; 462 | }"#; 463 | 464 | let mess = message(msg.as_bytes()); 465 | if let ::nom::IResult::Done(_, mess) = mess { 466 | assert!(mess.messages.len() == 1); 467 | } 468 | } 469 | 470 | #[test] 471 | fn test_map() { 472 | let msg = r#"message A 473 | { 474 | optional map b = 1; 475 | }"#; 476 | 477 | let mess = message(msg.as_bytes()); 478 | if let ::nom::IResult::Done(_, mess) = mess { 479 | assert_eq!(1, mess.fields.len()); 480 | match mess.fields[0].typ { 481 | FieldType::Map(ref f) => match &**f { 482 | &(FieldType::String, FieldType::Int32) => (), 483 | ref f => panic!("Expecting Map found {:?}", f), 484 | }, 485 | ref f => panic!("Expecting map, got {:?}", f), 486 | } 487 | } else { 488 | panic!("Could not parse map message"); 489 | } 490 | } 491 | 492 | #[test] 493 | fn test_oneof() { 494 | let msg = r#"message A 495 | { 496 | optional int32 a1 = 1; 497 | oneof a_oneof { 498 | string a2 = 2; 499 | int32 a3 = 3; 500 | bytes a4 = 4; 501 | } 502 | repeated bool a5 = 5; 503 | }"#; 504 | 505 | let mess = message(msg.as_bytes()); 506 | if let ::nom::IResult::Done(_, mess) = mess { 507 | assert_eq!(1, mess.oneofs.len()); 508 | assert_eq!(3, mess.oneofs[0].fields.len()); 509 | } 510 | } 511 | 512 | #[test] 513 | fn test_reserved() { 514 | let msg = r#"message Sample { 515 | reserved 4, 15, 17 to 20, 30; 516 | reserved "foo", "bar"; 517 | uint64 age =1; 518 | bytes name =2; 519 | }"#; 520 | 521 | let mess = message(msg.as_bytes()); 522 | if let ::nom::IResult::Done(_, mess) = mess { 523 | assert_eq!(vec![4..5, 15..16, 17..21, 30..31], mess.reserved_nums); 524 | assert_eq!( 525 | vec!["foo".to_string(), "bar".to_string()], 526 | mess.reserved_names 527 | ); 528 | assert_eq!(2, mess.fields.len()); 529 | } else { 530 | panic!("Could not parse reserved fields message"); 531 | } 532 | } 533 | 534 | #[test] 535 | fn test_default_value_int() { 536 | let msg = r#"message Sample { 537 | optional int32 x = 1 [default = 17]; 538 | }"#; 539 | 540 | let mess = message(msg.as_bytes()).unwrap().1; 541 | assert_eq!("17", mess.fields[0].default.as_ref().expect("default")); 542 | } 543 | 544 | #[test] 545 | fn test_default_value_string() { 546 | let msg = r#"message Sample { 547 | optional string x = 1 [default = "ab\nc d\"g\'h\0\"z"]; 548 | }"#; 549 | 550 | let mess = message(msg.as_bytes()).unwrap().1; 551 | assert_eq!(r#""ab\nc d\"g\'h\0\"z""#, mess.fields[0].default.as_ref().expect("default")); 552 | } 553 | 554 | #[test] 555 | fn test_default_value_bytes() { 556 | let msg = r#"message Sample { 557 | optional bytes x = 1 [default = "ab\nc d\xfeE\"g\'h\0\"z"]; 558 | }"#; 559 | 560 | let mess = message(msg.as_bytes()).unwrap().1; 561 | assert_eq!(r#""ab\nc d\xfeE\"g\'h\0\"z""#, mess.fields[0].default.as_ref().expect("default")); 562 | } 563 | 564 | #[test] 565 | fn test_group() { 566 | let msg = r#"message MessageWithGroup { 567 | optional string aaa = 1; 568 | 569 | repeated group Identifier = 18 { 570 | optional int32 iii = 19; 571 | optional string sss = 20; 572 | } 573 | 574 | required int bbb = 3; 575 | }"#; 576 | let mess = message(msg.as_bytes()).unwrap().1; 577 | 578 | assert_eq!("Identifier", mess.fields[1].name); 579 | if let FieldType::Group(ref group_fields) = mess.fields[1].typ { 580 | assert_eq!(2, group_fields.len()); 581 | } else { 582 | panic!("expecting group"); 583 | } 584 | 585 | assert_eq!("bbb", mess.fields[2].name); 586 | } 587 | 588 | #[test] 589 | fn test_incorrect_file_descriptor() { 590 | let msg = r#" 591 | message Foo {} 592 | 593 | dfgdg 594 | "#; 595 | 596 | assert!(FileDescriptor::parse(msg.as_bytes()).is_err()); 597 | } 598 | 599 | #[test] 600 | fn test_extend() { 601 | let proto = r#" 602 | syntax = "proto2"; 603 | 604 | extend google.protobuf.FileOptions { 605 | optional bool foo = 17001; 606 | optional string bar = 17002; 607 | } 608 | 609 | extend google.protobuf.MessageOptions { 610 | optional bool baz = 17003; 611 | } 612 | "#; 613 | 614 | let fd = FileDescriptor::parse(proto.as_bytes()).expect("fd"); 615 | assert_eq!(3, fd.extensions.len()); 616 | assert_eq!("google.protobuf.FileOptions", fd.extensions[0].extendee); 617 | assert_eq!("google.protobuf.FileOptions", fd.extensions[1].extendee); 618 | assert_eq!("google.protobuf.MessageOptions", fd.extensions[2].extendee); 619 | assert_eq!(17003, fd.extensions[2].field.number); 620 | } 621 | } 622 | --------------------------------------------------------------------------------