Skip to content

Commit

Permalink
jsonschema: rs#76: Address review commens
Browse files Browse the repository at this point in the history
  • Loading branch information
smyrman committed Jan 1, 2017
1 parent cbcec71 commit 5d54a2a
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 57 deletions.
50 changes: 40 additions & 10 deletions schema/encoding/jsonschema/all_test.go
Expand Up @@ -88,16 +88,15 @@ func (v dummyValidator) Validate(value interface{}) (interface{}, error) {
return value, nil
}

func TestErrNotImplemented(t *testing.T) {
s := schema.Schema{
Fields: schema.Fields{
"i": {
Validator: &dummyValidator{},
},
},
}
enc := jsonschema.NewEncoder(new(bytes.Buffer))
assert.Equal(t, jsonschema.ErrNotImplemented, enc.Encode(&s))
type dummyBuilder struct {
dummyValidator
}

func (f dummyBuilder) JSONSchema() (map[string]interface{}, error) {
return map[string]interface{}{
"type": "string",
"enum": []string{"this", "is", "a", "test"},
}, nil
}

// encoderTestCase is used to test the Encoder.Encode() function.
Expand Down Expand Up @@ -149,6 +148,37 @@ func fieldValidator(fieldName, expected string) encoderValidator {

func TestEncoder(t *testing.T) {
testCases := []encoderTestCase{
{
name: "Validator=&dummyValidator{}",
schema: schema.Schema{
Fields: schema.Fields{
"i": {
Validator: &dummyValidator{},
},
},
},
expectError: "not implemented",
},
{
name: "Validator=&dummyBuilder{}",
schema: schema.Schema{
Fields: schema.Fields{
"i": {
Validator: &dummyBuilder{},
},
},
},
expect: `{
"type": "object",
"additionalProperties": false,
"properties": {
"i": {
"type": "string",
"enum": ["this", "is", "a", "test"]
}
}
}`,
},
// readOnly is a custom extension to JSON Schema, also defined by the Swagger 2.0 Schema Object
// specification. See http://swagger.io/specification/#schemaObject.
{
Expand Down
8 changes: 4 additions & 4 deletions schema/encoding/jsonschema/array.go
Expand Up @@ -2,9 +2,9 @@ package jsonschema

import "github.com/rs/rest-layer/schema"

type arrayFormatter schema.Array
type arrayBuilder schema.Array

func (v arrayFormatter) JSONSchema() (map[string]interface{}, error) {
func (v arrayBuilder) JSONSchema() (map[string]interface{}, error) {
m := make(map[string]interface{}, fieldSizeHint+4)
m["type"] = "array"
if v.MinLen > 0 {
Expand All @@ -14,11 +14,11 @@ func (v arrayFormatter) JSONSchema() (map[string]interface{}, error) {
m["maxItems"] = v.MaxLen
}
if v.ValuesValidator != nil {
formatter, err := validatorFormatter(v.ValuesValidator)
builder, err := validatorBuilder(v.ValuesValidator)
if err != nil {
return nil, err
}
items, err := formatter.JSONSchema()
items, err := builder.JSONSchema()
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions schema/encoding/jsonschema/bool.go
Expand Up @@ -2,9 +2,9 @@ package jsonschema

import "github.com/rs/rest-layer/schema"

type boolFormatter schema.Bool
type boolBuilder schema.Bool

func (v boolFormatter) JSONSchema() (map[string]interface{}, error) {
func (v boolBuilder) JSONSchema() (map[string]interface{}, error) {
m := make(map[string]interface{}, fieldSizeHint+1)
m["type"] = "boolean"
return m, nil
Expand Down
12 changes: 6 additions & 6 deletions schema/encoding/jsonschema/encoder.go
Expand Up @@ -22,26 +22,26 @@ func NewEncoder(w io.Writer) *Encoder {
// Encode writes the JSON Schema representation of s to the stream, followed by a newline character.
func (e *Encoder) Encode(s *schema.Schema) error {
m := make(map[string]interface{}, schemaSizeHint)
if err := formatSchema(m, s); err != nil {
if err := addSchemaProperties(m, s); err != nil {
return err
}
enc := json.NewEncoder(e.w)
return enc.Encode(m)

}

// The Formatter interface should be implemented by custom schema.FieldValidator implementations to allow JSON Schema
// The Builder interface should be implemented by custom schema.FieldValidator implementations to allow JSON Schema
// serialization.
type Formatter interface {
type Builder interface {
// JSONSchema should return a map containing JSON Schema Draft 4 properties that can be set based on
// FieldValidator data. Application specific properties can be added as well, but should not conflict with any
// legal JSON Schema keys.
JSONSchema() (map[string]interface{}, error)
}

// formatterFunc is an adapter that allows pure functions to implement the Formatter interface.
type formatterFunc func() (map[string]interface{}, error)
// builderFunc is an adapter that allows pure functions to implement the Builder interface.
type builderFunc func() (map[string]interface{}, error)

func (f formatterFunc) JSONSchema() (map[string]interface{}, error) {
func (f builderFunc) JSONSchema() (map[string]interface{}, error) {
return f()
}
4 changes: 2 additions & 2 deletions schema/encoding/jsonschema/float.go
Expand Up @@ -6,9 +6,9 @@ import (
"github.com/rs/rest-layer/schema"
)

type floatFormatter schema.Float
type floatBuilder schema.Float

func (v floatFormatter) JSONSchema() (map[string]interface{}, error) {
func (v floatBuilder) JSONSchema() (map[string]interface{}, error) {
m := make(map[string]interface{}, fieldSizeHint+4)

m["type"] = "number"
Expand Down
4 changes: 2 additions & 2 deletions schema/encoding/jsonschema/integer.go
Expand Up @@ -2,9 +2,9 @@ package jsonschema

import "github.com/rs/rest-layer/schema"

type integerFormatter schema.Integer
type integerBuilder schema.Integer

func (v integerFormatter) JSONSchema() (map[string]interface{}, error) {
func (v integerBuilder) JSONSchema() (map[string]interface{}, error) {
m := make(map[string]interface{}, fieldSizeHint+4)

m["type"] = "integer"
Expand Down
6 changes: 3 additions & 3 deletions schema/encoding/jsonschema/object.go
Expand Up @@ -6,19 +6,19 @@ import (
"github.com/rs/rest-layer/schema"
)

type objectFormatter schema.Object
type objectBuilder schema.Object

var (
//ErrNoSchema is returned when trying to JSON Encode a schema.Object with the Schema property set to nil.
ErrNoSchema = fmt.Errorf("no schema defined for object")
)

func (v objectFormatter) JSONSchema() (map[string]interface{}, error) {
func (v objectBuilder) JSONSchema() (map[string]interface{}, error) {
m := make(map[string]interface{}, fieldSizeHint+schemaSizeHint)
if v.Schema == nil {
return nil, ErrNoSchema
}
err := formatSchema(m, v.Schema)
err := addSchemaProperties(m, v.Schema)
if err != nil {
return nil, err
}
Expand Down
47 changes: 23 additions & 24 deletions schema/encoding/jsonschema/schema.go
Expand Up @@ -16,12 +16,13 @@ var (
const (
// schemaSizeHint is the maximum number of keys that can be added to a schema map based on schema.Schema.
schemaSizeHint = 7
// fieldSizeHint is the maximum number of keys that can be added to a schema map based on schema.Field
// attributes excluding the Validator.
// fieldSizeHint is the maximum number of keys that can be added to a schema map based on all other
// schema.Field attributes than the Validator attribute. The total maximum is thus the sum of this hint and the
// maximum number of fields that can be added by the Validator itself.
fieldSizeHint = 3
)

func formatSchema(m map[string]interface{}, s *schema.Schema) (err error) {
func addSchemaProperties(m map[string]interface{}, s *schema.Schema) (err error) {
if s == nil {
return
}
Expand All @@ -37,29 +38,29 @@ func formatSchema(m map[string]interface{}, s *schema.Schema) (err error) {
m["maxProperties"] = s.MaxLen
}
if len(s.Fields) > 0 {
err = formatFields(m, s.Fields)
err = addFields(m, s.Fields)
}

return err
}

func formatFields(m map[string]interface{}, fields schema.Fields) error {
func addFields(m map[string]interface{}, fields schema.Fields) error {
props := make(map[string]interface{}, len(fields))
required := make([]string, 0, len(fields))

for key, field := range fields {
if field.Required {
required = append(required, key)
}
formatter, err := validatorFormatter(field.Validator)
builder, err := validatorBuilder(field.Validator)
if err != nil {
return err
}
fm, err := formatter.JSONSchema()
fm, err := builder.JSONSchema()
if err != nil {
return err
}
formatField(fm, field)
addFieldProperties(fm, field)
props[key] = fm
}
m["properties"] = props
Expand All @@ -71,7 +72,7 @@ func formatFields(m map[string]interface{}, fields schema.Fields) error {
return nil
}

func formatField(m map[string]interface{}, field schema.Field) {
func addFieldProperties(m map[string]interface{}, field schema.Field) {
if field.Description != "" {
m["description"] = field.Description
}
Expand All @@ -83,34 +84,32 @@ func formatField(m map[string]interface{}, field schema.Field) {
}
}

func validatorFormatter(v schema.FieldValidator) (f Formatter, err error) {
func validatorBuilder(v schema.FieldValidator) (Builder, error) {
if v == nil {
f = formatterFunc(nilFormatter)
return
return builderFunc(nilBuilder), nil
}
switch t := v.(type) {
case Formatter:
f = t
case Builder:
return t, nil
case *schema.Bool:
f = (*boolFormatter)(t)
return (*boolBuilder)(t), nil
case *schema.String:
f = (*stringFormatter)(t)
return (*stringBuilder)(t), nil
case *schema.Time:
f = (*timeFormatter)(t)
return (*timeBuilder)(t), nil
case *schema.Integer:
f = (*integerFormatter)(t)
return (*integerBuilder)(t), nil
case *schema.Float:
f = (*floatFormatter)(t)
return (*floatBuilder)(t), nil
case *schema.Array:
f = (*arrayFormatter)(t)
return (*arrayBuilder)(t), nil
case *schema.Object:
f = (*objectFormatter)(t)
return (*objectBuilder)(t), nil
default:
err = ErrNotImplemented
return nil, ErrNotImplemented
}
return
}

func nilFormatter() (map[string]interface{}, error) {
func nilBuilder() (map[string]interface{}, error) {
return make(map[string]interface{}, fieldSizeHint), nil
}
4 changes: 2 additions & 2 deletions schema/encoding/jsonschema/string.go
Expand Up @@ -2,9 +2,9 @@ package jsonschema

import "github.com/rs/rest-layer/schema"

type stringFormatter schema.String
type stringBuilder schema.String

func (v stringFormatter) JSONSchema() (map[string]interface{}, error) {
func (v stringBuilder) JSONSchema() (map[string]interface{}, error) {
m := make(map[string]interface{}, fieldSizeHint+5)
m["type"] = "string"
if v.Regexp != "" {
Expand Down
4 changes: 2 additions & 2 deletions schema/encoding/jsonschema/time.go
Expand Up @@ -2,9 +2,9 @@ package jsonschema

import "github.com/rs/rest-layer/schema"

type timeFormatter schema.Time
type timeBuilder schema.Time

func (v timeFormatter) JSONSchema() (map[string]interface{}, error) {
func (v timeBuilder) JSONSchema() (map[string]interface{}, error) {
m := make(map[string]interface{}, fieldSizeHint+2)
m["type"] = "string"
m["format"] = "date-time"
Expand Down

0 comments on commit 5d54a2a

Please sign in to comment.