Skip to content

Commit

Permalink
Allow inlining definitions by type (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop authored Nov 11, 2022
1 parent ddafeed commit 14d342f
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 11 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/cloc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ jobs:
- name: Count Lines Of Code
id: loc
run: |
curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.2/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz && echo "b17e76bede22af0206b4918d3b3c4e7357f2a21b57f8de9e7c9dc0eb56b676c0 sccdiff" | shasum -c
curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.3/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz
sccdiff_hash=$(git hash-object ./sccdiff)
[ "$sccdiff_hash" == "ae8a07b687bd3dba60861584efe724351aa7ff63" ] || (echo "::error::unexpected hash for sccdiff, possible tampering: $sccdiff_hash" && exit 1)
OUTPUT=$(cd pr && ../sccdiff -basedir ../base)
echo "${OUTPUT}"
OUTPUT="${OUTPUT//$'\n'/%0A}"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:
go-version: 1.19.x
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v3.1.0
uses: golangci/golangci-lint-action@v3.2.0
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.49.0
version: v1.50.0

# Optional: working directory, useful for monorepos
# working-directory: somedir
Expand Down
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ linters:
- ireturn
- exhaustruct
- nonamedreturns
- nosnakecase
- structcheck
- varcheck
- deadcode

issues:
exclude:
Expand All @@ -67,5 +71,6 @@ issues:
path: "_test.go"
- linters:
- errcheck # Error checking omitted for brevity.
- errchkjson
path: "example_"

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
GOLANGCI_LINT_VERSION := "v1.49.0" # Optional configuration to pinpoint golangci-lint version.
#GOLANGCI_LINT_VERSION := "v1.50.0" # Optional configuration to pinpoint golangci-lint version.

# The head of Makefile determines location of dev-go to include standard targets.
GO ?= go
Expand Down
105 changes: 105 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,111 @@ func (r *Resp) PrepareJSONSchema(s *jsonschema.Schema) error {
return nil
}

func ExampleReflector_InlineDefinition() {
reflector := jsonschema.Reflector{}

// Create custom schema mapping for 3rd party type.
uuidDef := jsonschema.Schema{}
uuidDef.AddType(jsonschema.String)
uuidDef.WithFormat("uuid")
uuidDef.WithExamples("248df4b7-aa70-47b8-a036-33ac447e668d")

// Map 3rd party type with your own schema.
reflector.AddTypeMapping(UUID{}, uuidDef)
reflector.InlineDefinition(UUID{})

type MyStruct struct {
ID UUID `json:"id"`
}

schema, _ := reflector.Reflect(MyStruct{})

schemaJSON, _ := json.MarshalIndent(schema, "", " ")

fmt.Println(string(schemaJSON))
// Output:
// {
// "properties": {
// "id": {
// "examples": [
// "248df4b7-aa70-47b8-a036-33ac447e668d"
// ],
// "type": "string",
// "format": "uuid"
// }
// },
// "type": "object"
// }
}

func ExampleReflector_AddTypeMapping_schema() {
reflector := jsonschema.Reflector{}

// Create custom schema mapping for 3rd party type.
uuidDef := jsonschema.Schema{}
uuidDef.AddType(jsonschema.String)
uuidDef.WithFormat("uuid")
uuidDef.WithExamples("248df4b7-aa70-47b8-a036-33ac447e668d")

// Map 3rd party type with your own schema.
reflector.AddTypeMapping(UUID{}, uuidDef)

type MyStruct struct {
ID UUID `json:"id"`
}

schema, _ := reflector.Reflect(MyStruct{})

schemaJSON, _ := json.MarshalIndent(schema, "", " ")

fmt.Println(string(schemaJSON))
// Output:
// {
// "definitions": {
// "JsonschemaGoTestUUID": {
// "examples": [
// "248df4b7-aa70-47b8-a036-33ac447e668d"
// ],
// "type": "string",
// "format": "uuid"
// }
// },
// "properties": {
// "id": {
// "$ref": "#/definitions/JsonschemaGoTestUUID"
// }
// },
// "type": "object"
// }
}

func ExampleReflector_AddTypeMapping_type() {
reflector := jsonschema.Reflector{}

// Map 3rd party type with a different type.
// Reflector will perceive all UUIDs as plain strings.
reflector.AddTypeMapping(UUID{}, "")

type MyStruct struct {
ID UUID `json:"id"`
}

schema, _ := reflector.Reflect(MyStruct{})

schemaJSON, _ := json.MarshalIndent(schema, "", " ")

fmt.Println(string(schemaJSON))
// Output:
// {
// "properties": {
// "id": {
// "type": "string"
// }
// },
// "type": "object"
// }
}

func ExampleReflector_Reflect() {
reflector := jsonschema.Reflector{}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/swaggest/jsonschema-go
go 1.17

require (
github.com/bool64/dev v0.2.19
github.com/bool64/dev v0.2.21
github.com/stretchr/testify v1.8.0
github.com/swaggest/assertjson v1.7.0
github.com/swaggest/refl v1.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ github.com/bool64/dev v0.2.5/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zR
github.com/bool64/dev v0.2.10/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
github.com/bool64/dev v0.2.16/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
github.com/bool64/dev v0.2.17/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/bool64/dev v0.2.19 h1:s++kaqTDpAJ53JJuCZr0up64tpjiMJFDJYRWZEYaIxc=
github.com/bool64/dev v0.2.19/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/bool64/dev v0.2.21 h1:BmjWiEalJNy1RNzef/U/8kppG81f7ZSXkdSWUpoEwK4=
github.com/bool64/dev v0.2.21/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/bool64/shared v0.1.4/go.mod h1:ryGjsnQFh6BnEXClfVlEJrzjwzat7CmA8PNS5E+jPp0=
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
Expand Down
14 changes: 13 additions & 1 deletion helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ func (s Schema) JSONSchemaBytes() ([]byte, error) {
return json.Marshal(s)
}

// ToSimpleMap encodes JSON Schema as generic map.
// ToSimpleMap encodes JSON Schema as a map.
func (s SchemaOrBool) ToSimpleMap() (map[string]interface{}, error) {
var m map[string]interface{}

Expand All @@ -297,3 +297,15 @@ func (s SchemaOrBool) ToSimpleMap() (map[string]interface{}, error) {

return m, nil
}

// FromSimpleMap decodes JSON Schema from a map.
func (s *SchemaOrBool) FromSimpleMap(m map[string]interface{}) error {
j, err := json.Marshal(m)
if err != nil {
return err
}

s.TypeBoolean = nil

return json.Unmarshal(j, s.TypeObjectEns())
}
8 changes: 8 additions & 0 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ func TestSchemaOrBool_JSONSchemaBytes(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, map[string]interface{}{"type": "string"}, m)

var sb jsonschema.SchemaOrBool

sb.WithTypeBoolean(true)
require.NoError(t, sb.FromSimpleMap(m))
assert.Nil(t, sb.TypeBoolean)
require.NotNil(t, sb.TypeObject)
assert.True(t, sb.TypeObject.HasType(jsonschema.String))

sbf := jsonschema.SchemaOrBool{}
sbf.WithTypeBoolean(false)
m, err = sbf.ToSimpleMap()
Expand Down
24 changes: 21 additions & 3 deletions reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,15 @@ func (r Ref) Schema() Schema {

// Reflector creates JSON Schemas from Go values.
type Reflector struct {
DefaultOptions []func(*ReflectContext)
typesMap map[reflect.Type]interface{}
defNames map[reflect.Type]string
DefaultOptions []func(*ReflectContext)
typesMap map[reflect.Type]interface{}
inlineDefinition map[refl.TypeString]bool
defNames map[reflect.Type]string
}

// AddTypeMapping creates substitution link between types of src and dst when reflecting JSON Schema.
//
// A configured Schema instance can also be used as dst.
func (r *Reflector) AddTypeMapping(src, dst interface{}) {
if r.typesMap == nil {
r.typesMap = map[reflect.Type]interface{}{}
Expand All @@ -90,6 +93,17 @@ func (r *Reflector) AddTypeMapping(src, dst interface{}) {
r.typesMap[refl.DeepIndirect(reflect.TypeOf(src))] = dst
}

// InlineDefinition enables schema inlining for a type of given sample.
//
// Inlined schema is used instead of a reference to a shared definition.
func (r *Reflector) InlineDefinition(sample interface{}) {
if r.inlineDefinition == nil {
r.inlineDefinition = map[refl.TypeString]bool{}
}

r.inlineDefinition[refl.GoType(refl.DeepIndirect(reflect.TypeOf(sample)))] = true
}

// InterceptDefName allows modifying reflected definition names.
func (r *Reflector) InterceptDefName(f func(t reflect.Type, defaultDefName string) string) {
r.DefaultOptions = append(r.DefaultOptions, func(rc *ReflectContext) {
Expand Down Expand Up @@ -279,6 +293,10 @@ func (r *Reflector) reflectDefer(defName string, typeString refl.TypeString, rc
return schema
}

if r.inlineDefinition[typeString] {
return schema
}

if !rc.RootRef && len(rc.Path) == 0 {
return schema
}
Expand Down
31 changes: 31 additions & 0 deletions reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1340,3 +1340,34 @@ func TestReflector_Reflect_namedSlice(t *testing.T) {
"type":"object"
}`), schema)
}

func TestReflector_Reflect_uuid(t *testing.T) {
reflector := jsonschema.Reflector{}

// Create custom schema mapping for 3rd party type.
uuidDef := jsonschema.Schema{}
uuidDef.AddType(jsonschema.String)
uuidDef.WithFormat("uuid")
uuidDef.WithExamples("248df4b7-aa70-47b8-a036-33ac447e668d")

// Map 3rd party type with your own schema.
reflector.AddTypeMapping(UUID{}, uuidDef)
reflector.InlineDefinition(UUID{})

type MyStruct struct {
ID UUID `json:"uuid"`
}

s, err := reflector.Reflect(MyStruct{})
require.NoError(t, err)

assertjson.EqualMarshal(t, []byte(`{
"properties":{
"uuid":{
"examples":["248df4b7-aa70-47b8-a036-33ac447e668d"],"type":"string",
"format":"uuid"
}
},
"type":"object"
}`), s)
}

0 comments on commit 14d342f

Please sign in to comment.