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

WIP: Use santhosh-tekuri/jsonschema #487

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
45 changes: 10 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,44 +156,19 @@ func xmlBodyDecoder(body io.Reader, h http.Header, schema *openapi3.SchemaRef, e
}
```

## Custom function to check uniqueness of array items

By defaut, the library check unique items by below predefined function

```go
func isSliceOfUniqueItems(xs []interface{}) bool {
s := len(xs)
m := make(map[string]struct{}, s)
for _, x := range xs {
key, _ := json.Marshal(&x)
m[string(key)] = struct{}{}
}
return s == len(m)
}
```

In the predefined function using `json.Marshal` to generate a string can
be used as a map key which is to support check the uniqueness of an array
when the array items are objects or arrays. You can register
you own function according to your input data to get better performance:

```go
func main() {
// ...

// Register a customized function used to check uniqueness of array.
openapi3.RegisterArrayUniqueItemsChecker(arrayUniqueItemsChecker)

// ... other validate codes
}

func arrayUniqueItemsChecker(items []interface{}) bool {
// Check the uniqueness of the input slice
}
```

## Sub-v0 breaking API changes

### v0.???
OpenAPIv3 "in-house" schema validation was replaced with a correct JSON Schema implementation and conversion from OpenAPIv3 Schema to JSON Schema.
* Dropped `openapi3.ErrOneOfConflict`: now when a value matches more than one `oneOf` schemas the error string contains `Must validate one and only one schema (oneOf)`
* Dropped `openapi3.SchemaFormatValidationDisabled`: any `openapi3.Schema.Format` value is valid.
* Dropped `openapi3.FailFast() openapi3.SchemaValidationOption` and `openapi3.SchemaErrorDetailsDisabled`: validating values against schemas is offloaded to a third-party library that does not provide such a mechanism.
* Dropped `openapi3.RegisterArrayUniqueItemsChecker(openapi3.SliceUniqueItemsChecker)`: validating values against schemas is offloaded to a third-party library that does not provide such a mechanism.
* Dropped `openapi3.SchemaStringFormats`, `openapi3.FormatCallback`, `openapi3.Format`, `openapi3.FormatOfStringForUUIDOfRFC4122`, `openapi3.DefineStringFormat(...)` and `openapi3.DefineStringFormatCallback(...)`. If your special format is not already under [`gojsonschema.FormatCheckers`](https://pkg.go.dev/github.com/xeipuuv/gojsonschema#pkg-variables), first define a [`gojsonschema.FormatChecker`](https://pkg.go.dev/github.com/xeipuuv/gojsonschema#FormatChecker) and register it with [`gojsonschema.FormatCheckers.Add("my-format", myImpl{})`](https://pkg.go.dev/github.com/xeipuuv/gojsonschema#FormatCheckerChain.Add) *before compiling your schemas*.
* Dropped `openapi3.ErrSchemaInputNaN` and `openapi3.ErrSchemaInputInf`: OpenAPIv3 does not explicitly mention the related values.
* Replaced `openapi3.SchemaError` with `openapi3.SchemaValidationError` which wraps `[]gojsonschema.ResultError` and thus provides similar functionality and more.\

### v0.84.0
* The prototype of `openapi3gen.NewSchemaRefForValue` changed:
* It no longer returns a map but that is still accessible under the field `(*Generator).SchemaRefs`.
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ module github.com/getkin/kin-openapi

go 1.14

replace github.com/santhosh-tekuri/jsonschema/v5 => /home/lasse/Development/github.com/lkm/jsonschema

require (
github.com/ghodss/yaml v1.0.0
github.com/go-openapi/jsonpointer v0.19.5
github.com/gorilla/mux v1.8.0
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
github.com/stretchr/testify v1.5.1
gopkg.in/yaml.v2 v2.3.0
)
72 changes: 72 additions & 0 deletions openapi3/components_extension.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package openapi3

import "github.com/santhosh-tekuri/jsonschema/v5"

const oasComponentSchema = `{
"$id": "https://spec.openapis.org/oas/3.1/schema/2021-09-28",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"components": {
"type": "object",
"properties": {
"schemas": {
"type": "object",
"additionalProperties": {
"$dynamicRef": "#meta"
}
}
}
}
},
"$defs": {
"schema": {
"$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object",
"$dynamicAnchor": "meta",
"type": [
"object",
"boolean"
]
}
}
}
`

type componentsCompiler struct{}

func (componentsCompiler) Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (jsonschema.ExtSchema, error) {
if c, ok := m["components"]; ok {
resultSchemas := make(componentsSchema)
if components, ok := c.(map[string]interface{}); ok {
if s, ok := components["schemas"]; ok {
if schemas, ok := s.(map[string]interface{}); ok {
for name := range schemas {
ss, err := ctx.Compile("components/schemas/"+name, false)
if err != nil {
return resultSchemas, err
}
resultSchemas[name] = ss
}
}
}
}
return resultSchemas, nil
}

// nothing to compile, return nil
return nil, nil
}

type componentsSchema map[string]*jsonschema.Schema

func (c componentsSchema) Validate(ctx jsonschema.ValidationContext, v interface{}) error {
return nil
}

func (c componentsSchema) GetSchema(key string) *jsonschema.Schema {
if s, ok := c[key]; ok {
return s
}

return nil
}
38 changes: 38 additions & 0 deletions openapi3/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,46 @@ package openapi3
import (
"bytes"
"errors"
"github.com/santhosh-tekuri/jsonschema/v5"
"strings"
)

// SchemaValidationError is a collection of errors
type SchemaValidationError jsonschema.ValidationError

var _ error = (*SchemaValidationError)(nil)

func (e SchemaValidationError) Error() string {
var buff strings.Builder
for i, re := range jsonschema.ValidationError(e).Causes {
if i != 0 {
buff.WriteString("\n")
}
re.AbsoluteKeywordLocation = ""
buff.WriteString(re.Error())
}
return buff.String()
}

// Errors unwraps into much detailed errors.
// See https://pkg.go.dev/github.com/xeipuuv/gojsonschema#ResultError
func (e SchemaValidationError) Errors() *jsonschema.ValidationError {
return e.Causes[0]
}

// JSONPointer returns a dot (.) delimited "JSON path" to the context of the first error.
func (e SchemaValidationError) JSONPointer() string {
return jsonschema.ValidationError(e).Causes[0].KeywordLocation
}

func (e SchemaValidationError) asMultiError() MultiError {
errs := make([]error, 0, len(e.Causes))
for _, re := range e.Causes {
errs = append(errs, errors.New(re.Error()))
}
return errs
}

// MultiError is a collection of errors, intended for when
// multiple issues need to be reported upstream
type MultiError []error
Expand Down
38 changes: 37 additions & 1 deletion openapi3/openapi3.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package openapi3

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"

"github.com/getkin/kin-openapi/jsoninfo"
"github.com/santhosh-tekuri/jsonschema/v5"
)

// T is the root of an OpenAPI v3 document
Expand All @@ -19,6 +21,40 @@ type T struct {
Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`

compiler *jsonschema.Compiler
}

type docSchemas struct {
Components Components `json:"components,omitempty" yaml:"components,omitempty"`
}

// CompileSchemas needs to be called before any use of VisitJSON*()
func (doc *T) CompileSchemas() error {
doc.compiler = jsonschema.NewCompiler()
doc.compiler.Draft = jsonschema.Draft2020
oas, err := jsonschema.CompileString("oas", oasComponentSchema)
if err != nil {
return err
}

doc.compiler.RegisterExtension("oas31", oas, componentsCompiler{})

docSch := docSchemas{doc.Components}
jsonStr, err := json.Marshal(docSch)

if err := doc.compiler.AddResource("root", bytes.NewReader(jsonStr)); err != nil {
return err
}

rootSchema, err := doc.compiler.Compile("root")
if oasSch, ok := rootSchema.Extensions["oas31"].(componentsSchema); ok {
for name := range doc.Components.Schemas {
doc.Components.Schemas[name].Value.compiledSchema = oasSch.GetSchema(name)
}
}

return err
}

func (doc *T) MarshalJSON() ([]byte, error) {
Expand Down
2 changes: 1 addition & 1 deletion openapi3/race_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestRaceyPatternSchema(t *testing.T) {
require.NoError(t, err)

visit := func() {
err := schema.VisitJSONString("test")
//err := schema.VisitJSONString("test")
require.NoError(t, err)
}

Expand Down
1 change: 0 additions & 1 deletion openapi3/refs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package openapi3

import (
"context"

"github.com/getkin/kin-openapi/jsoninfo"
"github.com/go-openapi/jsonpointer"
)
Expand Down
Loading