Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Commit

Permalink
internal/filetypes: add auto interpretation
Browse files Browse the repository at this point in the history
- automatically detect OpenAPI and JSONSchema
  based on strict criteria.
- json+schema no longer magically maps to jsonschema

For inputs, auto mode is now automatically enabled for .json
and .yaml/yml files. Any explicit tag, like json: or data: disables
auto mode.

Change-Id: I391179c4542b823c428e4989e31381e00caa4a45
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5410
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
  • Loading branch information
mpvl committed Apr 5, 2020
1 parent 3c437bd commit f9f8e62
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 73 deletions.
3 changes: 3 additions & 0 deletions cmd/cue/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ func (i *expressionIter) instance() *cue.Instance {

type config struct {
outMode filetypes.Mode

interpretation build.Interpretation

loadCfg *load.Config
}

Expand Down
6 changes: 5 additions & 1 deletion cmd/cue/cmd/testdata/script/def_jsonschema.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
cue def jsonschema: schema.json -p schema -l '"Person"::'
cue def jsonschema: schema.json -p schema -l 'Person::'
cmp stdout expect-stdout

# auto mode
cue def schema.json -p schema -l 'Person::'
cmp stdout expect-stdout

-- expect-stdout --
Expand Down
53 changes: 49 additions & 4 deletions cmd/cue/cmd/testdata/script/def_openapi.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ cmp stdout expect-cue-out
cue def foo.cue -o openapi+yaml:-
cmp stdout expect-yaml-out

cue def -p foo openapi: expect-json-out
cue def -p foo openapi: openapi.json
cmp stdout expect-cue

# auto mode
cue def -p foo openapi.json
cmp stdout expect-cue

-- foo.cue --
Expand All @@ -24,6 +28,47 @@ Bar :: {
foo: Foo
}

-- openapi.json --
{
"openapi": "3.0.0",
"info": {
"title": "My OpenAPI",
"version": "v1alpha1"
},
"paths": {},
"components": {
"schemas": {
"Bar": {
"type": "object",
"required": [
"foo"
],
"properties": {
"foo": {
"$ref": "#/components/schemas/Foo"
}
}
},
"Foo": {
"type": "object",
"required": [
"a",
"b"
],
"properties": {
"a": {
"type": "integer"
},
"b": {
"type": "integer",
"minimum": 0,
"exclusiveMaximum": 10
}
}
}
}
}
}
-- expect-json-out --
{
"openapi": "3.0.0",
Expand Down Expand Up @@ -120,12 +165,12 @@ components: schemas: {
}
-- expect-cue --

// Some clever title.
// My OpenAPI
package foo

info: {
title: "Some clever title."
version: "v1"
title: "My OpenAPI"
version: "v1alpha1"
}

Bar :: {
Expand Down
12 changes: 12 additions & 0 deletions cue/build/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ const (
type Interpretation string

const (
// Auto interprets the underlying data file as data, JSON Schema or OpenAPI,
// depending on the existence of certain marker fields.
//
// JSON Schema is identified by a top-level "$schema" field with a URL
// of the form "https?://json-schema.org/.*schema#?".
//
// OpenAPI is identified by the existence of a top-level field "openapi"
// with a major semantic version of 3, as well as the existence of
// the info.title and info.version fields.
//
// In all other cases, the underlying data is interpreted as is.
Auto Interpretation = "auto"
JSONSchema Interpretation = "jsonschema"
OpenAPI Interpretation = "openapi"
)
Expand Down
67 changes: 67 additions & 0 deletions internal/encoding/detect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2020 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package encoding

import (
"net/url"
"path"
"strings"

"cuelang.org/go/cue"
"cuelang.org/go/cue/build"
)

// Detect detects the interpretation.
func Detect(v cue.Value) (i build.Interpretation) {
switch {
case isOpenAPI(v):
return build.OpenAPI
case isJSONSchema(v):
return build.JSONSchema
}
return i
}

func isOpenAPI(v cue.Value) bool {
s, _ := v.Lookup("openapi").String()
if !strings.HasPrefix(s, "3.") {
return false
}
if _, err := v.Lookup("info", "title").String(); err != nil {
return false
}
if _, err := v.Lookup("info", "version").String(); err != nil {
return false
}
return true
}

func isJSONSchema(v cue.Value) bool {
s, err := v.Lookup("$schema").String()
if err != nil {
return false
}
u, err := url.Parse(s)
if err != nil {
return false
}
if u.Hostname() != "json-schema.org" {
return false
}
if _, base := path.Split(u.EscapedPath()); base != "schema" {
return false
}
return true
}
101 changes: 101 additions & 0 deletions internal/encoding/detect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2020 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package encoding

import (
"testing"

"cuelang.org/go/cue"
"cuelang.org/go/cue/build"
)

func TestDetect(t *testing.T) {
testCases := []struct {
name string
in string
out build.Interpretation
}{{
name: "validOpenAPI",
in: `
openapi: "3.0.0"
info: title: "Foo"
info: version: "v1alpha1"
`,
out: build.OpenAPI,
}, {
name: "noOpenAPI",
in: `
info: title: "Foo"
info: version: "v1alpha1"
`,
}, {
name: "noTitle",
in: `
openapi: "3.0.0"
info: version: "v1alpha1"
`,
}, {
name: "noVersion",
in: `
openapi: "3.0.0"
info: title: "Foo"
`,
}, {
name: "validJSONSchema",
in: `
$schema: "https://json-schema.org/schema#"
`,
out: build.JSONSchema,
}, {
name: "validJSONSchema",
in: `
$schema: "https://json-schema.org/draft-07/schema#"
`,
out: build.JSONSchema,
}, {
name: "noSchema",
in: `
$id: "https://acme.com/schema#"
`,
}, {
name: "wrongHost",
in: `
$schema: "https://acme.com/schema#"
`,
}, {
name: "invalidURL",
in: `
$schema: "://json-schema.org/draft-07"
`,
}, {
name: "invalidPath",
in: `
$schema: "https://json-schema.org/draft-07"
`,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var r cue.Runtime
inst, err := r.Compile(tc.name, tc.in)
if err != nil {
t.Fatal(err)
}
got := Detect(inst.Value())
if got != tc.out {
t.Errorf("got %v; want %v", got, tc.out)
}
})
}
}
Loading

0 comments on commit f9f8e62

Please sign in to comment.