Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes #53 support $ref in applyDefaults #66

Merged
merged 2 commits into from
Apr 17, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,31 @@ func convertJSONContext(jsonCtx string) context {
}

func applyDefaults(pData interface{}, schema interface{}) error {
return applyDefaultsRecursive(rootContext(), pData, schema)
return applyDefaultsRecursive(rootContext(), schema, pData, schema)
}

func applyDefaultsRecursive(ctx context, pData interface{}, schema interface{}) error {
func lookupSchema(schema interface{}, ref string) (interface{}, error) {
ref = strings.Trim(ref, "/")
parts := strings.Split(ref, "/")
if len(parts) == 0 || parts[0] != "#" {
return nil, makeError("Reference must start with root (#) '%v'", ref)
}
parts = parts[1:]
subSchema := schema
for _, part := range parts {
m, ok := subSchema.(map[string]interface{})
if !ok || m == nil {
return nil, makeError("Referenced schema is not a map '%v'", ref)
}
subSchema = m[part]
if subSchema == nil {
return nil, makeError("Referenced schema is not found '%v' in '%v'", part, ref)
}
}
return subSchema, nil
}

func applyDefaultsRecursive(ctx context, rootSchema interface{}, pData interface{}, schema interface{}) error {
if pData == nil {
return makeContextError(ctx, "Destination value must not be nil")
}
Expand All @@ -72,6 +93,19 @@ func applyDefaultsRecursive(ctx context, pData interface{}, schema interface{})
return makeContextError(ctx, "Schema section is not a map")
}

val, ok := schemaNode["$ref"]
if ok {
ref, ok := val.(string)
if !ok {
return makeContextError(ctx, "Invalid reference")
}
schema, err := lookupSchema(rootSchema, ref)
if err != nil {
return makeContextError(ctx, err.Error())
}
return applyDefaultsRecursive(ctx.add(ref), rootSchema, pData, schema)
}

for k := range schemaNode {
switch k {
case "anyOf":
Expand Down Expand Up @@ -114,7 +148,7 @@ func applyDefaultsRecursive(ctx context, pData interface{}, schema interface{})
schemaProps = props.(map[string]interface{})
for name, schemaProp := range schemaProps {
dataProp := dataProps[name]
err := applyDefaultsRecursive(ctx.add(name), &dataProp, schemaProp)
err := applyDefaultsRecursive(ctx.add(name), rootSchema, &dataProp, schemaProp)
if err != nil {
return wrapError(err, "Failed to apply defaults to object property")
}
Expand All @@ -127,7 +161,7 @@ func applyDefaultsRecursive(ctx context, pData interface{}, schema interface{})
if addProps, ok = addProps.(map[string]interface{}); ok {
for name, dataProp := range dataProps {
if schemaProps == nil || schemaProps[name] == nil {
err := applyDefaultsRecursive(ctx.add(name), &dataProp, addProps)
err := applyDefaultsRecursive(ctx.add(name), rootSchema, &dataProp, addProps)
if err != nil {
return wrapError(err, "Failed to apply defaults to additional object property")
}
Expand All @@ -149,7 +183,7 @@ func applyDefaultsRecursive(ctx context, pData interface{}, schema interface{})
if items, ok := schemaNode["items"]; ok {
schemaItem := items.(map[string]interface{})
for i, dataItem := range dataItems {
err := applyDefaultsRecursive(ctx.addInt(i), &dataItem, schemaItem)
err := applyDefaultsRecursive(ctx.addInt(i), rootSchema, &dataItem, schemaItem)
if err != nil {
return wrapError(err, "Failed to apply defaults to array item")
}
Expand Down
110 changes: 110 additions & 0 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,116 @@ func TestApplyDefaults_MissingIntFields(t *testing.T) {
assert.Equal(t, 1.0, arrObj["int"])
}

func TestApplyDefaults_Ref(t *testing.T) {
var schemaData = []byte(`
{
"type": "object",
"definitions": {
"int": { "type": "integer", "default": 1 }
},
"properties": {
"int": { "$ref": "#/definitions/int" },
"obj": { "$ref": "#" }
}
}`)
var rawData = []byte(` { "int": null, "obj": { "int": null} }`)

var data map[string]interface{}
err := JSONUnmarshal(rawData, &data)
assert.Nil(t, err)
var schema interface{}
err = JSONUnmarshal(schemaData, &schema)
assert.Nil(t, err)
err = applyDefaults(&data, schema)
assert.Nil(t, err)
assert.Equal(t, map[string]interface{}{"int": 1.0, "obj": map[string]interface{}{"int": 1.0}}, data)
}

func TestApplyDefaults_RefInvalidError(t *testing.T) {
var schemaData = []byte(`
{
"type": "object",
"properties": {
"int": { "$ref": {} }
}
}`)
var rawData = []byte(` { "int": 123 }`)

var data map[string]interface{}
err := JSONUnmarshal(rawData, &data)
assert.Nil(t, err)
var schema interface{}
err = JSONUnmarshal(schemaData, &schema)
assert.Nil(t, err)
err = applyDefaults(&data, schema)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "Invalid reference")
}

func TestApplyDefaults_RefNotMapError(t *testing.T) {
var schemaData = []byte(`
{
"type": "object",
"definitions": [],
"properties": {
"int": { "$ref": "#/definitions/int" }
}
}`)
var rawData = []byte(` { "int": 123 }`)

var data map[string]interface{}
err := JSONUnmarshal(rawData, &data)
assert.Nil(t, err)
var schema interface{}
err = JSONUnmarshal(schemaData, &schema)
assert.Nil(t, err)
err = applyDefaults(&data, schema)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "not a map")
}

func TestApplyDefaults_RefIsNullError(t *testing.T) {
var schemaData = []byte(`
{
"type": "object",
"properties": {
"int": { "$ref": "#/definitions" }
}
}`)
var rawData = []byte(` { "int": 123 }`)

var data map[string]interface{}
err := JSONUnmarshal(rawData, &data)
assert.Nil(t, err)
var schema interface{}
err = JSONUnmarshal(schemaData, &schema)
assert.Nil(t, err)
err = applyDefaults(&data, schema)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "schema is not found")
}

func TestApplyDefaults_RefNoRootError(t *testing.T) {
var schemaData = []byte(`
{
"type": "object",
"properties": {
"int": { "$ref": "definitions/int" }
}
}`)
var rawData = []byte(` { "int": 123 }`)

var data map[string]interface{}
err := JSONUnmarshal(rawData, &data)
assert.Nil(t, err)
var schema interface{}
err = JSONUnmarshal(schemaData, &schema)
assert.Nil(t, err)
err = applyDefaults(&data, schema)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "must start with root")
}

// -----------

var testSchemaData = []byte(`
Expand Down