8000 gRPC options by bhandfast · Pull Request #61 · ctaggart/froto · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
This repository was archived by the owner on Feb 8, 2022. It is now read-only.

gRPC options #61

Merged
merged 15 commits into from
Oct 4, 2016
Merged

gRPC options #61

merged 15 commits into from
Oct 4, 2016

Conversation

bhandfast
Copy link
Contributor

An attempt to solve issues described in #60. I consider this a work in progress. Any suggestions are welcome as I'm new to FParsec.

Copy link
Collaborator
@jhugard jhugard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great start! Thank you for attempting to tackle this one!

and PConstant =
| TIntLit of int32
| TFloatLit of float
| TBoolLit of bool
| TStrLit of string
| TEnumLit of TIdent
| TMap of Map<string, string>
Copy link
Collaborator
@jhugard jhugard Sep 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the temptation to use a map here, but a map will destroy the order of the aggregated options (since it will sort the entries by key). Plus, the intention of this syntax is to provide shorthand for setting multiple fields on a single (option definition) message. E.g., the following "alternative aggregate syntax" sample from Protobuf Language Guide:

[(foo_options) = { opt1: 123 opt2: "baz" }]

is semantically equivalent to:

[(foo_options).opt1 = 123, (foo_options).opt2 = "baz"]

The first form is just shorthand for setting both opt1 and opt2 fields on the (foo_options) message. In fact, a future transform on the AST should probably flatten the first form into the second form, to simplify processing.

Therefore, IMHO a POption list would better represents the original source here: each option name (e.g., opt1) should be parsed to a TIdent (using pIdent_ws), and each value (e.g., 123) should be parsed to a PConstant (using pConstant_ws) rather than a string.

I would therefore suggest redefining TMap as the following and adjust the rest of your code accordingly:

  |  TAggregateOptionsLit of POption list

Note: Google's protoc.exe supports nested option literals as the above definition would imply. Your changes to the parser should naturally support this if your new pAggregateOptions definition is added to pConstant as a case.

[(foo_options) = { opt1: 123 opt2: "baz" opt3 {ioptA: true ioptB: "fie"} }]

Another example from googleapis; see specifically additional_bindings.

service Messaging {
  rpc GetMessage(GetMessageRequest) returns (Message) {
    option (google.api.http) = {
      get: "/v1/messages/{message_id}"
      additional_bindings {
        get: "/v1/users/{user_id}/messages/{message_id}"
      }
    };
  }
}
message GetMessageRequest {
  string message_id = 1;
  string user_id = 2;
}

@@ -618,19 +634,44 @@ module Parse =
<|>
(isProto3 >>. pStrMessageType_P3)

let pRpcOptions =
Copy link
Collaborator
@jhugard jhugard Sep 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something about this section feels off. I'm pretty sure it can still be defined in terms of a pipe4, which to me better communicates the form of the TRPC production. Feels like there should be a common parser for all places where this type of option statement can occur.

Will try and look at this further when I get more time.

Copy link
Collaborator
@jhugard jhugard Oct 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned this up a bit and went back to a pipe4.

@bhandfast
Copy link
Contributor Author

Thanks for your feedback! I might have time to look more at this in the next week.

@bhandfast
Copy link
Contributor Author

I changed the map to a option list which mostly is working, but one test fails because additional_bindings isn't followed by a colon. A quick search indicated that this is an array but I couldn't find any definition on how arrays should be parsed.

@jhugard
Copy link
Collaborator
jhugard commented Sep 28, 2016

@bhandfast Wow. Totally missed that in my one-line example. Edited my previous post to remove the ":" between "opt3" and "{", in order to be consistent with other examples on-line.

The options block is written in Protobuf Text Format, but I couldn't find an official description... just this SO post. You might need to dig into the the Google protobuf source code, or maybe protobuf-c-text library for specifics. Yetch. Wish the protobuf spec was a bit more complete.

@bhandfast
Copy link
Contributor Author

Hmm, so essentially they use a different parser for options (data)? That's not something I was aware of. Maybe we need a completely new parser for protobuf text then?

let internal pConstant, internal pOptionDefR = createParserForwardedToRef<PConstant,State>()

let internal pColon = pstring ":" .>> ws
let internal pKey = manySatisfy (fun x -> isAsciiLetter x || x = '_') .>> ws
Copy link
Collaborator
@jhugard jhugard Sep 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pKey can be deleted; should use pIdent or pIdent_ws instead (the latter two both parse a non-dotted identifier).

between (str sOpen) (str sClose)
(ws >>. many1 (pElement .>> ws) |>> f)

let rec internal pKeyValue = pipe3 pKey pColon pConstant (fun k _ v -> k, v)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use pIdent_ws instead of pKey

@jhugard
Copy link
Collaborator
jhugard commented Sep 29, 2016

Don't think we need another entire parser. There are a couple of issues, though, but I think they can be resolved by looking at the actual syntax that needs to be parsed. Best I can tell, the EBNF should look like the following:

statement := ... | optionStatement | emptyStatement

optionStatement :="option" fullIdent "=" optionClause ";"
optionClause := ( literal | aggregateBlock )

literal := boolLit | strLit | numLit | enumLit

aggregateBlock := "{" [ aggregateLit | recursiveLit | emptyStatement ] "}"
aggregateLit := ident ":" literal ";"
recursiveLit := ident aggregateBlock

emptyStatement := ";"

There's a bit of subtly here. Note that aggregateBlock can include an empty statement, which handles constructs such as:

      option (google.api.http) = {
        get: "/v1/messages/{message_id}"
        additional_bindings {
          get: "/v1/users/{user_id}/messages/{message_id}"
        }
        additional_bindings {
          get: "/v2/users/{user_id}/messages/{message_id}"
        };
      };

The semicolon on the 2nd additional_bindings is allowed by the spec, and should be interpreted as an empty statement (which can be dropped from the AST).

Finally, IIRC option and optionClause can appear in a number of places. Take a look to make sure they are all handled.

Let me know if you have trouble with this (or if I made a mistake, which is entirely possible :P )

@bhandfast
Copy link
Contributor Author

I gave it another try now, but I'm not happy with the handling of empty statements. Maybe you can help with this? It feels like FParsec have another solution for that.

@ctaggart
Copy link
Owner
ctaggart commented Oct 2, 2016

I'm very excited to see progress here! I'm pushing for the adoption of gRPC in my company and this would help. I've now written a few services in Go that I'd love to be able to point an F# type provider at as the client. This is a step in that direction.

@jhugard
Copy link
Collaborator
jhugard commented Oct 2, 2016

@bhandfast Hmm, the empty statement business is a PITA. I took a crack at cleaning it up a bit and make sure it handles empty-statement-only, empty-at-beginning, multiple-empties-in-a-row, and empty-at-the-end. Also simplified the RPC parser a bit (went back to a pipe4).

See the attached patches: refactor-empty-statement-fixes.zip

The main change is in Parser.fs, but do see the patches as I also updated the unit tests:

        let internal pAggregateBlock_ws, internal pAggregateBlockR = createParserForwardedToRef<PConstant,State>()

        let internal pLiteral = pBoolLit <|> pStrLit <|> pNumLit <|> pEnumLit
        let internal pLiteral_ws = pLiteral .>> ws

        let internal pAggregateLit_ws = pIdent_ws .>> str_ws ":" .>>. pLiteral_ws .>> empty_ws
        let internal pRecursiveLit_ws = pIdent_ws .>>. pAggregateBlock_ws .>> empty_ws

        do pAggregateBlockR :=
            betweenCurly <|
                (empty_ws >>. many ( attempt pAggregateLit_ws <|> pRecursiveLit_ws ) .>> empty_ws)
                |>> TAggregateOptionsLit

        /// Parser for optionClause
        let pOptionClause_ws = pLiteral_ws <|> pAggregateBlock_ws

...

        /// Parser for optionName "=" optionClause
        and pOption_ws =
            pOptionName_ws .>> str_ws "=" .>>. pOptionClause_ws

Still fairly ugly, though. And I'm almost positive there are other places where a valid empty-statement will cause a parse error. This should work for now, but if anyone gets errors with valid empty-statements on other constructs, I/we may need to make more sweeping changes.

Finally, I forgot to mark skipEmpty as internal in the first patch.

@jhugard
Copy link
Collaborator
jhugard commented Oct 2, 2016

@bhandfast If you like, I could push these to your fork... just need write access...

@jhugard
Copy link
Collaborator
jhugard commented Oct 2, 2016

pAggregateBlockR can be simplified a bit by removing the superfluous empty_ws at the end:

                (empty_ws >>. many ( attempt pAggregateLit_ws <|> pRecursiveLit_ws ) )

@ctaggart
Copy link
Owner
ctaggart commented Oct 2, 2016

Let's me to know when this gets to a point that you want to release it. I'll help get 0.4 out there.

@bhandfast
Copy link
Contributor Author

@jhugard I added you as a collaborator if you find the time to push the changes.

It's late here now, I'll take a look at the changes tomorrow or something like that.

@jhugard
Copy link
Collaborator
jhugard commented Oct 2, 2016

@bhandfast Done. Pushed my changes (which also updated this PR).

betweenCurly
(sepBy pOption_ws (str_ws ","))

let pRpcOptions = pRpcOptionsNew <|> pRpcOptionsOld
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if betweenCurly consumes a token. If so, then this will need an attempt, as in:

            let pRpcOptions = attempt pRpcOptionsNew <|> pRpcOptionsOld

We need a unit test for an rpc statement with old-style options to verify if this is needed or not.

@jhugard
Copy link
Collaborator
jhugard commented Oct 3, 2016

@ctaggart Hold off a bit on the merge. Went over the code and (re)discovered I already did have empty statement handling in most places. I'd like to pull that out as an abstraction so it can be used (consistently and easily) everywhere. Will try and get to it later tonight...

@ctaggart
Copy link
Owner
ctaggart commented Oct 3, 2016

@jhugard Sounds good.

…ty, add unit test for Service options, and fixed bugs related to parsing options.
@jhugard
Copy link
Collaborator
jhugard commented Oct 3, 2016

@bhandfast Ok, done with refactoring to common handling of empty statements, via manyBetweenCurlySkippingEmpty. Fixed a couple of bugs, including improper handling of option at the service level.

NOTE: the last check-in also eliminated what you were calling pRpcOptionsOld. I could not find either a reference to that syntax, nor any examples. If I deleted this by mistake and you're certain that Google protoc.exe can handle that "old" options syntax, then feel free to add it back.

Otherwise, if it looks good to you, I'm OK with a merge.

@bhandfast
Copy link
Contributor Author

@jhugard Cool! I think you have more knowledge about the syntax then me. pRpcOptionsOld was more of a variable naming (a bad one) when I was messing around with the code at the beginning. If you think the code is ready now I think we should merge it.

I haven't time to look at all the changes yet, but I will take a look at it when I find time later this week.

@jhugard jhugard merged commit bc9d38d into ctaggart:master Oct 4, 2016
@jhugard
Copy link
Collaborator
jhugard commented Oct 4, 2016

@ctaggart @bhandfast - Done and done. Went over all options usage, added unit tests (some of which failed), fixed another bug or two, added option support to map, squashed, and merged to master.

@ctaggart - Can you update the readme.md before publishing? Forgot to do that...

@ctaggart
Copy link
Owner
ctaggart commented Oct 4, 2016

Great work @jhugard! Thanks @bhandfast. Yes, I'll take a look at the readme.

@jhugard
Copy link
Collaborator
jhugard commented Oct 5, 2016

Big shout-out to @bhandfast! You made an excellent start on the alt options parsers, especially considering the poor docs from Google. Wouldn't have gotten done without your PR and assistance :-) Thanks a ton!

@ctaggart ctaggart added this to the 0.4.0 milestone Oct 5, 2016
@ctaggart ctaggart changed the title GRpc options gRPC options Oct 5, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0