Skip to content

Support query on dict/object values (breaking change!) #151

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

Merged
merged 2 commits into from
Nov 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The REST Layer framework is composed of several sub-packages:

# Documentation

- [Breaking Changes](#breaking-changes)
- [Features](#features)
- [Extensions](#extensions)
- [Storage Handlers](#storage-handlers)
Expand Down Expand Up @@ -64,6 +65,16 @@ The REST Layer framework is composed of several sub-packages:
- [Hystrix](#hystrix)
- [JSONSchema](#jsonschema)

## Breaking Changes

Until version 1.0 of rest-layer, breaking changes may occur at any time if you rely on the latest master version.

Below is an overview over recent breaking changes, starting from an arbitrary point with PR #151:

- PR #151: `ValuesValidator FieldValidator` attribute in the `scheam.Dict` struct replaced by `Values Field`.

From the next release and onwards (0.2), this list will summarize breaking changes done to master since the last release.

## Features

- [x] Automatic handling of REST resource operations
Expand Down
23 changes: 17 additions & 6 deletions schema/dict.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"fmt"
)

// Dict validates array values
// Dict validates objects with variadic keys.
type Dict struct {
// KeysValidator is the validator to apply on dict keys.
KeysValidator FieldValidator
// ValuesValidator is the validator to apply on dict values.
ValuesValidator FieldValidator

// Values describes the properties for each dict value.
Values Field
// MinLen defines the minimum number of fields (default 0).
MinLen int
// MaxLen defines the maximum number of fields (default no limit).
Expand All @@ -23,8 +24,10 @@ func (v *Dict) Compile(rc ReferenceChecker) (err error) {
if err = c.Compile(rc); err != nil {
return
}

}
if c, ok := v.ValuesValidator.(Compiler); ok {

if c, ok := v.Values.Validator.(Compiler); ok {
if err = c.Compile(rc); err != nil {
return
}
Expand All @@ -49,9 +52,9 @@ func (v Dict) Validate(value interface{}) (interface{}, error) {
return nil, errors.New("key validator does not return string")
}
}
if v.ValuesValidator != nil {
if v.Values.Validator != nil {
var err error
val, err = v.ValuesValidator.Validate(val)
val, err = v.Values.Validator.Validate(val)
if err != nil {
return nil, fmt.Errorf("invalid value for key `%s': %s", key, err)
}
Expand All @@ -67,3 +70,11 @@ func (v Dict) Validate(value interface{}) (interface{}, error) {
}
return dest, nil
}

// GetField implements the FieldGetter interface.
func (v Dict) GetField(name string) *Field {
if _, err := v.KeysValidator.Validate(name); err != nil {
return nil
}
return &v.Values
}
8 changes: 5 additions & 3 deletions schema/dict_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ func ExampleDict() {
Allowed: []string{"foo", "bar"},
},
// Allow either string or integer as dict value
ValuesValidator: &schema.AnyOf{
0: &schema.String{},
1: &schema.Integer{},
Values: schema.Field{
Validator: &schema.AnyOf{
0: &schema.String{},
1: &schema.Integer{},
},
},
},
},
Expand Down
30 changes: 16 additions & 14 deletions schema/dict_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ func TestDictCompile(t *testing.T) {
{
Name: "{KeysValidator:String,ValuesValidator:String}",
Compiler: &schema.Dict{
KeysValidator: &schema.String{},
ValuesValidator: &schema.String{},
KeysValidator: &schema.String{},
Values: schema.Field{
Validator: &schema.String{},
},
},
ReferenceChecker: fakeReferenceChecker{},
},
Expand All @@ -25,19 +27,19 @@ func TestDictCompile(t *testing.T) {
Error: "invalid regexp: error parsing regexp: missing closing ]: `[invalid re`",
},
{
Name: "{ValuesValidator:String{Regexp:invalid}}",
Compiler: &schema.Dict{ValuesValidator: &schema.String{Regexp: "[invalid re"}},
Name: "{Values.Validator:String{Regexp:invalid}}",
Compiler: &schema.Dict{Values: schema.Field{Validator: &schema.String{Regexp: "[invalid re"}}},
ReferenceChecker: fakeReferenceChecker{},
Error: "invalid regexp: error parsing regexp: missing closing ]: `[invalid re`",
},
{
Name: "{ValuesValidator:Reference{Path:valid}}",
Compiler: &schema.Dict{ValuesValidator: &schema.Reference{Path: "foo"}},
Name: "{Values.Validator:Reference{Path:valid}}",
Compiler: &schema.Dict{Values: schema.Field{Validator: &schema.Reference{Path: "foo"}}},
ReferenceChecker: fakeReferenceChecker{"foo": {}},
},
{
Name: "{ValuesValidator:Reference{Path:invalid}}",
Compiler: &schema.Dict{ValuesValidator: &schema.Reference{Path: "bar"}},
Name: "{Values.Validator:Reference{Path:invalid}}",
Compiler: &schema.Dict{Values: schema.Field{Validator: &schema.Reference{Path: "bar"}}},
ReferenceChecker: fakeReferenceChecker{"foo": {}},
Error: "can't find resource 'bar'",
},
Expand Down Expand Up @@ -68,20 +70,20 @@ func TestDictValidate(t *testing.T) {
Error: "invalid key `ba': is shorter than 3",
},
{
Name: `{ValuesValidator:Bool}.Validate(valid)`,
Validator: &schema.Dict{ValuesValidator: &schema.Bool{}},
Name: `{Values.Validator:Bool}.Validate(valid)`,
Validator: &schema.Dict{Values: schema.Field{Validator: &schema.Bool{}}},
Input: map[string]interface{}{"foo": true, "bar": false},
Expect: map[string]interface{}{"foo": true, "bar": false},
},
{
Name: `{ValuesValidator:Bool}.Validate({"foo":true,"bar":"value"})`,
Validator: &schema.Dict{ValuesValidator: &schema.Bool{}},
Name: `{Values.Validator:Bool}.Validate({"foo":true,"bar":"value"})`,
Validator: &schema.Dict{Values: schema.Field{Validator: &schema.Bool{}}},
Input: map[string]interface{}{"foo": true, "bar": "value"},
Error: "invalid value for key `bar': not a Boolean",
},
{
Name: `{ValuesValidator:String}.Validate("")`,
Validator: &schema.Dict{ValuesValidator: &schema.String{}},
Name: `{Values.Validator:String}.Validate("")`,
Validator: &schema.Dict{Values: schema.Field{Validator: &schema.String{}}},
Input: "",
Error: "not a dict",
},
Expand Down
4 changes: 1 addition & 3 deletions schema/encoding/jsonschema/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ func complexSchema1() schema.Schema {
"m": schema.Field{
Description: "m",
Validator: &schema.Array{
ValuesValidator: &schema.Object{
Schema: mSchema,
},
ValuesValidator: &schema.Object{Schema: mSchema},
},
},
},
Expand Down
5 changes: 3 additions & 2 deletions schema/encoding/jsonschema/dict.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ func (v dictBuilder) BuildJSONSchema() (map[string]interface{}, error) {

// Retrieve values validator JSON schema.
var valuesSchema map[string]interface{}
if v.ValuesValidator != nil {
b, err := ValidatorBuilder(v.ValuesValidator)
if v.Values.Validator != nil {
b, err := ValidatorBuilder(v.Values.Validator)
if err != nil {
return nil, err
}
Expand All @@ -68,6 +68,7 @@ func (v dictBuilder) BuildJSONSchema() (map[string]interface{}, error) {
} else {
valuesSchema = map[string]interface{}{}
}
addFieldProperties(valuesSchema, v.Values)

// Compose JSON Schema.
switch len(patterns) {
Expand Down
18 changes: 12 additions & 6 deletions schema/encoding/jsonschema/dict_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
func TestDictValidatorEncode(t *testing.T) {
testCases := []encoderTestCase{
{
name: `KeysValidator=nil,ValuesValidator=nil}"`,
name: `KeysValidator=nil,Values.Validator=nil}"`,
schema: schema.Schema{
Fields: schema.Fields{
"d": {
Expand Down Expand Up @@ -46,12 +46,14 @@ func TestDictValidatorEncode(t *testing.T) {
customValidate: fieldValidator("d", `{"type": "object", "additionalProperties": true}`),
},
{
name: `ValuesValidator=Integer{}"`,
name: `Values.Validator=Integer{}"`,
schema: schema.Schema{
Fields: schema.Fields{
"d": {
Validator: &schema.Dict{
ValuesValidator: &schema.Integer{},
Values: schema.Field{
Validator: &schema.Integer{},
},
},
},
},
Expand Down Expand Up @@ -88,8 +90,10 @@ func TestDictValidatorEncode(t *testing.T) {
Fields: schema.Fields{
"d": {
Validator: &schema.Dict{
KeysValidator: &schema.String{Regexp: "re"},
ValuesValidator: &schema.Integer{},
KeysValidator: &schema.String{Regexp: "re"},
Values: schema.Field{
Validator: &schema.Integer{},
},
},
},
},
Expand Down Expand Up @@ -179,7 +183,9 @@ func TestDictValidatorEncode(t *testing.T) {
Regexp: "tch",
Allowed: []string{"match1", "match2"},
},
ValuesValidator: &schema.Integer{},
Values: schema.Field{
Validator: &schema.Integer{},
},
},
},
},
Expand Down
61 changes: 36 additions & 25 deletions schema/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,31 @@ type Field struct {
Schema *Schema
}

// FieldHandler is the piece of logic modifying the field value based on passed parameters
// Compile implements the ReferenceCompiler interface and recursively compile sub schemas
// and validators when they implement Compiler interface.
func (f Field) Compile(rc ReferenceChecker) error {
// TODO check field name format (alpha num + _ and -).
if f.Schema != nil {
// Recursively compile sub schema if any.
if err := f.Schema.Compile(rc); err != nil {
return fmt.Errorf(".%v", err)
}
} else if f.Validator != nil {
// Compile validator if it implements the ReferenceCompiler or Compiler interface.
if c, ok := f.Validator.(Compiler); ok {
if err := c.Compile(rc); err != nil {
return fmt.Errorf(": %v", err)
}
}
if reflect.ValueOf(f.Validator).Kind() != reflect.Ptr {
return errors.New(": not a schema.Validator pointer")
}
}
return nil
}

// FieldHandler is the piece of logic modifying the field value based on passed
// parameters
type FieldHandler func(ctx context.Context, value interface{}, params map[string]interface{}) (interface{}, error)

// FieldValidator is an interface for all individual validators. It takes a
Expand All @@ -73,9 +97,9 @@ type FieldValidator interface {
Validate(value interface{}) (interface{}, error)
}

//FieldValidatorFunc is an adapter to allow the use of ordinary functions as field validators.
// If f is a function with the appropriate signature, FieldValidatorFunc(f) is a FieldValidator
// that calls f.
//FieldValidatorFunc is an adapter to allow the use of ordinary functions as
// field validators. If f is a function with the appropriate signature,
// FieldValidatorFunc(f) is a FieldValidator that calls f.
type FieldValidatorFunc func(value interface{}) (interface{}, error)

// Validate calls f(value).
Expand All @@ -93,25 +117,12 @@ type FieldSerializer interface {
Serialize(value interface{}) (interface{}, error)
}

// Compile implements the ReferenceCompiler interface and recursively compile sub schemas
// and validators when they implement Compiler interface.
func (f Field) Compile(rc ReferenceChecker) error {
// TODO check field name format (alpha num + _ and -).
if f.Schema != nil {
// Recursively compile sub schema if any.
if err := f.Schema.Compile(rc); err != nil {
return fmt.Errorf(".%v", err)
}
} else if f.Validator != nil {
// Compile validator if it implements the ReferenceCompiler or Compiler interface.
if c, ok := f.Validator.(Compiler); ok {
if err := c.Compile(rc); err != nil {
return fmt.Errorf(": %v", err)
}
}
if reflect.ValueOf(f.Validator).Kind() != reflect.Ptr {
return errors.New(": not a schema.Validator pointer")
}
}
return nil
// FieldGetter defines an interface for fetching sub-fields from a Schema or
// FieldValidator implementation that allows (JSON) object values.
type FieldGetter interface {
// GetField returns a Field for the given name if the name is allowed by
// the schema. The field is expected to validate query values.
//
// You may reference a sub-field using dotted notation, e.g. field.subfield.
GetField(name string) *Field
}
5 changes: 5 additions & 0 deletions schema/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func (v Object) Validate(value interface{}) (interface{}, error) {
return dest, nil
}

// GetField implements the FieldGetter interface.
func (v Object) GetField(name string) *Field {
return v.Schema.GetField(name)
}

// ErrorMap contains a map of errors by field name.
type ErrorMap map[string][]interface{}

Expand Down
Loading