Skip to content

Commit 43dbac1

Browse files
authored
protoc-gen-openapiv2: support overriding path parameter names (grpc-ecosystem#2562)
* Support overriding path parameter names. * Support overriding path parameter names. * Revert using annotations in `message.proto` * Moved path_param_name to inner message as it is not defined in OpenAPIv2 * Added documentation for path_param_name * Moved pathParamName to FieldConfiguration struct to follow same pattern as in JSONSchema. * Fix tests. * Remove fieldConfiguration from openapiSchemaObject
1 parent 1d70eab commit 43dbac1

File tree

10 files changed

+1319
-1148
lines changed

10 files changed

+1319
-1148
lines changed

docs/docs/mapping/customizing_openapi_output.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,52 @@ For a more in depth example see [visibility_rule_echo_service.proto](https://git
403403
- [`visibility_restriction_selectors=INTERNAL,visibility_restriction_selectors=PREVIEW`](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/visibility_rule_preview_and_internal_echo_service.swagger.json)
404404
- [Not set](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/visibility_rule_none_echo_service.swagger.json)
405405

406+
### Path parameters
407+
408+
When defining HTTP bindings with path parameters that contain multiple path segments, as suggested by the [Google AIPs](https://google.aip.dev/), the path parameter names are numbered to avoid generating duplicate paths in the OpenAPI file.
409+
410+
For example, consider:
411+
```protobuf
412+
service LibraryService {
413+
rpc GetShelf(GetShelfRequest) returns (Shelf) {
414+
option (google.api.http) = {
415+
get: "/v1/{name=shelves/*}"
416+
};
417+
}
418+
rpc GetBook(GetBookRequest) returns (Book) {
419+
option (google.api.http) = {
420+
get: "/v1/{name=shelves/*/books/*}"
421+
};
422+
}
423+
}
424+
425+
message GetShelfRequest {
426+
string name = 1;
427+
}
428+
429+
message GetBookRequest {
430+
string name = 1;
431+
}
432+
```
433+
434+
This will generate the following paths:
435+
- `/v1/{name}`
436+
- `/v1/{name_1}`
437+
438+
To override the path parameter names, annotate the field used as path parameter:
439+
```protobuf
440+
message GetShelfRequest {
441+
string name = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {field_configuration: {path_param_name: "shelfName"}}];
442+
}
443+
message GetBookRequest {
444+
string name = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {field_configuration: {path_param_name: "bookName"}}];
445+
}
446+
```
447+
448+
This will instead generate the following paths:
449+
- `/v1/{shelfName}`
450+
- `/v1/{bookName}`
451+
406452
### Output format
407453

408454
By default the output format is JSON, but it is possible to configure it using the `output_format` option. Allowed values are: `json`, `yaml`. The output format will also change the extension of the output files.

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

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,17 +1059,17 @@ paths:
10591059
description: "An unexpected error response."
10601060
schema:
10611061
$ref: "#/definitions/rpcStatus"
1062-
/v1/example/a_bit_of_everything/query/{uuid}:
1062+
/v1/example/a_bit_of_everything/query/{uuidName}:
10631063
get:
10641064
tags:
10651065
- "ABitOfEverythingService"
10661066
operationId: "ABitOfEverythingService_GetQuery"
10671067
parameters:
1068-
- name: "uuid"
1068+
- name: "uuidName"
10691069
in: "path"
10701070
required: true
10711071
type: "string"
1072-
x-exportParamName: "Uuid"
1072+
x-exportParamName: "UuidName"
10731073
- name: "singleNested.name"
10741074
in: "query"
10751075
description: "name is nested field."
@@ -1692,22 +1692,27 @@ paths:
16921692
description: "An unexpected error response."
16931693
schema:
16941694
$ref: "#/definitions/rpcStatus"
1695-
/v1/example/a_bit_of_everything/{uuid}:
1696-
get:
1695+
/v1/example/a_bit_of_everything/{uuidName}:
1696+
put:
16971697
tags:
16981698
- "ABitOfEverythingService"
1699-
operationId: "ABitOfEverythingService_Lookup"
1699+
operationId: "ABitOfEverythingService_Update"
17001700
parameters:
1701-
- name: "uuid"
1701+
- name: "uuidName"
17021702
in: "path"
17031703
required: true
17041704
type: "string"
1705-
x-exportParamName: "Uuid"
1705+
x-exportParamName: "UuidName"
1706+
- in: "body"
1707+
name: "body"
1708+
required: true
1709+
schema:
1710+
$ref: "#/definitions/A bit of everything"
1711+
x-exportParamName: "Body"
17061712
responses:
17071713
200:
17081714
description: "A successful response."
1709-
schema:
1710-
$ref: "#/definitions/examplepbABitOfEverything"
1715+
schema: {}
17111716
403:
17121717
description: "Returned when the user does not have permission to access\
17131718
\ the resource."
@@ -1729,26 +1734,22 @@ paths:
17291734
description: "An unexpected error response."
17301735
schema:
17311736
$ref: "#/definitions/rpcStatus"
1732-
put:
1737+
/v1/example/a_bit_of_everything/{uuid}:
1738+
get:
17331739
tags:
17341740
- "ABitOfEverythingService"
1735-
operationId: "ABitOfEverythingService_Update"
1741+
operationId: "ABitOfEverythingService_Lookup"
17361742
parameters:
17371743
- name: "uuid"
17381744
in: "path"
17391745
required: true
17401746
type: "string"
17411747
x-exportParamName: "Uuid"
1742-
- in: "body"
1743-
name: "body"
1744-
required: true
1745-
schema:
1746-
$ref: "#/definitions/A bit of everything"
1747-
x-exportParamName: "Body"
17481748
responses:
17491749
200:
17501750
description: "A successful response."
1751-
schema: {}
1751+
schema:
1752+
$ref: "#/definitions/examplepbABitOfEverything"
17521753
403:
17531754
description: "Returned when the user does not have permission to access\
17541755
\ the resource."
@@ -2200,17 +2201,17 @@ paths:
22002201
description: "An unexpected error response."
22012202
schema:
22022203
$ref: "#/definitions/rpcStatus"
2203-
/v2/example/a_bit_of_everything/{abe.uuid}:
2204+
/v2/example/a_bit_of_everything/{uuidName}:
22042205
put:
22052206
tags:
22062207
- "ABitOfEverythingService"
22072208
operationId: "ABitOfEverythingService_UpdateV2"
22082209
parameters:
2209-
- name: "abe.uuid"
2210+
- name: "uuidName"
22102211
in: "path"
22112212
required: true
22122213
type: "string"
2213-
x-exportParamName: "AbeUuid"
2214+
x-exportParamName: "UuidName"
22142215
- in: "body"
22152216
name: "abe"
22162217
description: "A bit of everything\n\nIntentionally complicated message type\
@@ -2256,11 +2257,11 @@ paths:
22562257
- "ABitOfEverythingService"
22572258
operationId: "ABitOfEverythingService_UpdateV22"
22582259
parameters:
2259-
- name: "abe.uuid"
2260+
- name: "uuidName"
22602261
in: "path"
22612262
required: true
22622263
type: "string"
2263-
x-exportParamName: "AbeUuid"
2264+
x-exportParamName: "UuidName"
22642265
- in: "body"
22652266
name: "abe"
22662267
description: "A bit of everything\n\nIntentionally complicated message type\
@@ -2650,17 +2651,17 @@ paths:
26502651
description: "An unexpected error response."
26512652
schema:
26522653
$ref: "#/definitions/rpcStatus"
2653-
/v2a/example/a_bit_of_everything/{abe.uuid}:
2654+
/v2a/example/a_bit_of_everything/{uuidName}:
26542655
patch:
26552656
tags:
26562657
- "ABitOfEverythingService"
26572658
operationId: "ABitOfEverythingService_UpdateV23"
26582659
parameters:
2659-
- name: "abe.uuid"
2660+
- name: "uuidName"
26602661
in: "path"
26612662
required: true
26622663
type: "string"
2663-
x-exportParamName: "AbeUuid"
2664+
x-exportParamName: "UuidName"
26642665
- in: "body"
26652666
name: "body"
26662667
required: true

examples/internal/clients/abe/api_a_bit_of_everything_service.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2718,7 +2718,7 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceGetMessageWit
27182718
/*
27192719
ABitOfEverythingServiceApiService
27202720
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
2721-
* @param uuid
2721+
* @param uuidName
27222722
* @param floatValue Float value field
27232723
* @param requiredStringViaFieldBehaviorAnnotation mark a field as required in Open API definition
27242724
* @param optional nil or *ABitOfEverythingServiceGetQueryOpts - Optional Parameters:
@@ -2797,7 +2797,7 @@ type ABitOfEverythingServiceGetQueryOpts struct {
27972797
OptionalStringValue optional.String
27982798
}
27992799

2800-
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceGetQuery(ctx context.Context, uuid string, floatValue float32, requiredStringViaFieldBehaviorAnnotation string, localVarOptionals *ABitOfEverythingServiceGetQueryOpts) (interface{}, *http.Response, error) {
2800+
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceGetQuery(ctx context.Context, uuidName string, floatValue float32, requiredStringViaFieldBehaviorAnnotation string, localVarOptionals *ABitOfEverythingServiceGetQueryOpts) (interface{}, *http.Response, error) {
28012801
var (
28022802
localVarHttpMethod = strings.ToUpper("Get")
28032803
localVarPostBody interface{}
@@ -2807,8 +2807,8 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceGetQuery(ctx
28072807
)
28082808

28092809
// create path and map variables
2810-
localVarPath := a.client.cfg.BasePath + "/v1/example/a_bit_of_everything/query/{uuid}"
2811-
localVarPath = strings.Replace(localVarPath, "{"+"uuid"+"}", fmt.Sprintf("%v", uuid), -1)
2810+
localVarPath := a.client.cfg.BasePath + "/v1/example/a_bit_of_everything/query/{uuidName}"
2811+
localVarPath = strings.Replace(localVarPath, "{"+"uuidName"+"}", fmt.Sprintf("%v", uuidName), -1)
28122812

28132813
localVarHeaderParams := make(map[string]string)
28142814
localVarQueryParams := url.Values{}
@@ -3897,12 +3897,12 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceTimeout(ctx c
38973897
/*
38983898
ABitOfEverythingServiceApiService
38993899
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
3900-
* @param uuid
3900+
* @param uuidName
39013901
* @param body
39023902
39033903
@return interface{}
39043904
*/
3905-
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdate(ctx context.Context, uuid string, body ABitOfEverything) (interface{}, *http.Response, error) {
3905+
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdate(ctx context.Context, uuidName string, body ABitOfEverything) (interface{}, *http.Response, error) {
39063906
var (
39073907
localVarHttpMethod = strings.ToUpper("Put")
39083908
localVarPostBody interface{}
@@ -3912,8 +3912,8 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdate(ctx co
39123912
)
39133913

39143914
// create path and map variables
3915-
localVarPath := a.client.cfg.BasePath + "/v1/example/a_bit_of_everything/{uuid}"
3916-
localVarPath = strings.Replace(localVarPath, "{"+"uuid"+"}", fmt.Sprintf("%v", uuid), -1)
3915+
localVarPath := a.client.cfg.BasePath + "/v1/example/a_bit_of_everything/{uuidName}"
3916+
localVarPath = strings.Replace(localVarPath, "{"+"uuidName"+"}", fmt.Sprintf("%v", uuidName), -1)
39173917

39183918
localVarHeaderParams := make(map[string]string)
39193919
localVarQueryParams := url.Values{}
@@ -4230,7 +4230,7 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateBook(ct
42304230
/*
42314231
ABitOfEverythingServiceApiService
42324232
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
4233-
* @param abeUuid
4233+
* @param uuidName
42344234
* @param abe A bit of everything Intentionally complicated message type to cover many features of Protobuf.
42354235
* @param optional nil or *ABitOfEverythingServiceUpdateV2Opts - Optional Parameters:
42364236
* @param "UpdateMask" (optional.String) - The paths to update.
@@ -4242,7 +4242,7 @@ type ABitOfEverythingServiceUpdateV2Opts struct {
42424242
UpdateMask optional.String
42434243
}
42444244

4245-
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV2(ctx context.Context, abeUuid string, abe ABitOfEverything2, localVarOptionals *ABitOfEverythingServiceUpdateV2Opts) (interface{}, *http.Response, error) {
4245+
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV2(ctx context.Context, uuidName string, abe ABitOfEverything2, localVarOptionals *ABitOfEverythingServiceUpdateV2Opts) (interface{}, *http.Response, error) {
42464246
var (
42474247
localVarHttpMethod = strings.ToUpper("Put")
42484248
localVarPostBody interface{}
@@ -4252,8 +4252,8 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV2(ctx
42524252
)
42534253

42544254
// create path and map variables
4255-
localVarPath := a.client.cfg.BasePath + "/v2/example/a_bit_of_everything/{abe.uuid}"
4256-
localVarPath = strings.Replace(localVarPath, "{"+"abe.uuid"+"}", fmt.Sprintf("%v", abeUuid), -1)
4255+
localVarPath := a.client.cfg.BasePath + "/v2/example/a_bit_of_everything/{uuidName}"
4256+
localVarPath = strings.Replace(localVarPath, "{"+"uuidName"+"}", fmt.Sprintf("%v", uuidName), -1)
42574257

42584258
localVarHeaderParams := make(map[string]string)
42594259
localVarQueryParams := url.Values{}
@@ -4399,7 +4399,7 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV2(ctx
43994399
/*
44004400
ABitOfEverythingServiceApiService
44014401
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
4402-
* @param abeUuid
4402+
* @param uuidName
44034403
* @param abe A bit of everything Intentionally complicated message type to cover many features of Protobuf.
44044404
* @param optional nil or *ABitOfEverythingServiceUpdateV22Opts - Optional Parameters:
44054405
* @param "UpdateMask" (optional.String) - The paths to update.
@@ -4411,7 +4411,7 @@ type ABitOfEverythingServiceUpdateV22Opts struct {
44114411
UpdateMask optional.String
44124412
}
44134413

4414-
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV22(ctx context.Context, abeUuid string, abe ABitOfEverything3, localVarOptionals *ABitOfEverythingServiceUpdateV22Opts) (interface{}, *http.Response, error) {
4414+
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV22(ctx context.Context, uuidName string, abe ABitOfEverything3, localVarOptionals *ABitOfEverythingServiceUpdateV22Opts) (interface{}, *http.Response, error) {
44154415
var (
44164416
localVarHttpMethod = strings.ToUpper("Patch")
44174417
localVarPostBody interface{}
@@ -4421,8 +4421,8 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV22(ctx
44214421
)
44224422

44234423
// create path and map variables
4424-
localVarPath := a.client.cfg.BasePath + "/v2/example/a_bit_of_everything/{abe.uuid}"
4425-
localVarPath = strings.Replace(localVarPath, "{"+"abe.uuid"+"}", fmt.Sprintf("%v", abeUuid), -1)
4424+
localVarPath := a.client.cfg.BasePath + "/v2/example/a_bit_of_everything/{uuidName}"
4425+
localVarPath = strings.Replace(localVarPath, "{"+"uuidName"+"}", fmt.Sprintf("%v", uuidName), -1)
44264426

44274427
localVarHeaderParams := make(map[string]string)
44284428
localVarQueryParams := url.Values{}
@@ -4568,12 +4568,12 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV22(ctx
45684568
/*
45694569
ABitOfEverythingServiceApiService
45704570
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
4571-
* @param abeUuid
4571+
* @param uuidName
45724572
* @param body
45734573
45744574
@return interface{}
45754575
*/
4576-
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV23(ctx context.Context, abeUuid string, body UpdateV2RequestRequestForUpdateIncludesTheMessageAndTheUpdateMask) (interface{}, *http.Response, error) {
4576+
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV23(ctx context.Context, uuidName string, body UpdateV2RequestRequestForUpdateIncludesTheMessageAndTheUpdateMask) (interface{}, *http.Response, error) {
45774577
var (
45784578
localVarHttpMethod = strings.ToUpper("Patch")
45794579
localVarPostBody interface{}
@@ -4583,8 +4583,8 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV23(ctx
45834583
)
45844584

45854585
// create path and map variables
4586-
localVarPath := a.client.cfg.BasePath + "/v2a/example/a_bit_of_everything/{abe.uuid}"
4587-
localVarPath = strings.Replace(localVarPath, "{"+"abe.uuid"+"}", fmt.Sprintf("%v", abeUuid), -1)
4586+
localVarPath := a.client.cfg.BasePath + "/v2a/example/a_bit_of_everything/{uuidName}"
4587+
localVarPath = strings.Replace(localVarPath, "{"+"uuidName"+"}", fmt.Sprintf("%v", uuidName), -1)
45884588

45894589
localVarHeaderParams := make(map[string]string)
45904590
localVarQueryParams := url.Values{}

0 commit comments

Comments
 (0)