Skip to content

Commit

Permalink
Fix handling of embedded virtual struct (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop authored Aug 26, 2023
1 parent 34ecc32 commit bc13a7d
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ linters-settings:
check-type-assertions: true
check-blank: true
gocyclo:
min-complexity: 40
min-complexity: 45
dupl:
threshold: 100
misspell:
Expand Down
9 changes: 2 additions & 7 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,15 +564,10 @@ func ExampleReflector_Reflect_virtualStruct() {
// "properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"
// }
// Nested: {
// "definitions":{
// "TestStruct":{
// "title":"Test title","description":"Test description",
// "properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"
// }
// },
// "title":"Test title","description":"Test description",
// "properties":{
// "bar":{"type":"integer"},"foo":{"minLength":3,"type":"string"},
// "nested":{"$ref":"#/definitions/TestStruct"}
// "nested":{"$ref":"#"}
// },
// "type":"object"
// }
Expand Down
6 changes: 3 additions & 3 deletions reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,8 @@ func (r *Reflector) reflect(i interface{}, rc *ReflectContext, keepType bool, pa
defName string
)

if st, ok := i.(Struct); ok {
s = &st
if st, ok := i.(withStruct); ok {
s = st.structPtr()
}

defer func() {
Expand Down Expand Up @@ -421,7 +421,7 @@ func (r *Reflector) reflect(i interface{}, rc *ReflectContext, keepType bool, pa
defName, typeString = s.names()
}

if mappedTo, found := r.typesMap[t]; found {
if mappedTo, found := r.typesMap[t]; found && s == nil {
t = refl.DeepIndirect(reflect.TypeOf(mappedTo))
v = reflect.ValueOf(mappedTo)

Expand Down
10 changes: 9 additions & 1 deletion struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ func (s *Struct) SetDescription(description string) {
s.Description = &description
}

func (s *Struct) names() (string, refl.TypeString) {
type withStruct interface {
structPtr() *Struct
}

func (s Struct) structPtr() *Struct {
return &s
}

func (s Struct) names() (string, refl.TypeString) {
defName := s.DefName

if defName == "" {
Expand Down
145 changes: 144 additions & 1 deletion struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestReflector_Reflect_Struct(t *testing.T) {

s2 := jsonschema.Struct{}
s2.SetTitle("T2")
s2.DefName = "TestStruct2"

s2.Fields = append(s2.Fields, jsonschema.Field{
Name: "Quux",
Expand All @@ -62,6 +63,14 @@ func TestReflector_Reflect_Struct(t *testing.T) {
Tag: `json:"other"`,
})

s2.DefName = ""

s.Fields = append(s.Fields, jsonschema.Field{
Name: "Another",
Value: s2,
Tag: `json:"another"`,
})

sc, err := r.Reflect(s)
require.NoError(t, err)

Expand All @@ -85,19 +94,153 @@ func TestReflector_Reflect_Struct(t *testing.T) {
},
"type":"object"
},
"TestStruct2":{
"title":"T2","properties":{"quux":{"minLength":3,"type":"string"}},
"type":"object"
},
"struct1":{
"title":"T2","properties":{"quux":{"minLength":3,"type":"string"}},
"type":"object"
}
},
"properties":{
"another":{"$ref":"#/definitions/struct1"},
"b4r":{"minimum":3,"type":"integer"},
"b4z":{"items":{"type":"integer"},"minItems":4,"type":["array","null"]},
"fo0":{"minLength":3,"type":"string"},
"other":{"$ref":"#/definitions/struct1"},
"other":{"$ref":"#/definitions/TestStruct2"},
"pers":{"$ref":"#/definitions/JsonschemaGoTestPerson"},
"recursion":{"$ref":"#"}
},
"type":"object"
}`, sc)
}

func TestReflector_Reflect_StructEmbed(t *testing.T) {
type dynamicInput struct {
jsonschema.Struct

// Type is a static field example.
Type string `query:"type"`
}

type dynamicOutput struct {
// Embedded jsonschema.Struct exposes dynamic fields for documentation.
jsonschema.Struct

jsonFields map[string]interface{}
headerFields map[string]string

// Status is a static field example.
Status string `json:"status"`
}

dynIn := dynamicInput{}
dynIn.DefName = "DynIn123"
dynIn.Struct.Fields = []jsonschema.Field{
{Name: "Foo", Value: 123, Tag: `header:"foo" enum:"123,456,789"`},
{Name: "Bar", Value: "abc", Tag: `query:"bar"`},
}

dynOut := dynamicOutput{}
dynOut.DefName = "DynOut123"
dynOut.Struct.Fields = []jsonschema.Field{
{Name: "Foo", Value: 123, Tag: `header:"foo" enum:"123,456,789"`},
{Name: "Bar", Value: "abc", Tag: `json:"bar"`},
}

type S struct {
In dynamicInput `json:"in"`
Out dynamicOutput `json:"out"`
}

s := S{
In: dynIn,
Out: dynOut,
}

r := jsonschema.Reflector{}

ss, err := r.Reflect(s, func(rc *jsonschema.ReflectContext) {
rc.PropertyNameTag = "json"
rc.PropertyNameAdditionalTags = []string{"header", "query"}
})
require.NoError(t, err)

assertjson.EqMarshal(t, `{
"definitions":{
"DynIn123":{
"properties":{
"bar":{"type":"string"},
"foo":{"enum":["123","456","789"],"type":"integer"},
"type":{"type":"string"}
},
"type":"object"
},
"DynOut123":{
"properties":{
"bar":{"type":"string"},
"foo":{"enum":["123","456","789"],"type":"integer"},
"status":{"type":"string"}
},
"type":"object"
}
},
"properties":{
"in":{"$ref":"#/definitions/DynIn123"},
"out":{"$ref":"#/definitions/DynOut123"}
},
"type":"object"
}`, ss)
}

func TestReflector_Reflect_StructExample(t *testing.T) {
s := jsonschema.Struct{}
s.SetTitle("Test title")
s.SetDescription("Test description")
s.DefName = "TestStruct"
s.Nullable = true

s.Fields = append(s.Fields, jsonschema.Field{
Name: "Foo",
Value: "abc",
Tag: `json:"foo" minLength:"3"`,
})

r := jsonschema.Reflector{}

t.Run("standalone", func(t *testing.T) {
schema, err := r.Reflect(s)
require.NoError(t, err)

assertjson.EqMarshal(t, `{
"title":"Test title","description":"Test description",
"properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"
}`, schema)
})

type MyStruct struct {
jsonschema.Struct // Can be structPtr.

Bar int `json:"bar"`

Nested jsonschema.Struct `json:"nested"` // Can be nested.
}

ms := MyStruct{}
ms.Nested = s
ms.Struct = s

t.Run("nested", func(t *testing.T) {
schema, err := r.Reflect(ms)
require.NoError(t, err)
assertjson.EqMarshal(t, `{
"title":"Test title","description":"Test description",
"properties":{
"bar":{"type":"integer"},"foo":{"minLength":3,"type":"string"},
"nested":{"$ref":"#"}
},
"type":"object"
}`, schema)
})
}

0 comments on commit bc13a7d

Please sign in to comment.