Merge pull request #52793 from nikhita/crd-validation-conversion-tests

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

apiextensions: add round trip tests for CRD schema conversion

Follow up test for https://github.com/kubernetes/kubernetes/pull/52281.

Need to update go-openapi to get this - https://github.com/go-openapi/spec/pull/31 - in.

**Special notes for your reviewer**: The tests won't pass until https://github.com/kubernetes/kubernetes/pull/52281 is merged.

**Release note**:

```release-note
NONE
```

/cc @sttts
pull/6/head
Kubernetes Submit Queue 2017-10-09 10:22:41 -07:00 committed by GitHub
commit e339400f6f
53 changed files with 1702 additions and 574 deletions

13
Godeps/Godeps.json generated
View File

@ -1188,7 +1188,7 @@
},
{
"ImportPath": "github.com/go-openapi/loads",
"Rev": "18441dfa706d924a39a030ee2c3b1d8d81917b38"
"Rev": "a80dea3052f00e5f032e860dd7355cd0cc67e24d"
},
{
"ImportPath": "github.com/go-openapi/runtime",
@ -1196,7 +1196,7 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/strfmt",
@ -1204,7 +1204,7 @@
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/go-openapi/validate",
@ -1856,7 +1856,6 @@
},
{
"ImportPath": "github.com/inconshreveable/mousetrap",
"Comment": "v1.0",
"Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
},
{
@ -1978,15 +1977,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil",

View File

@ -32,11 +32,11 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/gogo/protobuf/proto",
@ -56,15 +56,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/spf13/pflag",
@ -134,6 +134,10 @@
"ImportPath": "gopkg.in/inf.v0",
"Rev": "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "53feefa2559fb8dfa8d81baad31be332c97d6c77"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

View File

@ -132,7 +132,7 @@
},
{
"ImportPath": "github.com/go-openapi/loads",
"Rev": "18441dfa706d924a39a030ee2c3b1d8d81917b38"
"Rev": "a80dea3052f00e5f032e860dd7355cd0cc67e24d"
},
{
"ImportPath": "github.com/go-openapi/runtime",
@ -140,7 +140,7 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/strfmt",
@ -148,7 +148,7 @@
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/go-openapi/validate",
@ -264,15 +264,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil",

View File

@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
@ -27,3 +28,20 @@ filegroup(
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["validation_test.go"],
library = ":go_default_library",
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/testing/fuzzer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
],
)

View File

@ -0,0 +1,87 @@
/*
Copyright 2017 The Kubernetes 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 validation
import (
"math/rand"
"testing"
"github.com/go-openapi/spec"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/testing/fuzzer"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsfuzzer "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/fuzzer"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
)
// TestRoundTrip checks the conversion to go-openapi types.
// internal -> go-openapi -> JSON -> external -> internal
func TestRoundTrip(t *testing.T) {
scheme := runtime.NewScheme()
codecs := serializer.NewCodecFactory(scheme)
// add internal and external types to scheme
if err := apiextensions.AddToScheme(scheme); err != nil {
t.Fatal(err)
}
if err := apiextensionsv1beta1.AddToScheme(scheme); err != nil {
t.Fatal(err)
}
seed := rand.Int63()
fuzzerFuncs := fuzzer.MergeFuzzerFuncs(apiextensionsfuzzer.Funcs)
f := fuzzer.FuzzerFor(fuzzerFuncs, rand.NewSource(seed), codecs)
for i := 0; i < 20; i++ {
// fuzz internal types
internal := &apiextensions.JSONSchemaProps{}
f.Fuzz(internal)
// internal -> go-openapi
openAPITypes := &spec.Schema{}
if err := convertJSONSchemaProps(internal, openAPITypes); err != nil {
t.Fatal(err)
}
// go-openapi -> JSON
openAPIJSON, err := json.Marshal(openAPITypes)
if err != nil {
t.Fatal(err)
}
// JSON -> external
external := &apiextensionsv1beta1.JSONSchemaProps{}
if err := json.Unmarshal(openAPIJSON, external); err != nil {
t.Fatal(err)
}
// external -> internal
internalRoundTripped := &apiextensions.JSONSchemaProps{}
if err := scheme.Convert(external, internalRoundTripped, nil); err != nil {
t.Fatal(err)
}
if !apiequality.Semantic.DeepEqual(internal, internalRoundTripped) {
t.Fatalf("expected\n\t%#v, got \n\t%#v", internal, internalRoundTripped)
}
}
}

View File

@ -56,11 +56,11 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/gogo/protobuf/proto",
@ -100,15 +100,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mxk/go-flowrate/flowrate",

View File

@ -340,11 +340,11 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/gogo/protobuf/proto",
@ -488,15 +488,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil",

View File

@ -112,11 +112,11 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/gogo/protobuf/proto",
@ -240,15 +240,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/peterbourgon/diskv",

View File

@ -32,11 +32,11 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/gogo/protobuf/gogoproto",
@ -144,15 +144,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/spf13/pflag",
@ -206,6 +206,10 @@
"ImportPath": "golang.org/x/text/width",
"Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01"
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "53feefa2559fb8dfa8d81baad31be332c97d6c77"
},
{
"ImportPath": "k8s.io/gengo/args",
"Rev": "70ad626ed2d7a483d89d2c4c56364d60b48ee8fc"

View File

@ -128,11 +128,11 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/gogo/protobuf/proto",
@ -244,15 +244,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil",

View File

@ -44,11 +44,11 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/gogo/protobuf/proto",
@ -120,15 +120,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/peterbourgon/diskv",

View File

@ -120,11 +120,11 @@
},
{
"ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff"
"Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
},
{
"ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72"
"Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
},
{
"ImportPath": "github.com/gogo/protobuf/proto",
@ -236,15 +236,15 @@
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
},
{
"ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil",

26
vendor/github.com/go-openapi/loads/.editorconfig generated vendored Normal file
View File

@ -0,0 +1,26 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# Set default charset
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
charset = utf-8
# Tab indentation (no size specified)
[*.go]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

View File

@ -1,13 +0,0 @@
approve_by_comment: true
approve_regex: '^(:shipit:|:\+1:|\+1|LGTM|lgtm|Approved)'
reject_regex: ^[Rr]ejected
reset_on_push: false
reviewers:
members:
- casualjim
- chancez
- frapposelli
- vburenin
- pytlesk4
name: pullapprove
required: 1

16
vendor/github.com/go-openapi/loads/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,16 @@
language: go
go:
- 1.8
install:
- go get -u github.com/stretchr/testify
- go get -u github.com/go-openapi/analysis
- go get -u github.com/go-openapi/spec
- go get -u github.com/go-openapi/swag
- go get -u gopkg.in/yaml.v2
script:
- ./hack/coverage
after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
slack:
secure: OxkPwVp35qBTUilgWC8xykSj+sGMcj0h8IIOKD+Rflx2schZVlFfdYdyVBM+s9OqeOfvtuvnR9v1Ye2rPKAvcjWdC4LpRGUsgmItZaI6Um8Aj6+K9udCw5qrtZVfOVmRu8LieH//XznWWKdOultUuniW0MLqw5+II87Gd00RWbCGi0hk0PykHe7uK+PDA2BEbqyZ2WKKYCvfB3j+0nrFOHScXqnh0V05l2E83J4+Sgy1fsPy+1WdX58ZlNBG333ibaC1FS79XvKSmTgKRkx3+YBo97u6ZtUmJa5WZjf2OdLG3KIckGWAv6R5xgxeU31N0Ng8L332w/Edpp2O/M2bZwdnKJ8hJQikXIAQbICbr+lTDzsoNzMdEIYcHpJ5hjPbiUl3Bmd+Jnsjf5McgAZDiWIfpCKZ29tPCEkVwRsOCqkyPRMNMzHHmoja495P5jR+ODS7+J8RFg5xgcnOgpP9D4Wlhztlf5WyZMpkLxTUD+bZq2SRf50HfHFXTkfq22zPl3d1eq0yrLwh/Z/fWKkfb6SyysROL8y6s8u3dpFX1YHSg0BR6i913h4aoZw9B2BG27cafLLTwKYsp2dFo1PWl4O6u9giFJIeqwloZHLKKrwh0cBFhB7RH0I58asxkZpCH6uWjJierahmHe7iS+E6i+9oCHkOZ59hmCYNimIs3hM=

View File

@ -1,4 +1,4 @@
# Loads OAI specs [![Build Status](https://ci.vmware.run/api/badges/go-openapi/loads/status.svg)](https://ci.vmware.run/go-openapi/loads) [![Coverage](https://coverage.vmware.run/badges/go-openapi/loads/coverage.svg)](https://coverage.vmware.run/go-openapi/loads) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
# Loads OAI specs [![Build Status](https://travis-ci.org/go-openapi/loads.svg?branch=master)](https://travis-ci.org/go-openapi/loads) [![codecov](https://codecov.io/gh/go-openapi/loads/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/loads) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/loads/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/loads?status.svg)](http://godoc.org/github.com/go-openapi/loads)

View File

@ -15,10 +15,13 @@
package loads
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"path/filepath"
"github.com/go-openapi/analysis"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag"
@ -39,7 +42,17 @@ type DocLoader func(string) (json.RawMessage, error)
// DocMatcher represents a predicate to check if a loader matches
type DocMatcher func(string) bool
var loaders = &loader{Match: func(_ string) bool { return true }, Fn: JSONDoc}
var (
loaders *loader
defaultLoader *loader
)
func init() {
defaultLoader = &loader{Match: func(_ string) bool { return true }, Fn: JSONDoc}
loaders = defaultLoader
spec.PathLoader = loaders.Fn
AddLoader(swag.YAMLMatcher, swag.YAMLDoc)
}
// AddLoader for a document
func AddLoader(predicate DocMatcher, load DocLoader) {
@ -49,7 +62,7 @@ func AddLoader(predicate DocMatcher, load DocLoader) {
Fn: load,
Next: prev,
}
spec.PathLoader = loaders.Fn
}
type loader struct {
@ -73,6 +86,7 @@ type Document struct {
// specAnalyzer
Analyzer *analysis.Spec
spec *spec.Swagger
specFilePath string
origSpec *spec.Swagger
schema *spec.Schema
raw json.RawMessage
@ -84,23 +98,39 @@ func Spec(path string) (*Document, error) {
if err != nil {
return nil, err
}
var lastErr error
for l := loaders.Next; l != nil; l = l.Next {
if loaders.Match(specURL.Path) {
b, err2 := loaders.Fn(path)
if err2 != nil {
return nil, err2
lastErr = err2
continue
}
return Analyzed(b, "")
}
}
b, err := loaders.Fn(path)
doc, err := Analyzed(b, "")
if err != nil {
return nil, err
}
return Analyzed(b, "")
if doc != nil {
doc.specFilePath = path
}
return doc, nil
}
}
if lastErr != nil {
return nil, lastErr
}
b, err := defaultLoader.Fn(path)
if err != nil {
return nil, err
}
var swag20Schema = spec.MustLoadSwagger20Schema()
document, err := Analyzed(b, "")
if document != nil {
document.specFilePath = path
}
return document, err
}
// Analyzed creates a new analyzed spec document
func Analyzed(data json.RawMessage, version string) (*Document, error) {
@ -111,40 +141,66 @@ func Analyzed(data json.RawMessage, version string) (*Document, error) {
return nil, fmt.Errorf("spec version %q is not supported", version)
}
raw := data
trimmed := bytes.TrimSpace(data)
if len(trimmed) > 0 {
if trimmed[0] != '{' && trimmed[0] != '[' {
yml, err := swag.BytesToYAMLDoc(trimmed)
if err != nil {
return nil, fmt.Errorf("analyzed: %v", err)
}
d, err := swag.YAMLToJSON(yml)
if err != nil {
return nil, fmt.Errorf("analyzed: %v", err)
}
raw = d
}
}
swspec := new(spec.Swagger)
if err := json.Unmarshal(data, swspec); err != nil {
if err := json.Unmarshal(raw, swspec); err != nil {
return nil, err
}
origsqspec := new(spec.Swagger)
if err := json.Unmarshal(data, origsqspec); err != nil {
if err := json.Unmarshal(raw, origsqspec); err != nil {
return nil, err
}
d := &Document{
Analyzer: analysis.New(swspec),
schema: swag20Schema,
schema: spec.MustLoadSwagger20Schema(),
spec: swspec,
raw: data,
raw: raw,
origSpec: origsqspec,
}
return d, nil
}
// Expanded expands the ref fields in the spec document and returns a new spec document
func (d *Document) Expanded() (*Document, error) {
func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) {
swspec := new(spec.Swagger)
if err := json.Unmarshal(d.raw, swspec); err != nil {
return nil, err
}
if err := spec.ExpandSpec(swspec); err != nil {
var expandOptions *spec.ExpandOptions
if len(options) > 0 {
expandOptions = options[1]
} else {
expandOptions = &spec.ExpandOptions{
RelativeBase: filepath.Dir(d.specFilePath),
}
}
if err := spec.ExpandSpec(swspec, expandOptions); err != nil {
return nil, err
}
dd := &Document{
Analyzer: analysis.New(swspec),
spec: swspec,
schema: swag20Schema,
schema: spec.MustLoadSwagger20Schema(),
raw: d.raw,
origSpec: d.origSpec,
}
@ -201,3 +257,8 @@ func (d *Document) Pristine() *Document {
dd, _ := Analyzed(d.Raw(), d.Version())
return dd
}
// SpecFilePath returns the file path of the spec if one is defined
func (d *Document) SpecFilePath() string {
return d.specFilePath
}

View File

@ -1 +0,0 @@
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.Epk8dDFH8U1RPYIPDpajZO26L5zFJ1wnQNGWxVHHo5cXrWF148kENoZzh35FT9cAxxPS_4CeVVpf59EgvCc8bem1puuj0gBZptn-lYa7iXZdI-ESN2Te7nF5VbZfwbnI62nEikYGyxz-ozL_IFuMl-qWek4iLerF8Z_xh0MZOJ_w8Nog7qb2WQov72d997TJv5ZKjWcRYPbnsAy1q60-Cqxq3a6enhcSPXqpK46nYSXGKfHvognWBJ_pxwkEqIBPN6hE4EfNtJjMf2LFKEdYy02nbHz78d-2YZ8wIUSJ-IWIwn3GTzObdGqRed20Qf3JtWTsOespmexDrLSeo3HW6A.7XaHW-Y1jjRAWt_W.S1Adut62RLOYZc-lN02M0MGczEucch3zIr4J1UPBPnZooWzntiE5UaUz0UdhjHVszQE5hTfG-yocKD1rDQGER6qrLtnJVrCm9J3n4lHglM-xOz1eZln1XKrWcAgZnAKaKSzuAa5scPG4iTHW6RwbWi_PWm04tBJ1yazdjaVo3uvuhflwvU9if7uMPMtscrDesbBVvpG89xmeudiFjX-wjsV5oGBIjz6ukEBAMKzNDMqikNoG4SnGenpxUpjUjMkDXxiC3BC8oL2_myeIfFeEOF066DqEN3CLkqBVO25zdpWAF4Ou2jKv--mgGEb_E1aMgiSoAVBnybene0TKn2IJ8rtkyRdmWlLIRKZdDT3v775C1FPK6-tYzS7NVg9nnuvpta5PhzYNkqI1Ie74Sl0I-RFClhsdx9dLDhoFEKCx2etC4UDX9jhj2u0Y2MrL76dRGE9kEV1hL1fh6HMvS4ZAAWw3Qce4skCjcL-2YyIOHzKjgLGkZsR5cTUQwCJyacVkdHUOUKFdDGZaUzWkFyeZ1oyrlG2d52svaplpU5-vCOVbWkqUN9rOALGPTC51Ur0L7DFx29aDImhaxZqTe2t9mcdqY7VLcO3JgUiD3JKsEet7s2EDeN44MqITv9KBS8wqJW4.sRv4ov0wB0IxTHw90kJy-A

View File

@ -1,35 +0,0 @@
clone:
path: github.com/go-openapi/spec
matrix:
GO_VERSION:
- "1.6"
build:
integration:
image: golang:$$GO_VERSION
pull: true
commands:
- go get -u github.com/stretchr/testify/assert
- go get -u gopkg.in/yaml.v2
- go get -u github.com/go-openapi/swag
- go get -u github.com/go-openapi/jsonpointer
- go get -u github.com/go-openapi/jsonreference
- go test -race
- go test -v -cover -coverprofile=coverage.out -covermode=count ./...
notify:
slack:
channel: bots
webhook_url: $$SLACK_URL
username: drone
publish:
coverage:
server: https://coverage.vmware.run
token: $$GITHUB_TOKEN
# threshold: 70
# must_increase: true
when:
matrix:
GO_VERSION: "1.6"

26
vendor/github.com/go-openapi/spec/.editorconfig generated vendored Normal file
View File

@ -0,0 +1,26 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# Set default charset
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
charset = utf-8
# Tab indentation (no size specified)
[*.go]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

View File

@ -1,13 +0,0 @@
approve_by_comment: true
approve_regex: '^(:shipit:|:\+1:|\+1|LGTM|lgtm|Approved)'
reject_regex: ^[Rr]ejected
reset_on_push: false
reviewers:
members:
- casualjim
- chancez
- frapposelli
- vburenin
- pytlesk4
name: pullapprove
required: 1

16
vendor/github.com/go-openapi/spec/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,16 @@
language: go
go:
- 1.7
install:
- go get -u github.com/stretchr/testify
- go get -u github.com/go-openapi/swag
- go get -u gopkg.in/yaml.v2
- go get -u github.com/go-openapi/jsonpointer
- go get -u github.com/go-openapi/jsonreference
script:
- go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic ./...
after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
slack:
secure: QUWvCkBBK09GF7YtEvHHVt70JOkdlNBG0nIKu/5qc4/nW5HP8I2w0SEf/XR2je0eED1Qe3L/AfMCWwrEj+IUZc3l4v+ju8X8R3Lomhme0Eb0jd1MTMCuPcBT47YCj0M7RON7vXtbFfm1hFJ/jLe5+9FXz0hpXsR24PJc5ZIi/ogNwkaPqG4BmndzecpSh0vc2FJPZUD9LT0I09REY/vXR0oQAalLkW0asGD5taHZTUZq/kBpsNxaAFrLM23i4mUcf33M5fjLpvx5LRICrX/57XpBrDh2TooBU6Qj3CgoY0uPRYUmSNxbVx1czNzl2JtEpb5yjoxfVPQeg0BvQM00G8LJINISR+ohrjhkZmAqchDupAX+yFrxTtORa78CtnIL6z/aTNlgwwVD8kvL/1pFA/JWYmKDmz93mV/+6wubGzNSQCstzjkFA4/iZEKewKUoRIAi/fxyscP6L/rCpmY/4llZZvrnyTqVbt6URWpopUpH4rwYqreXAtJxJsfBJIeSmUIiDIOMGkCTvyTEW3fWGmGoqWtSHLoaWDyAIGb7azb+KvfpWtEcoPFWfSWU+LGee0A/YsUhBl7ADB9A0CJEuR8q4BPpKpfLwPKSiKSAXL7zDkyjExyhtgqbSl2jS+rKIHOZNL8JkCcTP2MKMVd563C5rC5FMKqu3S9m2b6380E=

View File

@ -1,4 +1,4 @@
# OAI object model [![Build Status](https://ci.vmware.run/api/badges/go-openapi/spec/status.svg)](https://ci.vmware.run/go-openapi/spec) [![Coverage](https://coverage.vmware.run/badges/go-openapi/spec/coverage.svg)](https://coverage.vmware.run/go-openapi/spec) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
# OAI object model [![Build Status](https://travis-ci.org/go-openapi/spec.svg?branch=master)](https://travis-ci.org/go-openapi/spec) [![codecov](https://codecov.io/gh/go-openapi/spec/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/spec) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/spec/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/spec?status.svg)](http://godoc.org/github.com/go-openapi/spec)

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,10 @@ package spec
import (
"encoding/json"
"fmt"
"log"
"net/url"
"os"
"path/filepath"
"reflect"
"strings"
"sync"
@ -26,6 +29,18 @@ import (
"github.com/go-openapi/swag"
)
var (
// Debug enables logging when SWAGGER_DEBUG env var is not empty
Debug = os.Getenv("SWAGGER_DEBUG") != ""
)
// ExpandOptions provides options for expand.
type ExpandOptions struct {
RelativeBase string
SkipSchemas bool
ContinueOnError bool
}
// ResolutionCache a cache for resolving urls
type ResolutionCache interface {
Get(string) (interface{}, bool)
@ -37,7 +52,11 @@ type simpleCache struct {
store map[string]interface{}
}
var resCache = initResolutionCache()
var resCache ResolutionCache
func init() {
resCache = initResolutionCache()
}
func initResolutionCache() ResolutionCache {
return &simpleCache{store: map[string]interface{}{
@ -47,8 +66,11 @@ func initResolutionCache() ResolutionCache {
}
func (s *simpleCache) Get(uri string) (interface{}, bool) {
debugLog("getting %q from resolution cache", uri)
s.lock.Lock()
v, ok := s.store[uri]
debugLog("got %q from resolution cache: %t", uri, ok)
s.lock.Unlock()
return v, ok
}
@ -59,9 +81,9 @@ func (s *simpleCache) Set(uri string, data interface{}) {
s.lock.Unlock()
}
// ResolveRef resolves a reference against a context root
func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
resolver, err := defaultSchemaLoader(root, nil, nil)
// ResolveRefWithBase resolves a reference against a context root with preservation of base path
func ResolveRefWithBase(root interface{}, ref *Ref, opts *ExpandOptions) (*Schema, error) {
resolver, err := defaultSchemaLoader(root, nil, opts, nil)
if err != nil {
return nil, err
}
@ -73,9 +95,19 @@ func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
return result, nil
}
// ResolveRef resolves a reference against a context root
func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
return ResolveRefWithBase(root, ref, nil)
}
// ResolveParameter resolves a paramter reference against a context root
func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) {
resolver, err := defaultSchemaLoader(root, nil, nil)
return ResolveParameterWithBase(root, ref, nil)
}
// ResolveParameterWithBase resolves a paramter reference against a context root and base path
func ResolveParameterWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Parameter, error) {
resolver, err := defaultSchemaLoader(root, nil, opts, nil)
if err != nil {
return nil, err
}
@ -89,7 +121,12 @@ func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) {
// ResolveResponse resolves response a reference against a context root
func ResolveResponse(root interface{}, ref Ref) (*Response, error) {
resolver, err := defaultSchemaLoader(root, nil, nil)
return ResolveResponseWithBase(root, ref, nil)
}
// ResolveResponseWithBase resolves response a reference against a context root and base path
func ResolveResponseWithBase(root interface{}, ref Ref, opts *ExpandOptions) (*Response, error) {
resolver, err := defaultSchemaLoader(root, nil, opts, nil)
if err != nil {
return nil, err
}
@ -101,23 +138,72 @@ func ResolveResponse(root interface{}, ref Ref) (*Response, error) {
return result, nil
}
// ResolveItems resolves header and parameter items reference against a context root and base path
func ResolveItems(root interface{}, ref Ref, opts *ExpandOptions) (*Items, error) {
resolver, err := defaultSchemaLoader(root, nil, opts, nil)
if err != nil {
return nil, err
}
result := new(Items)
if err := resolver.Resolve(&ref, result); err != nil {
return nil, err
}
return result, nil
}
// ResolvePathItem resolves response a path item against a context root and base path
func ResolvePathItem(root interface{}, ref Ref, opts *ExpandOptions) (*PathItem, error) {
resolver, err := defaultSchemaLoader(root, nil, opts, nil)
if err != nil {
return nil, err
}
result := new(PathItem)
if err := resolver.Resolve(&ref, result); err != nil {
return nil, err
}
return result, nil
}
type schemaLoader struct {
loadingRef *Ref
startingRef *Ref
currentRef *Ref
root interface{}
options *ExpandOptions
cache ResolutionCache
loadDoc func(string) (json.RawMessage, error)
}
var idPtr, _ = jsonpointer.New("/id")
var schemaPtr, _ = jsonpointer.New("/$schema")
var refPtr, _ = jsonpointer.New("/$ref")
func defaultSchemaLoader(root interface{}, ref *Ref, cache ResolutionCache) (*schemaLoader, error) {
// PathLoader function to use when loading remote refs
var PathLoader func(string) (json.RawMessage, error)
func init() {
PathLoader = func(path string) (json.RawMessage, error) {
data, err := swag.LoadFromFileOrHTTP(path)
if err != nil {
return nil, err
}
return json.RawMessage(data), nil
}
}
func defaultSchemaLoader(
root interface{},
ref *Ref,
expandOptions *ExpandOptions,
cache ResolutionCache) (*schemaLoader, error) {
if cache == nil {
cache = resCache
}
if expandOptions == nil {
expandOptions = &ExpandOptions{}
}
var ptr *jsonpointer.Pointer
if ref != nil {
@ -127,18 +213,16 @@ func defaultSchemaLoader(root interface{}, ref *Ref, cache ResolutionCache) (*sc
currentRef := nextRef(root, ref, ptr)
return &schemaLoader{
root: root,
loadingRef: ref,
startingRef: ref,
currentRef: currentRef,
root: root,
options: expandOptions,
cache: cache,
loadDoc: func(path string) (json.RawMessage, error) {
data, err := swag.LoadFromFileOrHTTP(path)
if err != nil {
return nil, err
}
return json.RawMessage(data), nil
debugLog("fetching document at %q", path)
return PathLoader(path)
},
currentRef: currentRef,
}, nil
}
@ -159,6 +243,7 @@ func nextRef(startingNode interface{}, startingRef *Ref, ptr *jsonpointer.Pointe
if startingRef == nil {
return nil
}
if ptr == nil {
return startingRef
}
@ -184,32 +269,111 @@ func nextRef(startingNode interface{}, startingRef *Ref, ptr *jsonpointer.Pointe
refRef, _, _ := refPtr.Get(node)
if refRef != nil {
rf, _ := NewRef(refRef.(string))
var rf Ref
switch value := refRef.(type) {
case string:
rf, _ = NewRef(value)
}
nw, err := ret.Inherits(rf)
if err != nil {
break
}
nwURL := nw.GetURL()
if nwURL.Scheme == "file" || (nwURL.Scheme == "" && nwURL.Host == "") {
nwpt := filepath.ToSlash(nwURL.Path)
if filepath.IsAbs(nwpt) {
_, err := os.Stat(nwpt)
if err != nil {
nwURL.Path = filepath.Join(".", nwpt)
}
}
}
ret = nw
}
}
return ret
}
func debugLog(msg string, args ...interface{}) {
if Debug {
log.Printf(msg, args...)
}
}
func normalizeFileRef(ref *Ref, relativeBase string) *Ref {
refURL := ref.GetURL()
debugLog("normalizing %s against %s (%s)", ref.String(), relativeBase, refURL.String())
if strings.HasPrefix(refURL.String(), "#") {
return ref
}
if refURL.Scheme == "file" || (refURL.Scheme == "" && refURL.Host == "") {
filePath := refURL.Path
debugLog("normalizing file path: %s", filePath)
if !filepath.IsAbs(filepath.FromSlash(filePath)) && len(relativeBase) != 0 {
debugLog("joining %s with %s", relativeBase, filePath)
if fi, err := os.Stat(filepath.FromSlash(relativeBase)); err == nil {
if !fi.IsDir() {
relativeBase = filepath.Dir(filepath.FromSlash(relativeBase))
}
}
filePath = filepath.Join(filepath.FromSlash(relativeBase), filepath.FromSlash(filePath))
}
if !filepath.IsAbs(filepath.FromSlash(filePath)) {
pwd, err := os.Getwd()
if err == nil {
debugLog("joining cwd %s with %s", pwd, filePath)
filePath = filepath.Join(pwd, filepath.FromSlash(filePath))
}
}
debugLog("cleaning %s", filePath)
filePath = filepath.Clean(filepath.FromSlash(filePath))
_, err := os.Stat(filepath.FromSlash(filePath))
if err == nil {
debugLog("rewriting url %s to scheme \"\" path %s", refURL.String(), filePath)
slp := filepath.FromSlash(filePath)
if filepath.IsAbs(slp) && filepath.Separator == '\\' && len(slp) > 1 && slp[1] == ':' && ('a' <= slp[0] && slp[0] <= 'z' || 'A' <= slp[0] && slp[0] <= 'Z') {
slp = slp[2:]
}
refURL.Scheme = ""
refURL.Path = filepath.ToSlash(slp)
debugLog("new url with joined filepath: %s", refURL.String())
*ref = MustCreateRef(refURL.String())
}
}
debugLog("refurl: %s", ref.GetURL().String())
return ref
}
func (r *schemaLoader) resolveRef(currentRef, ref *Ref, node, target interface{}) error {
tgt := reflect.ValueOf(target)
if tgt.Kind() != reflect.Ptr {
return fmt.Errorf("resolve ref: target needs to be a pointer")
}
oldRef := currentRef
if currentRef != nil {
debugLog("resolve ref current %s new %s", currentRef.String(), ref.String())
nextRef := nextRef(node, ref, currentRef.GetPointer())
if nextRef == nil || nextRef.GetURL() == nil {
return nil
}
var err error
currentRef, err = currentRef.Inherits(*nextRef(node, ref, currentRef.GetPointer()))
currentRef, err = currentRef.Inherits(*nextRef)
debugLog("resolved ref current %s", currentRef.String())
if err != nil {
return err
}
}
if currentRef == nil {
currentRef = ref
}
@ -245,9 +409,16 @@ func (r *schemaLoader) resolveRef(currentRef, ref *Ref, node, target interface{}
return nil
}
if refURL.Scheme != "" && refURL.Host != "" {
// most definitely take the red pill
data, _, _, err := r.load(refURL)
relativeBase := ""
if r.options != nil && r.options.RelativeBase != "" {
relativeBase = r.options.RelativeBase
}
normalizeFileRef(currentRef, relativeBase)
debugLog("current ref normalized file: %s", currentRef.String())
normalizeFileRef(ref, relativeBase)
debugLog("ref normalized file: %s", currentRef.String())
data, _, _, err := r.load(currentRef.GetURL())
if err != nil {
return err
}
@ -265,9 +436,29 @@ func (r *schemaLoader) resolveRef(currentRef, ref *Ref, node, target interface{}
var res interface{}
if currentRef.String() != "" {
res, _, err = currentRef.GetPointer().Get(data)
if err != nil {
if strings.HasPrefix(ref.String(), "#") {
if r.loadingRef != nil {
rr, er := r.loadingRef.Inherits(*ref)
if er != nil {
return er
}
refURL = rr.GetURL()
data, _, _, err = r.load(refURL)
if err != nil {
return err
}
} else {
data = r.root
}
}
res, _, err = ref.GetPointer().Get(data)
if err != nil {
return err
}
}
} else {
res = data
}
@ -276,11 +467,13 @@ func (r *schemaLoader) resolveRef(currentRef, ref *Ref, node, target interface{}
return err
}
}
r.currentRef = currentRef
return nil
}
func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) {
debugLog("loading schema from url: %s", refURL)
toFetch := *refURL
toFetch.Fragment = ""
@ -299,44 +492,51 @@ func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error)
return data, toFetch, fromCache, nil
}
func (r *schemaLoader) Resolve(ref *Ref, target interface{}) error {
if err := r.resolveRef(r.currentRef, ref, r.root, target); err != nil {
return err
return r.resolveRef(r.currentRef, ref, r.root, target)
}
return nil
func (r *schemaLoader) reset() {
ref := r.startingRef
var ptr *jsonpointer.Pointer
if ref != nil {
ptr = ref.GetPointer()
}
type specExpander struct {
spec *Swagger
resolver *schemaLoader
r.currentRef = nextRef(r.root, ref, ptr)
}
// ExpandSpec expands the references in a swagger spec
func ExpandSpec(spec *Swagger) error {
resolver, err := defaultSchemaLoader(spec, nil, nil)
if err != nil {
func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
resolver, err := defaultSchemaLoader(spec, nil, options, nil)
// Just in case this ever returns an error.
if shouldStopOnError(err, resolver.options) {
return err
}
for key, defintition := range spec.Definitions {
if options == nil || !options.SkipSchemas {
for key, definition := range spec.Definitions {
var def *Schema
var err error
if def, err = expandSchema(defintition, []string{"#/definitions/" + key}, resolver); err != nil {
if def, err = expandSchema(definition, []string{"#/definitions/" + key}, resolver); shouldStopOnError(err, resolver.options) {
return err
}
resolver.reset()
spec.Definitions[key] = *def
}
}
for key, parameter := range spec.Parameters {
if err := expandParameter(&parameter, resolver); err != nil {
if err := expandParameter(&parameter, resolver); shouldStopOnError(err, resolver.options) {
return err
}
spec.Parameters[key] = parameter
}
for key, response := range spec.Responses {
if err := expandResponse(&response, resolver); err != nil {
if err := expandResponse(&response, resolver); shouldStopOnError(err, resolver.options) {
return err
}
spec.Responses[key] = response
@ -344,7 +544,7 @@ func ExpandSpec(spec *Swagger) error {
if spec.Paths != nil {
for key, path := range spec.Paths.Paths {
if err := expandPathItem(&path, resolver); err != nil {
if err := expandPathItem(&path, resolver); shouldStopOnError(err, resolver.options) {
return err
}
spec.Paths.Paths[key] = path
@ -354,9 +554,25 @@ func ExpandSpec(spec *Swagger) error {
return nil
}
func shouldStopOnError(err error, opts *ExpandOptions) bool {
if err != nil && !opts.ContinueOnError {
return true
}
if err != nil {
log.Println(err)
}
return false
}
// ExpandSchema expands the refs in the schema object
func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error {
return ExpandSchemaWithBasePath(schema, root, cache, nil)
}
// ExpandSchemaWithBasePath expands the refs in the schema object, base path configured through expand options
func ExpandSchemaWithBasePath(schema *Schema, root interface{}, cache ResolutionCache, opts *ExpandOptions) error {
if schema == nil {
return nil
}
@ -367,18 +583,17 @@ func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error
nrr, _ := NewRef(schema.ID)
var rrr *Ref
if nrr.String() != "" {
switch root.(type) {
switch rt := root.(type) {
case *Schema:
rid, _ := NewRef(root.(*Schema).ID)
rid, _ := NewRef(rt.ID)
rrr, _ = rid.Inherits(nrr)
case *Swagger:
rid, _ := NewRef(root.(*Swagger).ID)
rid, _ := NewRef(rt.ID)
rrr, _ = rid.Inherits(nrr)
}
}
resolver, err := defaultSchemaLoader(root, rrr, cache)
resolver, err := defaultSchemaLoader(root, rrr, opts, cache)
if err != nil {
return err
}
@ -389,7 +604,7 @@ func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error
}
var s *Schema
if s, err = expandSchema(*schema, refs, resolver); err != nil {
return nil
return err
}
*schema = *s
return nil
@ -399,9 +614,17 @@ func expandItems(target Schema, parentRefs []string, resolver *schemaLoader) (*S
if target.Items != nil {
if target.Items.Schema != nil {
t, err := expandSchema(*target.Items.Schema, parentRefs, resolver)
if err != nil {
if target.Items.Schema.ID == "" {
target.Items.Schema.ID = target.ID
if err != nil {
t, err = expandSchema(*target.Items.Schema, parentRefs, resolver)
if err != nil {
return nil, err
}
}
}
}
*target.Items.Schema = *t
}
for i := range target.Items.Schemas {
@ -415,137 +638,173 @@ func expandItems(target Schema, parentRefs []string, resolver *schemaLoader) (*S
return &target, nil
}
func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader) (schema *Schema, err error) {
defer func() {
schema = &target
}()
func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader) (*Schema, error) {
if target.Ref.String() == "" && target.Ref.IsRoot() {
target = *resolver.root.(*Schema)
return
debugLog("skipping expand schema for no ref and root: %v", resolver.root)
return resolver.root.(*Schema), nil
}
// t is the new expanded schema
var t *Schema
for target.Ref.String() != "" {
// var newTarget Schema
pRefs := strings.Join(parentRefs, ",")
pRefs += ","
if strings.Contains(pRefs, target.Ref.String()+",") {
err = nil
return
if swag.ContainsStringsCI(parentRefs, target.Ref.String()) {
return &target, nil
}
if err = resolver.Resolve(&target.Ref, &t); err != nil {
return
if err := resolver.Resolve(&target.Ref, &t); shouldStopOnError(err, resolver.options) {
return &target, err
}
if swag.ContainsStringsCI(parentRefs, target.Ref.String()) {
debugLog("ref already exists in parent")
return &target, nil
}
parentRefs = append(parentRefs, target.Ref.String())
if t != nil {
target = *t
}
}
if t, err = expandItems(target, parentRefs, resolver); err != nil {
return
t, err := expandItems(target, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target = *t
}
for i := range target.AllOf {
if t, err = expandSchema(target.AllOf[i], parentRefs, resolver); err != nil {
return
t, err := expandSchema(target.AllOf[i], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target.AllOf[i] = *t
}
}
for i := range target.AnyOf {
if t, err = expandSchema(target.AnyOf[i], parentRefs, resolver); err != nil {
return
t, err := expandSchema(target.AnyOf[i], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
target.AnyOf[i] = *t
}
for i := range target.OneOf {
if t, err = expandSchema(target.OneOf[i], parentRefs, resolver); err != nil {
return
t, err := expandSchema(target.OneOf[i], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target.OneOf[i] = *t
}
if target.Not != nil {
if t, err = expandSchema(*target.Not, parentRefs, resolver); err != nil {
return
}
if target.Not != nil {
t, err := expandSchema(*target.Not, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
*target.Not = *t
}
for k, _ := range target.Properties {
if t, err = expandSchema(target.Properties[k], parentRefs, resolver); err != nil {
return
}
for k := range target.Properties {
t, err := expandSchema(target.Properties[k], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target.Properties[k] = *t
}
if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
if t, err = expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver); err != nil {
return
}
if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
*target.AdditionalProperties.Schema = *t
}
for k, _ := range target.PatternProperties {
if t, err = expandSchema(target.PatternProperties[k], parentRefs, resolver); err != nil {
return
}
for k := range target.PatternProperties {
t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target.PatternProperties[k] = *t
}
for k, _ := range target.Dependencies {
if target.Dependencies[k].Schema != nil {
if t, err = expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver); err != nil {
return
}
for k := range target.Dependencies {
if target.Dependencies[k].Schema != nil {
t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
*target.Dependencies[k].Schema = *t
}
}
if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
if t, err = expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver); err != nil {
return
}
if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
*target.AdditionalItems.Schema = *t
}
for k, _ := range target.Definitions {
if t, err = expandSchema(target.Definitions[k], parentRefs, resolver); err != nil {
return
}
for k := range target.Definitions {
t, err := expandSchema(target.Definitions[k], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target.Definitions[k] = *t
}
return
}
return &target, nil
}
func expandPathItem(pathItem *PathItem, resolver *schemaLoader) error {
if pathItem == nil {
return nil
}
if pathItem.Ref.String() != "" {
if err := resolver.Resolve(&pathItem.Ref, &pathItem); err != nil {
return err
}
resolver.reset()
pathItem.Ref = Ref{}
}
for idx := range pathItem.Parameters {
if err := expandParameter(&(pathItem.Parameters[idx]), resolver); err != nil {
if err := expandParameter(&(pathItem.Parameters[idx]), resolver); shouldStopOnError(err, resolver.options) {
return err
}
}
if err := expandOperation(pathItem.Get, resolver); err != nil {
if err := expandOperation(pathItem.Get, resolver); shouldStopOnError(err, resolver.options) {
return err
}
if err := expandOperation(pathItem.Head, resolver); err != nil {
if err := expandOperation(pathItem.Head, resolver); shouldStopOnError(err, resolver.options) {
return err
}
if err := expandOperation(pathItem.Options, resolver); err != nil {
if err := expandOperation(pathItem.Options, resolver); shouldStopOnError(err, resolver.options) {
return err
}
if err := expandOperation(pathItem.Put, resolver); err != nil {
if err := expandOperation(pathItem.Put, resolver); shouldStopOnError(err, resolver.options) {
return err
}
if err := expandOperation(pathItem.Post, resolver); err != nil {
if err := expandOperation(pathItem.Post, resolver); shouldStopOnError(err, resolver.options) {
return err
}
if err := expandOperation(pathItem.Patch, resolver); err != nil {
if err := expandOperation(pathItem.Patch, resolver); shouldStopOnError(err, resolver.options) {
return err
}
if err := expandOperation(pathItem.Delete, resolver); err != nil {
if err := expandOperation(pathItem.Delete, resolver); shouldStopOnError(err, resolver.options) {
return err
}
return nil
@ -555,8 +814,9 @@ func expandOperation(op *Operation, resolver *schemaLoader) error {
if op == nil {
return nil
}
for i, param := range op.Parameters {
if err := expandParameter(&param, resolver); err != nil {
if err := expandParameter(&param, resolver); shouldStopOnError(err, resolver.options) {
return err
}
op.Parameters[i] = param
@ -564,11 +824,11 @@ func expandOperation(op *Operation, resolver *schemaLoader) error {
if op.Responses != nil {
responses := op.Responses
if err := expandResponse(responses.Default, resolver); err != nil {
if err := expandResponse(responses.Default, resolver); shouldStopOnError(err, resolver.options) {
return err
}
for code, response := range responses.StatusCodeResponses {
if err := expandResponse(&response, resolver); err != nil {
if err := expandResponse(&response, resolver); shouldStopOnError(err, resolver.options) {
return err
}
responses.StatusCodeResponses[code] = response
@ -582,23 +842,30 @@ func expandResponse(response *Response, resolver *schemaLoader) error {
return nil
}
var parentRefs []string
if response.Ref.String() != "" {
if err := resolver.Resolve(&response.Ref, response); err != nil {
parentRefs = append(parentRefs, response.Ref.String())
if err := resolver.Resolve(&response.Ref, response); shouldStopOnError(err, resolver.options) {
return err
}
resolver.reset()
response.Ref = Ref{}
}
if response.Schema != nil {
parentRefs := []string{response.Schema.Ref.String()}
if err := resolver.Resolve(&response.Schema.Ref, &response.Schema); err != nil {
if !resolver.options.SkipSchemas && response.Schema != nil {
parentRefs = append(parentRefs, response.Schema.Ref.String())
debugLog("response ref: %s", response.Schema.Ref)
if err := resolver.Resolve(&response.Schema.Ref, &response.Schema); shouldStopOnError(err, resolver.options) {
return err
}
if s, err := expandSchema(*response.Schema, parentRefs, resolver); err != nil {
s, err := expandSchema(*response.Schema, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return err
} else {
}
resolver.reset()
*response.Schema = *s
}
}
return nil
}
@ -606,21 +873,28 @@ func expandParameter(parameter *Parameter, resolver *schemaLoader) error {
if parameter == nil {
return nil
}
var parentRefs []string
if parameter.Ref.String() != "" {
if err := resolver.Resolve(&parameter.Ref, parameter); err != nil {
parentRefs = append(parentRefs, parameter.Ref.String())
if err := resolver.Resolve(&parameter.Ref, parameter); shouldStopOnError(err, resolver.options) {
return err
}
resolver.reset()
parameter.Ref = Ref{}
}
if parameter.Schema != nil {
parentRefs := []string{parameter.Schema.Ref.String()}
if err := resolver.Resolve(&parameter.Schema.Ref, &parameter.Schema); err != nil {
if !resolver.options.SkipSchemas && parameter.Schema != nil {
parentRefs = append(parentRefs, parameter.Schema.Ref.String())
if err := resolver.Resolve(&parameter.Schema.Ref, &parameter.Schema); shouldStopOnError(err, resolver.options) {
return err
}
if s, err := expandSchema(*parameter.Schema, parentRefs, resolver); err != nil {
s, err := expandSchema(*parameter.Schema, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return err
} else {
}
resolver.reset()
*parameter.Schema = *s
}
}
return nil
}

View File

@ -16,7 +16,9 @@ package spec
import (
"encoding/json"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/swag"
)
@ -30,6 +32,7 @@ type HeaderProps struct {
type Header struct {
CommonValidations
SimpleSchema
VendorExtensible
HeaderProps
}
@ -158,8 +161,35 @@ func (h *Header) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &h.SimpleSchema); err != nil {
return err
}
if err := json.Unmarshal(data, &h.VendorExtensible); err != nil {
return err
}
if err := json.Unmarshal(data, &h.HeaderProps); err != nil {
return err
}
return nil
}
// JSONLookup look up a value by the json property name
func (p Header) JSONLookup(token string) (interface{}, error) {
if ex, ok := p.Extensions[token]; ok {
return &ex, nil
}
r, _, err := jsonpointer.GetForToken(p.CommonValidations, token)
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
return nil, err
}
if r != nil {
return r, nil
}
r, _, err = jsonpointer.GetForToken(p.SimpleSchema, token)
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
return nil, err
}
if r != nil {
return r, nil
}
r, _, err = jsonpointer.GetForToken(p.HeaderProps, token)
return r, err
}

View File

@ -16,7 +16,9 @@ package spec
import (
"encoding/json"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/swag"
)
@ -60,11 +62,12 @@ type CommonValidations struct {
// Items a limited subset of JSON-Schema's items object.
// It is used by parameter definitions that are not located in "body".
//
// For more information: http://goo.gl/8us55a#items-object-
// For more information: http://goo.gl/8us55a#items-object
type Items struct {
Refable
CommonValidations
SimpleSchema
VendorExtensible
}
// NewItems creates a new instance of items
@ -197,3 +200,20 @@ func (i Items) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b3, b1, b2), nil
}
// JSONLookup look up a value by the json property name
func (p Items) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return &p.Ref, nil
}
r, _, err := jsonpointer.GetForToken(p.CommonValidations, token)
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
return nil, err
}
if r != nil {
return r, nil
}
r, _, err = jsonpointer.GetForToken(p.SimpleSchema, token)
return r, err
}

View File

@ -16,6 +16,7 @@ package spec
import (
"encoding/json"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/swag"
@ -100,15 +101,16 @@ func (p Parameter) JSONLookup(token string) (interface{}, error) {
if token == "$ref" {
return &p.Ref, nil
}
r, _, err := jsonpointer.GetForToken(p.CommonValidations, token)
if err != nil {
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
return nil, err
}
if r != nil {
return r, nil
}
r, _, err = jsonpointer.GetForToken(p.SimpleSchema, token)
if err != nil {
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
return nil, err
}
if r != nil {

View File

@ -55,7 +55,7 @@ func (r *Ref) RemoteURI() string {
}
// IsValidURI returns true when the url the ref points to can be found
func (r *Ref) IsValidURI() bool {
func (r *Ref) IsValidURI(basepaths ...string) bool {
if r.String() == "" {
return true
}
@ -81,14 +81,18 @@ func (r *Ref) IsValidURI() bool {
// check for local file
pth := v
if r.HasURLPathOnly {
p, e := filepath.Abs(pth)
base := "."
if len(basepaths) > 0 {
base = filepath.Dir(filepath.Join(basepaths...))
}
p, e := filepath.Abs(filepath.ToSlash(filepath.Join(base, pth)))
if e != nil {
return false
}
pth = p
}
fi, err := os.Stat(pth)
fi, err := os.Stat(filepath.ToSlash(pth))
if err != nil {
return false
}
@ -116,25 +120,18 @@ func NewRef(refURI string) (Ref, error) {
return Ref{Ref: ref}, nil
}
// MustCreateRef creates a ref object but
// MustCreateRef creates a ref object but panics when refURI is invalid.
// Use the NewRef method for a version that returns an error.
func MustCreateRef(refURI string) Ref {
return Ref{Ref: jsonreference.MustCreateRef(refURI)}
}
// // NewResolvedRef creates a resolved ref
// func NewResolvedRef(refURI string, data interface{}) Ref {
// return Ref{
// Ref: jsonreference.MustCreateRef(refURI),
// Resolved: data,
// }
// }
// MarshalJSON marshals this ref into a JSON object
func (r Ref) MarshalJSON() ([]byte, error) {
str := r.String()
if str == "" {
if r.IsRoot() {
return []byte(`{"$ref":"#"}`), nil
return []byte(`{"$ref":""}`), nil
}
return []byte("{}"), nil
}

View File

@ -17,6 +17,7 @@ package spec
import (
"encoding/json"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/swag"
)
@ -34,6 +35,19 @@ type ResponseProps struct {
type Response struct {
Refable
ResponseProps
VendorExtensible
}
// JSONLookup look up a value by the json property name
func (p Response) JSONLookup(token string) (interface{}, error) {
if ex, ok := p.Extensions[token]; ok {
return &ex, nil
}
if token == "$ref" {
return &p.Ref, nil
}
r, _, err := jsonpointer.GetForToken(p.ResponseProps, token)
return r, err
}
// UnmarshalJSON hydrates this items instance with the data from JSON
@ -44,6 +58,9 @@ func (r *Response) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &r.Refable); err != nil {
return err
}
if err := json.Unmarshal(data, &r.VendorExtensible); err != nil {
return err
}
return nil
}
@ -57,7 +74,11 @@ func (r Response) MarshalJSON() ([]byte, error) {
if err != nil {
return nil, err
}
return swag.ConcatJSON(b1, b2), nil
b3, err := json.Marshal(r.VendorExtensible)
if err != nil {
return nil, err
}
return swag.ConcatJSON(b1, b2, b3), nil
}
// NewResponse creates a new response instance

View File

@ -51,7 +51,7 @@ func (r Responses) JSONLookup(token string) (interface{}, error) {
}
if i, err := strconv.Atoi(token); err == nil {
if scr, ok := r.StatusCodeResponses[i]; ok {
return &scr, nil
return scr, nil
}
}
return nil, fmt.Errorf("object has no field %q", token)

View File

@ -201,8 +201,8 @@ func (r *SchemaURL) UnmarshalJSON(data []byte) error {
type SchemaProps struct {
ID string `json:"id,omitempty"`
Ref Ref `json:"-,omitempty"`
Schema SchemaURL `json:"-,omitempty"`
Ref Ref `json:"-"`
Schema SchemaURL `json:"-"`
Description string `json:"description,omitempty"`
Type StringOrArray `json:"type,omitempty"`
Format string `json:"format,omitempty"`
@ -269,7 +269,7 @@ func (s Schema) JSONLookup(token string) (interface{}, error) {
}
r, _, err := jsonpointer.GetForToken(s.SchemaProps, token)
if r != nil || err != nil {
if r != nil || (err != nil && !strings.HasPrefix(err.Error(), "object has no field")) {
return r, err
}
r, _, err = jsonpointer.GetForToken(s.SwaggerSchemaProps, token)

View File

@ -16,6 +16,8 @@ package spec
import "encoding/json"
//go:generate curl -L --progress -o ./schemas/v2/schema.json http://swagger.io/v2/schema.json
//go:generate curl -L --progress -o ./schemas/jsonschema-draft-04.json http://json-schema.org/draft-04/schema
//go:generate go-bindata -pkg=spec -prefix=./schemas -ignore=.*\.md ./schemas/...
//go:generate perl -pi -e s,Json,JSON,g bindata.go
@ -27,9 +29,14 @@ const (
)
var (
jsonSchema *Schema
swaggerSchema *Schema
)
func init() {
jsonSchema = MustLoadJSONSchemaDraft04()
swaggerSchema = MustLoadSwagger20Schema()
)
}
// MustLoadJSONSchemaDraft04 panics when Swagger20Schema returns an error
func MustLoadJSONSchemaDraft04() *Schema {

View File

@ -77,7 +77,7 @@ type SwaggerProps struct {
Host string `json:"host,omitempty"`
BasePath string `json:"basePath,omitempty"` // must start with a leading "/"
Paths *Paths `json:"paths"` // required
Definitions Definitions `json:"definitions"`
Definitions Definitions `json:"definitions,omitempty"`
Parameters map[string]Parameter `json:"parameters,omitempty"`
Responses map[string]Response `json:"responses,omitempty"`
SecurityDefinitions SecurityDefinitions `json:"securityDefinitions,omitempty"`
@ -156,7 +156,7 @@ func (s SchemaOrStringArray) MarshalJSON() ([]byte, error) {
if s.Schema != nil {
return json.Marshal(s.Schema)
}
return nil, nil
return []byte("null"), nil
}
// UnmarshalJSON converts this schema object or array from a JSON structure

View File

@ -1 +0,0 @@
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.darMHuSYnLhsknrnqjdXLGcyEJ5kM13yQbLGi2UWcKXpddHvPT3dMCnP5y7e27c76R2HFnvr56BqDMI-x0zyuQtjYKzSFUGjOcEhOH3OL1Hxu-Cm2z2443BS8asXNA3sEveAGQvG68jffm7CvtEtMo57wBpggI7UHbfIdLMK47s1EDpC4xanjiS1BJe5NC_Ikf0jfa6vf18oggbjxuoqSEvSNVdNRyZwG_npFaZFJzvdtehTG7GqunWjGiqBb81qNcEdzSdIZW7A_Esv4U-nOL5gGr55E9jKhv3bX4Z9ygGcSrJ3NCgR_3zRhYKkPEAOXIqQKfL6-h82BY--cHq9uw.NHL3X-1tjb8a8zF7.eRmLvOG32e7260K8rkI-HmUGG5Gb6Hu-obKKxBqHd-vVzsKnwTVJavLWktPqlXGMsnDt7MimSysNqsWemMUEviW2p3godnvjOOXTDb-RAtQ-39rvxnZ2bN8qwUVFrdiKTZD06l60yTeLW7L1psyLj50NxklFObhkpUcK5uukxLXT1SzGM9aY6_3dzW4HU9pZGQrIH1pj1UzvWIjz7iIzE1a37DHBN-FiYSASsw01v1SSIFr34gwlGcqzGfJBonffVrM4ordm3IiVm50Zvr25DrmYTKrQpJRB-KOvYxBNYDzjCaanHDyWGUGN44FUx38azHHEVBTaiOM7xwPeyCc-xTTv8WXGnL1xrhL3M_jNuwnbAjzL9X_li7KUSeYajwhGihdMZaHLYaqxh3NNnbPfYhR6sBxu8vaT1Sc4eE84QC4dV4OaAglPvrPdWL-DC7OYQyoPU8u9ggwUQHpFUzJyD549T_Tlgn-2Cw7kTe41VonH9HkoXGANDGtQCGTqTIEeFQJ3MDDucf5VteFP8_SJPfyJYxpStFt5U1AuULV9sXmpGQL_-GGFXowd0X0bHxFeo_eu1vm-oTqQQNbKRnyt5V3n4U9jhOUGnnIBy3JOG3DA2YhVJsHdlLZ9vaDpFYcxts4.SqYfES30FqVSufGbPZ6YXA

View File

@ -1,32 +0,0 @@
clone:
path: github.com/go-openapi/swag
matrix:
GO_VERSION:
- "1.6"
build:
integration:
image: golang:$$GO_VERSION
pull: true
commands:
- go get -u github.com/stretchr/testify
- go get -u github.com/mailru/easyjson
- go test -race
- go test -v -cover -coverprofile=coverage.out -covermode=count ./...
notify:
slack:
channel: bots
webhook_url: $$SLACK_URL
username: drone
publish:
coverage:
server: https://coverage.vmware.run
token: $$GITHUB_TOKEN
# threshold: 70
# must_increase: true
when:
matrix:
GO_VERSION: "1.6"

26
vendor/github.com/go-openapi/swag/.editorconfig generated vendored Normal file
View File

@ -0,0 +1,26 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# Set default charset
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
charset = utf-8
# Tab indentation (no size specified)
[*.go]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

View File

@ -1,13 +0,0 @@
approve_by_comment: true
approve_regex: '^(:shipit:|:\+1:|\+1|LGTM|lgtm|Approved)'
reject_regex: ^[Rr]ejected
reset_on_push: false
reviewers:
members:
- casualjim
- chancez
- frapposelli
- vburenin
- pytlesk4
name: pullapprove
required: 1

14
vendor/github.com/go-openapi/swag/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,14 @@
language: go
go:
- 1.8
install:
- go get -u github.com/stretchr/testify
- go get -u github.com/mailru/easyjson
- go get -u gopkg.in/yaml.v2
script:
- go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic ./...
after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
slack:
secure: QUWvCkBBK09GF7YtEvHHVt70JOkdlNBG0nIKu/5qc4/nW5HP8I2w0SEf/XR2je0eED1Qe3L/AfMCWwrEj+IUZc3l4v+ju8X8R3Lomhme0Eb0jd1MTMCuPcBT47YCj0M7RON7vXtbFfm1hFJ/jLe5+9FXz0hpXsR24PJc5ZIi/ogNwkaPqG4BmndzecpSh0vc2FJPZUD9LT0I09REY/vXR0oQAalLkW0asGD5taHZTUZq/kBpsNxaAFrLM23i4mUcf33M5fjLpvx5LRICrX/57XpBrDh2TooBU6Qj3CgoY0uPRYUmSNxbVx1czNzl2JtEpb5yjoxfVPQeg0BvQM00G8LJINISR+ohrjhkZmAqchDupAX+yFrxTtORa78CtnIL6z/aTNlgwwVD8kvL/1pFA/JWYmKDmz93mV/+6wubGzNSQCstzjkFA4/iZEKewKUoRIAi/fxyscP6L/rCpmY/4llZZvrnyTqVbt6URWpopUpH4rwYqreXAtJxJsfBJIeSmUIiDIOMGkCTvyTEW3fWGmGoqWtSHLoaWDyAIGb7azb+KvfpWtEcoPFWfSWU+LGee0A/YsUhBl7ADB9A0CJEuR8q4BPpKpfLwPKSiKSAXL7zDkyjExyhtgqbSl2jS+rKIHOZNL8JkCcTP2MKMVd563C5rC5FMKqu3S9m2b6380E=

View File

@ -10,11 +10,13 @@ go_library(
"net.go",
"path.go",
"util.go",
"yaml.go",
],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mailru/easyjson/jlexer:go_default_library",
"//vendor/github.com/mailru/easyjson/jwriter:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
],
)

View File

@ -1,4 +1,4 @@
# Swag [![Build Status](https://ci.vmware.run/api/badges/go-openapi/swag/status.svg)](https://ci.vmware.run/go-openapi/swag) [![Coverage](https://coverage.vmware.run/badges/go-openapi/swag/coverage.svg)](https://coverage.vmware.run/go-openapi/swag) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
# Swag [![Build Status](https://travis-ci.org/go-openapi/swag.svg?branch=master)](https://travis-ci.org/go-openapi/swag) [![codecov](https://codecov.io/gh/go-openapi/swag/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/swag) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/swag/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/swag?status.svg)](http://godoc.org/github.com/go-openapi/swag)

View File

@ -159,7 +159,7 @@ func FormatInt16(value int16) string {
// FormatInt32 turns an int32 into a string
func FormatInt32(value int32) string {
return strconv.FormatInt(int64(value), 10)
return strconv.Itoa(int(value))
}
// FormatInt64 turns an int64 into a string

View File

@ -17,6 +17,7 @@ package swag
import (
"bytes"
"encoding/json"
"log"
"reflect"
"strings"
"sync"
@ -110,28 +111,40 @@ func ConcatJSON(blobs ...[]byte) []byte {
if len(b) < 3 { // yep empty but also the last one, so closing this thing
if i == last && a > 0 {
buf.WriteByte(closing)
if err := buf.WriteByte(closing); err != nil {
log.Println(err)
}
}
continue
}
idx = 0
if a > 0 { // we need to join with a comma for everything beyond the first non-empty item
buf.WriteByte(comma)
if err := buf.WriteByte(comma); err != nil {
log.Println(err)
}
idx = 1 // this is not the first or the last so we want to drop the leading bracket
}
if i != last { // not the last one, strip brackets
buf.Write(b[idx : len(b)-1])
if _, err := buf.Write(b[idx : len(b)-1]); err != nil {
log.Println(err)
}
} else { // last one, strip only the leading bracket
buf.Write(b[idx:])
if _, err := buf.Write(b[idx:]); err != nil {
log.Println(err)
}
}
a++
}
// somehow it ended up being empty, so provide a default value
if buf.Len() == 0 {
buf.WriteByte(opening)
buf.WriteByte(closing)
if err := buf.WriteByte(opening); err != nil {
log.Println(err)
}
if err := buf.WriteByte(closing); err != nil {
log.Println(err)
}
}
return buf.Bytes()
}
@ -139,15 +152,23 @@ func ConcatJSON(blobs ...[]byte) []byte {
// ToDynamicJSON turns an object into a properly JSON typed structure
func ToDynamicJSON(data interface{}) interface{} {
// TODO: convert straight to a json typed map (mergo + iterate?)
b, _ := json.Marshal(data)
b, err := json.Marshal(data)
if err != nil {
log.Println(err)
}
var res interface{}
json.Unmarshal(b, &res)
if err := json.Unmarshal(b, &res); err != nil {
log.Println(err)
}
return res
}
// FromDynamicJSON turns an object into a properly JSON typed structure
func FromDynamicJSON(data, target interface{}) error {
b, _ := json.Marshal(data)
b, err := json.Marshal(data)
if err != nil {
log.Println(err)
}
return json.Unmarshal(b, target)
}
@ -216,6 +237,8 @@ func newNameIndex(tpe reflect.Type) nameIndex {
// GetJSONNames gets all the json property names for a type
func (n *NameProvider) GetJSONNames(subject interface{}) []string {
n.lock.Lock()
defer n.lock.Unlock()
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
names, ok := n.index[tpe]
if !ok {
@ -237,6 +260,8 @@ func (n *NameProvider) GetJSONName(subject interface{}, name string) (string, bo
// GetJSONNameForType gets the json name for a go property name on a given type
func (n *NameProvider) GetJSONNameForType(tpe reflect.Type, name string) (string, bool) {
n.lock.Lock()
defer n.lock.Unlock()
names, ok := n.index[tpe]
if !ok {
names = n.makeNameIndex(tpe)
@ -246,8 +271,6 @@ func (n *NameProvider) GetJSONNameForType(tpe reflect.Type, name string) (string
}
func (n *NameProvider) makeNameIndex(tpe reflect.Type) nameIndex {
n.lock.Lock()
defer n.lock.Unlock()
names := newNameIndex(tpe)
n.index[tpe] = names
return names
@ -261,6 +284,8 @@ func (n *NameProvider) GetGoName(subject interface{}, name string) (string, bool
// GetGoNameForType gets the go name for a given type for a json property name
func (n *NameProvider) GetGoNameForType(tpe reflect.Type, name string) (string, bool) {
n.lock.Lock()
defer n.lock.Unlock()
names, ok := n.index[tpe]
if !ok {
names = n.makeNameIndex(tpe)

View File

@ -17,13 +17,25 @@ package swag
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"path/filepath"
"strings"
"time"
)
// LoadHTTPTimeout the default timeout for load requests
var LoadHTTPTimeout = 30 * time.Second
// LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in
func LoadFromFileOrHTTP(path string) ([]byte, error) {
return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes)(path)
return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(path)
}
// LoadFromFileOrHTTPWithTimeout loads the bytes from a file or a remote http server based on the path passed in
// timeout arg allows for per request overriding of the request timeout
func LoadFromFileOrHTTPWithTimeout(path string, timeout time.Duration) ([]byte, error) {
return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes(timeout))(path)
}
// LoadStrategy returns a loader function for a given path or uri
@ -31,15 +43,27 @@ func LoadStrategy(path string, local, remote func(string) ([]byte, error)) func(
if strings.HasPrefix(path, "http") {
return remote
}
return local
return func(pth string) ([]byte, error) { return local(filepath.FromSlash(pth)) }
}
func loadHTTPBytes(path string) ([]byte, error) {
resp, err := http.Get(path)
func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) {
return func(path string) ([]byte, error) {
client := &http.Client{Timeout: timeout}
req, err := http.NewRequest("GET", path, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
defer func() {
if resp != nil {
if e := resp.Body.Close(); e != nil {
log.Println(e)
}
}
}()
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status)
@ -47,3 +71,4 @@ func loadHTTPBytes(path string) ([]byte, error) {
return ioutil.ReadAll(resp.Body)
}
}

View File

@ -47,6 +47,9 @@ func FindInGoSearchPath(pkg string) string {
// FullGoSearchPath gets the search paths for finding packages
func FullGoSearchPath() string {
allPaths := os.Getenv(GOPATHKey)
if allPaths == "" {
allPaths = filepath.Join(os.Getenv("HOME"), "go")
}
if allPaths != "" {
allPaths = strings.Join([]string{allPaths, runtime.GOROOT()}, ":")
} else {

View File

@ -20,10 +20,12 @@ import (
"regexp"
"sort"
"strings"
"unicode"
)
// Taken from https://github.com/golang/lint/blob/1fab560e16097e5b69afb66eb93aab843ef77845/lint.go#L663-L698
// Taken from https://github.com/golang/lint/blob/3390df4df2787994aea98de825b964ac7944b817/lint.go#L732-L769
var commonInitialisms = map[string]bool{
"ACL": true,
"API": true,
"ASCII": true,
"CPU": true,
@ -44,19 +46,21 @@ var commonInitialisms = map[string]bool{
"RPC": true,
"SLA": true,
"SMTP": true,
"SQL": true,
"SSH": true,
"TCP": true,
"TLS": true,
"TTL": true,
"UDP": true,
"UUID": true,
"UID": true,
"UI": true,
"UID": true,
"UUID": true,
"URI": true,
"URL": true,
"UTF8": true,
"VM": true,
"XML": true,
"XMPP": true,
"XSRF": true,
"XSS": true,
}
@ -246,6 +250,9 @@ func ToJSONName(name string) string {
// ToVarName camelcases a name which can be underscored or pascal cased
func ToVarName(name string) string {
res := ToGoName(name)
if _, ok := commonInitialisms[res]; ok {
return lower(res)
}
if len(res) <= 1 {
return lower(res)
}
@ -263,7 +270,18 @@ func ToGoName(name string) string {
}
out = append(out, uw)
}
return strings.Join(out, "")
result := strings.Join(out, "")
if len(result) > 0 {
ud := upper(result[:1])
ru := []rune(ud)
if unicode.IsUpper(ru[0]) {
result = ud + result[1:]
} else {
result = "X" + ud + result[1:]
}
}
return result
}
// ContainsStringsCI searches a slice of strings for a case-insensitive match

215
vendor/github.com/go-openapi/swag/yaml.go generated vendored Normal file
View File

@ -0,0 +1,215 @@
// Copyright 2015 go-swagger maintainers
//
// 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 swag
import (
"encoding/json"
"fmt"
"path/filepath"
"strconv"
"github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
yaml "gopkg.in/yaml.v2"
)
// YAMLMatcher matches yaml
func YAMLMatcher(path string) bool {
ext := filepath.Ext(path)
return ext == ".yaml" || ext == ".yml"
}
// YAMLToJSON converts YAML unmarshaled data into json compatible data
func YAMLToJSON(data interface{}) (json.RawMessage, error) {
jm, err := transformData(data)
if err != nil {
return nil, err
}
b, err := WriteJSON(jm)
return json.RawMessage(b), err
}
func BytesToYAMLDoc(data []byte) (interface{}, error) {
var canary map[interface{}]interface{} // validate this is an object and not a different type
if err := yaml.Unmarshal(data, &canary); err != nil {
return nil, err
}
var document yaml.MapSlice // preserve order that is present in the document
if err := yaml.Unmarshal(data, &document); err != nil {
return nil, err
}
return document, nil
}
type JSONMapSlice []JSONMapItem
func (s JSONMapSlice) MarshalJSON() ([]byte, error) {
w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty}
s.MarshalEasyJSON(w)
return w.BuildBytes()
}
func (s JSONMapSlice) MarshalEasyJSON(w *jwriter.Writer) {
w.RawByte('{')
ln := len(s)
last := ln - 1
for i := 0; i < ln; i++ {
s[i].MarshalEasyJSON(w)
if i != last { // last item
w.RawByte(',')
}
}
w.RawByte('}')
}
func (s *JSONMapSlice) UnmarshalJSON(data []byte) error {
l := jlexer.Lexer{Data: data}
s.UnmarshalEasyJSON(&l)
return l.Error()
}
func (s *JSONMapSlice) UnmarshalEasyJSON(in *jlexer.Lexer) {
if in.IsNull() {
in.Skip()
return
}
var result JSONMapSlice
in.Delim('{')
for !in.IsDelim('}') {
var mi JSONMapItem
mi.UnmarshalEasyJSON(in)
result = append(result, mi)
}
*s = result
}
type JSONMapItem struct {
Key string
Value interface{}
}
func (s JSONMapItem) MarshalJSON() ([]byte, error) {
w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty}
s.MarshalEasyJSON(w)
return w.BuildBytes()
}
func (s JSONMapItem) MarshalEasyJSON(w *jwriter.Writer) {
w.String(s.Key)
w.RawByte(':')
w.Raw(WriteJSON(s.Value))
}
func (s *JSONMapItem) UnmarshalEasyJSON(in *jlexer.Lexer) {
key := in.UnsafeString()
in.WantColon()
value := in.Interface()
in.WantComma()
s.Key = key
s.Value = value
}
func (s *JSONMapItem) UnmarshalJSON(data []byte) error {
l := jlexer.Lexer{Data: data}
s.UnmarshalEasyJSON(&l)
return l.Error()
}
func transformData(input interface{}) (out interface{}, err error) {
switch in := input.(type) {
case yaml.MapSlice:
o := make(JSONMapSlice, len(in))
for i, mi := range in {
var nmi JSONMapItem
switch k := mi.Key.(type) {
case string:
nmi.Key = k
case int:
nmi.Key = strconv.Itoa(k)
default:
return nil, fmt.Errorf("types don't match expect map key string or int got: %T", mi.Key)
}
v, err := transformData(mi.Value)
if err != nil {
return nil, err
}
nmi.Value = v
o[i] = nmi
}
return o, nil
case map[interface{}]interface{}:
o := make(JSONMapSlice, 0, len(in))
for ke, va := range in {
var nmi JSONMapItem
switch k := ke.(type) {
case string:
nmi.Key = k
case int:
nmi.Key = strconv.Itoa(k)
default:
return nil, fmt.Errorf("types don't match expect map key string or int got: %T", ke)
}
v, err := transformData(va)
if err != nil {
return nil, err
}
nmi.Value = v
o = append(o, nmi)
}
return o, nil
case []interface{}:
len1 := len(in)
o := make([]interface{}, len1)
for i := 0; i < len1; i++ {
o[i], err = transformData(in[i])
if err != nil {
return nil, err
}
}
return o, nil
}
return input, nil
}
// YAMLDoc loads a yaml document from either http or a file and converts it to json
func YAMLDoc(path string) (json.RawMessage, error) {
yamlDoc, err := YAMLData(path)
if err != nil {
return nil, err
}
data, err := YAMLToJSON(yamlDoc)
if err != nil {
return nil, err
}
return json.RawMessage(data), nil
}
// YAMLData loads a yaml document from either http or a file
func YAMLData(path string) (interface{}, error) {
data, err := LoadFromFileOrHTTP(path)
if err != nil {
return nil, err
}
return BytesToYAMLDoc(data)
}

View File

@ -179,18 +179,25 @@ func (b *Buffer) DumpTo(w io.Writer) (written int, err error) {
}
// BuildBytes creates a single byte slice with all the contents of the buffer. Data is
// copied if it does not fit in a single chunk.
func (b *Buffer) BuildBytes() []byte {
// copied if it does not fit in a single chunk. You can optionally provide one byte
// slice as argument that it will try to reuse.
func (b *Buffer) BuildBytes(reuse ...[]byte) []byte {
if len(b.bufs) == 0 {
ret := b.Buf
b.toPool = nil
b.Buf = nil
return ret
}
ret := make([]byte, 0, b.Size())
var ret []byte
size := b.Size()
// If we got a buffer as argument and it is big enought, reuse it.
if len(reuse) == 1 && cap(reuse[0]) >= size {
ret = reuse[0][:0]
} else {
ret = make([]byte, 0, size)
}
for _, buf := range b.bufs {
ret = append(ret, buf...)
putBuf(buf)
@ -205,3 +212,59 @@ func (b *Buffer) BuildBytes() []byte {
return ret
}
type readCloser struct {
offset int
bufs [][]byte
}
func (r *readCloser) Read(p []byte) (n int, err error) {
for _, buf := range r.bufs {
// Copy as much as we can.
x := copy(p[n:], buf[r.offset:])
n += x // Increment how much we filled.
// Did we empty the whole buffer?
if r.offset+x == len(buf) {
// On to the next buffer.
r.offset = 0
r.bufs = r.bufs[1:]
// We can release this buffer.
putBuf(buf)
} else {
r.offset += x
}
if n == len(p) {
break
}
}
// No buffers left or nothing read?
if len(r.bufs) == 0 {
err = io.EOF
}
return
}
func (r *readCloser) Close() error {
// Release all remaining buffers.
for _, buf := range r.bufs {
putBuf(buf)
}
// In case Close gets called multiple times.
r.bufs = nil
return nil
}
// ReadCloser creates an io.ReadCloser with all the contents of the buffer.
func (b *Buffer) ReadCloser() io.ReadCloser {
ret := &readCloser{0, append(b.bufs, b.Buf)}
b.bufs = nil
b.toPool = nil
b.Buf = nil
return ret
}

View File

@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"bytestostr.go",
"error.go",
"lexer.go",
],

24
vendor/github.com/mailru/easyjson/jlexer/bytestostr.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
// This file will only be included to the build if neither
// easyjson_nounsafe nor appengine build tag is set. See README notes
// for more details.
//+build !easyjson_nounsafe
//+build !appengine
package jlexer
import (
"reflect"
"unsafe"
)
// bytesToStr creates a string pointing at the slice to avoid copying.
//
// Warning: the string returned by the function should be used with care, as the whole input data
// chunk may be either blocked from being freed by GC because of a single string or the buffer.Data
// may be garbage-collected even when the string exists.
func bytesToStr(data []byte) string {
h := (*reflect.SliceHeader)(unsafe.Pointer(&data))
shdr := reflect.StringHeader{Data: h.Data, Len: h.Len}
return *(*string)(unsafe.Pointer(&shdr))
}

View File

@ -0,0 +1,13 @@
// This file is included to the build if any of the buildtags below
// are defined. Refer to README notes for more details.
//+build easyjson_nounsafe appengine
package jlexer
// bytesToStr creates a string normally from []byte
//
// Note that this method is roughly 1.5x slower than using the 'unsafe' method.
func bytesToStr(data []byte) string {
return string(data)
}

View File

@ -5,12 +5,14 @@
package jlexer
import (
"encoding/base64"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"unicode"
"unicode/utf16"
"unicode/utf8"
"unsafe"
)
// tokenKind determines type of a token.
@ -45,11 +47,13 @@ type Lexer struct {
firstElement bool // Whether current element is the first in array or an object.
wantSep byte // A comma or a colon character, which need to occur before a token.
err error // Error encountered during lexing, if any.
UseMultipleErrors bool // If we want to use multiple errors.
fatalError error // Fatal error occurred during lexing. It is usually a syntax error.
multipleErrors []*LexerError // Semantic errors occurred during lexing. Marshalling will be continued after finding this errors.
}
// fetchToken scans the input for the next token.
func (r *Lexer) fetchToken() {
// FetchToken scans the input for the next token.
func (r *Lexer) FetchToken() {
r.token.kind = tokenUndef
r.start = r.pos
@ -147,7 +151,7 @@ func (r *Lexer) fetchToken() {
return
}
}
r.err = io.EOF
r.fatalError = io.EOF
return
}
@ -199,17 +203,6 @@ func (r *Lexer) fetchFalse() {
}
}
// bytesToStr creates a string pointing at the slice to avoid copying.
//
// Warning: the string returned by the function should be used with care, as the whole input data
// chunk may be either blocked from being freed by GC because of a single string or the buffer.Data
// may be garbage-collected even when the string exists.
func bytesToStr(data []byte) string {
h := (*reflect.SliceHeader)(unsafe.Pointer(&data))
shdr := reflect.StringHeader{h.Data, h.Len}
return *(*string)(unsafe.Pointer(&shdr))
}
// fetchNumber scans a number literal token.
func (r *Lexer) fetchNumber() {
hasE := false
@ -265,6 +258,33 @@ func findStringLen(data []byte) (hasEscapes bool, length int) {
return false, len(data)
}
// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
// or it returns -1.
func getu4(s []byte) rune {
if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
return -1
}
var val rune
for i := 2; i < len(s) && i < 6; i++ {
var v byte
c := s[i]
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
v = c - '0'
case 'a', 'b', 'c', 'd', 'e', 'f':
v = c - 'a' + 10
case 'A', 'B', 'C', 'D', 'E', 'F':
v = c - 'A' + 10
default:
return -1
}
val <<= 4
val |= rune(v)
}
return val
}
// processEscape processes a single escape sequence and returns number of bytes processed.
func (r *Lexer) processEscape(data []byte) (int, error) {
if len(data) < 2 {
@ -292,39 +312,28 @@ func (r *Lexer) processEscape(data []byte) (int, error) {
r.token.byteValue = append(r.token.byteValue, '\t')
return 2, nil
case 'u':
default:
return 0, fmt.Errorf("syntax error")
rr := getu4(data)
if rr < 0 {
return 0, errors.New("syntax error")
}
var val rune
for i := 2; i < len(data) && i < 6; i++ {
var v byte
c = data[i]
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
v = c - '0'
case 'a', 'b', 'c', 'd', 'e', 'f':
v = c - 'a' + 10
case 'A', 'B', 'C', 'D', 'E', 'F':
v = c - 'A' + 10
default:
return 0, fmt.Errorf("syntax error")
read := 6
if utf16.IsSurrogate(rr) {
rr1 := getu4(data[read:])
if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
read += 6
rr = dec
} else {
rr = unicode.ReplacementChar
}
val <<= 4
val |= rune(v)
}
l := utf8.RuneLen(val)
if l == -1 {
return 0, fmt.Errorf("invalid unicode escape")
}
var d [4]byte
utf8.EncodeRune(d[:], val)
r.token.byteValue = append(r.token.byteValue, d[:l]...)
return 6, nil
s := utf8.EncodeRune(d[:], rr)
r.token.byteValue = append(r.token.byteValue, d[:s]...)
return read, nil
}
return 0, errors.New("syntax error")
}
// fetchString scans a string literal token.
@ -368,11 +377,11 @@ func (r *Lexer) fetchString() {
// scanToken scans the next token if no token is currently available in the lexer.
func (r *Lexer) scanToken() {
if r.token.kind != tokenUndef || r.err != nil {
if r.token.kind != tokenUndef || r.fatalError != nil {
return
}
r.fetchToken()
r.FetchToken()
}
// consume resets the current token to allow scanning the next one.
@ -383,20 +392,20 @@ func (r *Lexer) consume() {
// Ok returns true if no error (including io.EOF) was encountered during scanning.
func (r *Lexer) Ok() bool {
return r.err == nil
return r.fatalError == nil
}
const maxErrorContextLen = 13
func (r *Lexer) errParse(what string) {
if r.err == nil {
if r.fatalError == nil {
var str string
if len(r.Data)-r.pos <= maxErrorContextLen {
str = string(r.Data)
} else {
str = string(r.Data[r.pos:r.pos+maxErrorContextLen-3]) + "..."
}
r.err = &LexerError{
r.fatalError = &LexerError{
Reason: what,
Offset: r.pos,
Data: str,
@ -409,36 +418,64 @@ func (r *Lexer) errSyntax() {
}
func (r *Lexer) errInvalidToken(expected string) {
if r.err == nil {
if r.fatalError != nil {
return
}
if r.UseMultipleErrors {
r.pos = r.start
r.consume()
r.SkipRecursive()
switch expected {
case "[":
r.token.delimValue = ']'
r.token.kind = tokenDelim
case "{":
r.token.delimValue = '}'
r.token.kind = tokenDelim
}
r.addNonfatalError(&LexerError{
Reason: fmt.Sprintf("expected %s", expected),
Offset: r.start,
Data: string(r.Data[r.start:r.pos]),
})
return
}
var str string
if len(r.token.byteValue) <= maxErrorContextLen {
str = string(r.token.byteValue)
} else {
str = string(r.token.byteValue[:maxErrorContextLen-3]) + "..."
}
r.err = &LexerError{
r.fatalError = &LexerError{
Reason: fmt.Sprintf("expected %s", expected),
Offset: r.pos,
Data: str,
}
}
func (r *Lexer) GetPos() int {
return r.pos
}
// Delim consumes a token and verifies that it is the given delimiter.
func (r *Lexer) Delim(c byte) {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
r.FetchToken()
}
if !r.Ok() || r.token.delimValue != c {
r.consume() // errInvalidToken can change token if UseMultipleErrors is enabled.
r.errInvalidToken(string([]byte{c}))
}
} else {
r.consume()
}
}
// IsDelim returns true if there was no scanning error and next token is the given delimiter.
func (r *Lexer) IsDelim(c byte) bool {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
r.FetchToken()
}
return !r.Ok() || r.token.delimValue == c
}
@ -446,7 +483,7 @@ func (r *Lexer) IsDelim(c byte) bool {
// Null verifies that the next token is null and consumes it.
func (r *Lexer) Null() {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
r.FetchToken()
}
if !r.Ok() || r.token.kind != tokenNull {
r.errInvalidToken("null")
@ -457,7 +494,7 @@ func (r *Lexer) Null() {
// IsNull returns true if the next token is a null keyword.
func (r *Lexer) IsNull() bool {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
r.FetchToken()
}
return r.Ok() && r.token.kind == tokenNull
}
@ -465,7 +502,7 @@ func (r *Lexer) IsNull() bool {
// Skip skips a single token.
func (r *Lexer) Skip() {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
r.FetchToken()
}
r.consume()
}
@ -476,7 +513,6 @@ func (r *Lexer) Skip() {
// Note: no syntax validation is performed on the skipped data.
func (r *Lexer) SkipRecursive() {
r.scanToken()
var start, end byte
if r.token.delimValue == '{' {
@ -505,7 +541,7 @@ func (r *Lexer) SkipRecursive() {
return
}
case c == '\\' && inQuotes:
wasEscape = true
wasEscape = !wasEscape
continue
case c == '"' && inQuotes:
inQuotes = wasEscape
@ -515,7 +551,11 @@ func (r *Lexer) SkipRecursive() {
wasEscape = false
}
r.pos = len(r.Data)
r.err = io.EOF
r.fatalError = &LexerError{
Reason: "EOF reached while skipping array/object or token",
Offset: r.pos,
Data: string(r.Data[r.pos:]),
}
}
// Raw fetches the next item recursively as a data slice
@ -527,48 +567,107 @@ func (r *Lexer) Raw() []byte {
return r.Data[r.start:r.pos]
}
// IsStart returns whether the lexer is positioned at the start
// of an input string.
func (r *Lexer) IsStart() bool {
return r.pos == 0
}
// Consumed reads all remaining bytes from the input, publishing an error if
// there is anything but whitespace remaining.
func (r *Lexer) Consumed() {
if r.pos > len(r.Data) || !r.Ok() {
return
}
for _, c := range r.Data[r.pos:] {
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
r.AddError(&LexerError{
Reason: "invalid character '" + string(c) + "' after top-level value",
Offset: r.pos,
Data: string(r.Data[r.pos:]),
})
return
}
r.pos++
r.start++
}
}
func (r *Lexer) unsafeString() (string, []byte) {
if r.token.kind == tokenUndef && r.Ok() {
r.FetchToken()
}
if !r.Ok() || r.token.kind != tokenString {
r.errInvalidToken("string")
return "", nil
}
bytes := r.token.byteValue
ret := bytesToStr(r.token.byteValue)
r.consume()
return ret, bytes
}
// UnsafeString returns the string value if the token is a string literal.
//
// Warning: returned string may point to the input buffer, so the string should not outlive
// the input buffer. Intended pattern of usage is as an argument to a switch statement.
func (r *Lexer) UnsafeString() string {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
}
if !r.Ok() || r.token.kind != tokenString {
r.errInvalidToken("string")
return ""
ret, _ := r.unsafeString()
return ret
}
ret := bytesToStr(r.token.byteValue)
r.consume()
// UnsafeBytes returns the byte slice if the token is a string literal.
func (r *Lexer) UnsafeBytes() []byte {
_, ret := r.unsafeString()
return ret
}
// String reads a string literal.
func (r *Lexer) String() string {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
r.FetchToken()
}
if !r.Ok() || r.token.kind != tokenString {
r.errInvalidToken("string")
return ""
}
ret := string(r.token.byteValue)
r.consume()
return ret
}
// Bytes reads a string literal and base64 decodes it into a byte slice.
func (r *Lexer) Bytes() []byte {
if r.token.kind == tokenUndef && r.Ok() {
r.FetchToken()
}
if !r.Ok() || r.token.kind != tokenString {
r.errInvalidToken("string")
return nil
}
ret := make([]byte, base64.StdEncoding.DecodedLen(len(r.token.byteValue)))
len, err := base64.StdEncoding.Decode(ret, r.token.byteValue)
if err != nil {
r.fatalError = &LexerError{
Reason: err.Error(),
}
return nil
}
r.consume()
return ret[:len]
}
// Bool reads a true or false boolean keyword.
func (r *Lexer) Bool() bool {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
r.FetchToken()
}
if !r.Ok() || r.token.kind != tokenBool {
r.errInvalidToken("bool")
return false
}
ret := r.token.boolValue
r.consume()
@ -577,12 +676,11 @@ func (r *Lexer) Bool() bool {
func (r *Lexer) number() string {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
r.FetchToken()
}
if !r.Ok() || r.token.kind != tokenNumber {
r.errInvalidToken("number")
return ""
}
ret := bytesToStr(r.token.byteValue)
r.consume()
@ -597,9 +695,11 @@ func (r *Lexer) Uint8() uint8 {
n, err := strconv.ParseUint(s, 10, 8)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return uint8(n)
}
@ -612,9 +712,11 @@ func (r *Lexer) Uint16() uint16 {
n, err := strconv.ParseUint(s, 10, 16)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return uint16(n)
}
@ -627,9 +729,11 @@ func (r *Lexer) Uint32() uint32 {
n, err := strconv.ParseUint(s, 10, 32)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return uint32(n)
}
@ -642,9 +746,11 @@ func (r *Lexer) Uint64() uint64 {
n, err := strconv.ParseUint(s, 10, 64)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return n
}
@ -661,9 +767,11 @@ func (r *Lexer) Int8() int8 {
n, err := strconv.ParseInt(s, 10, 8)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return int8(n)
}
@ -676,9 +784,11 @@ func (r *Lexer) Int16() int16 {
n, err := strconv.ParseInt(s, 10, 16)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return int16(n)
}
@ -691,9 +801,11 @@ func (r *Lexer) Int32() int32 {
n, err := strconv.ParseInt(s, 10, 32)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return int32(n)
}
@ -706,9 +818,11 @@ func (r *Lexer) Int64() int64 {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return n
}
@ -718,61 +832,69 @@ func (r *Lexer) Int() int {
}
func (r *Lexer) Uint8Str() uint8 {
s := r.UnsafeString()
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseUint(s, 10, 8)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: string(b),
})
}
return uint8(n)
}
func (r *Lexer) Uint16Str() uint16 {
s := r.UnsafeString()
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseUint(s, 10, 16)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: string(b),
})
}
return uint16(n)
}
func (r *Lexer) Uint32Str() uint32 {
s := r.UnsafeString()
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseUint(s, 10, 32)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: string(b),
})
}
return uint32(n)
}
func (r *Lexer) Uint64Str() uint64 {
s := r.UnsafeString()
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseUint(s, 10, 64)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: string(b),
})
}
return n
}
@ -782,61 +904,69 @@ func (r *Lexer) UintStr() uint {
}
func (r *Lexer) Int8Str() int8 {
s := r.UnsafeString()
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseInt(s, 10, 8)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: string(b),
})
}
return int8(n)
}
func (r *Lexer) Int16Str() int16 {
s := r.UnsafeString()
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseInt(s, 10, 16)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: string(b),
})
}
return int16(n)
}
func (r *Lexer) Int32Str() int32 {
s := r.UnsafeString()
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseInt(s, 10, 32)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: string(b),
})
}
return int32(n)
}
func (r *Lexer) Int64Str() int64 {
s := r.UnsafeString()
s, b := r.unsafeString()
if !r.Ok() {
return 0
}
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: string(b),
})
}
return n
}
@ -853,9 +983,11 @@ func (r *Lexer) Float32() float32 {
n, err := strconv.ParseFloat(s, 32)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return float32(n)
}
@ -868,27 +1000,53 @@ func (r *Lexer) Float64() float64 {
n, err := strconv.ParseFloat(s, 64)
if err != nil {
r.err = &LexerError{
r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(),
}
Data: s,
})
}
return n
}
func (r *Lexer) Error() error {
return r.err
return r.fatalError
}
func (r *Lexer) AddError(e error) {
if r.err == nil {
r.err = e
if r.fatalError == nil {
r.fatalError = e
}
}
func (r *Lexer) AddNonFatalError(e error) {
r.addNonfatalError(&LexerError{
Offset: r.start,
Data: string(r.Data[r.start:r.pos]),
Reason: e.Error(),
})
}
func (r *Lexer) addNonfatalError(err *LexerError) {
if r.UseMultipleErrors {
// We don't want to add errors with the same offset.
if len(r.multipleErrors) != 0 && r.multipleErrors[len(r.multipleErrors)-1].Offset == err.Offset {
return
}
r.multipleErrors = append(r.multipleErrors, err)
return
}
r.fatalError = err
}
func (r *Lexer) GetNonFatalErrors() []*LexerError {
return r.multipleErrors
}
// Interface fetches an interface{} analogous to the 'encoding/json' package.
func (r *Lexer) Interface() interface{} {
if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken()
r.FetchToken()
}
if !r.Ok() {

View File

@ -2,6 +2,7 @@
package jwriter
import (
"encoding/base64"
"io"
"strconv"
"unicode/utf8"
@ -9,10 +10,22 @@ import (
"github.com/mailru/easyjson/buffer"
)
// Flags describe various encoding options. The behavior may be actually implemented in the encoder, but
// Flags field in Writer is used to set and pass them around.
type Flags int
const (
NilMapAsEmpty Flags = 1 << iota // Encode nil map as '{}' rather than 'null'.
NilSliceAsEmpty // Encode nil slice as '[]' rather than 'null'.
)
// Writer is a JSON writer.
type Writer struct {
Flags Flags
Error error
Buffer buffer.Buffer
NoEscapeHTML bool
}
// Size returns the size of the data that was written out.
@ -25,13 +38,24 @@ func (w *Writer) DumpTo(out io.Writer) (written int, err error) {
return w.Buffer.DumpTo(out)
}
// BuildBytes returns writer data as a single byte slice.
func (w *Writer) BuildBytes() ([]byte, error) {
// BuildBytes returns writer data as a single byte slice. You can optionally provide one byte slice
// as argument that it will try to reuse.
func (w *Writer) BuildBytes(reuse ...[]byte) ([]byte, error) {
if w.Error != nil {
return nil, w.Error
}
return w.Buffer.BuildBytes(), nil
return w.Buffer.BuildBytes(reuse...), nil
}
// ReadCloser returns an io.ReadCloser that can be used to read the data.
// ReadCloser also resets the buffer.
func (w *Writer) ReadCloser() (io.ReadCloser, error) {
if w.Error != nil {
return nil, w.Error
}
return w.Buffer.ReadCloser(), nil
}
// RawByte appends raw binary data to the buffer.
@ -44,7 +68,7 @@ func (w *Writer) RawString(s string) {
w.Buffer.AppendString(s)
}
// RawByte appends raw binary data to the buffer or sets the error if it is given. Useful for
// Raw appends raw binary data to the buffer or sets the error if it is given. Useful for
// calling with results of MarshalJSON-like functions.
func (w *Writer) Raw(data []byte, err error) {
switch {
@ -59,6 +83,34 @@ func (w *Writer) Raw(data []byte, err error) {
}
}
// RawText encloses raw binary data in quotes and appends in to the buffer.
// Useful for calling with results of MarshalText-like functions.
func (w *Writer) RawText(data []byte, err error) {
switch {
case w.Error != nil:
return
case err != nil:
w.Error = err
case len(data) > 0:
w.String(string(data))
default:
w.RawString("null")
}
}
// Base64Bytes appends data to the buffer after base64 encoding it
func (w *Writer) Base64Bytes(data []byte) {
if data == nil {
w.Buffer.AppendString("null")
return
}
w.Buffer.AppendByte('"')
dst := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
base64.StdEncoding.Encode(dst, data)
w.Buffer.AppendBytes(dst)
w.Buffer.AppendByte('"')
}
func (w *Writer) Uint8(n uint8) {
w.Buffer.EnsureSpace(3)
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
@ -200,6 +252,16 @@ func (w *Writer) Bool(v bool) {
const chars = "0123456789abcdef"
func isNotEscapedSingleChar(c byte, escapeHTML bool) bool {
// Note: might make sense to use a table if there are more chars to escape. With 4 chars
// it benchmarks the same.
if escapeHTML {
return c != '<' && c != '>' && c != '&' && c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf
} else {
return c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf
}
}
func (w *Writer) String(s string) {
w.Buffer.AppendByte('"')
@ -209,39 +271,32 @@ func (w *Writer) String(s string) {
p := 0 // last non-escape symbol
for i := 0; i < len(s); {
// single-with character
if c := s[i]; c < utf8.RuneSelf {
var escape byte
switch c {
case '\t':
escape = 't'
case '\r':
escape = 'r'
case '\n':
escape = 'n'
case '\\':
escape = '\\'
case '"':
escape = '"'
case '<', '>':
// do nothing
default:
if c >= 0x20 {
// no escaping is required
c := s[i]
if isNotEscapedSingleChar(c, !w.NoEscapeHTML) {
// single-width character, no escaping is required
i++
continue
}
}
if escape != 0 {
w.Buffer.AppendString(s[p:i])
w.Buffer.AppendByte('\\')
w.Buffer.AppendByte(escape)
} else {
} else if c < utf8.RuneSelf {
// single-with character, need to escape
w.Buffer.AppendString(s[p:i])
switch c {
case '\t':
w.Buffer.AppendString(`\t`)
case '\r':
w.Buffer.AppendString(`\r`)
case '\n':
w.Buffer.AppendString(`\n`)
case '\\':
w.Buffer.AppendString(`\\`)
case '"':
w.Buffer.AppendString(`\"`)
default:
w.Buffer.AppendString(`\u00`)
w.Buffer.AppendByte(chars[c>>4])
w.Buffer.AppendByte(chars[c&0xf])
}
i++
p = i
continue