Skip to content

Commit

Permalink
feat: support request header parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
krak3n committed Nov 11, 2022
1 parent 765c9b7 commit 93e49cf
Show file tree
Hide file tree
Showing 4 changed files with 1,016 additions and 487 deletions.
28 changes: 28 additions & 0 deletions protoc-gen-openapiv2/internal/genopenapi/template.go
Expand Up @@ -1515,6 +1515,34 @@ 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:
// TODO(krak3n): Should we error or assume string?
param.Type = "string"
}

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
},
},
},
},
},
},
},
}
}

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

0 comments on commit 93e49cf

Please sign in to comment.