Skip to content

Commit

Permalink
Expose OperationContext in reflect context hooks (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop authored Apr 19, 2022
1 parent d01f6e1 commit 5ede87a
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 22 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/bool64/dev v0.2.10
github.com/stretchr/testify v1.7.1
github.com/swaggest/assertjson v1.6.8
github.com/swaggest/jsonschema-go v0.3.32
github.com/swaggest/jsonschema-go v0.3.33
github.com/swaggest/refl v1.0.2
gopkg.in/yaml.v2 v2.4.0
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/swaggest/assertjson v1.6.8 h1:1O/9UI5M+2OJI7BeEWKGj0wTvpRXZt5FkOJ4nRkY4rA=
github.com/swaggest/assertjson v1.6.8/go.mod h1:Euf0upn9Vlaf1/llYHTs+Kx5K3vVbpMbsZhth7zlN7M=
github.com/swaggest/jsonschema-go v0.3.32 h1:C8XiNEoR1L0QwBAWTvRyY0xYdEmEeJYthImF9GzSST0=
github.com/swaggest/jsonschema-go v0.3.32/go.mod h1:JAF1nm+uIaMOXktuQepmkiRcgQ5yJk4Ccwx9HVt2cXw=
github.com/swaggest/jsonschema-go v0.3.33 h1:G9FxhWHdINUXUshHwHr4X2ptdZWEyBBDu53+VY94oy0=
github.com/swaggest/jsonschema-go v0.3.33/go.mod h1:JAF1nm+uIaMOXktuQepmkiRcgQ5yJk4Ccwx9HVt2cXw=
github.com/swaggest/refl v1.0.2 h1:VmP8smuDS1EzUPn31++TzMi13CAaVJdlWpIxzj0up88=
github.com/swaggest/refl v1.0.2/go.mod h1:DoiPoBJPYHU6Z9fIA6zXQ9uI6VRL6M8BFX5YFT+ym9g=
github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU=
Expand Down
9 changes: 3 additions & 6 deletions openapi3/entities.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 56 additions & 13 deletions openapi3/reflect.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package openapi3

import (
"context"
"errors"
"fmt"
"mime/multipart"
Expand Down Expand Up @@ -80,17 +81,20 @@ type OperationContext struct {
HTTPStatus int
RespContentType string
RespHeaderMapping map[string]string

ProcessingResponse bool
ProcessingIn string
}

// SetupRequest sets up operation parameters.
func (r *Reflector) SetupRequest(oc OperationContext) error {
return joinErrors(
r.parseParametersIn(oc.Operation, oc.Input, ParameterInQuery, oc.ReqQueryMapping),
r.parseParametersIn(oc.Operation, oc.Input, ParameterInPath, oc.ReqPathMapping),
r.parseParametersIn(oc.Operation, oc.Input, ParameterInCookie, oc.ReqCookieMapping),
r.parseParametersIn(oc.Operation, oc.Input, ParameterInHeader, oc.ReqHeaderMapping),
r.parseRequestBody(oc.Operation, oc.Input, tagJSON, mimeJSON, oc.HTTPMethod, nil),
r.parseRequestBody(oc.Operation, oc.Input, tagFormData, mimeFormUrlencoded, oc.HTTPMethod, oc.ReqFormDataMapping),
r.parseParametersIn(oc, ParameterInQuery, oc.ReqQueryMapping),
r.parseParametersIn(oc, ParameterInPath, oc.ReqPathMapping),
r.parseParametersIn(oc, ParameterInCookie, oc.ReqCookieMapping),
r.parseParametersIn(oc, ParameterInHeader, oc.ReqHeaderMapping),
r.parseRequestBody(oc, tagJSON, mimeJSON, oc.HTTPMethod, nil),
r.parseRequestBody(oc, tagFormData, mimeFormUrlencoded, oc.HTTPMethod, oc.ReqFormDataMapping),
)
}

Expand Down Expand Up @@ -120,8 +124,11 @@ type RequestBodyEnforcer interface {
}

func (r *Reflector) parseRequestBody(
o *Operation, input interface{}, tag, mime string, httpMethod string, mapping map[string]string,
oc OperationContext, tag, mime string, httpMethod string, mapping map[string]string,
) error {
o := oc.Operation
input := oc.Input

httpMethod = strings.ToUpper(httpMethod)
_, forceRequestBody := input.(RequestBodyEnforcer)

Expand Down Expand Up @@ -153,6 +160,7 @@ func (r *Reflector) parseRequestBody(
}

schema, err := r.Reflect(input,
r.withOperation(oc, false, "body"),
jsonschema.DefinitionsPrefix("#/components/schemas/"+definitionPrefix),
jsonschema.RootRef,
jsonschema.PropertyNameMapping(mapping),
Expand Down Expand Up @@ -217,13 +225,17 @@ const (
)

func (r *Reflector) parseParametersIn(
o *Operation, input interface{}, in ParameterIn, propertyMapping map[string]string,
oc OperationContext, in ParameterIn, propertyMapping map[string]string,
) error {
o := oc.Operation
input := oc.Input

if refl.IsSliceOrMap(input) {
return nil
}

s, err := r.Reflect(input,
r.withOperation(oc, false, string(in)),
jsonschema.DefinitionsPrefix("#/components/schemas/"),
jsonschema.CollectDefinitions(r.collectDefinition),
jsonschema.PropertyNameMapping(propertyMapping),
Expand Down Expand Up @@ -265,6 +277,7 @@ func (r *Reflector) parseParametersIn(
property := reflect.New(field.Type).Interface()
if refl.HasTaggedFields(property, tagJSON) {
propertySchema, err := r.Reflect(property,
r.withOperation(oc, false, string(in)),
jsonschema.DefinitionsPrefix("#/components/schemas/"),
jsonschema.CollectDefinitions(r.collectDefinition),
jsonschema.RootRef,
Expand All @@ -278,7 +291,9 @@ func (r *Reflector) parseParametersIn(
p.Schema = nil
p.WithContentItem("application/json", MediaType{Schema: &openapiSchema})
} else {
ps, err := r.Reflect(reflect.New(field.Type).Interface(), jsonschema.InlineRefs)
ps, err := r.Reflect(reflect.New(field.Type).Interface(),
r.withOperation(oc, false, string(in)),
jsonschema.InlineRefs)
if err != nil {
return err
}
Expand Down Expand Up @@ -339,10 +354,14 @@ func (r *Reflector) collectDefinition(name string, schema jsonschema.Schema) {
r.SpecEns().ComponentsEns().SchemasEns().WithMapOfSchemaOrRefValuesItem(name, s)
}

func (r *Reflector) parseResponseHeader(resp *Response, output interface{}, mapping map[string]string) error {
func (r *Reflector) parseResponseHeader(resp *Response, oc OperationContext) error {
output := oc.Output
mapping := oc.RespHeaderMapping

res := make(map[string]HeaderOrRef)

schema, err := r.Reflect(output,
r.withOperation(oc, true, "header"),
jsonschema.InlineRefs,
jsonschema.PropertyNameMapping(mapping),
jsonschema.PropertyNameTag("header"),
Expand Down Expand Up @@ -418,12 +437,12 @@ func (r *Reflector) SetupResponse(oc OperationContext) error {
if oc.Output != nil {
oc.RespContentType = strings.Split(oc.RespContentType, ";")[0]

err := r.parseJSONResponse(&resp, oc.Output, oc.RespContentType)
err := r.parseJSONResponse(&resp, oc)
if err != nil {
return err
}

err = r.parseResponseHeader(&resp, oc.Output, oc.RespHeaderMapping)
err = r.parseResponseHeader(&resp, oc)
if err != nil {
return err
}
Expand Down Expand Up @@ -456,13 +475,17 @@ func (r *Reflector) ensureResponseContentType(resp *Response, contentType string
}
}

func (r *Reflector) parseJSONResponse(resp *Response, output interface{}, contentType string) error {
func (r *Reflector) parseJSONResponse(resp *Response, oc OperationContext) error {
output := oc.Output
contentType := oc.RespContentType

// Check if output structure exposes meaningful schema.
if hasJSONBody, err := r.hasJSONBody(output); err == nil && !hasJSONBody {
return nil
}

schema, err := r.Reflect(output,
r.withOperation(oc, true, "body"),
jsonschema.RootRef,
jsonschema.DefinitionsPrefix("#/components/schemas/"),
jsonschema.CollectDefinitions(r.collectDefinition),
Expand Down Expand Up @@ -500,3 +523,23 @@ func (r *Reflector) parseJSONResponse(resp *Response, output interface{}, conten

return nil
}

type ocCtxKey struct{}

func (r *Reflector) withOperation(oc OperationContext, processingResponse bool, in string) func(rc *jsonschema.ReflectContext) {
return func(rc *jsonschema.ReflectContext) {
oc.ProcessingResponse = processingResponse
oc.ProcessingIn = in

rc.Context = context.WithValue(rc.Context, ocCtxKey{}, oc)
}
}

// OperationCtx retrieves operation context from reflect context.
func OperationCtx(rc *jsonschema.ReflectContext) (OperationContext, bool) {
if oc, ok := rc.Value(ocCtxKey{}).(OperationContext); ok {
return oc, true
}

return OperationContext{}, false
}
53 changes: 53 additions & 0 deletions openapi3/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io/ioutil"
"mime/multipart"
"net/http"
"reflect"
"strconv"
"testing"

Expand Down Expand Up @@ -700,3 +701,55 @@ func TestReflector_SetupRequest_noBody(t *testing.T) {
}`), oc.Operation)
}
}

func TestOperationCtx(t *testing.T) {
type req struct {
Query string `query:"query"`
Header string `header:"header"`
Cookie string `cookie:"cookie"`
Path string `path:"path"`
Body string `json:"body"`
}

type resp struct {
Header string `header:"header"`
Body string `json:"body"`
}

r := openapi3.Reflector{}
oc := openapi3.OperationContext{
Operation: &openapi3.Operation{},
Input: new(req),
Output: new(resp),
}

visited := map[string]bool{}

r.DefaultOptions = append(r.DefaultOptions, func(rc *jsonschema.ReflectContext) {
it := rc.InterceptType
rc.InterceptType = func(value reflect.Value, schema *jsonschema.Schema) (bool, error) {
if occ, ok := openapi3.OperationCtx(rc); ok {
if occ.ProcessingResponse {
visited["resp:"+occ.ProcessingIn] = true
} else {
visited["req:"+occ.ProcessingIn] = true
}
}

return it(value, schema)
}
})

require.NoError(t, r.SetupRequest(oc))
require.NoError(t, r.SetupResponse(oc))

assert.Equal(t, map[string]bool{
"req:body": true,
"req:cookie": true,
"req:header": true,
"req:path": true,
"req:query": true,
"resp:body": true,
"resp:header": true,
}, visited)
}

0 comments on commit 5ede87a

Please sign in to comment.