Skip to content
This repository has been archived by the owner on Feb 8, 2022. It is now read-only.

Commit

Permalink
GRpc options (#61) / fully implement options
Browse files Browse the repository at this point in the history
Fully implement (and otherwise fix) option statements and option annotations on messages, fields, maps, services, rpc, etc,, including alternate syntax.  Now fully supports gRPC option annotations.

Squashed the following commits:
* Attempt to parse proto3 option maps.
* Got simple parsing working.
* More unit-tests.
* Unit-tests for grpc with options are green.
* Cleanup
* Changed aggregate options as per feedback.
* Implemented aggregated option parser.
* Added tests and support for recursive literals.
* Handle empty literals.
* Refactor options parsing to better handle empty statements.
* Minor refactor of RPC parser to improve readability.
* Mark skipEmpty as internal.
* Remove superfluous empty_ws.
* Refactor to eliminate duplication via new manyBetweenCurlySkippingEmpty, add unit test for Service options, and fixed bugs related to parsing options.
* Support options on map field, add more option unit tests, move/rename unit tests for consistency.
  • Loading branch information
bhandfast authored and jhugard committed Oct 4, 2016
1 parent ba4d5e5 commit bc9d38d
Show file tree
Hide file tree
Showing 8 changed files with 425 additions and 61 deletions.
3 changes: 2 additions & 1 deletion Froto.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Froto.Roslyn", "Roslyn\Froto.Roslyn.fsproj", "{49D025ED-F804-4D97-9027-461671B333B6}"
EndProject
Expand All @@ -24,6 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{992FDC44-0
test\addressbook.proto = test\addressbook.proto
test\addressbook1.proto = test\addressbook1.proto
test\FooOptions.proto = test\FooOptions.proto
test\grpc.proto = test\grpc.proto
test\javatutorial.proto = test\javatutorial.proto
test\nwind.proto = test\nwind.proto
test\person.proto = test\person.proto
Expand Down
9 changes: 8 additions & 1 deletion Parser.Test/TestClassModel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,11 @@ let ``can parse riak proto`` () =
let ``can parse enum proto`` () =
let proto = getTestFile "protoenum.proto" |> parseFile
1 |> should equal proto.Enums.Length
3 |> should equal proto.Enums.[0].Items.Length
3 |> should equal proto.Enums.[0].Items.Length

[<Fact>]
// from https://github.com/googleapis/googleapis/blob/master/google/pubsub/v1/pubsub.proto
let ``can parse PubSub proto`` () =
let proto = getTestFile "grpc.proto" |> parseFile
10 |> should equal proto.Sections.Length
"Subscriber" |> should equal proto.Services.[0].Name
249 changes: 244 additions & 5 deletions Parser.Test/TestParser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,112 @@ module OptionStatement =
Parse.fromStringWithParser pOptionStatement @"option (test).field.more = true;"
|> should equal (TOption ("test.field.more", TBoolLit true))

[<Fact>]
let ``Alternate option syntax parses`` () =
let option = """option (google.api.http) = { put: "/v1/{name=projects/*/subscriptions/*}" body: "*" };"""
let result = Parse.fromStringWithParser Parse.Parsers.pOptionStatement option

let expectedResults =
[ ("put", TStrLit "/v1/{name=projects/*/subscriptions/*}");
("body", TStrLit "*") ]
let expectedResult = TOption( "google.api.http", PConstant.TAggregateOptionsLit expectedResults )

result
|> should equal (expectedResult)

[<Fact>]
let ``Numeric option parses`` () =
let option = """option (google.api.http) = { put: "/v1/{name=projects/*/subscriptions/*}" body: 1 };"""
let result = Parse.fromStringWithParser Parse.Parsers.pOptionStatement option

let expectedResults =
[ ("put", TStrLit "/v1/{name=projects/*/subscriptions/*}");
("body", TIntLit 1) ]
let expectedResult = TOption( "google.api.http", PConstant.TAggregateOptionsLit expectedResults )

result
|> should equal (expectedResult)

[<Fact>]
let ``Recursive option parses`` () =
let option = """option (google.api.http) = {
get: "/v1/messages/{message_id}"
additional_bindings {
get: "/v1/users/{user_id}/messages/{message_id}"
}
};"""
let result = Parse.fromStringWithParser Parse.Parsers.pOptionStatement option

let expectedResults =
[ ("get", TStrLit "/v1/messages/{message_id}");
("additional_bindings", TAggregateOptionsLit [ ("get", TStrLit "/v1/users/{user_id}/messages/{message_id}") ]) ]
let expectedResult = TOption( "google.api.http", PConstant.TAggregateOptionsLit expectedResults )

result
|> should equal (expectedResult)

[<Fact>]
let ``Two recursive options parses`` () =
let option = """option (google.api.http) = {
get: "/v1/messages/{message_id}"
additional_bindings {
get: "/v1/users/{user_id}/messages/{message_id}"
}
additional_bindings {
put: "/v1/users/{user_id}/messages/{message_id}"
}
};"""
let result = Parse.fromStringWithParser Parse.Parsers.pOptionStatement option

let expectedResults =
[ ("get", TStrLit "/v1/messages/{message_id}");
("additional_bindings", TAggregateOptionsLit [ ("get", TStrLit "/v1/users/{user_id}/messages/{message_id}") ])
("additional_bindings", TAggregateOptionsLit [ ("put", TStrLit "/v1/users/{user_id}/messages/{message_id}") ]) ]
let expectedResult = TOption( "google.api.http", PConstant.TAggregateOptionsLit expectedResults )

result
|> should equal (expectedResult)

[<Fact>]
let ``Two recursive options with empty statements parses`` () =
let option = """option (google.api.http) = {
;
get: "/v1/messages/{message_id}"
;
additional_bindings {
get: "/v1/users/{user_id}/messages/{message_id}"
}
;
;
additional_bindings {
put: "/v1/users/{user_id}/messages/{message_id}"
};
};"""
let result = Parse.fromStringWithParser Parse.Parsers.pOptionStatement option

let expectedResults =
[ ("get", TStrLit "/v1/messages/{message_id}");
("additional_bindings", TAggregateOptionsLit [ ("get", TStrLit "/v1/users/{user_id}/messages/{message_id}") ])
("additional_bindings", TAggregateOptionsLit [ ("put", TStrLit "/v1/users/{user_id}/messages/{message_id}") ]) ]
let expectedResult = TOption( "google.api.http", PConstant.TAggregateOptionsLit expectedResults )

result
|> should equal (expectedResult)

[<Fact>]
let ``Option with only empty statement parses`` () =
let option = """option (google.api.http) = {
;
};"""
let result = Parse.fromStringWithParser Parse.Parsers.pOptionStatement option

let expectedResults =
List.Empty
let expectedResult = TOption( "google.api.http", PConstant.TAggregateOptionsLit expectedResults )

result
|> should equal (expectedResult)

[<Xunit.Trait("Kind", "Unit")>]
module Message =

Expand All @@ -292,6 +398,17 @@ module Message =
[ ("packed", TBoolLit(true)); ("custom.option", TIntLit(2)) ]
)

[<Fact>]
let ``Field with alt syntax options parses`` () =
Parse.fromStringWithParser pField @"repeated sint32 samples=2 [(custom) = { option:2 another:false }];"
|> should equal
(TField( "samples",
TRepeated,
TSInt32,
2u,
[ "custom",TAggregateOptionsLit( [ ("option",TIntLit 2); ("another",TBoolLit false) ] ) ]
))

[<Fact>]
let ``Parse simple message`` () =
Parse.fromStringWithParser (ws >>. pMessage .>> ws)
Expand All @@ -305,6 +422,21 @@ module Message =
TField( "blob", TRequired, TBytes, 2u, [ ("myopt",TStrLit("yes")) ])
])

[<Fact>]
let ``Parse simple message with option`` () =
Parse.fromStringWithParser (ws >>. pMessage .>> ws)
"""message Echo {
option (foo) = "bar";
required string msg=1;
required bytes blob=2 [(myopt)="yes"] ;
}"""
|> should equal
<| TMessage ("Echo",
[ TMessageOption( "foo", TStrLit "bar")
TField( "msg", TRequired, TString, 1u, [] )
TField( "blob", TRequired, TBytes, 2u, [ ("myopt",TStrLit("yes")) ])
])

[<Fact>]
let ``Parse group`` () =
Parse.fromStringWithParser (ws >>. pGroup) """
Expand All @@ -316,6 +448,20 @@ module Message =
[ TField( "abc", TOptional, TString, 42u, [] ) ]
)

[<Fact>]
let ``Parse group with option`` () =
Parse.fromStringWithParser (ws >>. pGroup) """
optional group MyGroup = 41 {
optional string abc = 42;
option (foo) = "bar";
}"""
|> should equal
<| TGroup( "MyGroup", TOptional, 41u,
[ TField( "abc", TOptional, TString, 42u, [] )
TMessageOption( "foo", TStrLit "bar" )
]
)

[<Fact>]
let ``Parse oneof`` () =
Parse.fromStringWithParser (ws >>. pOneOf) """
Expand All @@ -325,12 +471,28 @@ module Message =
|> should equal
<| TOneOf("MyOneof", [ TOneOfField("name",TString,1u,[]) ])

[<Fact>]
let ``Parse oneof with field option`` () =
Parse.fromStringWithParser (ws >>. pOneOf) """
oneof MyOneof {
string name = 1 [(foo)="bar"];
}"""
|> should equal
<| TOneOf("MyOneof", [ TOneOfField("name",TString,1u,[("foo",TStrLit("bar"))]) ])

[<Fact>]
let ``Parse map`` () =
Parse.fromStringWithParser (ws >>. pMap) """
map<string,Project> projects = 3;"""
|> should equal
<| TMap ("projects", TKString, TIdent("Project"), 3u)
<| TMap ("projects", TKString, TIdent("Project"), 3u, [])

[<Fact>]
let ``Parse map with option`` () =
Parse.fromStringWithParser (ws >>. pMap) """
map<string,Project> projects = 3 [(foo)="bar"];"""
|> should equal
<| TMap ("projects", TKString, TIdent("Project"), 3u, ["foo",TStrLit("bar")])

[<Fact>]
let ``Parse extensions`` () =
Expand Down Expand Up @@ -366,7 +528,7 @@ module Message =
"bar" ]

[<Fact>]
let ``Parse enum`` () =
let ``Parse enum with option`` () =
Parse.fromStringWithParser (ws >>. pMessageEnum) """
enum EnumAllowingAlias {
option allow_alias = true;
Expand Down Expand Up @@ -420,6 +582,23 @@ module Service =
rpc TestMethod (outer) returns (foo);"""
|> should equal (TRpc ("TestMethod", "outer", false, "foo", false, []))

[<Fact>]
let ``Parse rpc with optional option syntax`` () =
let expectedResults =
[ ("get", TStrLit "/v1/{name=projects/*/subscriptions/*}") ]

let actual =
Parse.fromStringWithParser pRpc ("""
rpc TestMethod (outer) returns (foo) {
option (google.api.http) = { get: "/v1/{name=projects/*/subscriptions/*}" };
}
""".Trim())

let expected = TRpc ("TestMethod", "outer", false, "foo", false,
[ "google.api.http", PConstant.TAggregateOptionsLit expectedResults ])

actual |> should equal expected

[<Fact>]
let ``Parse service`` () =
Parse.fromStringWithParser (ws >>. pService) """
Expand All @@ -432,6 +611,37 @@ module Service =
TRpc ("TestMethod", "outer", false, "foo", false, [])
]))

[<Fact>]
let ``Parse service with rpc, options, and empty statements`` () =
Parse.fromStringWithParser (ws >>. pService) """
service TestService {
; // empty
option (foo).bar = "fee";
rpc TestMethod (outer) returns (foo) {
; // empty
option (foo.baz) = "fie";
; // empty
option foo = { bat : "foe" qux : "foo" }
; // empty
}
; // empty
}"""
|> should equal (
TService ("TestService",
[
TServiceOption ( "foo.bar", TStrLit "fee")
TRpc ("TestMethod", "outer", false, "foo", false,
[
"foo.baz", TStrLit "fie"
"foo", TAggregateOptionsLit [
"bat", TStrLit "foe"
"qux", TStrLit "foo"
]
])
]))



[<Xunit.Trait("Kind", "Unit")>]
module Proto =

Expand Down Expand Up @@ -499,7 +709,7 @@ module Proto =
TMessageMessage ( "inner", [ TField ("ival", TRequired, TInt64, 1u, [] ) ])
TField ("inner_message", TRepeated, TIdent "inner", 2u, [])
TField ("enum_field", TOptional, TIdent "EnumAllowingAlias", 3u, [])
TMap ("my_map", TKInt32, TString, 4u)
TMap ("my_map", TKInt32, TString, 4u, [])
TExtensions ( [ (20u,Some(30u)) ])
])
]
Expand Down Expand Up @@ -583,7 +793,7 @@ module Proto =
TMessageMessage ( "inner", [ TField ("ival", TRequired, TInt64, 1u, [] ) ])
TField ("inner_message", TRepeated, TIdent "inner", 2u, [])
TField ("enum_field", TOptional, TIdent "EnumAllowingAlias", 3u, [])
TMap ("my_map", TKInt32, TString, 4u)
TMap ("my_map", TKInt32, TString, 4u, [])
TExtensions ( [ (20u,Some(30u)) ])
])
TMessage ( "foo",
Expand All @@ -600,6 +810,35 @@ module Proto =
]
)

[<Fact>]
let ``Parse proto with optional option syntax`` () =
let expectedResults =
[ ("put", TStrLit "/v1/{name=projects/*/subscriptions/*}");
("body", TStrLit "*") ]

Parse.fromStringWithParser pProto """
syntax = "proto3";
service TestService {
rpc TestMethod (outer) returns (foo) {
option (google.api.http) = { put: "/v1/{name=projects/*/subscriptions/*}" body: "*" };
}
}
"""
|> should equal (
[
TSyntax TProto3
TService ("TestService",
[
TRpc ("TestMethod", "outer", false, "foo", false,
[
"google.api.http", PConstant.TAggregateOptionsLit expectedResults
])
])
]
)


open System.IO

/// gets the path for a test file based on the relative path from the executing assembly
Expand Down Expand Up @@ -717,6 +956,7 @@ module Proto3 =
""" |> ignore
|> should throw typeof<System.FormatException>


[<Xunit.Trait("Kind", "Unit")>]
module Proto2 =

Expand Down Expand Up @@ -803,4 +1043,3 @@ module Proto2 =
}
""" |> ignore
|> should not' (throw typeof<System.FormatException>)

Loading

0 comments on commit bc9d38d

Please sign in to comment.