Skip to content

Commit

Permalink
Optionally expose embedded structs with allOf
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop committed Feb 23, 2024
1 parent 04e0738 commit d8fe4c9
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 1 deletion.
23 changes: 22 additions & 1 deletion reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var (
typeOfTextMarshaler = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
typeOfEmptyInterface = reflect.TypeOf((*interface{})(nil)).Elem()
typeOfSchemaInliner = reflect.TypeOf((*SchemaInliner)(nil)).Elem()
typeOfEmbedReferencer = reflect.TypeOf((*EmbedReferencer)(nil)).Elem()
)

const (
Expand All @@ -47,6 +48,11 @@ type SchemaInliner interface {
InlineJSONSchema()
}

// EmbedReferencer is a marker interface to enable reference to embedded struct type.
type EmbedReferencer interface {
ReferEmbedded()
}

// IgnoreTypeName instructs reflector to keep original type name during mapping.
func (s Schema) IgnoreTypeName() {}

Expand Down Expand Up @@ -261,6 +267,10 @@ func checkSchemaSetup(params InterceptSchemaParams) (bool, error) {
// ProcessWithoutTags
// SkipEmbeddedMapsSlices
// SkipUnsupportedProperties
//
// Fields from embedded structures are processed as if they were defined in the root structure.
// Alternatively, if embedded structure has a field tag `refer:"true"` or implements EmbedReferencer,
// its reference will be added to `allOf` of the parent schema.
func (r *Reflector) Reflect(i interface{}, options ...func(rc *ReflectContext)) (Schema, error) {
rc := ReflectContext{}
rc.Context = context.Background()
Expand Down Expand Up @@ -914,7 +924,18 @@ func (r *Reflector) walkProperties(v reflect.Value, parent *Schema, rc *ReflectC

if tag == "" && field.Anonymous &&

Check failure on line 925 in reflect.go

View workflow job for this annotation

GitHub Actions / golangci-lint

`if tag == "" && field.Anonymous &&
(field.Type.Kind() == reflect.Struct || deepIndirect.Kind() == reflect.Struct) {

Check failure on line 926 in reflect.go

View workflow job for this annotation

GitHub Actions / golangci-lint

unnecessary leading newline (whitespace)
if err := r.walkProperties(values[i], parent, rc); err != nil {

forceReference := (field.Type.Implements(typeOfEmbedReferencer) && field.Tag.Get("refer") == "") ||
field.Tag.Get("refer") == "true"

if forceReference {
rc.Path = append(rc.Path, "")
if s, err := r.reflect(values[i].Interface(), rc, false, parent); err != nil {
return err

Check warning on line 934 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L934

Added line #L934 was not covered by tests
} else {

Check warning on line 935 in reflect.go

View workflow job for this annotation

GitHub Actions / golangci-lint

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)

Check notice on line 935 in reflect.go

View workflow job for this annotation

GitHub Actions / test (1.22.x)

1 statement(s) on lines 933:935 are not covered by tests.
parent.AllOf = append(parent.AllOf, s.ToSchemaOrBool())
}
} else if err := r.walkProperties(values[i], parent, rc); err != nil {
return err
}

Expand Down
35 changes: 35 additions & 0 deletions reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1827,3 +1827,38 @@ func TestReflector_Reflect_multipleTags(t *testing.T) {
"type":"object"
}`, s)
}

func TestReflector_Reflect_embedded(t *testing.T) {
type A struct {
FieldA int `json:"field_a"`
}

type C struct {
jsonschema.EmbedReferencer
FieldC int `json:"field_c"`
}

type B struct {
A `refer:"true"`
FieldB int `json:"field_b"`
C
}

r := jsonschema.Reflector{}

s, err := r.Reflect(B{}, jsonschema.InterceptProp(func(params jsonschema.InterceptPropParams) error {

Check warning on line 1849 in reflect_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

unused-parameter: parameter 'params' seems to be unused, consider removing or renaming it as _ (revive)
return nil
}))
require.NoError(t, err)
assertjson.EqMarshal(t, `{
"definitions":{
"JsonschemaGoTestA":{"properties":{"field_a":{"type":"integer"}},"type":"object"},
"JsonschemaGoTestC":{"properties":{"field_c":{"type":"integer"}},"type":"object"}
},
"properties":{"field_b":{"type":"integer"}},"type":"object",
"allOf":[
{"$ref":"#/definitions/JsonschemaGoTestA"},
{"$ref":"#/definitions/JsonschemaGoTestC"}
]
}`, s)
}

0 comments on commit d8fe4c9

Please sign in to comment.