Skip to content

Commit 5ede87a

Browse files
authored
Expose OperationContext in reflect context hooks (#34)
1 parent d01f6e1 commit 5ede87a

File tree

5 files changed

+115
-22
lines changed

5 files changed

+115
-22
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/bool64/dev v0.2.10
77
github.com/stretchr/testify v1.7.1
88
github.com/swaggest/assertjson v1.6.8
9-
github.com/swaggest/jsonschema-go v0.3.32
9+
github.com/swaggest/jsonschema-go v0.3.33
1010
github.com/swaggest/refl v1.0.2
1111
gopkg.in/yaml.v2 v2.4.0
1212
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
5656
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
5757
github.com/swaggest/assertjson v1.6.8 h1:1O/9UI5M+2OJI7BeEWKGj0wTvpRXZt5FkOJ4nRkY4rA=
5858
github.com/swaggest/assertjson v1.6.8/go.mod h1:Euf0upn9Vlaf1/llYHTs+Kx5K3vVbpMbsZhth7zlN7M=
59-
github.com/swaggest/jsonschema-go v0.3.32 h1:C8XiNEoR1L0QwBAWTvRyY0xYdEmEeJYthImF9GzSST0=
60-
github.com/swaggest/jsonschema-go v0.3.32/go.mod h1:JAF1nm+uIaMOXktuQepmkiRcgQ5yJk4Ccwx9HVt2cXw=
59+
github.com/swaggest/jsonschema-go v0.3.33 h1:G9FxhWHdINUXUshHwHr4X2ptdZWEyBBDu53+VY94oy0=
60+
github.com/swaggest/jsonschema-go v0.3.33/go.mod h1:JAF1nm+uIaMOXktuQepmkiRcgQ5yJk4Ccwx9HVt2cXw=
6161
github.com/swaggest/refl v1.0.2 h1:VmP8smuDS1EzUPn31++TzMi13CAaVJdlWpIxzj0up88=
6262
github.com/swaggest/refl v1.0.2/go.mod h1:DoiPoBJPYHU6Z9fIA6zXQ9uI6VRL6M8BFX5YFT+ym9g=
6363
github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU=

openapi3/entities.go

Lines changed: 3 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

openapi3/reflect.go

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package openapi3
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
67
"mime/multipart"
@@ -80,17 +81,20 @@ type OperationContext struct {
8081
HTTPStatus int
8182
RespContentType string
8283
RespHeaderMapping map[string]string
84+
85+
ProcessingResponse bool
86+
ProcessingIn string
8387
}
8488

8589
// SetupRequest sets up operation parameters.
8690
func (r *Reflector) SetupRequest(oc OperationContext) error {
8791
return joinErrors(
88-
r.parseParametersIn(oc.Operation, oc.Input, ParameterInQuery, oc.ReqQueryMapping),
89-
r.parseParametersIn(oc.Operation, oc.Input, ParameterInPath, oc.ReqPathMapping),
90-
r.parseParametersIn(oc.Operation, oc.Input, ParameterInCookie, oc.ReqCookieMapping),
91-
r.parseParametersIn(oc.Operation, oc.Input, ParameterInHeader, oc.ReqHeaderMapping),
92-
r.parseRequestBody(oc.Operation, oc.Input, tagJSON, mimeJSON, oc.HTTPMethod, nil),
93-
r.parseRequestBody(oc.Operation, oc.Input, tagFormData, mimeFormUrlencoded, oc.HTTPMethod, oc.ReqFormDataMapping),
92+
r.parseParametersIn(oc, ParameterInQuery, oc.ReqQueryMapping),
93+
r.parseParametersIn(oc, ParameterInPath, oc.ReqPathMapping),
94+
r.parseParametersIn(oc, ParameterInCookie, oc.ReqCookieMapping),
95+
r.parseParametersIn(oc, ParameterInHeader, oc.ReqHeaderMapping),
96+
r.parseRequestBody(oc, tagJSON, mimeJSON, oc.HTTPMethod, nil),
97+
r.parseRequestBody(oc, tagFormData, mimeFormUrlencoded, oc.HTTPMethod, oc.ReqFormDataMapping),
9498
)
9599
}
96100

@@ -120,8 +124,11 @@ type RequestBodyEnforcer interface {
120124
}
121125

122126
func (r *Reflector) parseRequestBody(
123-
o *Operation, input interface{}, tag, mime string, httpMethod string, mapping map[string]string,
127+
oc OperationContext, tag, mime string, httpMethod string, mapping map[string]string,
124128
) error {
129+
o := oc.Operation
130+
input := oc.Input
131+
125132
httpMethod = strings.ToUpper(httpMethod)
126133
_, forceRequestBody := input.(RequestBodyEnforcer)
127134

@@ -153,6 +160,7 @@ func (r *Reflector) parseRequestBody(
153160
}
154161

155162
schema, err := r.Reflect(input,
163+
r.withOperation(oc, false, "body"),
156164
jsonschema.DefinitionsPrefix("#/components/schemas/"+definitionPrefix),
157165
jsonschema.RootRef,
158166
jsonschema.PropertyNameMapping(mapping),
@@ -217,13 +225,17 @@ const (
217225
)
218226

219227
func (r *Reflector) parseParametersIn(
220-
o *Operation, input interface{}, in ParameterIn, propertyMapping map[string]string,
228+
oc OperationContext, in ParameterIn, propertyMapping map[string]string,
221229
) error {
230+
o := oc.Operation
231+
input := oc.Input
232+
222233
if refl.IsSliceOrMap(input) {
223234
return nil
224235
}
225236

226237
s, err := r.Reflect(input,
238+
r.withOperation(oc, false, string(in)),
227239
jsonschema.DefinitionsPrefix("#/components/schemas/"),
228240
jsonschema.CollectDefinitions(r.collectDefinition),
229241
jsonschema.PropertyNameMapping(propertyMapping),
@@ -265,6 +277,7 @@ func (r *Reflector) parseParametersIn(
265277
property := reflect.New(field.Type).Interface()
266278
if refl.HasTaggedFields(property, tagJSON) {
267279
propertySchema, err := r.Reflect(property,
280+
r.withOperation(oc, false, string(in)),
268281
jsonschema.DefinitionsPrefix("#/components/schemas/"),
269282
jsonschema.CollectDefinitions(r.collectDefinition),
270283
jsonschema.RootRef,
@@ -278,7 +291,9 @@ func (r *Reflector) parseParametersIn(
278291
p.Schema = nil
279292
p.WithContentItem("application/json", MediaType{Schema: &openapiSchema})
280293
} else {
281-
ps, err := r.Reflect(reflect.New(field.Type).Interface(), jsonschema.InlineRefs)
294+
ps, err := r.Reflect(reflect.New(field.Type).Interface(),
295+
r.withOperation(oc, false, string(in)),
296+
jsonschema.InlineRefs)
282297
if err != nil {
283298
return err
284299
}
@@ -339,10 +354,14 @@ func (r *Reflector) collectDefinition(name string, schema jsonschema.Schema) {
339354
r.SpecEns().ComponentsEns().SchemasEns().WithMapOfSchemaOrRefValuesItem(name, s)
340355
}
341356

342-
func (r *Reflector) parseResponseHeader(resp *Response, output interface{}, mapping map[string]string) error {
357+
func (r *Reflector) parseResponseHeader(resp *Response, oc OperationContext) error {
358+
output := oc.Output
359+
mapping := oc.RespHeaderMapping
360+
343361
res := make(map[string]HeaderOrRef)
344362

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

421-
err := r.parseJSONResponse(&resp, oc.Output, oc.RespContentType)
440+
err := r.parseJSONResponse(&resp, oc)
422441
if err != nil {
423442
return err
424443
}
425444

426-
err = r.parseResponseHeader(&resp, oc.Output, oc.RespHeaderMapping)
445+
err = r.parseResponseHeader(&resp, oc)
427446
if err != nil {
428447
return err
429448
}
@@ -456,13 +475,17 @@ func (r *Reflector) ensureResponseContentType(resp *Response, contentType string
456475
}
457476
}
458477

459-
func (r *Reflector) parseJSONResponse(resp *Response, output interface{}, contentType string) error {
478+
func (r *Reflector) parseJSONResponse(resp *Response, oc OperationContext) error {
479+
output := oc.Output
480+
contentType := oc.RespContentType
481+
460482
// Check if output structure exposes meaningful schema.
461483
if hasJSONBody, err := r.hasJSONBody(output); err == nil && !hasJSONBody {
462484
return nil
463485
}
464486

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

501524
return nil
502525
}
526+
527+
type ocCtxKey struct{}
528+
529+
func (r *Reflector) withOperation(oc OperationContext, processingResponse bool, in string) func(rc *jsonschema.ReflectContext) {
530+
return func(rc *jsonschema.ReflectContext) {
531+
oc.ProcessingResponse = processingResponse
532+
oc.ProcessingIn = in
533+
534+
rc.Context = context.WithValue(rc.Context, ocCtxKey{}, oc)
535+
}
536+
}
537+
538+
// OperationCtx retrieves operation context from reflect context.
539+
func OperationCtx(rc *jsonschema.ReflectContext) (OperationContext, bool) {
540+
if oc, ok := rc.Value(ocCtxKey{}).(OperationContext); ok {
541+
return oc, true
542+
}
543+
544+
return OperationContext{}, false
545+
}

openapi3/reflect_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io/ioutil"
66
"mime/multipart"
77
"net/http"
8+
"reflect"
89
"strconv"
910
"testing"
1011

@@ -700,3 +701,55 @@ func TestReflector_SetupRequest_noBody(t *testing.T) {
700701
}`), oc.Operation)
701702
}
702703
}
704+
705+
func TestOperationCtx(t *testing.T) {
706+
type req struct {
707+
Query string `query:"query"`
708+
Header string `header:"header"`
709+
Cookie string `cookie:"cookie"`
710+
Path string `path:"path"`
711+
Body string `json:"body"`
712+
}
713+
714+
type resp struct {
715+
Header string `header:"header"`
716+
Body string `json:"body"`
717+
}
718+
719+
r := openapi3.Reflector{}
720+
oc := openapi3.OperationContext{
721+
Operation: &openapi3.Operation{},
722+
Input: new(req),
723+
Output: new(resp),
724+
}
725+
726+
visited := map[string]bool{}
727+
728+
r.DefaultOptions = append(r.DefaultOptions, func(rc *jsonschema.ReflectContext) {
729+
it := rc.InterceptType
730+
rc.InterceptType = func(value reflect.Value, schema *jsonschema.Schema) (bool, error) {
731+
if occ, ok := openapi3.OperationCtx(rc); ok {
732+
if occ.ProcessingResponse {
733+
visited["resp:"+occ.ProcessingIn] = true
734+
} else {
735+
visited["req:"+occ.ProcessingIn] = true
736+
}
737+
}
738+
739+
return it(value, schema)
740+
}
741+
})
742+
743+
require.NoError(t, r.SetupRequest(oc))
744+
require.NoError(t, r.SetupResponse(oc))
745+
746+
assert.Equal(t, map[string]bool{
747+
"req:body": true,
748+
"req:cookie": true,
749+
"req:header": true,
750+
"req:path": true,
751+
"req:query": true,
752+
"resp:body": true,
753+
"resp:header": true,
754+
}, visited)
755+
}

0 commit comments

Comments
 (0)