Skip to content

Commit

Permalink
protoc-gen-openapiv2: support overriding path parameter names (#2562)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
oyvindwe authored Mar 21, 2022
1 parent 1d70eab commit 43dbac1
Show file tree
Hide file tree
Showing 10 changed files with 1,319 additions and 1,148 deletions.
46 changes: 46 additions & 0 deletions docs/docs/mapping/customizing_openapi_output.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,52 @@ For a more in depth example see [visibility_rule_echo_service.proto](https://git
- [`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)
- [Not set](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/visibility_rule_none_echo_service.swagger.json)

### Path parameters

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.

For example, consider:
```protobuf
service LibraryService {
rpc GetShelf(GetShelfRequest) returns (Shelf) {
option (google.api.http) = {
get: "/v1/{name=shelves/*}"
};
}
rpc GetBook(GetBookRequest) returns (Book) {
option (google.api.http) = {
get: "/v1/{name=shelves/*/books/*}"
};
}
}
message GetShelfRequest {
string name = 1;
}
message GetBookRequest {
string name = 1;
}
```

This will generate the following paths:
- `/v1/{name}`
- `/v1/{name_1}`

To override the path parameter names, annotate the field used as path parameter:
```protobuf
message GetShelfRequest {
string name = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {field_configuration: {path_param_name: "shelfName"}}];
}
message GetBookRequest {
string name = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {field_configuration: {path_param_name: "bookName"}}];
}
```

This will instead generate the following paths:
- `/v1/{shelfName}`
- `/v1/{bookName}`

### Output format

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.
Expand Down
55 changes: 28 additions & 27 deletions examples/internal/clients/abe/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1059,17 +1059,17 @@ paths:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
/v1/example/a_bit_of_everything/query/{uuid}:
/v1/example/a_bit_of_everything/query/{uuidName}:
get:
tags:
- "ABitOfEverythingService"
operationId: "ABitOfEverythingService_GetQuery"
parameters:
- name: "uuid"
- name: "uuidName"
in: "path"
required: true
type: "string"
x-exportParamName: "Uuid"
x-exportParamName: "UuidName"
- name: "singleNested.name"
in: "query"
description: "name is nested field."
Expand Down Expand Up @@ -1692,22 +1692,27 @@ paths:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
/v1/example/a_bit_of_everything/{uuid}:
get:
/v1/example/a_bit_of_everything/{uuidName}:
put:
tags:
- "ABitOfEverythingService"
operationId: "ABitOfEverythingService_Lookup"
operationId: "ABitOfEverythingService_Update"
parameters:
- name: "uuid"
- name: "uuidName"
in: "path"
required: true
type: "string"
x-exportParamName: "Uuid"
x-exportParamName: "UuidName"
- in: "body"
name: "body"
required: true
schema:
$ref: "#/definitions/A bit of everything"
x-exportParamName: "Body"
responses:
200:
description: "A successful response."
schema:
$ref: "#/definitions/examplepbABitOfEverything"
schema: {}
403:
description: "Returned when the user does not have permission to access\
\ the resource."
Expand All @@ -1729,26 +1734,22 @@ paths:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
put:
/v1/example/a_bit_of_everything/{uuid}:
get:
tags:
- "ABitOfEverythingService"
operationId: "ABitOfEverythingService_Update"
operationId: "ABitOfEverythingService_Lookup"
parameters:
- name: "uuid"
in: "path"
required: true
type: "string"
x-exportParamName: "Uuid"
- in: "body"
name: "body"
required: true
schema:
$ref: "#/definitions/A bit of everything"
x-exportParamName: "Body"
responses:
200:
description: "A successful response."
schema: {}
schema:
$ref: "#/definitions/examplepbABitOfEverything"
403:
description: "Returned when the user does not have permission to access\
\ the resource."
Expand Down Expand Up @@ -2200,17 +2201,17 @@ paths:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
/v2/example/a_bit_of_everything/{abe.uuid}:
/v2/example/a_bit_of_everything/{uuidName}:
put:
tags:
- "ABitOfEverythingService"
operationId: "ABitOfEverythingService_UpdateV2"
parameters:
- name: "abe.uuid"
- name: "uuidName"
in: "path"
required: true
type: "string"
x-exportParamName: "AbeUuid"
x-exportParamName: "UuidName"
- in: "body"
name: "abe"
description: "A bit of everything\n\nIntentionally complicated message type\
Expand Down Expand Up @@ -2256,11 +2257,11 @@ paths:
- "ABitOfEverythingService"
operationId: "ABitOfEverythingService_UpdateV22"
parameters:
- name: "abe.uuid"
- name: "uuidName"
in: "path"
required: true
type: "string"
x-exportParamName: "AbeUuid"
x-exportParamName: "UuidName"
- in: "body"
name: "abe"
description: "A bit of everything\n\nIntentionally complicated message type\
Expand Down Expand Up @@ -2650,17 +2651,17 @@ paths:
description: "An unexpected error response."
schema:
$ref: "#/definitions/rpcStatus"
/v2a/example/a_bit_of_everything/{abe.uuid}:
/v2a/example/a_bit_of_everything/{uuidName}:
patch:
tags:
- "ABitOfEverythingService"
operationId: "ABitOfEverythingService_UpdateV23"
parameters:
- name: "abe.uuid"
- name: "uuidName"
in: "path"
required: true
type: "string"
x-exportParamName: "AbeUuid"
x-exportParamName: "UuidName"
- in: "body"
name: "body"
required: true
Expand Down
40 changes: 20 additions & 20 deletions examples/internal/clients/abe/api_a_bit_of_everything_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2718,7 +2718,7 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceGetMessageWit
/*
ABitOfEverythingServiceApiService
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
* @param uuid
* @param uuidName
* @param floatValue Float value field
* @param requiredStringViaFieldBehaviorAnnotation mark a field as required in Open API definition
* @param optional nil or *ABitOfEverythingServiceGetQueryOpts - Optional Parameters:
Expand Down Expand Up @@ -2797,7 +2797,7 @@ type ABitOfEverythingServiceGetQueryOpts struct {
OptionalStringValue optional.String
}

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

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

localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
Expand Down Expand Up @@ -3897,12 +3897,12 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceTimeout(ctx c
/*
ABitOfEverythingServiceApiService
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
* @param uuid
* @param uuidName
* @param body
@return interface{}
*/
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdate(ctx context.Context, uuid string, body ABitOfEverything) (interface{}, *http.Response, error) {
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdate(ctx context.Context, uuidName string, body ABitOfEverything) (interface{}, *http.Response, error) {
var (
localVarHttpMethod = strings.ToUpper("Put")
localVarPostBody interface{}
Expand All @@ -3912,8 +3912,8 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdate(ctx co
)

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

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

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

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

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

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

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

localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
Expand Down Expand Up @@ -4568,12 +4568,12 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV22(ctx
/*
ABitOfEverythingServiceApiService
* @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
* @param abeUuid
* @param uuidName
* @param body
@return interface{}
*/
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV23(ctx context.Context, abeUuid string, body UpdateV2RequestRequestForUpdateIncludesTheMessageAndTheUpdateMask) (interface{}, *http.Response, error) {
func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV23(ctx context.Context, uuidName string, body UpdateV2RequestRequestForUpdateIncludesTheMessageAndTheUpdateMask) (interface{}, *http.Response, error) {
var (
localVarHttpMethod = strings.ToUpper("Patch")
localVarPostBody interface{}
Expand All @@ -4583,8 +4583,8 @@ func (a *ABitOfEverythingServiceApiService) ABitOfEverythingServiceUpdateV23(ctx
)

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

localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
Expand Down
Loading

0 comments on commit 43dbac1

Please sign in to comment.