Skip to content

Commit 58634e4

Browse files
authored
Fixes #407 and #1700 - google.api.http path parameter constraints and multiple HTTP path/method endpoint support (#2461)
* Support standard OpenAPI paths for all valid path params for gRPC endpoints in protoc-gen-openapiv2 (#407) * Fix issue with URL encoded path segments not being split when unescaping mode is legacy, default or all characters * Adding docs for path template syntax and new functionality (#1700) * Removed unused old code * Additional tests for regular expressions * Formatting code * Fix lint error * Removing duplicate test * Adding additional test for path escaping * Fix failing test because of path change * Fix failing tests in code coverage * Change determination of unescaping by supporting default mode change * Regenerated files from changes * Remove unused parameters in test * Remove code warnings in test * Implement changes from PR #2461 comments * Correcting incorrect documentation * Adding test case to show change in docs is warranted
1 parent 65c0585 commit 58634e4

File tree

9 files changed

+779
-61
lines changed

9 files changed

+779
-61
lines changed

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,19 @@ Alternatively, see the section on remotely managed plugin versions below.
429429
Note that this plugin also supports generating OpenAPI definitions for unannotated methods;
430430
use the `generate_unbound_methods` option to enable this.
431431

432+
It is possible with the HTTP mapping for a gRPC service method to create duplicate mappings
433+
with the only difference being constraints on the path parameter.
434+
435+
`/v1/{name=projects/*}` and `/v1/{name=organizations/*}` both become `/v1/{name}`. When
436+
this occurs the plugin will rename the path parameter with a "_1" (or "_2" etc) suffix
437+
to differentiate the different operations. So in the above example, the 2nd path would become
438+
`/v1/{name_1=organizations/*}`. This can also cause OpenAPI clients to URL encode the "/" that is
439+
part of the path parameter as that is what OpenAPI defines in the specification. To allow gRPC gateway to
440+
accept the URL encoded slash and still route the request, use the UnescapingModeAllCharacters or
441+
UnescapingModeLegacy (which is the default currently though may change in future versions). See
442+
[Customizing Your Gateway](https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/customizing_your_gateway/)
443+
for more information.
444+
432445
## Usage with remote plugins
433446

434447
As an alternative to all of the above, you can use `buf` with
@@ -564,13 +577,15 @@ But patches are welcome.
564577
- HTTP request host is added as `X-Forwarded-Host` gRPC request header.
565578
- HTTP `Authorization` header is added as `authorization` gRPC request header.
566579
- Remaining Permanent HTTP header keys (as specified by the IANA
567-
[here](http://www.iana.org/assignments/message-headers/message-headers.xhtml)
580+
[here](http://www.iana.org/assignments/message-headers/message-headers.xhtml))
568581
are prefixed with `grpcgateway-` and added with their values to gRPC request
569582
header.
570583
- HTTP headers that start with 'Grpc-Metadata-' are mapped to gRPC metadata
571584
(prefixed with `grpcgateway-`).
572585
- While configurable, the default {un,}marshaling uses
573586
[protojson](https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson).
587+
- The path template used to map gRPC service methods to HTTP endpoints supports the [google.api.http](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto)
588+
path template syntax. For example, `/api/v1/{name=projects/*/topics/*}` or `/prefix/{path=organizations/**}`.
574589

575590
## Contribution
576591

examples/internal/clients/abe/api/swagger.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ paths:
158158
in: "query"
159159
required: false
160160
type: "string"
161+
pattern: "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
161162
x-exportParamName: "Uuid"
162163
x-optionalDataType: "String"
163164
- name: "floatValue"
@@ -470,6 +471,7 @@ paths:
470471
in: "query"
471472
required: false
472473
type: "string"
474+
pattern: "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
473475
x-exportParamName: "Uuid"
474476
x-optionalDataType: "String"
475477
- name: "floatValue"
@@ -773,6 +775,7 @@ paths:
773775
in: "query"
774776
required: false
775777
type: "string"
778+
pattern: "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
776779
x-exportParamName: "Uuid"
777780
x-optionalDataType: "String"
778781
- name: "floatValue"
@@ -1445,6 +1448,7 @@ paths:
14451448
in: "path"
14461449
required: true
14471450
type: "string"
1451+
pattern: "strprefix/[^/]+"
14481452
x-exportParamName: "StringValue"
14491453
- name: "uint32Value"
14501454
in: "path"
@@ -1544,6 +1548,7 @@ paths:
15441548
in: "query"
15451549
required: false
15461550
type: "string"
1551+
pattern: "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
15471552
x-exportParamName: "Uuid"
15481553
x-optionalDataType: "String"
15491554
- name: "bytesValue"
@@ -2076,7 +2081,7 @@ paths:
20762081
description: "An unexpected error response."
20772082
schema:
20782083
$ref: "#/definitions/rpcStatus"
2079-
/v1/{book.name=publishers/*/books/*}:
2084+
/v1/{book.name}:
20802085
patch:
20812086
tags:
20822087
- "ABitOfEverythingService"
@@ -2088,6 +2093,7 @@ paths:
20882093
\nExample: `publishers/1257894000000000000/books/my-book`"
20892094
required: true
20902095
type: "string"
2096+
pattern: "publishers/[^/]+/books/[^/]+"
20912097
x-exportParamName: "BookName"
20922098
- in: "body"
20932099
name: "body"
@@ -2138,7 +2144,7 @@ paths:
21382144
description: "An unexpected error response."
21392145
schema:
21402146
$ref: "#/definitions/rpcStatus"
2141-
/v1/{parent=publishers/*}/books:
2147+
/v1/{parent}/books:
21422148
post:
21432149
tags:
21442150
- "ABitOfEverythingService"
@@ -2151,6 +2157,7 @@ paths:
21512157
\nExample: `publishers/1257894000000000000`"
21522158
required: true
21532159
type: "string"
2160+
pattern: "publishers/[^/]+"
21542161
x-exportParamName: "Parent"
21552162
- in: "body"
21562163
name: "body"

examples/internal/clients/abe/api_a_bit_of_everything_service.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,7 +1943,7 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceCreateBook(ct
19431943
)
19441944

19451945
// create path and map variables
1946-
localVarPath := a.client.cfg.BasePath + "/v1/{parent=publishers/*}/books"
1946+
localVarPath := a.client.cfg.BasePath + "/v1/{parent}/books"
19471947
localVarPath = strings.Replace(localVarPath, "{"+"parent"+"}", fmt.Sprintf("%v", parent), -1)
19481948

19491949
localVarHeaderParams := make(map[string]string)
@@ -4080,7 +4080,7 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateBook(ct
40804080
)
40814081

40824082
// create path and map variables
4083-
localVarPath := a.client.cfg.BasePath + "/v1/{book.name=publishers/*/books/*}"
4083+
localVarPath := a.client.cfg.BasePath + "/v1/{book.name}"
40844084
localVarPath = strings.Replace(localVarPath, "{"+"book.name"+"}", fmt.Sprintf("%v", bookName), -1)
40854085

40864086
localVarHeaderParams := make(map[string]string)

examples/internal/proto/examplepb/a_bit_of_everything.swagger.json

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@
238238
"name": "uuid",
239239
"in": "query",
240240
"required": false,
241-
"type": "string"
241+
"type": "string",
242+
"pattern": "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
242243
},
243244
{
244245
"name": "floatValue",
@@ -579,7 +580,8 @@
579580
"name": "uuid",
580581
"in": "query",
581582
"required": false,
582-
"type": "string"
583+
"type": "string",
584+
"pattern": "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
583585
},
584586
{
585587
"name": "floatValue",
@@ -913,7 +915,8 @@
913915
"name": "uuid",
914916
"in": "query",
915917
"required": false,
916-
"type": "string"
918+
"type": "string",
919+
"pattern": "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
917920
},
918921
{
919922
"name": "floatValue",
@@ -1652,7 +1655,8 @@
16521655
"name": "stringValue",
16531656
"in": "path",
16541657
"required": true,
1655-
"type": "string"
1658+
"type": "string",
1659+
"pattern": "strprefix/[^/]+"
16561660
},
16571661
{
16581662
"name": "uint32Value",
@@ -1766,7 +1770,8 @@
17661770
"name": "uuid",
17671771
"in": "query",
17681772
"required": false,
1769-
"type": "string"
1773+
"type": "string",
1774+
"pattern": "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"
17701775
},
17711776
{
17721777
"name": "bytesValue",
@@ -2615,7 +2620,7 @@
26152620
]
26162621
}
26172622
},
2618-
"/v1/{book.name=publishers/*/books/*}": {
2623+
"/v1/{book.name}": {
26192624
"patch": {
26202625
"operationId": "ABitOfEverythingService_UpdateBook",
26212626
"responses": {
@@ -2661,7 +2666,8 @@
26612666
"description": "The resource name of the book.\n\nFormat: `publishers/{publisher}/books/{book}`\n\nExample: `publishers/1257894000000000000/books/my-book`",
26622667
"in": "path",
26632668
"required": true,
2664-
"type": "string"
2669+
"type": "string",
2670+
"pattern": "publishers/[^/]+/books/[^/]+"
26652671
},
26662672
{
26672673
"name": "body",
@@ -2692,7 +2698,7 @@
26922698
]
26932699
}
26942700
},
2695-
"/v1/{parent=publishers/*}/books": {
2701+
"/v1/{parent}/books": {
26962702
"post": {
26972703
"summary": "Create a book.",
26982704
"operationId": "ABitOfEverythingService_CreateBook",
@@ -2739,7 +2745,8 @@
27392745
"description": "The publisher in which to create the book.\n\nFormat: `publishers/{publisher}`\n\nExample: `publishers/1257894000000000000`",
27402746
"in": "path",
27412747
"required": true,
2742-
"type": "string"
2748+
"type": "string",
2749+
"pattern": "publishers/[^/]+"
27432750
},
27442751
{
27452752
"name": "body",

0 commit comments

Comments
 (0)