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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
load( load(
"@io_bazel_rules_go//go:def.bzl", "@io_bazel_rules_go//go:def.bzl",
"go_library", "go_library",
"go_test",
) )
go_library( go_library(
@ -27,3 +28,20 @@ filegroup(
srcs = [":package-srcs"], srcs = [":package-srcs"],
tags = ["automanaged"], 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", "ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff" "Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
}, },
{ {
"ImportPath": "github.com/go-openapi/swag", "ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72" "Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
}, },
{ {
"ImportPath": "github.com/gogo/protobuf/proto", "ImportPath": "github.com/gogo/protobuf/proto",
@ -100,15 +100,15 @@
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/buffer", "ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0" "Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/jlexer", "ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0" "Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/jwriter", "ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0" "Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
}, },
{ {
"ImportPath": "github.com/mxk/go-flowrate/flowrate", "ImportPath": "github.com/mxk/go-flowrate/flowrate",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -120,11 +120,11 @@
}, },
{ {
"ImportPath": "github.com/go-openapi/spec", "ImportPath": "github.com/go-openapi/spec",
"Rev": "6aced65f8501fe1217321abf0749d354824ba2ff" "Rev": "7abd5745472fff5eb3685386d5fb8bf38683154d"
}, },
{ {
"ImportPath": "github.com/go-openapi/swag", "ImportPath": "github.com/go-openapi/swag",
"Rev": "1d0bd113de87027671077d3c71eb3ac5d7dbba72" "Rev": "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
}, },
{ {
"ImportPath": "github.com/gogo/protobuf/proto", "ImportPath": "github.com/gogo/protobuf/proto",
@ -236,15 +236,15 @@
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/buffer", "ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0" "Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/jlexer", "ImportPath": "github.com/mailru/easyjson/jlexer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0" "Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/jwriter", "ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0" "Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
}, },
{ {
"ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil", "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) [![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 package loads
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"path/filepath"
"github.com/go-openapi/analysis" "github.com/go-openapi/analysis"
"github.com/go-openapi/spec" "github.com/go-openapi/spec"
"github.com/go-openapi/swag" "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 // DocMatcher represents a predicate to check if a loader matches
type DocMatcher func(string) bool 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 // AddLoader for a document
func AddLoader(predicate DocMatcher, load DocLoader) { func AddLoader(predicate DocMatcher, load DocLoader) {
@ -49,7 +62,7 @@ func AddLoader(predicate DocMatcher, load DocLoader) {
Fn: load, Fn: load,
Next: prev, Next: prev,
} }
spec.PathLoader = loaders.Fn
} }
type loader struct { type loader struct {
@ -71,11 +84,12 @@ func JSONSpec(path string) (*Document, error) {
// Document represents a swagger spec document // Document represents a swagger spec document
type Document struct { type Document struct {
// specAnalyzer // specAnalyzer
Analyzer *analysis.Spec Analyzer *analysis.Spec
spec *spec.Swagger spec *spec.Swagger
origSpec *spec.Swagger specFilePath string
schema *spec.Schema origSpec *spec.Swagger
raw json.RawMessage schema *spec.Schema
raw json.RawMessage
} }
// Spec loads a new spec document // Spec loads a new spec document
@ -84,23 +98,39 @@ func Spec(path string) (*Document, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var lastErr error
for l := loaders.Next; l != nil; l = l.Next { for l := loaders.Next; l != nil; l = l.Next {
if loaders.Match(specURL.Path) { if loaders.Match(specURL.Path) {
b, err2 := loaders.Fn(path) b, err2 := loaders.Fn(path)
if err2 != nil { if err2 != nil {
return nil, err2 lastErr = err2
continue
} }
return Analyzed(b, "") doc, err := Analyzed(b, "")
if err != nil {
return nil, err
}
if doc != nil {
doc.specFilePath = path
}
return doc, nil
} }
} }
b, err := loaders.Fn(path) if lastErr != nil {
return nil, lastErr
}
b, err := defaultLoader.Fn(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return Analyzed(b, "")
}
var swag20Schema = spec.MustLoadSwagger20Schema() document, err := Analyzed(b, "")
if document != nil {
document.specFilePath = path
}
return document, err
}
// Analyzed creates a new analyzed spec document // Analyzed creates a new analyzed spec document
func Analyzed(data json.RawMessage, version string) (*Document, error) { 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) 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) swspec := new(spec.Swagger)
if err := json.Unmarshal(data, swspec); err != nil { if err := json.Unmarshal(raw, swspec); err != nil {
return nil, err return nil, err
} }
origsqspec := new(spec.Swagger) origsqspec := new(spec.Swagger)
if err := json.Unmarshal(data, origsqspec); err != nil { if err := json.Unmarshal(raw, origsqspec); err != nil {
return nil, err return nil, err
} }
d := &Document{ d := &Document{
Analyzer: analysis.New(swspec), Analyzer: analysis.New(swspec),
schema: swag20Schema, schema: spec.MustLoadSwagger20Schema(),
spec: swspec, spec: swspec,
raw: data, raw: raw,
origSpec: origsqspec, origSpec: origsqspec,
} }
return d, nil return d, nil
} }
// Expanded expands the ref fields in the spec document and returns a new spec document // 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) swspec := new(spec.Swagger)
if err := json.Unmarshal(d.raw, swspec); err != nil { if err := json.Unmarshal(d.raw, swspec); err != nil {
return nil, err 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 return nil, err
} }
dd := &Document{ dd := &Document{
Analyzer: analysis.New(swspec), Analyzer: analysis.New(swspec),
spec: swspec, spec: swspec,
schema: swag20Schema, schema: spec.MustLoadSwagger20Schema(),
raw: d.raw, raw: d.raw,
origSpec: d.origSpec, origSpec: d.origSpec,
} }
@ -201,3 +257,8 @@ func (d *Document) Pristine() *Document {
dd, _ := Analyzed(d.Raw(), d.Version()) dd, _ := Analyzed(d.Raw(), d.Version())
return dd 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,5 +1,5 @@
# 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) [![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)
The object model for OpenAPI specification documents The object model for OpenAPI specification documents

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,10 @@ package spec
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"net/url" "net/url"
"os"
"path/filepath"
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
@ -26,6 +29,18 @@ import (
"github.com/go-openapi/swag" "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 // ResolutionCache a cache for resolving urls
type ResolutionCache interface { type ResolutionCache interface {
Get(string) (interface{}, bool) Get(string) (interface{}, bool)
@ -37,7 +52,11 @@ type simpleCache struct {
store map[string]interface{} store map[string]interface{}
} }
var resCache = initResolutionCache() var resCache ResolutionCache
func init() {
resCache = initResolutionCache()
}
func initResolutionCache() ResolutionCache { func initResolutionCache() ResolutionCache {
return &simpleCache{store: map[string]interface{}{ return &simpleCache{store: map[string]interface{}{
@ -47,8 +66,11 @@ func initResolutionCache() ResolutionCache {
} }
func (s *simpleCache) Get(uri string) (interface{}, bool) { func (s *simpleCache) Get(uri string) (interface{}, bool) {
debugLog("getting %q from resolution cache", uri)
s.lock.Lock() s.lock.Lock()
v, ok := s.store[uri] v, ok := s.store[uri]
debugLog("got %q from resolution cache: %t", uri, ok)
s.lock.Unlock() s.lock.Unlock()
return v, ok return v, ok
} }
@ -59,9 +81,9 @@ func (s *simpleCache) Set(uri string, data interface{}) {
s.lock.Unlock() s.lock.Unlock()
} }
// ResolveRef resolves a reference against a context root // ResolveRefWithBase resolves a reference against a context root with preservation of base path
func ResolveRef(root interface{}, ref *Ref) (*Schema, error) { func ResolveRefWithBase(root interface{}, ref *Ref, opts *ExpandOptions) (*Schema, error) {
resolver, err := defaultSchemaLoader(root, nil, nil) resolver, err := defaultSchemaLoader(root, nil, opts, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -73,9 +95,19 @@ func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
return result, nil 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 // ResolveParameter resolves a paramter reference against a context root
func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -89,7 +121,12 @@ func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) {
// ResolveResponse resolves response a reference against a context root // ResolveResponse resolves response a reference against a context root
func ResolveResponse(root interface{}, ref Ref) (*Response, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -101,23 +138,72 @@ func ResolveResponse(root interface{}, ref Ref) (*Response, error) {
return result, nil 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 { type schemaLoader struct {
loadingRef *Ref loadingRef *Ref
startingRef *Ref startingRef *Ref
currentRef *Ref currentRef *Ref
root interface{} root interface{}
options *ExpandOptions
cache ResolutionCache cache ResolutionCache
loadDoc func(string) (json.RawMessage, error) loadDoc func(string) (json.RawMessage, error)
} }
var idPtr, _ = jsonpointer.New("/id") var idPtr, _ = jsonpointer.New("/id")
var schemaPtr, _ = jsonpointer.New("/$schema")
var refPtr, _ = jsonpointer.New("/$ref") 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 { if cache == nil {
cache = resCache cache = resCache
} }
if expandOptions == nil {
expandOptions = &ExpandOptions{}
}
var ptr *jsonpointer.Pointer var ptr *jsonpointer.Pointer
if ref != nil { if ref != nil {
@ -127,18 +213,16 @@ func defaultSchemaLoader(root interface{}, ref *Ref, cache ResolutionCache) (*sc
currentRef := nextRef(root, ref, ptr) currentRef := nextRef(root, ref, ptr)
return &schemaLoader{ return &schemaLoader{
root: root,
loadingRef: ref, loadingRef: ref,
startingRef: ref, startingRef: ref,
currentRef: currentRef,
root: root,
options: expandOptions,
cache: cache, cache: cache,
loadDoc: func(path string) (json.RawMessage, error) { loadDoc: func(path string) (json.RawMessage, error) {
data, err := swag.LoadFromFileOrHTTP(path) debugLog("fetching document at %q", path)
if err != nil { return PathLoader(path)
return nil, err
}
return json.RawMessage(data), nil
}, },
currentRef: currentRef,
}, nil }, nil
} }
@ -159,6 +243,7 @@ func nextRef(startingNode interface{}, startingRef *Ref, ptr *jsonpointer.Pointe
if startingRef == nil { if startingRef == nil {
return nil return nil
} }
if ptr == nil { if ptr == nil {
return startingRef return startingRef
} }
@ -184,32 +269,111 @@ func nextRef(startingNode interface{}, startingRef *Ref, ptr *jsonpointer.Pointe
refRef, _, _ := refPtr.Get(node) refRef, _, _ := refPtr.Get(node)
if refRef != nil { if refRef != nil {
rf, _ := NewRef(refRef.(string)) var rf Ref
switch value := refRef.(type) {
case string:
rf, _ = NewRef(value)
}
nw, err := ret.Inherits(rf) nw, err := ret.Inherits(rf)
if err != nil { if err != nil {
break 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 ret = nw
} }
} }
return ret 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 { func (r *schemaLoader) resolveRef(currentRef, ref *Ref, node, target interface{}) error {
tgt := reflect.ValueOf(target) tgt := reflect.ValueOf(target)
if tgt.Kind() != reflect.Ptr { if tgt.Kind() != reflect.Ptr {
return fmt.Errorf("resolve ref: target needs to be a pointer") return fmt.Errorf("resolve ref: target needs to be a pointer")
} }
oldRef := currentRef oldRef := currentRef
if currentRef != nil { 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 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 { if err != nil {
return err return err
} }
} }
if currentRef == nil { if currentRef == nil {
currentRef = ref currentRef = ref
} }
@ -245,42 +409,71 @@ func (r *schemaLoader) resolveRef(currentRef, ref *Ref, node, target interface{}
return nil return nil
} }
if refURL.Scheme != "" && refURL.Host != "" { relativeBase := ""
// most definitely take the red pill if r.options != nil && r.options.RelativeBase != "" {
data, _, _, err := r.load(refURL) 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
}
if ((oldRef == nil && currentRef != nil) ||
(oldRef != nil && currentRef == nil) ||
oldRef.String() != currentRef.String()) &&
((oldRef == nil && ref != nil) ||
(oldRef != nil && ref == nil) ||
(oldRef.String() != ref.String())) {
return r.resolveRef(currentRef, ref, data, target)
}
var res interface{}
if currentRef.String() != "" {
res, _, err = currentRef.GetPointer().Get(data)
if err != nil { if err != nil {
return err if strings.HasPrefix(ref.String(), "#") {
} if r.loadingRef != nil {
rr, er := r.loadingRef.Inherits(*ref)
if er != nil {
return er
}
refURL = rr.GetURL()
if ((oldRef == nil && currentRef != nil) || data, _, _, err = r.load(refURL)
(oldRef != nil && currentRef == nil) || if err != nil {
oldRef.String() != currentRef.String()) && return err
((oldRef == nil && ref != nil) || }
(oldRef != nil && ref == nil) || } else {
(oldRef.String() != ref.String())) { data = r.root
}
}
return r.resolveRef(currentRef, ref, data, target) res, _, err = ref.GetPointer().Get(data)
}
var res interface{}
if currentRef.String() != "" {
res, _, err = currentRef.GetPointer().Get(data)
if err != nil { if err != nil {
return err return err
} }
} else {
res = data
} }
} else {
if err := swag.DynamicJSONToStruct(res, target); err != nil { res = data
return err
}
} }
if err := swag.DynamicJSONToStruct(res, target); err != nil {
return err
}
r.currentRef = currentRef
return nil return nil
} }
func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) { func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) {
debugLog("loading schema from url: %s", refURL)
toFetch := *refURL toFetch := *refURL
toFetch.Fragment = "" toFetch.Fragment = ""
@ -299,44 +492,51 @@ func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error)
return data, toFetch, fromCache, nil 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 nil func (r *schemaLoader) Resolve(ref *Ref, target interface{}) error {
return r.resolveRef(r.currentRef, ref, r.root, target)
} }
type specExpander struct { func (r *schemaLoader) reset() {
spec *Swagger ref := r.startingRef
resolver *schemaLoader
var ptr *jsonpointer.Pointer
if ref != nil {
ptr = ref.GetPointer()
}
r.currentRef = nextRef(r.root, ref, ptr)
} }
// ExpandSpec expands the references in a swagger spec // ExpandSpec expands the references in a swagger spec
func ExpandSpec(spec *Swagger) error { func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
resolver, err := defaultSchemaLoader(spec, nil, nil) resolver, err := defaultSchemaLoader(spec, nil, options, nil)
if err != nil { // Just in case this ever returns an error.
if shouldStopOnError(err, resolver.options) {
return err return err
} }
for key, defintition := range spec.Definitions { if options == nil || !options.SkipSchemas {
var def *Schema for key, definition := range spec.Definitions {
var err error var def *Schema
if def, err = expandSchema(defintition, []string{"#/definitions/" + key}, resolver); err != nil { var err error
return err if def, err = expandSchema(definition, []string{"#/definitions/" + key}, resolver); shouldStopOnError(err, resolver.options) {
return err
}
resolver.reset()
spec.Definitions[key] = *def
} }
spec.Definitions[key] = *def
} }
for key, parameter := range spec.Parameters { for key, parameter := range spec.Parameters {
if err := expandParameter(&parameter, resolver); err != nil { if err := expandParameter(&parameter, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
spec.Parameters[key] = parameter spec.Parameters[key] = parameter
} }
for key, response := range spec.Responses { for key, response := range spec.Responses {
if err := expandResponse(&response, resolver); err != nil { if err := expandResponse(&response, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
spec.Responses[key] = response spec.Responses[key] = response
@ -344,7 +544,7 @@ func ExpandSpec(spec *Swagger) error {
if spec.Paths != nil { if spec.Paths != nil {
for key, path := range spec.Paths.Paths { 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 return err
} }
spec.Paths.Paths[key] = path spec.Paths.Paths[key] = path
@ -354,9 +554,25 @@ func ExpandSpec(spec *Swagger) error {
return nil 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 // ExpandSchema expands the refs in the schema object
func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error { 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 { if schema == nil {
return nil return nil
} }
@ -367,18 +583,17 @@ func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error
nrr, _ := NewRef(schema.ID) nrr, _ := NewRef(schema.ID)
var rrr *Ref var rrr *Ref
if nrr.String() != "" { if nrr.String() != "" {
switch root.(type) { switch rt := root.(type) {
case *Schema: case *Schema:
rid, _ := NewRef(root.(*Schema).ID) rid, _ := NewRef(rt.ID)
rrr, _ = rid.Inherits(nrr) rrr, _ = rid.Inherits(nrr)
case *Swagger: case *Swagger:
rid, _ := NewRef(root.(*Swagger).ID) rid, _ := NewRef(rt.ID)
rrr, _ = rid.Inherits(nrr) rrr, _ = rid.Inherits(nrr)
} }
} }
resolver, err := defaultSchemaLoader(root, rrr, cache) resolver, err := defaultSchemaLoader(root, rrr, opts, cache)
if err != nil { if err != nil {
return err return err
} }
@ -389,7 +604,7 @@ func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error
} }
var s *Schema var s *Schema
if s, err = expandSchema(*schema, refs, resolver); err != nil { if s, err = expandSchema(*schema, refs, resolver); err != nil {
return nil return err
} }
*schema = *s *schema = *s
return nil return nil
@ -400,7 +615,15 @@ func expandItems(target Schema, parentRefs []string, resolver *schemaLoader) (*S
if target.Items.Schema != nil { if target.Items.Schema != nil {
t, err := expandSchema(*target.Items.Schema, parentRefs, resolver) t, err := expandSchema(*target.Items.Schema, parentRefs, resolver)
if err != nil { if err != nil {
return nil, err 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 *target.Items.Schema = *t
} }
@ -415,137 +638,173 @@ func expandItems(target Schema, parentRefs []string, resolver *schemaLoader) (*S
return &target, nil return &target, nil
} }
func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader) (schema *Schema, err error) { func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader) (*Schema, error) {
defer func() {
schema = &target
}()
if target.Ref.String() == "" && target.Ref.IsRoot() { if target.Ref.String() == "" && target.Ref.IsRoot() {
target = *resolver.root.(*Schema) debugLog("skipping expand schema for no ref and root: %v", resolver.root)
return
return resolver.root.(*Schema), nil
} }
// t is the new expanded schema // t is the new expanded schema
var t *Schema var t *Schema
for target.Ref.String() != "" { for target.Ref.String() != "" {
// var newTarget Schema if swag.ContainsStringsCI(parentRefs, target.Ref.String()) {
pRefs := strings.Join(parentRefs, ",") return &target, nil
pRefs += ","
if strings.Contains(pRefs, target.Ref.String()+",") {
err = nil
return
} }
if err = resolver.Resolve(&target.Ref, &t); err != nil { if err := resolver.Resolve(&target.Ref, &t); shouldStopOnError(err, resolver.options) {
return 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()) parentRefs = append(parentRefs, target.Ref.String())
if t != nil {
target = *t
}
}
t, err := expandItems(target, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target = *t target = *t
} }
if t, err = expandItems(target, parentRefs, resolver); err != nil {
return
}
target = *t
for i := range target.AllOf { for i := range target.AllOf {
if t, err = expandSchema(target.AllOf[i], parentRefs, resolver); err != nil { t, err := expandSchema(target.AllOf[i], parentRefs, resolver)
return if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target.AllOf[i] = *t
} }
target.AllOf[i] = *t
} }
for i := range target.AnyOf { for i := range target.AnyOf {
if t, err = expandSchema(target.AnyOf[i], parentRefs, resolver); err != nil { t, err := expandSchema(target.AnyOf[i], parentRefs, resolver)
return if shouldStopOnError(err, resolver.options) {
return &target, err
} }
target.AnyOf[i] = *t target.AnyOf[i] = *t
} }
for i := range target.OneOf { for i := range target.OneOf {
if t, err = expandSchema(target.OneOf[i], parentRefs, resolver); err != nil { t, err := expandSchema(target.OneOf[i], parentRefs, resolver)
return if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target.OneOf[i] = *t
} }
target.OneOf[i] = *t
} }
if target.Not != nil { if target.Not != nil {
if t, err = expandSchema(*target.Not, parentRefs, resolver); err != nil { t, err := expandSchema(*target.Not, parentRefs, resolver)
return if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
*target.Not = *t
} }
*target.Not = *t
} }
for k, _ := range target.Properties { for k := range target.Properties {
if t, err = expandSchema(target.Properties[k], parentRefs, resolver); err != nil { t, err := expandSchema(target.Properties[k], parentRefs, resolver)
return if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
target.Properties[k] = *t
} }
target.Properties[k] = *t
} }
if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil { if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
if t, err = expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver); err != nil { t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver)
return if shouldStopOnError(err, resolver.options) {
return &target, err
} }
*target.AdditionalProperties.Schema = *t if t != nil {
} *target.AdditionalProperties.Schema = *t
for k, _ := range target.PatternProperties {
if t, err = expandSchema(target.PatternProperties[k], parentRefs, resolver); err != nil {
return
} }
target.PatternProperties[k] = *t
} }
for k, _ := range target.Dependencies { 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 target.Dependencies[k].Schema != nil {
if t, err = expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver); err != nil { t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver)
return if shouldStopOnError(err, resolver.options) {
return &target, err
}
if t != nil {
*target.Dependencies[k].Schema = *t
} }
*target.Dependencies[k].Schema = *t
} }
} }
if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil { if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
if t, err = expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver); err != nil { t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver)
return if shouldStopOnError(err, resolver.options) {
return &target, err
} }
*target.AdditionalItems.Schema = *t if t != nil {
} *target.AdditionalItems.Schema = *t
for k, _ := range target.Definitions {
if t, err = expandSchema(target.Definitions[k], parentRefs, resolver); err != nil {
return
} }
target.Definitions[k] = *t
} }
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 &target, nil
} }
func expandPathItem(pathItem *PathItem, resolver *schemaLoader) error { func expandPathItem(pathItem *PathItem, resolver *schemaLoader) error {
if pathItem == nil { if pathItem == nil {
return nil return nil
} }
if pathItem.Ref.String() != "" { if pathItem.Ref.String() != "" {
if err := resolver.Resolve(&pathItem.Ref, &pathItem); err != nil { if err := resolver.Resolve(&pathItem.Ref, &pathItem); err != nil {
return err return err
} }
resolver.reset()
pathItem.Ref = Ref{}
} }
for idx := range pathItem.Parameters { 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 return err
} }
} }
if err := expandOperation(pathItem.Get, resolver); err != nil { if err := expandOperation(pathItem.Get, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
if err := expandOperation(pathItem.Head, resolver); err != nil { if err := expandOperation(pathItem.Head, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
if err := expandOperation(pathItem.Options, resolver); err != nil { if err := expandOperation(pathItem.Options, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
if err := expandOperation(pathItem.Put, resolver); err != nil { if err := expandOperation(pathItem.Put, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
if err := expandOperation(pathItem.Post, resolver); err != nil { if err := expandOperation(pathItem.Post, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
if err := expandOperation(pathItem.Patch, resolver); err != nil { if err := expandOperation(pathItem.Patch, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
if err := expandOperation(pathItem.Delete, resolver); err != nil { if err := expandOperation(pathItem.Delete, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
return nil return nil
@ -555,8 +814,9 @@ func expandOperation(op *Operation, resolver *schemaLoader) error {
if op == nil { if op == nil {
return nil return nil
} }
for i, param := range op.Parameters { for i, param := range op.Parameters {
if err := expandParameter(&param, resolver); err != nil { if err := expandParameter(&param, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
op.Parameters[i] = param op.Parameters[i] = param
@ -564,11 +824,11 @@ func expandOperation(op *Operation, resolver *schemaLoader) error {
if op.Responses != nil { if op.Responses != nil {
responses := op.Responses responses := op.Responses
if err := expandResponse(responses.Default, resolver); err != nil { if err := expandResponse(responses.Default, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
for code, response := range responses.StatusCodeResponses { for code, response := range responses.StatusCodeResponses {
if err := expandResponse(&response, resolver); err != nil { if err := expandResponse(&response, resolver); shouldStopOnError(err, resolver.options) {
return err return err
} }
responses.StatusCodeResponses[code] = response responses.StatusCodeResponses[code] = response
@ -582,22 +842,29 @@ func expandResponse(response *Response, resolver *schemaLoader) error {
return nil return nil
} }
var parentRefs []string
if response.Ref.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 return err
} }
resolver.reset()
response.Ref = Ref{}
} }
if response.Schema != nil { if !resolver.options.SkipSchemas && response.Schema != nil {
parentRefs := []string{response.Schema.Ref.String()} parentRefs = append(parentRefs, response.Schema.Ref.String())
if err := resolver.Resolve(&response.Schema.Ref, &response.Schema); err != nil { debugLog("response ref: %s", response.Schema.Ref)
if err := resolver.Resolve(&response.Schema.Ref, &response.Schema); shouldStopOnError(err, resolver.options) {
return err 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 return err
} else {
*response.Schema = *s
} }
resolver.reset()
*response.Schema = *s
} }
return nil return nil
} }
@ -606,21 +873,28 @@ func expandParameter(parameter *Parameter, resolver *schemaLoader) error {
if parameter == nil { if parameter == nil {
return nil return nil
} }
var parentRefs []string
if parameter.Ref.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 return err
} }
resolver.reset()
parameter.Ref = Ref{}
} }
if parameter.Schema != nil { if !resolver.options.SkipSchemas && parameter.Schema != nil {
parentRefs := []string{parameter.Schema.Ref.String()} parentRefs = append(parentRefs, parameter.Schema.Ref.String())
if err := resolver.Resolve(&parameter.Schema.Ref, &parameter.Schema); err != nil { if err := resolver.Resolve(&parameter.Schema.Ref, &parameter.Schema); shouldStopOnError(err, resolver.options) {
return err 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 return err
} else {
*parameter.Schema = *s
} }
resolver.reset()
*parameter.Schema = *s
} }
return nil return nil
} }

View File

@ -16,7 +16,9 @@ package spec
import ( import (
"encoding/json" "encoding/json"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/swag" "github.com/go-openapi/swag"
) )
@ -30,6 +32,7 @@ type HeaderProps struct {
type Header struct { type Header struct {
CommonValidations CommonValidations
SimpleSchema SimpleSchema
VendorExtensible
HeaderProps HeaderProps
} }
@ -158,8 +161,35 @@ func (h *Header) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &h.SimpleSchema); err != nil { if err := json.Unmarshal(data, &h.SimpleSchema); err != nil {
return err return err
} }
if err := json.Unmarshal(data, &h.VendorExtensible); err != nil {
return err
}
if err := json.Unmarshal(data, &h.HeaderProps); err != nil { if err := json.Unmarshal(data, &h.HeaderProps); err != nil {
return err return err
} }
return nil 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 ( import (
"encoding/json" "encoding/json"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/swag" "github.com/go-openapi/swag"
) )
@ -60,11 +62,12 @@ type CommonValidations struct {
// Items a limited subset of JSON-Schema's items object. // Items a limited subset of JSON-Schema's items object.
// It is used by parameter definitions that are not located in "body". // 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 { type Items struct {
Refable Refable
CommonValidations CommonValidations
SimpleSchema SimpleSchema
VendorExtensible
} }
// NewItems creates a new instance of items // NewItems creates a new instance of items
@ -197,3 +200,20 @@ func (i Items) MarshalJSON() ([]byte, error) {
} }
return swag.ConcatJSON(b3, b1, b2), nil 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 ( import (
"encoding/json" "encoding/json"
"strings"
"github.com/go-openapi/jsonpointer" "github.com/go-openapi/jsonpointer"
"github.com/go-openapi/swag" "github.com/go-openapi/swag"
@ -100,15 +101,16 @@ func (p Parameter) JSONLookup(token string) (interface{}, error) {
if token == "$ref" { if token == "$ref" {
return &p.Ref, nil return &p.Ref, nil
} }
r, _, err := jsonpointer.GetForToken(p.CommonValidations, token) r, _, err := jsonpointer.GetForToken(p.CommonValidations, token)
if err != nil { if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
return nil, err return nil, err
} }
if r != nil { if r != nil {
return r, nil return r, nil
} }
r, _, err = jsonpointer.GetForToken(p.SimpleSchema, token) r, _, err = jsonpointer.GetForToken(p.SimpleSchema, token)
if err != nil { if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
return nil, err return nil, err
} }
if r != nil { 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 // 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() == "" { if r.String() == "" {
return true return true
} }
@ -81,14 +81,18 @@ func (r *Ref) IsValidURI() bool {
// check for local file // check for local file
pth := v pth := v
if r.HasURLPathOnly { 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 { if e != nil {
return false return false
} }
pth = p pth = p
} }
fi, err := os.Stat(pth) fi, err := os.Stat(filepath.ToSlash(pth))
if err != nil { if err != nil {
return false return false
} }
@ -116,25 +120,18 @@ func NewRef(refURI string) (Ref, error) {
return Ref{Ref: ref}, nil 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 { func MustCreateRef(refURI string) Ref {
return Ref{Ref: jsonreference.MustCreateRef(refURI)} 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 // MarshalJSON marshals this ref into a JSON object
func (r Ref) MarshalJSON() ([]byte, error) { func (r Ref) MarshalJSON() ([]byte, error) {
str := r.String() str := r.String()
if str == "" { if str == "" {
if r.IsRoot() { if r.IsRoot() {
return []byte(`{"$ref":"#"}`), nil return []byte(`{"$ref":""}`), nil
} }
return []byte("{}"), nil return []byte("{}"), nil
} }

View File

@ -17,6 +17,7 @@ package spec
import ( import (
"encoding/json" "encoding/json"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/swag" "github.com/go-openapi/swag"
) )
@ -34,6 +35,19 @@ type ResponseProps struct {
type Response struct { type Response struct {
Refable Refable
ResponseProps 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 // 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 { if err := json.Unmarshal(data, &r.Refable); err != nil {
return err return err
} }
if err := json.Unmarshal(data, &r.VendorExtensible); err != nil {
return err
}
return nil return nil
} }
@ -57,7 +74,11 @@ func (r Response) MarshalJSON() ([]byte, error) {
if err != nil { if err != nil {
return nil, err 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 // 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 i, err := strconv.Atoi(token); err == nil {
if scr, ok := r.StatusCodeResponses[i]; ok { if scr, ok := r.StatusCodeResponses[i]; ok {
return &scr, nil return scr, nil
} }
} }
return nil, fmt.Errorf("object has no field %q", token) 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 { type SchemaProps struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Ref Ref `json:"-,omitempty"` Ref Ref `json:"-"`
Schema SchemaURL `json:"-,omitempty"` Schema SchemaURL `json:"-"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Type StringOrArray `json:"type,omitempty"` Type StringOrArray `json:"type,omitempty"`
Format string `json:"format,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) 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 return r, err
} }
r, _, err = jsonpointer.GetForToken(s.SwaggerSchemaProps, token) r, _, err = jsonpointer.GetForToken(s.SwaggerSchemaProps, token)

View File

@ -16,6 +16,8 @@ package spec
import "encoding/json" 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 go-bindata -pkg=spec -prefix=./schemas -ignore=.*\.md ./schemas/...
//go:generate perl -pi -e s,Json,JSON,g bindata.go //go:generate perl -pi -e s,Json,JSON,g bindata.go
@ -27,10 +29,15 @@ const (
) )
var ( var (
jsonSchema = MustLoadJSONSchemaDraft04() jsonSchema *Schema
swaggerSchema = MustLoadSwagger20Schema() swaggerSchema *Schema
) )
func init() {
jsonSchema = MustLoadJSONSchemaDraft04()
swaggerSchema = MustLoadSwagger20Schema()
}
// MustLoadJSONSchemaDraft04 panics when Swagger20Schema returns an error // MustLoadJSONSchemaDraft04 panics when Swagger20Schema returns an error
func MustLoadJSONSchemaDraft04() *Schema { func MustLoadJSONSchemaDraft04() *Schema {
d, e := JSONSchemaDraft04() d, e := JSONSchemaDraft04()

View File

@ -77,7 +77,7 @@ type SwaggerProps struct {
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
BasePath string `json:"basePath,omitempty"` // must start with a leading "/" BasePath string `json:"basePath,omitempty"` // must start with a leading "/"
Paths *Paths `json:"paths"` // required Paths *Paths `json:"paths"` // required
Definitions Definitions `json:"definitions"` Definitions Definitions `json:"definitions,omitempty"`
Parameters map[string]Parameter `json:"parameters,omitempty"` Parameters map[string]Parameter `json:"parameters,omitempty"`
Responses map[string]Response `json:"responses,omitempty"` Responses map[string]Response `json:"responses,omitempty"`
SecurityDefinitions SecurityDefinitions `json:"securityDefinitions,omitempty"` SecurityDefinitions SecurityDefinitions `json:"securityDefinitions,omitempty"`
@ -156,7 +156,7 @@ func (s SchemaOrStringArray) MarshalJSON() ([]byte, error) {
if s.Schema != nil { if s.Schema != nil {
return json.Marshal(s.Schema) return json.Marshal(s.Schema)
} }
return nil, nil return []byte("null"), nil
} }
// UnmarshalJSON converts this schema object or array from a JSON structure // 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", "net.go",
"path.go", "path.go",
"util.go", "util.go",
"yaml.go",
], ],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//vendor/github.com/mailru/easyjson/jlexer:go_default_library", "//vendor/github.com/mailru/easyjson/jlexer:go_default_library",
"//vendor/github.com/mailru/easyjson/jwriter: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) [![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 // FormatInt32 turns an int32 into a string
func FormatInt32(value int32) string { func FormatInt32(value int32) string {
return strconv.FormatInt(int64(value), 10) return strconv.Itoa(int(value))
} }
// FormatInt64 turns an int64 into a string // FormatInt64 turns an int64 into a string

View File

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

View File

@ -17,13 +17,25 @@ package swag
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"path/filepath"
"strings" "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 // LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in
func LoadFromFileOrHTTP(path string) ([]byte, error) { 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 // LoadStrategy returns a loader function for a given path or uri
@ -31,19 +43,32 @@ func LoadStrategy(path string, local, remote func(string) ([]byte, error)) func(
if strings.HasPrefix(path, "http") { if strings.HasPrefix(path, "http") {
return remote return remote
} }
return local return func(pth string) ([]byte, error) { return local(filepath.FromSlash(pth)) }
} }
func loadHTTPBytes(path string) ([]byte, error) { func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) {
resp, err := http.Get(path) return func(path string) ([]byte, error) {
if err != nil { client := &http.Client{Timeout: timeout}
return nil, err req, err := http.NewRequest("GET", path, nil)
} if err != nil {
defer resp.Body.Close() 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
}
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status) return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status)
} }
return ioutil.ReadAll(resp.Body) return ioutil.ReadAll(resp.Body)
}
} }

View File

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

View File

@ -20,10 +20,12 @@ import (
"regexp" "regexp"
"sort" "sort"
"strings" "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{ var commonInitialisms = map[string]bool{
"ACL": true,
"API": true, "API": true,
"ASCII": true, "ASCII": true,
"CPU": true, "CPU": true,
@ -44,19 +46,21 @@ var commonInitialisms = map[string]bool{
"RPC": true, "RPC": true,
"SLA": true, "SLA": true,
"SMTP": true, "SMTP": true,
"SQL": true,
"SSH": true, "SSH": true,
"TCP": true, "TCP": true,
"TLS": true, "TLS": true,
"TTL": true, "TTL": true,
"UDP": true, "UDP": true,
"UUID": true,
"UID": true,
"UI": true, "UI": true,
"UID": true,
"UUID": true,
"URI": true, "URI": true,
"URL": true, "URL": true,
"UTF8": true, "UTF8": true,
"VM": true, "VM": true,
"XML": true, "XML": true,
"XMPP": true,
"XSRF": true, "XSRF": true,
"XSS": true, "XSS": true,
} }
@ -246,6 +250,9 @@ func ToJSONName(name string) string {
// ToVarName camelcases a name which can be underscored or pascal cased // ToVarName camelcases a name which can be underscored or pascal cased
func ToVarName(name string) string { func ToVarName(name string) string {
res := ToGoName(name) res := ToGoName(name)
if _, ok := commonInitialisms[res]; ok {
return lower(res)
}
if len(res) <= 1 { if len(res) <= 1 {
return lower(res) return lower(res)
} }
@ -263,7 +270,18 @@ func ToGoName(name string) string {
} }
out = append(out, uw) 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 // 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 // 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. // copied if it does not fit in a single chunk. You can optionally provide one byte
func (b *Buffer) BuildBytes() []byte { // slice as argument that it will try to reuse.
func (b *Buffer) BuildBytes(reuse ...[]byte) []byte {
if len(b.bufs) == 0 { if len(b.bufs) == 0 {
ret := b.Buf ret := b.Buf
b.toPool = nil b.toPool = nil
b.Buf = nil b.Buf = nil
return ret 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 { for _, buf := range b.bufs {
ret = append(ret, buf...) ret = append(ret, buf...)
putBuf(buf) putBuf(buf)
@ -205,3 +212,59 @@ func (b *Buffer) BuildBytes() []byte {
return ret 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( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"bytestostr.go",
"error.go", "error.go",
"lexer.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 package jlexer
import ( import (
"encoding/base64"
"errors"
"fmt" "fmt"
"io" "io"
"reflect"
"strconv" "strconv"
"unicode"
"unicode/utf16"
"unicode/utf8" "unicode/utf8"
"unsafe"
) )
// tokenKind determines type of a token. // 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. 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. 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. // FetchToken scans the input for the next token.
func (r *Lexer) fetchToken() { func (r *Lexer) FetchToken() {
r.token.kind = tokenUndef r.token.kind = tokenUndef
r.start = r.pos r.start = r.pos
@ -147,7 +151,7 @@ func (r *Lexer) fetchToken() {
return return
} }
} }
r.err = io.EOF r.fatalError = io.EOF
return 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. // fetchNumber scans a number literal token.
func (r *Lexer) fetchNumber() { func (r *Lexer) fetchNumber() {
hasE := false hasE := false
@ -265,6 +258,33 @@ func findStringLen(data []byte) (hasEscapes bool, length int) {
return false, len(data) 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. // processEscape processes a single escape sequence and returns number of bytes processed.
func (r *Lexer) processEscape(data []byte) (int, error) { func (r *Lexer) processEscape(data []byte) (int, error) {
if len(data) < 2 { if len(data) < 2 {
@ -292,39 +312,28 @@ func (r *Lexer) processEscape(data []byte) (int, error) {
r.token.byteValue = append(r.token.byteValue, '\t') r.token.byteValue = append(r.token.byteValue, '\t')
return 2, nil return 2, nil
case 'u': case 'u':
default: rr := getu4(data)
return 0, fmt.Errorf("syntax error") 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")
} }
val <<= 4 read := 6
val |= rune(v) 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
}
}
var d [4]byte
s := utf8.EncodeRune(d[:], rr)
r.token.byteValue = append(r.token.byteValue, d[:s]...)
return read, nil
} }
l := utf8.RuneLen(val) return 0, errors.New("syntax error")
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
} }
// fetchString scans a string literal token. // 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. // scanToken scans the next token if no token is currently available in the lexer.
func (r *Lexer) scanToken() { func (r *Lexer) scanToken() {
if r.token.kind != tokenUndef || r.err != nil { if r.token.kind != tokenUndef || r.fatalError != nil {
return return
} }
r.fetchToken() r.FetchToken()
} }
// consume resets the current token to allow scanning the next one. // 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. // Ok returns true if no error (including io.EOF) was encountered during scanning.
func (r *Lexer) Ok() bool { func (r *Lexer) Ok() bool {
return r.err == nil return r.fatalError == nil
} }
const maxErrorContextLen = 13 const maxErrorContextLen = 13
func (r *Lexer) errParse(what string) { func (r *Lexer) errParse(what string) {
if r.err == nil { if r.fatalError == nil {
var str string var str string
if len(r.Data)-r.pos <= maxErrorContextLen { if len(r.Data)-r.pos <= maxErrorContextLen {
str = string(r.Data) str = string(r.Data)
} else { } else {
str = string(r.Data[r.pos:r.pos+maxErrorContextLen-3]) + "..." str = string(r.Data[r.pos:r.pos+maxErrorContextLen-3]) + "..."
} }
r.err = &LexerError{ r.fatalError = &LexerError{
Reason: what, Reason: what,
Offset: r.pos, Offset: r.pos,
Data: str, Data: str,
@ -409,36 +418,64 @@ func (r *Lexer) errSyntax() {
} }
func (r *Lexer) errInvalidToken(expected string) { func (r *Lexer) errInvalidToken(expected string) {
if r.err == nil { if r.fatalError != nil {
var str string return
if len(r.token.byteValue) <= maxErrorContextLen {
str = string(r.token.byteValue)
} else {
str = string(r.token.byteValue[:maxErrorContextLen-3]) + "..."
}
r.err = &LexerError{
Reason: fmt.Sprintf("expected %s", expected),
Offset: r.pos,
Data: str,
}
} }
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.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. // Delim consumes a token and verifies that it is the given delimiter.
func (r *Lexer) Delim(c byte) { func (r *Lexer) Delim(c byte) {
if r.token.kind == tokenUndef && r.Ok() { if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken() r.FetchToken()
} }
if !r.Ok() || r.token.delimValue != c { if !r.Ok() || r.token.delimValue != c {
r.consume() // errInvalidToken can change token if UseMultipleErrors is enabled.
r.errInvalidToken(string([]byte{c})) r.errInvalidToken(string([]byte{c}))
} else {
r.consume()
} }
r.consume()
} }
// IsDelim returns true if there was no scanning error and next token is the given delimiter. // IsDelim returns true if there was no scanning error and next token is the given delimiter.
func (r *Lexer) IsDelim(c byte) bool { func (r *Lexer) IsDelim(c byte) bool {
if r.token.kind == tokenUndef && r.Ok() { if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken() r.FetchToken()
} }
return !r.Ok() || r.token.delimValue == c 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. // Null verifies that the next token is null and consumes it.
func (r *Lexer) Null() { func (r *Lexer) Null() {
if r.token.kind == tokenUndef && r.Ok() { if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken() r.FetchToken()
} }
if !r.Ok() || r.token.kind != tokenNull { if !r.Ok() || r.token.kind != tokenNull {
r.errInvalidToken("null") r.errInvalidToken("null")
@ -457,7 +494,7 @@ func (r *Lexer) Null() {
// IsNull returns true if the next token is a null keyword. // IsNull returns true if the next token is a null keyword.
func (r *Lexer) IsNull() bool { func (r *Lexer) IsNull() bool {
if r.token.kind == tokenUndef && r.Ok() { if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken() r.FetchToken()
} }
return r.Ok() && r.token.kind == tokenNull return r.Ok() && r.token.kind == tokenNull
} }
@ -465,7 +502,7 @@ func (r *Lexer) IsNull() bool {
// Skip skips a single token. // Skip skips a single token.
func (r *Lexer) Skip() { func (r *Lexer) Skip() {
if r.token.kind == tokenUndef && r.Ok() { if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken() r.FetchToken()
} }
r.consume() r.consume()
} }
@ -476,7 +513,6 @@ func (r *Lexer) Skip() {
// Note: no syntax validation is performed on the skipped data. // Note: no syntax validation is performed on the skipped data.
func (r *Lexer) SkipRecursive() { func (r *Lexer) SkipRecursive() {
r.scanToken() r.scanToken()
var start, end byte var start, end byte
if r.token.delimValue == '{' { if r.token.delimValue == '{' {
@ -505,7 +541,7 @@ func (r *Lexer) SkipRecursive() {
return return
} }
case c == '\\' && inQuotes: case c == '\\' && inQuotes:
wasEscape = true wasEscape = !wasEscape
continue continue
case c == '"' && inQuotes: case c == '"' && inQuotes:
inQuotes = wasEscape inQuotes = wasEscape
@ -515,7 +551,11 @@ func (r *Lexer) SkipRecursive() {
wasEscape = false wasEscape = false
} }
r.pos = len(r.Data) 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 // 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] 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. // 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 // 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. // the input buffer. Intended pattern of usage is as an argument to a switch statement.
func (r *Lexer) UnsafeString() string { func (r *Lexer) UnsafeString() string {
if r.token.kind == tokenUndef && r.Ok() { ret, _ := r.unsafeString()
r.fetchToken() return ret
} }
if !r.Ok() || r.token.kind != tokenString {
r.errInvalidToken("string")
return ""
}
ret := bytesToStr(r.token.byteValue) // UnsafeBytes returns the byte slice if the token is a string literal.
r.consume() func (r *Lexer) UnsafeBytes() []byte {
_, ret := r.unsafeString()
return ret return ret
} }
// String reads a string literal. // String reads a string literal.
func (r *Lexer) String() string { func (r *Lexer) String() string {
if r.token.kind == tokenUndef && r.Ok() { if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken() r.FetchToken()
} }
if !r.Ok() || r.token.kind != tokenString { if !r.Ok() || r.token.kind != tokenString {
r.errInvalidToken("string") r.errInvalidToken("string")
return "" return ""
} }
ret := string(r.token.byteValue) ret := string(r.token.byteValue)
r.consume() r.consume()
return ret 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. // Bool reads a true or false boolean keyword.
func (r *Lexer) Bool() bool { func (r *Lexer) Bool() bool {
if r.token.kind == tokenUndef && r.Ok() { if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken() r.FetchToken()
} }
if !r.Ok() || r.token.kind != tokenBool { if !r.Ok() || r.token.kind != tokenBool {
r.errInvalidToken("bool") r.errInvalidToken("bool")
return false return false
} }
ret := r.token.boolValue ret := r.token.boolValue
r.consume() r.consume()
@ -577,12 +676,11 @@ func (r *Lexer) Bool() bool {
func (r *Lexer) number() string { func (r *Lexer) number() string {
if r.token.kind == tokenUndef && r.Ok() { if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken() r.FetchToken()
} }
if !r.Ok() || r.token.kind != tokenNumber { if !r.Ok() || r.token.kind != tokenNumber {
r.errInvalidToken("number") r.errInvalidToken("number")
return "" return ""
} }
ret := bytesToStr(r.token.byteValue) ret := bytesToStr(r.token.byteValue)
r.consume() r.consume()
@ -597,9 +695,11 @@ func (r *Lexer) Uint8() uint8 {
n, err := strconv.ParseUint(s, 10, 8) n, err := strconv.ParseUint(s, 10, 8)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return uint8(n) return uint8(n)
} }
@ -612,9 +712,11 @@ func (r *Lexer) Uint16() uint16 {
n, err := strconv.ParseUint(s, 10, 16) n, err := strconv.ParseUint(s, 10, 16)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return uint16(n) return uint16(n)
} }
@ -627,9 +729,11 @@ func (r *Lexer) Uint32() uint32 {
n, err := strconv.ParseUint(s, 10, 32) n, err := strconv.ParseUint(s, 10, 32)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return uint32(n) return uint32(n)
} }
@ -642,9 +746,11 @@ func (r *Lexer) Uint64() uint64 {
n, err := strconv.ParseUint(s, 10, 64) n, err := strconv.ParseUint(s, 10, 64)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return n return n
} }
@ -661,9 +767,11 @@ func (r *Lexer) Int8() int8 {
n, err := strconv.ParseInt(s, 10, 8) n, err := strconv.ParseInt(s, 10, 8)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return int8(n) return int8(n)
} }
@ -676,9 +784,11 @@ func (r *Lexer) Int16() int16 {
n, err := strconv.ParseInt(s, 10, 16) n, err := strconv.ParseInt(s, 10, 16)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return int16(n) return int16(n)
} }
@ -691,9 +801,11 @@ func (r *Lexer) Int32() int32 {
n, err := strconv.ParseInt(s, 10, 32) n, err := strconv.ParseInt(s, 10, 32)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return int32(n) return int32(n)
} }
@ -706,9 +818,11 @@ func (r *Lexer) Int64() int64 {
n, err := strconv.ParseInt(s, 10, 64) n, err := strconv.ParseInt(s, 10, 64)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return n return n
} }
@ -718,61 +832,69 @@ func (r *Lexer) Int() int {
} }
func (r *Lexer) Uint8Str() uint8 { func (r *Lexer) Uint8Str() uint8 {
s := r.UnsafeString() s, b := r.unsafeString()
if !r.Ok() { if !r.Ok() {
return 0 return 0
} }
n, err := strconv.ParseUint(s, 10, 8) n, err := strconv.ParseUint(s, 10, 8)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: string(b),
})
} }
return uint8(n) return uint8(n)
} }
func (r *Lexer) Uint16Str() uint16 { func (r *Lexer) Uint16Str() uint16 {
s := r.UnsafeString() s, b := r.unsafeString()
if !r.Ok() { if !r.Ok() {
return 0 return 0
} }
n, err := strconv.ParseUint(s, 10, 16) n, err := strconv.ParseUint(s, 10, 16)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: string(b),
})
} }
return uint16(n) return uint16(n)
} }
func (r *Lexer) Uint32Str() uint32 { func (r *Lexer) Uint32Str() uint32 {
s := r.UnsafeString() s, b := r.unsafeString()
if !r.Ok() { if !r.Ok() {
return 0 return 0
} }
n, err := strconv.ParseUint(s, 10, 32) n, err := strconv.ParseUint(s, 10, 32)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: string(b),
})
} }
return uint32(n) return uint32(n)
} }
func (r *Lexer) Uint64Str() uint64 { func (r *Lexer) Uint64Str() uint64 {
s := r.UnsafeString() s, b := r.unsafeString()
if !r.Ok() { if !r.Ok() {
return 0 return 0
} }
n, err := strconv.ParseUint(s, 10, 64) n, err := strconv.ParseUint(s, 10, 64)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: string(b),
})
} }
return n return n
} }
@ -782,61 +904,69 @@ func (r *Lexer) UintStr() uint {
} }
func (r *Lexer) Int8Str() int8 { func (r *Lexer) Int8Str() int8 {
s := r.UnsafeString() s, b := r.unsafeString()
if !r.Ok() { if !r.Ok() {
return 0 return 0
} }
n, err := strconv.ParseInt(s, 10, 8) n, err := strconv.ParseInt(s, 10, 8)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: string(b),
})
} }
return int8(n) return int8(n)
} }
func (r *Lexer) Int16Str() int16 { func (r *Lexer) Int16Str() int16 {
s := r.UnsafeString() s, b := r.unsafeString()
if !r.Ok() { if !r.Ok() {
return 0 return 0
} }
n, err := strconv.ParseInt(s, 10, 16) n, err := strconv.ParseInt(s, 10, 16)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: string(b),
})
} }
return int16(n) return int16(n)
} }
func (r *Lexer) Int32Str() int32 { func (r *Lexer) Int32Str() int32 {
s := r.UnsafeString() s, b := r.unsafeString()
if !r.Ok() { if !r.Ok() {
return 0 return 0
} }
n, err := strconv.ParseInt(s, 10, 32) n, err := strconv.ParseInt(s, 10, 32)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: string(b),
})
} }
return int32(n) return int32(n)
} }
func (r *Lexer) Int64Str() int64 { func (r *Lexer) Int64Str() int64 {
s := r.UnsafeString() s, b := r.unsafeString()
if !r.Ok() { if !r.Ok() {
return 0 return 0
} }
n, err := strconv.ParseInt(s, 10, 64) n, err := strconv.ParseInt(s, 10, 64)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: string(b),
})
} }
return n return n
} }
@ -853,9 +983,11 @@ func (r *Lexer) Float32() float32 {
n, err := strconv.ParseFloat(s, 32) n, err := strconv.ParseFloat(s, 32)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return float32(n) return float32(n)
} }
@ -868,27 +1000,53 @@ func (r *Lexer) Float64() float64 {
n, err := strconv.ParseFloat(s, 64) n, err := strconv.ParseFloat(s, 64)
if err != nil { if err != nil {
r.err = &LexerError{ r.addNonfatalError(&LexerError{
Offset: r.start,
Reason: err.Error(), Reason: err.Error(),
} Data: s,
})
} }
return n return n
} }
func (r *Lexer) Error() error { func (r *Lexer) Error() error {
return r.err return r.fatalError
} }
func (r *Lexer) AddError(e error) { func (r *Lexer) AddError(e error) {
if r.err == nil { if r.fatalError == nil {
r.err = e 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. // Interface fetches an interface{} analogous to the 'encoding/json' package.
func (r *Lexer) Interface() interface{} { func (r *Lexer) Interface() interface{} {
if r.token.kind == tokenUndef && r.Ok() { if r.token.kind == tokenUndef && r.Ok() {
r.fetchToken() r.FetchToken()
} }
if !r.Ok() { if !r.Ok() {

View File

@ -2,6 +2,7 @@
package jwriter package jwriter
import ( import (
"encoding/base64"
"io" "io"
"strconv" "strconv"
"unicode/utf8" "unicode/utf8"
@ -9,10 +10,22 @@ import (
"github.com/mailru/easyjson/buffer" "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. // Writer is a JSON writer.
type Writer struct { type Writer struct {
Error error Flags Flags
Buffer buffer.Buffer
Error error
Buffer buffer.Buffer
NoEscapeHTML bool
} }
// Size returns the size of the data that was written out. // 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) return w.Buffer.DumpTo(out)
} }
// BuildBytes returns writer data as a single byte slice. // BuildBytes returns writer data as a single byte slice. You can optionally provide one byte slice
func (w *Writer) BuildBytes() ([]byte, error) { // as argument that it will try to reuse.
func (w *Writer) BuildBytes(reuse ...[]byte) ([]byte, error) {
if w.Error != nil { if w.Error != nil {
return nil, w.Error 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. // RawByte appends raw binary data to the buffer.
@ -44,7 +68,7 @@ func (w *Writer) RawString(s string) {
w.Buffer.AppendString(s) 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. // calling with results of MarshalJSON-like functions.
func (w *Writer) Raw(data []byte, err error) { func (w *Writer) Raw(data []byte, err error) {
switch { 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) { func (w *Writer) Uint8(n uint8) {
w.Buffer.EnsureSpace(3) w.Buffer.EnsureSpace(3)
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10) w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
@ -200,6 +252,16 @@ func (w *Writer) Bool(v bool) {
const chars = "0123456789abcdef" 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) { func (w *Writer) String(s string) {
w.Buffer.AppendByte('"') w.Buffer.AppendByte('"')
@ -209,39 +271,32 @@ func (w *Writer) String(s string) {
p := 0 // last non-escape symbol p := 0 // last non-escape symbol
for i := 0; i < len(s); { for i := 0; i < len(s); {
// single-with character c := s[i]
if c := s[i]; c < utf8.RuneSelf {
var escape byte if isNotEscapedSingleChar(c, !w.NoEscapeHTML) {
// single-width character, no escaping is required
i++
continue
} else if c < utf8.RuneSelf {
// single-with character, need to escape
w.Buffer.AppendString(s[p:i])
switch c { switch c {
case '\t': case '\t':
escape = 't' w.Buffer.AppendString(`\t`)
case '\r': case '\r':
escape = 'r' w.Buffer.AppendString(`\r`)
case '\n': case '\n':
escape = 'n' w.Buffer.AppendString(`\n`)
case '\\': case '\\':
escape = '\\' w.Buffer.AppendString(`\\`)
case '"': case '"':
escape = '"' w.Buffer.AppendString(`\"`)
case '<', '>':
// do nothing
default: default:
if c >= 0x20 {
// no escaping is required
i++
continue
}
}
if escape != 0 {
w.Buffer.AppendString(s[p:i])
w.Buffer.AppendByte('\\')
w.Buffer.AppendByte(escape)
} else {
w.Buffer.AppendString(s[p:i])
w.Buffer.AppendString(`\u00`) w.Buffer.AppendString(`\u00`)
w.Buffer.AppendByte(chars[c>>4]) w.Buffer.AppendByte(chars[c>>4])
w.Buffer.AppendByte(chars[c&0xf]) w.Buffer.AppendByte(chars[c&0xf])
} }
i++ i++
p = i p = i
continue continue