#[cfg(test)]
mod tests {
use proto_file_parser::{ParserError, Proto};
#[test]
fn test_whitespace_rule() -> Result<(), ParserError> {
let input = "syntax = \"proto3\";\n\t\rmessage Test {\n string name = 1;\r\n}";
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["syntax"], "proto3");
assert_eq!(value["messages"][0]["name"], "Test");
Ok(())
}
#[test]
fn test_comment_rule() -> Result<(), ParserError> {
let input = r#"
// Single line comment
syntax = "proto3"; // End of line comment
/* Multi-line
comment */
message Test { // Comment after message
// Comment before field
string name = 1; /* Multi-line
field comment */
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["syntax"], "proto3");
assert_eq!(value["messages"][0]["name"], "Test");
Ok(())
}
#[test]
fn test_ident_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
// Testing various valid identifiers
message ValidIdents {
string normal = 1;
string withUnderscore = 2;
string with123Numbers = 3;
string startWithLetter = 4;
string camelCase = 5;
string PascalCase = 6;
string UPPERCASE = 7;
string mixtureOfALLStyles123 = 8;
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let fields = &value["messages"][0]["fields"];
assert_eq!(fields[0]["name"], "normal");
assert_eq!(fields[1]["name"], "withUnderscore");
assert_eq!(fields[2]["name"], "with123Numbers");
assert_eq!(fields[3]["name"], "startWithLetter");
assert_eq!(fields[4]["name"], "camelCase");
assert_eq!(fields[5]["name"], "PascalCase");
assert_eq!(fields[6]["name"], "UPPERCASE");
assert_eq!(fields[7]["name"], "mixtureOfALLStyles123");
Ok(())
}
#[test]
fn test_number_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
message NumberTest {
string field1 = 1;
string field2 = 12;
string field3 = 123;
string field4 = 1234;
string field5 = 12345;
string field6 = 123456;
string field7 = 536870911; // Max allowed
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let fields = &value["messages"][0]["fields"];
assert_eq!(fields[0]["tag"], 1);
assert_eq!(fields[1]["tag"], 12);
assert_eq!(fields[2]["tag"], 123);
assert_eq!(fields[3]["tag"], 1234);
assert_eq!(fields[4]["tag"], 12345);
assert_eq!(fields[5]["tag"], 123456);
assert_eq!(fields[6]["tag"], 536870911);
Ok(())
}
#[test]
fn test_string_lit_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
import "simple/path.proto";
import "path/with/multiple/segments.proto";
import "path_with_underscore.proto";
import "path.with.dots.proto";
import "path-with-dashes.proto";
import "UPPERCASE.proto";
import "MixedCase.proto";
import "with_numbers123.proto";
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let imports = value["imports"].as_array().unwrap();
assert_eq!(imports[0], "simple/path.proto");
assert_eq!(imports[1], "path/with/multiple/segments.proto");
assert_eq!(imports[2], "path_with_underscore.proto");
assert_eq!(imports[3], "path.with.dots.proto");
assert_eq!(imports[4], "path-with-dashes.proto");
assert_eq!(imports[5], "UPPERCASE.proto");
assert_eq!(imports[6], "MixedCase.proto");
assert_eq!(imports[7], "with_numbers123.proto");
Ok(())
}
#[test]
fn test_full_ident_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
package com.example.project.v1;
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["package"], "com.example.project.v1");
Ok(())
}
#[test]
fn test_syntax_rule() -> Result<(), ParserError> {
let input = r#"syntax = "proto3";"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["syntax"], "proto3");
Ok(())
}
#[test]
fn test_package_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
package test.v1.api;
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["package"], "test.v1.api");
Ok(())
}
#[test]
fn test_import_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
import "other.proto";
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let imports = value["imports"].as_array().unwrap();
assert_eq!(imports[0], "other.proto");
Ok(())
}
#[test]
fn test_field_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
message FieldRuleTest {
string single = 1;
repeated string repeated_field = 2;
optional string optional_field = 3;
required string required_field = 4;
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let fields = &value["messages"][0]["fields"];
assert_eq!(fields[0]["repeated"], false);
assert_eq!(fields[1]["repeated"], true);
Ok(())
}
#[test]
fn test_primitive_type_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
message PrimitiveTypes {
double double_field = 1;
float float_field = 2;
int32 int32_field = 3;
int64 int64_field = 4;
uint32 uint32_field = 5;
uint64 uint64_field = 6;
sint32 sint32_field = 7;
sint64 sint64_field = 8;
fixed32 fixed32_field = 9;
fixed64 fixed64_field = 10;
sfixed32 sfixed32_field = 11;
sfixed64 sfixed64_field = 12;
bool bool_field = 13;
string string_field = 14;
bytes bytes_field = 15;
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let fields = &value["messages"][0]["fields"];
let types = [
"double", "float", "int32", "int64", "uint32", "uint64",
"sint32", "sint64", "fixed32", "fixed64", "sfixed32",
"sfixed64", "bool", "string", "bytes"
];
for (i, expected_type) in types.iter().enumerate() {
assert_eq!(fields[i]["type_name"], *expected_type);
}
Ok(())
}
#[test]
fn test_message_type_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
service TestService {
rpc Method1 (Request) returns (Response);
rpc Method2 (stream StreamingRequest) returns (stream StreamingResponse);
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let methods = &value["services"][0]["methods"];
assert_eq!(methods[0]["input_type"], "Request");
assert_eq!(methods[0]["output_type"], "Response");
assert_eq!(methods[1]["input_type"], "StreamingRequest");
assert_eq!(methods[1]["output_type"], "StreamingResponse");
Ok(())
}
#[test]
fn test_message_element_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
message TestMessage {
// Regular field
string regular_field = 1;
// Nested enum
enum NestedEnum {
UNKNOWN = 0;
VALUE = 1;
}
// Nested message
message NestedMessage {
int32 value = 1;
}
// Fields using nested types
NestedEnum enum_field = 2;
NestedMessage message_field = 3;
// Empty statement
;
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let message = &value["messages"][0];
assert_eq!(message["fields"][0]["name"], "regular_field");
assert_eq!(message["nested_enums"][0]["name"], "NestedEnum");
assert_eq!(message["nested_messages"][0]["name"], "NestedMessage");
assert_eq!(message["fields"][1]["name"], "enum_field");
assert_eq!(message["fields"][1]["type_name"], "NestedEnum");
assert_eq!(message["fields"][2]["name"], "message_field");
assert_eq!(message["fields"][2]["type_name"], "NestedMessage");
Ok(())
}
#[test]
fn test_enum_def_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
enum Status {
UNKNOWN = 0;
PENDING = 1;
ACTIVE = 2;
INACTIVE = 3;
DELETED = 4;
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let enum_def = &value["enums"][0];
assert_eq!(enum_def["name"], "Status");
let values = enum_def["values"].as_array().unwrap();
assert_eq!(values[0]["name"], "UNKNOWN");
assert_eq!(values[0]["number"], 0);
assert_eq!(values[1]["name"], "PENDING");
assert_eq!(values[1]["number"], 1);
assert_eq!(values[2]["name"], "ACTIVE");
assert_eq!(values[2]["number"], 2);
assert_eq!(values[3]["name"], "INACTIVE");
assert_eq!(values[3]["number"], 3);
assert_eq!(values[4]["name"], "DELETED");
assert_eq!(values[4]["number"], 4);
Ok(())
}
#[test]
fn test_enum_value_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
enum TestEnum {
// Testing different naming conventions for enum values
SIMPLE = 0;
COMPOUND_NAME = 1;
NUMBER_123 = 2;
LARGE_NUMBER = 100;
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let values = &value["enums"][0]["values"];
assert_eq!(values[0]["name"], "SIMPLE");
assert_eq!(values[0]["number"], 0);
assert_eq!(values[1]["name"], "COMPOUND_NAME");
assert_eq!(values[1]["number"], 1);
assert_eq!(values[2]["name"], "NUMBER_123");
assert_eq!(values[2]["number"], 2);
assert_eq!(values[3]["name"], "LARGE_NUMBER");
assert_eq!(values[3]["number"], 100);
Ok(())
}
#[test]
fn test_service_def_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
service TestService {
// Empty service
}
service ComplexService {
rpc Method1 (Request1) returns (Response1);
rpc Method2 (Request2) returns (Response2);
}
service StreamingService {
rpc Method1 (stream Request1) returns (Response1);
rpc Method2 (Request2) returns (stream Response2);
rpc Method3 (stream Request3) returns (stream Response3);
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let services = value["services"].as_array().unwrap();
assert_eq!(services[0]["name"], "TestService");
assert!(services[0]["methods"].as_array().unwrap().is_empty());
assert_eq!(services[1]["name"], "ComplexService");
let complex_methods = services[1]["methods"].as_array().unwrap();
assert_eq!(complex_methods[0]["name"], "Method1");
assert_eq!(complex_methods[0]["input_type"], "Request1");
assert_eq!(complex_methods[0]["output_type"], "Response1");
assert_eq!(complex_methods[1]["name"], "Method2");
assert_eq!(complex_methods[1]["input_type"], "Request2");
assert_eq!(complex_methods[1]["output_type"], "Response2");
assert_eq!(services[2]["name"], "StreamingService");
let streaming_methods = services[2]["methods"].as_array().unwrap();
assert_eq!(streaming_methods[0]["name"], "Method1");
assert_eq!(streaming_methods[0]["input_type"], "Request1");
assert_eq!(streaming_methods[0]["output_type"], "Response1");
assert_eq!(streaming_methods[1]["name"], "Method2");
assert_eq!(streaming_methods[1]["input_type"], "Request2");
assert_eq!(streaming_methods[1]["output_type"], "Response2");
assert_eq!(streaming_methods[2]["name"], "Method3");
assert_eq!(streaming_methods[2]["input_type"], "Request3");
assert_eq!(streaming_methods[2]["output_type"], "Response3");
Ok(())
}
#[test]
fn test_rpc_def_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
service RPCTest {
// Simple RPC
rpc Simple (SimpleRequest) returns (SimpleResponse);
// RPC with empty body
rpc WithEmptyBody (EmptyRequest) returns (EmptyResponse) {}
// Streaming RPCs
rpc ClientStream (stream StreamRequest) returns (StreamResponse);
rpc ServerStream (StreamRequest) returns (stream StreamResponse);
rpc BidiStream (stream StreamRequest) returns (stream StreamResponse);
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let methods = &value["services"][0]["methods"];
assert_eq!(methods[0]["name"], "Simple");
assert_eq!(methods[0]["input_type"], "SimpleRequest");
assert_eq!(methods[0]["output_type"], "SimpleResponse");
assert_eq!(methods[1]["name"], "WithEmptyBody");
assert_eq!(methods[1]["input_type"], "EmptyRequest");
assert_eq!(methods[1]["output_type"], "EmptyResponse");
assert_eq!(methods[2]["name"], "ClientStream");
assert_eq!(methods[2]["input_type"], "StreamRequest");
assert_eq!(methods[2]["output_type"], "StreamResponse");
assert_eq!(methods[3]["name"], "ServerStream");
assert_eq!(methods[3]["input_type"], "StreamRequest");
assert_eq!(methods[3]["output_type"], "StreamResponse");
assert_eq!(methods[4]["name"], "BidiStream");
assert_eq!(methods[4]["input_type"], "StreamRequest");
assert_eq!(methods[4]["output_type"], "StreamResponse");
Ok(())
}
#[test]
fn test_field_definition_rule() -> Result<(), ParserError> {
let input = r#"
syntax = "proto3";
message FieldTest {
// Simple fields
string simple_field = 1;
int32 another_field = 2;
// Fields with rules
repeated string repeated_field = 3;
optional int32 optional_field = 4;
required bool required_field = 5;
// Fields with complex types
OtherMessage message_field = 6;
repeated OtherMessage repeated_message = 7;
// Fields with enum types
TestEnum enum_field = 8;
repeated TestEnum repeated_enum = 9;
// Fields with all primitive types
bool bool_field = 10;
string string_field = 11;
bytes bytes_field = 12;
double double_field = 13;
float float_field = 14;
int32 int32_field = 15;
int64 int64_field = 16;
uint32 uint32_field = 17;
uint64 uint64_field = 18;
sint32 sint32_field = 19;
sint64 sint64_field = 20;
fixed32 fixed32_field = 21;
fixed64 fixed64_field = 22;
sfixed32 sfixed32_field = 23;
sfixed64 sfixed64_field = 24;
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let fields = &value["messages"][0]["fields"];
assert_eq!(fields[0]["name"], "simple_field");
assert_eq!(fields[0]["type_name"], "string");
assert_eq!(fields[0]["tag"], 1);
assert_eq!(fields[0]["repeated"], false);
assert_eq!(fields[2]["name"], "repeated_field");
assert_eq!(fields[2]["type_name"], "string");
assert_eq!(fields[2]["repeated"], true);
assert_eq!(fields[6]["name"], "repeated_message");
assert_eq!(fields[6]["type_name"], "OtherMessage");
assert_eq!(fields[6]["repeated"], true);
assert_eq!(fields[7]["name"], "enum_field");
assert_eq!(fields[7]["type_name"], "TestEnum");
let primitive_types = [
"bool", "string", "bytes", "double", "float",
"int32", "int64", "uint32", "uint64", "sint32",
"sint64", "fixed32", "fixed64", "sfixed32", "sfixed64"
];
for (i, type_name) in primitive_types.iter().enumerate() {
assert_eq!(fields[i + 9]["type_name"], *type_name);
}
Ok(())
}
#[test]
fn test_proto_file_rule() -> Result<(), ParserError> {
let input = r#"
// File starts with syntax
syntax = "proto3";
// Package declaration
package test.v1;
// Imports
import "other.proto";
import "another.proto";
// Top-level enum
enum Status {
UNKNOWN = 0;
ACTIVE = 1;
}
// Top-level message
message Test {
string name = 1;
Status status = 2;
// Nested enum
enum Type {
UNSPECIFIED = 0;
NORMAL = 1;
}
// Nested message
message Nested {
int32 value = 1;
}
Type type = 3;
Nested nested = 4;
}
// Service definition
service TestService {
rpc Get (Test) returns (Test);
}
"#;
let json = Proto::parse(input)?;
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["syntax"], "proto3");
assert_eq!(value["package"], "test.v1");
let imports = value["imports"].as_array().unwrap();
assert_eq!(imports[0], "other.proto");
assert_eq!(imports[1], "another.proto");
let enums = value["enums"].as_array().unwrap();
assert_eq!(enums[0]["name"], "Status");
assert_eq!(enums[0]["values"][0]["name"], "UNKNOWN");
assert_eq!(enums[0]["values"][1]["name"], "ACTIVE");
let messages = value["messages"].as_array().unwrap();
let message = &messages[0];
assert_eq!(message["name"], "Test");
let nested_enums = message["nested_enums"].as_array().unwrap();
assert_eq!(nested_enums[0]["name"], "Type");
let nested_messages = message["nested_messages"].as_array().unwrap();
assert_eq!(nested_messages[0]["name"], "Nested");
let services = value["services"].as_array().unwrap();
assert_eq!(services[0]["name"], "TestService");
assert_eq!(services[0]["methods"][0]["name"], "Get");
Ok(())
}
}