Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support request header parameters #3010

Merged
27 changes: 27 additions & 0 deletions protoc-gen-openapiv2/internal/genopenapi/template.go
Expand Up @@ -1515,6 +1515,33 @@ func renderServices(services []*descriptor.Service, paths openapiPathsObject, re
copy(operationObject.Produces, opts.Produces)
}

if params := opts.Parameters; params != nil && len(params.Headers) > 0 {
for _, header := range params.Headers {
param := openapiParameterObject{
In: "header",
Name: header.Name,
Description: header.Description,
Required: header.Required,
Format: header.Format,
}

switch header.Type {
case openapi_options.HeaderParameter_STRING:
param.Type = "string"
case openapi_options.HeaderParameter_NUMBER:
param.Type = "number"
case openapi_options.HeaderParameter_INTEGER:
param.Type = "integer"
case openapi_options.HeaderParameter_BOOLEAN:
param.Type = "boolean"
default:
return fmt.Errorf("invalid header parameter type: %+v", header.Type)
}

operationObject.Parameters = append(operationObject.Parameters, param)
}
}

// TODO(ivucica): add remaining fields of operation object
}

Expand Down
207 changes: 207 additions & 0 deletions protoc-gen-openapiv2/internal/genopenapi/template_test.go
Expand Up @@ -7965,6 +7965,213 @@ func TestRenderServicesWithColonInSegment(t *testing.T) {
}
}

func TestRenderServiceWithHeaderParameters(t *testing.T) {
file := func() descriptor.File {
msgdesc := &descriptorpb.DescriptorProto{
Name: proto.String("ExampleMessage"),
}

meth := &descriptorpb.MethodDescriptorProto{
Name: proto.String("Example"),
InputType: proto.String("ExampleMessage"),
OutputType: proto.String("ExampleMessage"),
Options: &descriptorpb.MethodOptions{},
}

svc := &descriptorpb.ServiceDescriptorProto{
Name: proto.String("ExampleService"),
Method: []*descriptorpb.MethodDescriptorProto{meth},
}

msg := &descriptor.Message{
DescriptorProto: msgdesc,
}

return descriptor.File{
FileDescriptorProto: &descriptorpb.FileDescriptorProto{
SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
Name: proto.String("example.proto"),
Package: proto.String("example"),
MessageType: []*descriptorpb.DescriptorProto{msgdesc},
Service: []*descriptorpb.ServiceDescriptorProto{svc},
Options: &descriptorpb.FileOptions{
GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
},
},
GoPkg: descriptor.GoPackage{
Path: "example.com/path/to/example/example.pb",
Name: "example_pb",
},
Messages: []*descriptor.Message{msg},
Services: []*descriptor.Service{
{
ServiceDescriptorProto: svc,
Methods: []*descriptor.Method{
{
MethodDescriptorProto: meth,
RequestType: msg,
ResponseType: msg,
Bindings: []*descriptor.Binding{
{
HTTPMethod: "GET",
PathTmpl: httprule.Template{
Version: 1,
OpCodes: []int{0, 0},
Template: "/v1/echo", // TODO(achew22): Figure out what this should really be
krak3n marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
},
},
},
},
}
}

type test struct {
file func() descriptor.File
openapiOperation *openapi_options.Operation
parameters openapiParametersObject
}

tests := map[string]*test{
"type string": {
file: file,
openapiOperation: &openapi_options.Operation{
Parameters: &openapi_options.Parameters{
Headers: []*openapi_options.HeaderParameter{
{
Name: "X-Custom-Header",
Type: openapi_options.HeaderParameter_STRING,
},
},
},
},
parameters: openapiParametersObject{
{
Name: "X-Custom-Header",
In: "header",
Type: "string",
},
},
},
"type integer": {
file: file,
openapiOperation: &openapi_options.Operation{
Parameters: &openapi_options.Parameters{
Headers: []*openapi_options.HeaderParameter{
{
Name: "X-Custom-Header",
Type: openapi_options.HeaderParameter_INTEGER,
},
},
},
},
parameters: openapiParametersObject{
{
Name: "X-Custom-Header",
In: "header",
Type: "integer",
},
},
},
"type number": {
file: file,
openapiOperation: &openapi_options.Operation{
Parameters: &openapi_options.Parameters{
Headers: []*openapi_options.HeaderParameter{
{
Name: "X-Custom-Header",
Type: openapi_options.HeaderParameter_NUMBER,
},
},
},
},
parameters: openapiParametersObject{
{
Name: "X-Custom-Header",
In: "header",
Type: "number",
},
},
},
"type boolean": {
file: file,
openapiOperation: &openapi_options.Operation{
Parameters: &openapi_options.Parameters{
Headers: []*openapi_options.HeaderParameter{
{
Name: "X-Custom-Header",
Type: openapi_options.HeaderParameter_BOOLEAN,
},
},
},
},
parameters: openapiParametersObject{
{
Name: "X-Custom-Header",
In: "header",
Type: "boolean",
},
},
},
"header required": {
file: file,
openapiOperation: &openapi_options.Operation{
Parameters: &openapi_options.Parameters{
Headers: []*openapi_options.HeaderParameter{
{
Name: "X-Custom-Header",
Required: true,
},
},
},
},
parameters: openapiParametersObject{
{
Name: "X-Custom-Header",
In: "header",
Required: true,
Type: "string",
},
},
},
}

for name, test := range tests {
test := test

t.Run(name, func(t *testing.T) {
file := test.file()

proto.SetExtension(
proto.Message(file.Services[0].Methods[0].Options),
openapi_options.E_Openapiv2Operation,
test.openapiOperation)

reg := descriptor.NewRegistry()

fileCL := crossLinkFixture(&file)

err := reg.Load(reqFromFile(fileCL))
if err != nil {
t.Errorf("reg.Load(%#v) failed with %v; want success", file, err)
}

result, err := applyTemplate(param{File: fileCL, reg: reg})
if err != nil {
t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
}

params := result.Paths["/v1/echo"].Get.Parameters

if !reflect.DeepEqual(params, test.parameters) {
t.Errorf("expected %+v, got %+v", test.parameters, params)
}
})
}
}

func GetPaths(req *openapiSwaggerObject) []string {
paths := make([]string, len(req.Paths))
i := 0
Expand Down