Skip to content

Commit

Permalink
feat: initial support for local references
Browse files Browse the repository at this point in the history
admittedly, dealing with references properly has taken longer than
I'd initially anticipated. So far the best solution I can come up
with requires two changes:
1. move away from a master struct to keeping slices of validators
2. implement json-pointer traversal for decoded go types

The implementation of string token traversal will take some time
to get right, but the bones are there. This also sets the stage
for adding user-defined validators, which sounds nice
  • Loading branch information
b5 committed Jan 15, 2018
1 parent c193501 commit a99baf2
Show file tree
Hide file tree
Showing 11 changed files with 735 additions and 247 deletions.
File renamed without changes.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
# jsonschema
[![Qri](https://img.shields.io/badge/made%20by-qri-magenta.svg?style=flat-square)](https://qri.io)
[![GoDoc](https://godoc.org/github.com/qri-io/jsonschema?status.svg)](http://godoc.org/github.com/qri-io/jsonschema)
[![License](https://img.shields.io/github/license/qri-io/jsonschema.svg?style=flat-square)](./LICENSE)
[![Codecov](https://img.shields.io/codecov/c/github/qri-io/jsonschema.svg?style=flat-square)](https://codecov.io/gh/qri-io/jsonschema)
[![CI](https://img.shields.io/circleci/project/github/qri-io/jsonschema.svg?style=flat-square)](https://circleci.com/gh/qri-io/jsonschema)
[![Go Report Card](https://goreportcard.com/badge/github.com/qri-io/jsonschema)](https://goreportcard.com/report/github.com/qri-io/jsonschema)


# jsonschema

Provides utility for converting lengthy titles into condensed but still recognizable variable names

### 🚧🚧 Under Construction 🚧🚧
golang implementation of http://json-schema.org/
105 changes: 84 additions & 21 deletions keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,44 @@ import (
"encoding/json"
"fmt"
"reflect"
"strconv"
)

// primitiveTypes is a map of strings to check types against
var primitiveTypes = map[string]bool{
"null": true,
"boolean": true,
"object": true,
"array": true,
"number": true,
"string": true,
"integer": true,
}

// DataType gives the primitive json type of a value, plus the special case
// "integer" for when numbers are whole
func DataType(data interface{}) string {
switch v := data.(type) {
case nil:
return "null"
case bool:
return "boolean"
case float64:
if float64(int(v)) == v {
return "integer"
}
return "number"
case string:
return "string"
case []interface{}:
return "array"
case map[string]interface{}:
return "object"
default:
return "unknown"
}
}

// Type specifies one of the six json primitive types.
// The value of this keyword MUST be either a string or an array.
// If it is an array, elements of the array MUST be strings and MUST be unique.
Expand All @@ -22,18 +58,27 @@ func (t Type) Validate(data interface{}) error {
return nil
}
}
return fmt.Errorf(`expected "%v" to be a %s`, data, jt)
if len(t) == 1 {
return fmt.Errorf(`expected "%v" to be of type %s`, data, t[0])
} else {
str := ""
for _, ts := range t {
str += ts + ","
}
return fmt.Errorf(`expected "%v" to be one of type: %s`, data, str[:len(str)-1])
}
}

// primitiveTypes is a map of strings to check types against
var primitiveTypes = map[string]bool{
"null": true,
"boolean": true,
"object": true,
"array": true,
"number": true,
"string": true,
"integer": true,
// JSONProp implements JSON property name indexing for Type
func (t Type) JSONProp(name string) interface{} {
idx, err := strconv.Atoi(name)
if err != nil {
return nil
}
if idx > len(t) || idx < 0 {
return nil
}
return t[idx]
}

// UnmarshalJSON implements the json.Unmarshaler interface for Type
Expand Down Expand Up @@ -93,30 +138,48 @@ func (e Enum) Validate(data interface{}) error {
return fmt.Errorf("expected %s to be one of %s", data)
}

// JSONProp implements JSON property name indexing for Enum
func (e Enum) JSONProp(name string) interface{} {
idx, err := strconv.Atoi(name)
if err != nil {
return nil
}
if idx > len(e) || idx < 0 {
return nil
}
return e[idx]
}

// Const MAY be of any type, including null.
// An instance validates successfully against this keyword if its
// value is equal to the value of the keyword.
type Const []byte

// String implements the Stringer interface for Const
func (c Const) String() string {
return string(c)
}

// UnmarshalJSON implements the json.Unmarshaler interface for Const
func (c *Const) UnmarshalJSON(data []byte) error {
*c = data
return nil
}

// Validate implements the validate interface for Const
func (c Const) Validate(data interface{}) error {
var con interface{}
if err := json.Unmarshal(c, &con); err != nil {
return err
}

if !reflect.DeepEqual(con, data) {
return fmt.Errorf(`%s must equal %s`, string(c), data)
}
return nil
}

// JSONProp implements JSON property name indexing for Const
func (c Const) JSONProp(name string) interface{} {
return nil
}

// String implements the Stringer interface for Const
func (c Const) String() string {
return string(c)
}

// UnmarshalJSON implements the json.Unmarshaler interface for Const
func (c *Const) UnmarshalJSON(data []byte) error {
*c = data
return nil
}
35 changes: 33 additions & 2 deletions keywords_arrays.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"strconv"
)

// Items MUST be either a valid JSON Schema or an array of valid JSON Schemas.
Expand All @@ -27,14 +28,14 @@ func (it Items) Validate(data interface{}) error {
if it.single {
for i, elem := range arr {
if err := it.Schemas[0].Validate(elem); err != nil {
return fmt.Errorf("element %d: %s", i, err.Error())
return fmt.Errorf("element %d %s", i, err.Error())
}
}
} else {
for i, vs := range it.Schemas {
if i < len(arr) {
if err := vs.Validate(arr[i]); err != nil {
return fmt.Errorf("element %d: %s", i, err.Error())
return fmt.Errorf("element %d %s", i, err.Error())
}
}
}
Expand All @@ -43,6 +44,26 @@ func (it Items) Validate(data interface{}) error {
return nil
}

// JSONProp implements JSON property name indexing for Items
func (it Items) JSONProp(name string) interface{} {
idx, err := strconv.Atoi(name)
if err != nil {
return nil
}
if idx > len(it.Schemas) || idx < 0 {
return nil
}
return it.Schemas[idx]
}

func (it Items) JSONChildren() (res map[string]JSONPather) {
res = map[string]JSONPather{}
for i, sch := range it.Schemas {
res[strconv.Itoa(i)] = sch
}
return
}

// UnmarshalJSON implements the json.Unmarshaler interface for Items
func (it *Items) UnmarshalJSON(data []byte) error {
s := &Schema{}
Expand Down Expand Up @@ -94,6 +115,11 @@ func (a *AdditionalItems) Validate(data interface{}) error {
return nil
}

// JSONProp implements JSON property name indexing for AdditionalItems
func (a *AdditionalItems) JSONProp(name string) interface{} {
return a.Schema.JSONProp(name)
}

// UnmarshalJSON implements the json.Unmarshaler interface for AdditionalItems
func (a *AdditionalItems) UnmarshalJSON(data []byte) error {
sch := &Schema{}
Expand Down Expand Up @@ -175,6 +201,11 @@ func (c *Contains) Validate(data interface{}) error {
return nil
}

// JSONProp implements JSON property name indexing for Contains
func (m Contains) JSONProp(name string) interface{} {
return Schema(m).JSONProp(name)
}

// UnmarshalJSON implements the json.Unmarshaler interface for Contains
func (c *Contains) UnmarshalJSON(data []byte) error {
var sch Schema
Expand Down
42 changes: 42 additions & 0 deletions keywords_booleans.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package jsonschema
import (
"encoding/json"
"fmt"
"strconv"
)

// AllOf MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
Expand All @@ -19,6 +20,18 @@ func (a AllOf) Validate(data interface{}) error {
return nil
}

// JSONProp implements JSON property name indexing for AllOf
func (a AllOf) JSONProp(name string) interface{} {
idx, err := strconv.Atoi(name)
if err != nil {
return nil
}
if idx > len(a) || idx < 0 {
return nil
}
return a[idx]
}

// AnyOf MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
// An instance validates successfully against this keyword if it validates successfully against at
// least one schema defined by this keyword's value.
Expand All @@ -34,6 +47,18 @@ func (a AnyOf) Validate(data interface{}) error {
return fmt.Errorf("value did not match any specified anyOf schemas: %v", data)
}

// JSONProp implements JSON property name indexing for AnyOf
func (a AnyOf) JSONProp(name string) interface{} {
idx, err := strconv.Atoi(name)
if err != nil {
return nil
}
if idx > len(a) || idx < 0 {
return nil
}
return a[idx]
}

// OneOf MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
// An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value.
type OneOf []*Schema
Expand All @@ -55,6 +80,18 @@ func (o OneOf) Validate(data interface{}) error {
return nil
}

// JSONProp implements JSON property name indexing for OneOf
func (o OneOf) JSONProp(name string) interface{} {
idx, err := strconv.Atoi(name)
if err != nil {
return nil
}
if idx > len(o) || idx < 0 {
return nil
}
return o[idx]
}

// Not MUST be a valid JSON Schema.
// An instance is valid against this keyword if it fails to validate successfully against the schema defined
// by this keyword.
Expand All @@ -70,6 +107,11 @@ func (n *Not) Validate(data interface{}) error {
return nil
}

// JSONProp implements JSON property name indexing for Not
func (n Not) JSONProp(name string) interface{} {
return Schema(n).JSONProp(name)
}

// UnmarshalJSON implements the json.Unmarshaler interface for Not
func (n *Not) UnmarshalJSON(data []byte) error {
var sch Schema
Expand Down
15 changes: 15 additions & 0 deletions keywords_conditionals.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ func (i *If) Validate(data interface{}) error {
return nil
}

// JSONProp implements JSON property name indexing for If
func (i If) JSONProp(name string) interface{} {
return Schema(i).JSONProp(name)
}

// UnmarshalJSON implements the json.Unmarshaler interface for If
func (i *If) UnmarshalJSON(data []byte) error {
var sch Schema
Expand All @@ -35,6 +40,11 @@ func (t *Then) Validate(data interface{}) error {
return nil
}

// JSONProp implements JSON property name indexing for Then
func (t Then) JSONProp(name string) interface{} {
return Schema(t).JSONProp(name)
}

// UnmarshalJSON implements the json.Unmarshaler interface for Then
func (t *Then) UnmarshalJSON(data []byte) error {
var sch Schema
Expand All @@ -55,6 +65,11 @@ func (e *Else) Validate(data interface{}) error {
return nil
}

// JSONProp implements JSON property name indexing for Else
func (e Else) JSONProp(name string) interface{} {
return Schema(e).JSONProp(name)
}

// UnmarshalJSON implements the json.Unmarshaler interface for Else
func (e *Else) UnmarshalJSON(data []byte) error {
var sch Schema
Expand Down
Loading

0 comments on commit a99baf2

Please sign in to comment.