Update k8s, client-go, apimachinery deps

Update k8s.io/kubernetes to v1.8.
Update k8s.io/client-go to v5.0.0
Update k8s.io/apimachinery to match
Pull in k8s.io/api release-1.8 branch
Pull in k8s.io/code-generator release-1.8 branch

Signed-off-by: Andy Goldstein <andy.goldstein@gmail.com>
This commit is contained in:
Andy Goldstein
2017-10-30 13:30:27 -04:00
parent e5c8d3316f
commit 18eafd5606
941 changed files with 87960 additions and 298422 deletions
Generated
+62 -69
View File
@@ -31,12 +31,6 @@
packages = ["."]
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
[[projects]]
name = "github.com/asaskevich/govalidator"
packages = ["."]
revision = "4918b99a7cb949bb295f3c7bbaf24b577d806e35"
version = "v6"
[[projects]]
name = "github.com/aws/aws-sdk-go"
packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/ec2query","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/ec2","service/s3","service/sts"]
@@ -61,12 +55,6 @@
revision = "d2709f9f1f31ebcda9651b03077758c1f3a0018c"
version = "v3.0.0"
[[projects]]
name = "github.com/docker/distribution"
packages = ["digest","reference"]
revision = "48294d928ced5dd9b378f7fd7c6f5da3ff3f2c89"
version = "v2.6.2"
[[projects]]
branch = "master"
name = "github.com/docker/spdystream"
@@ -85,12 +73,6 @@
revision = "dcef7f55730566d41eae5db10e7d6981829720f6"
version = "1.0.1"
[[projects]]
branch = "master"
name = "github.com/fatih/camelcase"
packages = ["."]
revision = "f6a740d52f961c60348ebb109adde9f4635d7540"
[[projects]]
name = "github.com/ghodss/yaml"
packages = ["."]
@@ -103,18 +85,6 @@
revision = "20b96f641a5ea98f2f8619ff4f3e061cff4833bd"
version = "v1.28.2"
[[projects]]
branch = "master"
name = "github.com/go-openapi/analysis"
packages = ["."]
revision = "8ed83f2ea9f00f945516462951a288eaa68bf0d6"
[[projects]]
branch = "master"
name = "github.com/go-openapi/errors"
packages = ["."]
revision = "03cfca65330da08a5a440053faf994a3c682b5bf"
[[projects]]
branch = "master"
name = "github.com/go-openapi/jsonpointer"
@@ -127,24 +97,12 @@
packages = ["."]
revision = "36d33bfe519efae5632669801b180bf1a245da3b"
[[projects]]
branch = "master"
name = "github.com/go-openapi/loads"
packages = ["."]
revision = "a80dea3052f00e5f032e860dd7355cd0cc67e24d"
[[projects]]
branch = "master"
name = "github.com/go-openapi/spec"
packages = ["."]
revision = "3faa0055dbbf2110abc1f3b4e3adbb22721e96e7"
[[projects]]
branch = "master"
name = "github.com/go-openapi/strfmt"
packages = ["."]
revision = "93a31ef21ac23f317792fff78f9539219dd74619"
[[projects]]
branch = "master"
name = "github.com/go-openapi/swag"
@@ -166,9 +124,15 @@
[[projects]]
branch = "master"
name = "github.com/golang/protobuf"
packages = ["proto","protoc-gen-go/descriptor","ptypes/any"]
packages = ["proto","protoc-gen-go/descriptor","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"]
revision = "ab9f9a6dab164b7d1246e0e688b0ab7b94d8553e"
[[projects]]
branch = "master"
name = "github.com/google/btree"
packages = ["."]
revision = "316fb6d3f031ae8f4d457c6c5186b9e3ded70435"
[[projects]]
branch = "master"
name = "github.com/google/gofuzz"
@@ -181,6 +145,18 @@
packages = ["."]
revision = "84ed26760e7f6f80887a2fbfb50db3cc415d2cea"
[[projects]]
name = "github.com/googleapis/gnostic"
packages = ["OpenAPIv2","compiler","extensions"]
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/gregjones/httpcache"
packages = [".","diskcache"]
revision = "c1f8028e62adb3d518b823a2f8e6a95c38bdd3aa"
[[projects]]
branch = "master"
name = "github.com/hashicorp/golang-lru"
@@ -211,6 +187,12 @@
revision = "3433f3ea46d9f8019119e7dd41274e112a2359a9"
version = "0.2.2"
[[projects]]
name = "github.com/json-iterator/go"
packages = ["."]
revision = "6240e1e7983a85228f7fd9c3e1b6932d46ec58e2"
version = "1.0.3"
[[projects]]
branch = "master"
name = "github.com/juju/ratelimit"
@@ -225,9 +207,15 @@
[[projects]]
branch = "master"
name = "github.com/mitchellh/mapstructure"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
[[projects]]
name = "github.com/peterbourgon/diskv"
packages = ["."]
revision = "d0303fe809921458f417bcf828397a65db30a7e4"
revision = "5f041e8faa004a95c88a202771f4cc3e991971e6"
version = "v2.0.1"
[[projects]]
name = "github.com/pkg/errors"
@@ -305,12 +293,6 @@
packages = ["assert","mock","require"]
revision = "890a5c3458b43e6104ff5da8dfa139d013d77544"
[[projects]]
branch = "master"
name = "github.com/ugorji/go"
packages = ["codec"]
revision = "5efa3251c7f7d05e5d9704a69a984ec9f1386a40"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
@@ -371,44 +353,55 @@
revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
version = "v0.9.0"
[[projects]]
branch = "v2"
name = "gopkg.in/mgo.v2"
packages = ["bson","internal/json"]
revision = "3f83fa5005286a7fe593b055f0d7771a7dce4655"
[[projects]]
branch = "v2"
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
[[projects]]
branch = "release-1.8"
name = "k8s.io/api"
packages = ["admissionregistration/v1alpha1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","core/v1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","settings/v1alpha1","storage/v1","storage/v1beta1"]
revision = "6c6dac0277229b9e9578c5ca3f74a4345d35cdc2"
[[projects]]
name = "k8s.io/apimachinery"
packages = ["pkg/api/equality","pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/apimachinery","pkg/apimachinery/announced","pkg/apimachinery/registered","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1alpha1","pkg/conversion","pkg/conversion/queryparams","pkg/conversion/unstructured","pkg/fields","pkg/labels","pkg/openapi","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/cache","pkg/util/clock","pkg/util/diff","pkg/util/errors","pkg/util/framer","pkg/util/httpstream","pkg/util/httpstream/spdy","pkg/util/intstr","pkg/util/json","pkg/util/net","pkg/util/rand","pkg/util/remotecommand","pkg/util/runtime","pkg/util/sets","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/netutil","third_party/forked/golang/reflect"]
revision = "1fd2e63a9a370677308a42f24fd40c86438afddf"
packages = ["pkg/api/equality","pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/apis/meta/internalversion","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1alpha1","pkg/conversion","pkg/conversion/queryparams","pkg/conversion/unstructured","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/cache","pkg/util/clock","pkg/util/diff","pkg/util/errors","pkg/util/framer","pkg/util/httpstream","pkg/util/httpstream/spdy","pkg/util/intstr","pkg/util/json","pkg/util/net","pkg/util/remotecommand","pkg/util/runtime","pkg/util/sets","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/netutil","third_party/forked/golang/reflect"]
revision = "019ae5ada31de202164b118aee88ee2d14075c31"
[[projects]]
name = "k8s.io/client-go"
packages = ["discovery","discovery/fake","dynamic","kubernetes","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/apps/v1beta1","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1beta1","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v2alpha1","kubernetes/typed/batch/v1","kubernetes/typed/batch/v2alpha1","kubernetes/typed/certificates/v1beta1","kubernetes/typed/core/v1","kubernetes/typed/extensions/v1beta1","kubernetes/typed/networking/v1","kubernetes/typed/policy/v1beta1","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1beta1","kubernetes/typed/settings/v1alpha1","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1beta1","pkg/api","pkg/api/v1","pkg/api/v1/ref","pkg/apis/admissionregistration","pkg/apis/admissionregistration/v1alpha1","pkg/apis/apps","pkg/apis/apps/v1beta1","pkg/apis/authentication","pkg/apis/authentication/v1","pkg/apis/authentication/v1beta1","pkg/apis/authorization","pkg/apis/authorization/v1","pkg/apis/authorization/v1beta1","pkg/apis/autoscaling","pkg/apis/autoscaling/v1","pkg/apis/autoscaling/v2alpha1","pkg/apis/batch","pkg/apis/batch/v1","pkg/apis/batch/v2alpha1","pkg/apis/certificates","pkg/apis/certificates/v1beta1","pkg/apis/extensions","pkg/apis/extensions/v1beta1","pkg/apis/networking","pkg/apis/networking/v1","pkg/apis/policy","pkg/apis/policy/v1beta1","pkg/apis/rbac","pkg/apis/rbac/v1alpha1","pkg/apis/rbac/v1beta1","pkg/apis/settings","pkg/apis/settings/v1alpha1","pkg/apis/storage","pkg/apis/storage/v1","pkg/apis/storage/v1beta1","pkg/util","pkg/util/parsers","pkg/version","plugin/pkg/client/auth/azure","plugin/pkg/client/auth/gcp","plugin/pkg/client/auth/oidc","rest","rest/watch","testing","third_party/forked/golang/template","tools/auth","tools/cache","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/metrics","tools/remotecommand","transport","util/cert","util/exec","util/flowcontrol","util/homedir","util/integer","util/jsonpath","util/workqueue"]
revision = "d92e8497f71b7b4e0494e5bd204b48d34bd6f254"
version = "v4.0.0"
packages = ["discovery","discovery/fake","dynamic","kubernetes","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta2","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1beta1","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v2alpha1","kubernetes/typed/certificates/v1beta1","kubernetes/typed/core/v1","kubernetes/typed/extensions/v1beta1","kubernetes/typed/networking/v1","kubernetes/typed/policy/v1beta1","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1beta1","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/settings/v1alpha1","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1beta1","pkg/version","plugin/pkg/client/auth/azure","plugin/pkg/client/auth/gcp","plugin/pkg/client/auth/oidc","rest","rest/watch","testing","third_party/forked/golang/template","tools/auth","tools/cache","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/metrics","tools/pager","tools/reference","tools/remotecommand","transport","transport/spdy","util/cert","util/exec","util/flowcontrol","util/homedir","util/integer","util/jsonpath","util/workqueue"]
revision = "35874c597fed17ca62cd197e516d7d5ff9a2958c"
version = "v5.0.0"
[[projects]]
branch = "release-1.8"
name = "k8s.io/code-generator"
packages = ["cmd/client-gen","cmd/client-gen/args","cmd/client-gen/generators","cmd/client-gen/generators/fake","cmd/client-gen/generators/scheme","cmd/client-gen/generators/util","cmd/client-gen/path","cmd/client-gen/types","cmd/deepcopy-gen","cmd/defaulter-gen","cmd/informer-gen","cmd/informer-gen/generators","cmd/lister-gen","cmd/lister-gen/generators"]
revision = "0c5165a97e055df28cca8bbcb8b7c8ae1019b3f1"
[[projects]]
name = "k8s.io/gengo"
packages = ["args","examples/deepcopy-gen/generators","examples/defaulter-gen/generators","examples/set-gen/sets","generator","namer","parser","types"]
revision = "9e661e9308f078838e266cca1c673922088c0ea4"
[[projects]]
branch = "master"
name = "k8s.io/gengo"
packages = ["args","generator","namer","parser","types"]
revision = "2ef5ef33e269934e14149598f5a85d1f561a7219"
name = "k8s.io/kube-openapi"
packages = ["pkg/common"]
revision = "61b46af70dfed79c6d24530cd23b41440a7f22a5"
[[projects]]
name = "k8s.io/kubernetes"
packages = ["cmd/libs/go2idl/client-gen","cmd/libs/go2idl/client-gen/args","cmd/libs/go2idl/client-gen/generators","cmd/libs/go2idl/client-gen/generators/fake","cmd/libs/go2idl/client-gen/generators/scheme","cmd/libs/go2idl/client-gen/path","cmd/libs/go2idl/client-gen/types","cmd/libs/go2idl/informer-gen","cmd/libs/go2idl/informer-gen/generators","cmd/libs/go2idl/lister-gen","cmd/libs/go2idl/lister-gen/generators","pkg/printers","pkg/util/slice"]
revision = "793658f2d7ca7f064d2bdf606519f9fe1229c381"
version = "v1.7.4"
packages = ["pkg/printers"]
revision = "bdaeafa71f6c7c04636251031f93464384d54963"
version = "v1.8.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "37edb445765bd183e89ff47d8a7822a132c3752a8b528e34f499ad4858f792a8"
inputs-digest = "a53b8261d3ecdb48b3417a4e2ff28ddc2588ead8dc9566351db5dab5c2ce53aa"
solver-name = "gps-cdcl"
solver-version = 1
+68 -6
View File
@@ -21,9 +21,59 @@
# version = "2.4.0"
required = [
"k8s.io/kubernetes/cmd/libs/go2idl/client-gen",
"k8s.io/kubernetes/cmd/libs/go2idl/lister-gen",
"k8s.io/kubernetes/cmd/libs/go2idl/informer-gen"
"k8s.io/code-generator/cmd/client-gen",
"k8s.io/code-generator/cmd/deepcopy-gen",
"k8s.io/code-generator/cmd/defaulter-gen",
"k8s.io/code-generator/cmd/lister-gen",
"k8s.io/code-generator/cmd/informer-gen",
# package moves client-go 4.0 to 5.0
"k8s.io/api/core/v1",
"k8s.io/api/admissionregistration/v1alpha1",
"k8s.io/api/apps/v1beta1",
"k8s.io/api/authentication/v1",
"k8s.io/api/authentication/v1beta1",
"k8s.io/api/authorization/v1",
"k8s.io/api/authorization/v1beta1",
"k8s.io/api/autoscaling/v1",
"k8s.io/api/autoscaling/v2beta1",
"k8s.io/api/batch/v1",
"k8s.io/api/batch/v2alpha1",
"k8s.io/api/certificates/v1beta1",
"k8s.io/api/extensions/v1beta1",
"k8s.io/api/networking/v1",
"k8s.io/api/policy/v1beta1",
"k8s.io/api/rbac/v1alpha1",
"k8s.io/api/rbac/v1beta1",
"k8s.io/api/settings/v1alpha1",
"k8s.io/api/storage/v1",
"k8s.io/api/storage/v1beta1",
"k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1",
]
ignored = [
"k8s.io/client-go/pkg/api",
"k8s.io/client-go/pkg/api/v1",
"k8s.io/client-go/pkg/apis/admissionregistration/v1alpha1",
"k8s.io/client-go/pkg/apis/apps/v1beta1",
"k8s.io/client-go/pkg/apis/authentication/v1",
"k8s.io/client-go/pkg/apis/authentication/v1beta1",
"k8s.io/client-go/pkg/apis/authorization/v1",
"k8s.io/client-go/pkg/apis/authorization/v1beta1",
"k8s.io/client-go/pkg/apis/autoscaling/v1",
"k8s.io/client-go/pkg/apis/autoscaling/v2alpha1",
"k8s.io/client-go/pkg/apis/batch/v1",
"k8s.io/client-go/pkg/apis/batch/v2alpha1",
"k8s.io/client-go/pkg/apis/certificates/v1beta1",
"k8s.io/client-go/pkg/apis/extensions/v1beta1",
"k8s.io/client-go/pkg/apis/networking/v1",
"k8s.io/client-go/pkg/apis/policy/v1beta1",
"k8s.io/client-go/pkg/apis/rbac/v1alpha1",
"k8s.io/client-go/pkg/apis/rbac/v1beta1",
"k8s.io/client-go/pkg/apis/settings/v1alpha1",
"k8s.io/client-go/pkg/apis/storage/v1",
"k8s.io/client-go/pkg/apis/storage/v1beta1",
"k8s.io/client-go/kubernetes/typed/autoscaling/v2alpha1",
]
@@ -85,16 +135,28 @@ required = [
[[constraint]]
name = "k8s.io/apimachinery"
revision = "1fd2e63a9a370677308a42f24fd40c86438afddf"
revision = "019ae5ada31de202164b118aee88ee2d14075c31"
[[constraint]]
name = "k8s.io/client-go"
version = "~4.0"
version = "~5.0"
[[constraint]]
name = "k8s.io/kubernetes"
version = "~1.7"
version = "~1.8"
[[override]]
name = "github.com/russross/blackfriday"
revision = "93622da34e54fb6529bfb7c57e710f37a8d9cbd8"
[[constraint]]
branch = "release-1.8"
name = "k8s.io/api"
[[constraint]]
branch = "release-1.8"
name = "k8s.io/code-generator"
[[override]]
name = "k8s.io/gengo"
revision = "9e661e9308f078838e266cca1c673922088c0ea4"
-410
View File
@@ -1,410 +0,0 @@
govalidator
===========
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/asaskevich/govalidator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![GoDoc](https://godoc.org/github.com/asaskevich/govalidator?status.png)](https://godoc.org/github.com/asaskevich/govalidator) [![Coverage Status](https://img.shields.io/coveralls/asaskevich/govalidator.svg)](https://coveralls.io/r/asaskevich/govalidator?branch=master) [![wercker status](https://app.wercker.com/status/1ec990b09ea86c910d5f08b0e02c6043/s "wercker status")](https://app.wercker.com/project/bykey/1ec990b09ea86c910d5f08b0e02c6043)
[![Build Status](https://travis-ci.org/asaskevich/govalidator.svg?branch=master)](https://travis-ci.org/asaskevich/govalidator)
A package of validators and sanitizers for strings, structs and collections. Based on [validator.js](https://github.com/chriso/validator.js).
#### Installation
Make sure that Go is installed on your computer.
Type the following command in your terminal:
go get github.com/asaskevich/govalidator
or you can get specified release of the package with `gopkg.in`:
go get gopkg.in/asaskevich/govalidator.v4
After it the package is ready to use.
#### Import package in your project
Add following line in your `*.go` file:
```go
import "github.com/asaskevich/govalidator"
```
If you are unhappy to use long `govalidator`, you can do something like this:
```go
import (
valid "github.com/asaskevich/govalidator"
)
```
#### Activate behavior to require all fields have a validation tag by default
`SetFieldsRequiredByDefault` causes validation to fail when struct fields do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). A good place to activate this is a package init function or the main() function.
```go
import "github.com/asaskevich/govalidator"
func init() {
govalidator.SetFieldsRequiredByDefault(true)
}
```
Here's some code to explain it:
```go
// this struct definition will fail govalidator.ValidateStruct() (and the field values do not matter):
type exampleStruct struct {
Name string ``
Email string `valid:"email"`
}
// this, however, will only fail when Email is empty or an invalid email address:
type exampleStruct2 struct {
Name string `valid:"-"`
Email string `valid:"email"`
}
// lastly, this will only fail when Email is an invalid email address but not when it's empty:
type exampleStruct2 struct {
Name string `valid:"-"`
Email string `valid:"email,optional"`
}
```
#### Recent breaking changes (see [#123](https://github.com/asaskevich/govalidator/pull/123))
##### Custom validator function signature
A context was added as the second parameter, for structs this is the object being validated this makes dependent validation possible.
```go
import "github.com/asaskevich/govalidator"
// old signature
func(i interface{}) bool
// new signature
func(i interface{}, o interface{}) bool
```
##### Adding a custom validator
This was changed to prevent data races when accessing custom validators.
```go
import "github.com/asaskevich/govalidator"
// before
govalidator.CustomTypeTagMap["customByteArrayValidator"] = CustomTypeValidator(func(i interface{}, o interface{}) bool {
// ...
})
// after
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator(func(i interface{}, o interface{}) bool {
// ...
}))
```
#### List of functions:
```go
func Abs(value float64) float64
func BlackList(str, chars string) string
func ByteLength(str string, params ...string) bool
func StringLength(str string, params ...string) bool
func StringMatches(s string, params ...string) bool
func CamelCaseToUnderscore(str string) string
func Contains(str, substring string) bool
func Count(array []interface{}, iterator ConditionIterator) int
func Each(array []interface{}, iterator Iterator)
func ErrorByField(e error, field string) string
func Filter(array []interface{}, iterator ConditionIterator) []interface{}
func Find(array []interface{}, iterator ConditionIterator) interface{}
func GetLine(s string, index int) (string, error)
func GetLines(s string) []string
func IsHost(s string) bool
func InRange(value, left, right float64) bool
func IsASCII(str string) bool
func IsAlpha(str string) bool
func IsAlphanumeric(str string) bool
func IsBase64(str string) bool
func IsByteLength(str string, min, max int) bool
func IsCreditCard(str string) bool
func IsDataURI(str string) bool
func IsDialString(str string) bool
func IsDNSName(str string) bool
func IsDivisibleBy(str, num string) bool
func IsEmail(str string) bool
func IsFilePath(str string) (bool, int)
func IsFloat(str string) bool
func IsFullWidth(str string) bool
func IsHalfWidth(str string) bool
func IsHexadecimal(str string) bool
func IsHexcolor(str string) bool
func IsIP(str string) bool
func IsIPv4(str string) bool
func IsIPv6(str string) bool
func IsISBN(str string, version int) bool
func IsISBN10(str string) bool
func IsISBN13(str string) bool
func IsISO3166Alpha2(str string) bool
func IsISO3166Alpha3(str string) bool
func IsInt(str string) bool
func IsIn(str string, params ...string) bool
func IsJSON(str string) bool
func IsLatitude(str string) bool
func IsLongitude(str string) bool
func IsLowerCase(str string) bool
func IsMAC(str string) bool
func IsMongoID(str string) bool
func IsMultibyte(str string) bool
func IsNatural(value float64) bool
func IsNegative(value float64) bool
func IsNonNegative(value float64) bool
func IsNonPositive(value float64) bool
func IsNull(str string) bool
func IsNumeric(str string) bool
func IsPort(str string) bool
func IsPositive(value float64) bool
func IsPrintableASCII(str string) bool
func IsRGBcolor(str string) bool
func IsRequestURI(rawurl string) bool
func IsRequestURL(rawurl string) bool
func IsSSN(str string) bool
func IsSemver(str string) bool
func IsURL(str string) bool
func IsUTFDigit(str string) bool
func IsUTFLetter(str string) bool
func IsUTFLetterNumeric(str string) bool
func IsUTFNumeric(str string) bool
func IsUUID(str string) bool
func IsUUIDv3(str string) bool
func IsUUIDv4(str string) bool
func IsUUIDv5(str string) bool
func IsUpperCase(str string) bool
func IsVariableWidth(str string) bool
func IsWhole(value float64) bool
func LeftTrim(str, chars string) string
func Map(array []interface{}, iterator ResultIterator) []interface{}
func Matches(str, pattern string) bool
func NormalizeEmail(str string) (string, error)
func PadBoth(str string, padStr string, padLen int) string
func PadLeft(str string, padStr string, padLen int) string
func PadRight(str string, padStr string, padLen int) string
func RemoveTags(s string) string
func ReplacePattern(str, pattern, replace string) string
func Reverse(s string) string
func RightTrim(str, chars string) string
func SafeFileName(str string) string
func Sign(value float64) float64
func StripLow(str string, keepNewLines bool) string
func ToBoolean(str string) (bool, error)
func ToFloat(str string) (float64, error)
func ToInt(str string) (int64, error)
func ToJSON(obj interface{}) (string, error)
func ToString(obj interface{}) string
func Trim(str, chars string) string
func Truncate(str string, length int, ending string) string
func UnderscoreToCamelCase(s string) string
func ValidateStruct(s interface{}) (bool, error)
func WhiteList(str, chars string) string
type ConditionIterator
type Error
func (e Error) Error() string
type Errors
func (es Errors) Error() string
type ISO3166Entry
type Iterator
type ParamValidator
type ResultIterator
type UnsupportedTypeError
func (e *UnsupportedTypeError) Error() string
type Validator
```
#### Examples
###### IsURL
```go
println(govalidator.IsURL(`http://user@pass:domain.com/path/page`))
```
###### ToString
```go
type User struct {
FirstName string
LastName string
}
str := govalidator.ToString(&User{"John", "Juan"})
println(str)
```
###### Each, Map, Filter, Count for slices
Each iterates over the slice/array and calls Iterator for every item
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.Iterator = func(value interface{}, index int) {
println(value.(int))
}
govalidator.Each(data, fn)
```
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.ResultIterator = func(value interface{}, index int) interface{} {
return value.(int) * 3
}
_ = govalidator.Map(data, fn) // result = []interface{}{1, 6, 9, 12, 15}
```
```go
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var fn govalidator.ConditionIterator = func(value interface{}, index int) bool {
return value.(int)%2 == 0
}
_ = govalidator.Filter(data, fn) // result = []interface{}{2, 4, 6, 8, 10}
_ = govalidator.Count(data, fn) // result = 5
```
###### ValidateStruct [#2](https://github.com/asaskevich/govalidator/pull/2)
If you want to validate structs, you can use tag `valid` for any field in your structure. All validators used with this field in one tag are separated by comma. If you want to skip validation, place `-` in your tag. If you need a validator that is not on the list below, you can add it like this:
```go
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
```
For completely custom validators (interface-based), see below.
Here is a list of available validators for struct fields (validator - used function):
```go
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
```
Validators with parameters
```go
"length(min|max)": ByteLength,
"runelength(min|max)": RuneLegth,
"matches(pattern)": StringMatches,
"in(string1|string2|...|stringN)": IsIn,
```
And here is small example of usage:
```go
type Post struct {
Title string `valid:"alphanum,required"`
Message string `valid:"duck,ascii"`
AuthorIP string `valid:"ipv4"`
Date string `valid:"-"`
}
post := &Post{
Title: "My Example Post",
Message: "duck",
AuthorIP: "123.234.54.3",
}
// Add your own struct validation tags
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
result, err := govalidator.ValidateStruct(post)
if err != nil {
println("error: " + err.Error())
}
println(result)
```
###### WhiteList
```go
// Remove all characters from string ignoring characters between "a" and "z"
println(govalidator.WhiteList("a3a43a5a4a3a2a23a4a5a4a3a4", "a-z") == "aaaaaaaaaaaa")
```
###### Custom validation functions
Custom validation using your own domain specific validators is also available - here's an example of how to use it:
```go
import "github.com/asaskevich/govalidator"
type CustomByteArray [6]byte // custom types are supported and can be validated
type StructWithCustomByteArray struct {
ID CustomByteArray `valid:"customByteArrayValidator,customMinLengthValidator"` // multiple custom validators are possible as well and will be evaluated in sequence
Email string `valid:"email"`
CustomMinLength int `valid:"-"`
}
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", CustomTypeValidator(func(i interface{}, context interface{}) bool {
switch v := context.(type) { // you can type switch on the context interface being validated
case StructWithCustomByteArray:
// you can check and validate against some other field in the context,
// return early or not validate against the context at all your choice
case SomeOtherType:
// ...
default:
// expecting some other type? Throw/panic here or continue
}
switch v := i.(type) { // type switch on the struct field being validated
case CustomByteArray:
for _, e := range v { // this validator checks that the byte array is not empty, i.e. not all zeroes
if e != 0 {
return true
}
}
}
return false
}))
govalidator.CustomTypeTagMap.Set("customMinLengthValidator", CustomTypeValidator(func(i interface{}, context interface{}) bool {
switch v := context.(type) { // this validates a field against the value in another field, i.e. dependent validation
case StructWithCustomByteArray:
return len(v.ID) >= v.CustomMinLength
}
return false
}))
```
#### Notes
Documentation is available here: [godoc.org](https://godoc.org/github.com/asaskevich/govalidator).
Full information about code coverage is also available here: [govalidator on gocover.io](http://gocover.io/github.com/asaskevich/govalidator).
#### Support
If you do have a contribution for the package feel free to put up a Pull Request or open Issue.
#### Special thanks to [contributors](https://github.com/asaskevich/govalidator/graphs/contributors)
* [Daniel Lohse](https://github.com/annismckenzie)
* [Attila Oláh](https://github.com/attilaolah)
* [Daniel Korner](https://github.com/Dadie)
* [Steven Wilkin](https://github.com/stevenwilkin)
* [Deiwin Sarjas](https://github.com/deiwin)
* [Noah Shibley](https://github.com/slugmobile)
* [Nathan Davies](https://github.com/nathj07)
* [Matt Sanford](https://github.com/mzsanford)
* [Simon ccl1115](https://github.com/ccl1115)
-58
View File
@@ -1,58 +0,0 @@
package govalidator
// Iterator is the function that accepts element of slice/array and its index
type Iterator func(interface{}, int)
// ResultIterator is the function that accepts element of slice/array and its index and returns any result
type ResultIterator func(interface{}, int) interface{}
// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean
type ConditionIterator func(interface{}, int) bool
// Each iterates over the slice and apply Iterator to every item
func Each(array []interface{}, iterator Iterator) {
for index, data := range array {
iterator(data, index)
}
}
// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result.
func Map(array []interface{}, iterator ResultIterator) []interface{} {
var result = make([]interface{}, len(array))
for index, data := range array {
result[index] = iterator(data, index)
}
return result
}
// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise.
func Find(array []interface{}, iterator ConditionIterator) interface{} {
for index, data := range array {
if iterator(data, index) {
return data
}
}
return nil
}
// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice.
func Filter(array []interface{}, iterator ConditionIterator) []interface{} {
var result = make([]interface{}, 0)
for index, data := range array {
if iterator(data, index) {
result = append(result, data)
}
}
return result
}
// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator.
func Count(array []interface{}, iterator ConditionIterator) int {
count := 0
for index, data := range array {
if iterator(data, index) {
count = count + 1
}
}
return count
}
-49
View File
@@ -1,49 +0,0 @@
package govalidator
import (
"encoding/json"
"fmt"
"strconv"
)
// ToString convert the input to a string.
func ToString(obj interface{}) string {
res := fmt.Sprintf("%v", obj)
return string(res)
}
// ToJSON convert the input to a valid JSON string
func ToJSON(obj interface{}) (string, error) {
res, err := json.Marshal(obj)
if err != nil {
res = []byte("")
}
return string(res), err
}
// ToFloat convert the input string to a float, or 0.0 if the input is not a float.
func ToFloat(str string) (float64, error) {
res, err := strconv.ParseFloat(str, 64)
if err != nil {
res = 0.0
}
return res, err
}
// ToInt convert the input string to an integer, or 0 if the input is not an integer.
func ToInt(str string) (int64, error) {
res, err := strconv.ParseInt(str, 0, 64)
if err != nil {
res = 0
}
return res, err
}
// ToBoolean convert the input string to a boolean.
func ToBoolean(str string) (bool, error) {
res, err := strconv.ParseBool(str)
if err != nil {
res = false
}
return res, err
}
-31
View File
@@ -1,31 +0,0 @@
package govalidator
// Errors is an array of multiple errors and conforms to the error interface.
type Errors []error
// Errors returns itself.
func (es Errors) Errors() []error {
return es
}
func (es Errors) Error() string {
var err string
for _, e := range es {
err += e.Error() + ";"
}
return err
}
// Error encapsulates a name, an error and whether there's a custom error message or not.
type Error struct {
Name string
Err error
CustomErrorMessageExists bool
}
func (e Error) Error() string {
if e.CustomErrorMessageExists {
return e.Err.Error()
}
return e.Name + ": " + e.Err.Error()
}
-57
View File
@@ -1,57 +0,0 @@
package govalidator
import "math"
// Abs returns absolute value of number
func Abs(value float64) float64 {
return value * Sign(value)
}
// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise
func Sign(value float64) float64 {
if value > 0 {
return 1
} else if value < 0 {
return -1
} else {
return 0
}
}
// IsNegative returns true if value < 0
func IsNegative(value float64) bool {
return value < 0
}
// IsPositive returns true if value > 0
func IsPositive(value float64) bool {
return value > 0
}
// IsNonNegative returns true if value >= 0
func IsNonNegative(value float64) bool {
return value >= 0
}
// IsNonPositive returns true if value <= 0
func IsNonPositive(value float64) bool {
return value <= 0
}
// InRange returns true if value lies between left and right border
func InRange(value, left, right float64) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// IsWhole returns true if value is whole number
func IsWhole(value float64) bool {
return Abs(math.Remainder(value, 1)) == 0
}
// IsNatural returns true if value is natural number (positive and whole)
func IsNatural(value float64) bool {
return IsWhole(value) && IsPositive(value)
}
-91
View File
@@ -1,91 +0,0 @@
package govalidator
import "regexp"
// Basic regular expressions for validating strings
const (
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$"
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
ISBN13 string = "^(?:[0-9]{13})$"
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[0-9]+$"
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
Hexadecimal string = "^[0-9a-fA-F]+$"
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
ASCII string = "^[\x00-\x7F]+$"
Multibyte string = "[^\x00-\x7F]"
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
PrintableASCII string = "^[\x20-\x7E]+$"
DataURI string = "^data:.+\\/(.+);base64$"
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
DNSName string = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9]{1}[a-zA-Z0-9_-]{1,62})*$`
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername string = `(\S+(:\S*)?@)`
Hostname string = ``
URLPath string = `((\/|\?|#)[^\s]*)`
URLPort string = `(:(\d{1,5}))`
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))`
URLSubdomain string = `((www\.)|([a-zA-Z0-9]([-\.][-\._a-zA-Z0-9]+)*))`
URL string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^(/[^/\x00]*)+/?$`
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
tagName string = "valid"
)
// Used by IsFilePath func
const (
// Unknown is unresolved OS type
Unknown = iota
// Win is Windows type
Win
// Unix is *nix OS types
Unix
)
var (
rxEmail = regexp.MustCompile(Email)
rxCreditCard = regexp.MustCompile(CreditCard)
rxISBN10 = regexp.MustCompile(ISBN10)
rxISBN13 = regexp.MustCompile(ISBN13)
rxUUID3 = regexp.MustCompile(UUID3)
rxUUID4 = regexp.MustCompile(UUID4)
rxUUID5 = regexp.MustCompile(UUID5)
rxUUID = regexp.MustCompile(UUID)
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
rxInt = regexp.MustCompile(Int)
rxFloat = regexp.MustCompile(Float)
rxHexadecimal = regexp.MustCompile(Hexadecimal)
rxHexcolor = regexp.MustCompile(Hexcolor)
rxRGBcolor = regexp.MustCompile(RGBcolor)
rxASCII = regexp.MustCompile(ASCII)
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
rxMultibyte = regexp.MustCompile(Multibyte)
rxFullWidth = regexp.MustCompile(FullWidth)
rxHalfWidth = regexp.MustCompile(HalfWidth)
rxBase64 = regexp.MustCompile(Base64)
rxDataURI = regexp.MustCompile(DataURI)
rxLatitude = regexp.MustCompile(Latitude)
rxLongitude = regexp.MustCompile(Longitude)
rxDNSName = regexp.MustCompile(DNSName)
rxURL = regexp.MustCompile(URL)
rxSSN = regexp.MustCompile(SSN)
rxWinPath = regexp.MustCompile(WinPath)
rxUnixPath = regexp.MustCompile(UnixPath)
rxSemver = regexp.MustCompile(Semver)
)
-415
View File
@@ -1,415 +0,0 @@
package govalidator
import (
"reflect"
"regexp"
"sync"
)
// Validator is a wrapper for a validator function that returns bool and accepts string.
type Validator func(str string) bool
// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type.
// The second parameter should be the context (in the case of validating a struct: the whole object being validated).
type CustomTypeValidator func(i interface{}, o interface{}) bool
// ParamValidator is a wrapper for validator functions that accepts additional parameters.
type ParamValidator func(str string, params ...string) bool
type tagOptionsMap map[string]string
// UnsupportedTypeError is a wrapper for reflect.Type
type UnsupportedTypeError struct {
Type reflect.Type
}
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements the methods to sort by string.
type stringValues []reflect.Value
// ParamTagMap is a map of functions accept variants parameters
var ParamTagMap = map[string]ParamValidator{
"length": ByteLength,
"runelength": RuneLength,
"stringlength": StringLength,
"matches": StringMatches,
"in": isInRaw,
}
// ParamTagRegexMap maps param tags to their respective regexes.
var ParamTagRegexMap = map[string]*regexp.Regexp{
"length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"),
"runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"),
"stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"),
"in": regexp.MustCompile(`^in\((.*)\)`),
"matches": regexp.MustCompile(`^matches\((.+)\)$`),
}
type customTypeTagMap struct {
validators map[string]CustomTypeValidator
sync.RWMutex
}
func (tm *customTypeTagMap) Get(name string) (CustomTypeValidator, bool) {
tm.RLock()
defer tm.RUnlock()
v, ok := tm.validators[name]
return v, ok
}
func (tm *customTypeTagMap) Set(name string, ctv CustomTypeValidator) {
tm.Lock()
defer tm.Unlock()
tm.validators[name] = ctv
}
// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function.
// Use this to validate compound or custom types that need to be handled as a whole, e.g.
// `type UUID [16]byte` (this would be handled as an array of bytes).
var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeValidator)}
// TagMap is a map of functions, that can be used as tags for ValidateStruct function.
var TagMap = map[string]Validator{
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"ISO4217": IsISO4217,
}
// ISO3166Entry stores country codes
type ISO3166Entry struct {
EnglishShortName string
FrenchShortName string
Alpha2Code string
Alpha3Code string
Numeric string
}
//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes"
var ISO3166List = []ISO3166Entry{
{"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"},
{"Albania", "Albanie (l')", "AL", "ALB", "008"},
{"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"},
{"Algeria", "Algérie (l')", "DZ", "DZA", "012"},
{"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"},
{"Andorra", "Andorre (l')", "AD", "AND", "020"},
{"Angola", "Angola (l')", "AO", "AGO", "024"},
{"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"},
{"Azerbaijan", "Azerbaïdjan (l')", "AZ", "AZE", "031"},
{"Argentina", "Argentine (l')", "AR", "ARG", "032"},
{"Australia", "Australie (l')", "AU", "AUS", "036"},
{"Austria", "Autriche (l')", "AT", "AUT", "040"},
{"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"},
{"Bahrain", "Bahreïn", "BH", "BHR", "048"},
{"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"},
{"Armenia", "Arménie (l')", "AM", "ARM", "051"},
{"Barbados", "Barbade (la)", "BB", "BRB", "052"},
{"Belgium", "Belgique (la)", "BE", "BEL", "056"},
{"Bermuda", "Bermudes (les)", "BM", "BMU", "060"},
{"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"},
{"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"},
{"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"},
{"Botswana", "Botswana (le)", "BW", "BWA", "072"},
{"Bouvet Island", "Bouvet (l'Île)", "BV", "BVT", "074"},
{"Brazil", "Brésil (le)", "BR", "BRA", "076"},
{"Belize", "Belize (le)", "BZ", "BLZ", "084"},
{"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"},
{"Solomon Islands", "Salomon (Îles)", "SB", "SLB", "090"},
{"Virgin Islands (British)", "Vierges britanniques (les Îles)", "VG", "VGB", "092"},
{"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"},
{"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"},
{"Myanmar", "Myanmar (le)", "MM", "MMR", "104"},
{"Burundi", "Burundi (le)", "BI", "BDI", "108"},
{"Belarus", "Bélarus (le)", "BY", "BLR", "112"},
{"Cambodia", "Cambodge (le)", "KH", "KHM", "116"},
{"Cameroon", "Cameroun (le)", "CM", "CMR", "120"},
{"Canada", "Canada (le)", "CA", "CAN", "124"},
{"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"},
{"Cayman Islands (the)", "Caïmans (les Îles)", "KY", "CYM", "136"},
{"Central African Republic (the)", "République centrafricaine (la)", "CF", "CAF", "140"},
{"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"},
{"Chad", "Tchad (le)", "TD", "TCD", "148"},
{"Chile", "Chili (le)", "CL", "CHL", "152"},
{"China", "Chine (la)", "CN", "CHN", "156"},
{"Taiwan (Province of China)", "Taïwan (Province de Chine)", "TW", "TWN", "158"},
{"Christmas Island", "Christmas (l'Île)", "CX", "CXR", "162"},
{"Cocos (Keeling) Islands (the)", "Cocos (les Îles)/ Keeling (les Îles)", "CC", "CCK", "166"},
{"Colombia", "Colombie (la)", "CO", "COL", "170"},
{"Comoros (the)", "Comores (les)", "KM", "COM", "174"},
{"Mayotte", "Mayotte", "YT", "MYT", "175"},
{"Congo (the)", "Congo (le)", "CG", "COG", "178"},
{"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"},
{"Cook Islands (the)", "Cook (les Îles)", "CK", "COK", "184"},
{"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"},
{"Croatia", "Croatie (la)", "HR", "HRV", "191"},
{"Cuba", "Cuba", "CU", "CUB", "192"},
{"Cyprus", "Chypre", "CY", "CYP", "196"},
{"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"},
{"Benin", "Bénin (le)", "BJ", "BEN", "204"},
{"Denmark", "Danemark (le)", "DK", "DNK", "208"},
{"Dominica", "Dominique (la)", "DM", "DMA", "212"},
{"Dominican Republic (the)", "dominicaine (la République)", "DO", "DOM", "214"},
{"Ecuador", "Équateur (l')", "EC", "ECU", "218"},
{"El Salvador", "El Salvador", "SV", "SLV", "222"},
{"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"},
{"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"},
{"Eritrea", "Érythrée (l')", "ER", "ERI", "232"},
{"Estonia", "Estonie (l')", "EE", "EST", "233"},
{"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"},
{"Falkland Islands (the) [Malvinas]", "Falkland (les Îles)/Malouines (les Îles)", "FK", "FLK", "238"},
{"South Georgia and the South Sandwich Islands", "Géorgie du Sud-et-les Îles Sandwich du Sud (la)", "GS", "SGS", "239"},
{"Fiji", "Fidji (les)", "FJ", "FJI", "242"},
{"Finland", "Finlande (la)", "FI", "FIN", "246"},
{"Åland Islands", "Åland(les Îles)", "AX", "ALA", "248"},
{"France", "France (la)", "FR", "FRA", "250"},
{"French Guiana", "Guyane française (la )", "GF", "GUF", "254"},
{"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"},
{"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"},
{"Djibouti", "Djibouti", "DJ", "DJI", "262"},
{"Gabon", "Gabon (le)", "GA", "GAB", "266"},
{"Georgia", "Géorgie (la)", "GE", "GEO", "268"},
{"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"},
{"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"},
{"Germany", "Allemagne (l')", "DE", "DEU", "276"},
{"Ghana", "Ghana (le)", "GH", "GHA", "288"},
{"Gibraltar", "Gibraltar", "GI", "GIB", "292"},
{"Kiribati", "Kiribati", "KI", "KIR", "296"},
{"Greece", "Grèce (la)", "GR", "GRC", "300"},
{"Greenland", "Groenland (le)", "GL", "GRL", "304"},
{"Grenada", "Grenade (la)", "GD", "GRD", "308"},
{"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"},
{"Guam", "Guam", "GU", "GUM", "316"},
{"Guatemala", "Guatemala (le)", "GT", "GTM", "320"},
{"Guinea", "Guinée (la)", "GN", "GIN", "324"},
{"Guyana", "Guyana (le)", "GY", "GUY", "328"},
{"Haiti", "Haïti", "HT", "HTI", "332"},
{"Heard Island and McDonald Islands", "Heard-et-Îles MacDonald (l'Île)", "HM", "HMD", "334"},
{"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"},
{"Honduras", "Honduras (le)", "HN", "HND", "340"},
{"Hong Kong", "Hong Kong", "HK", "HKG", "344"},
{"Hungary", "Hongrie (la)", "HU", "HUN", "348"},
{"Iceland", "Islande (l')", "IS", "ISL", "352"},
{"India", "Inde (l')", "IN", "IND", "356"},
{"Indonesia", "Indonésie (l')", "ID", "IDN", "360"},
{"Iran (Islamic Republic of)", "Iran (République Islamique d')", "IR", "IRN", "364"},
{"Iraq", "Iraq (l')", "IQ", "IRQ", "368"},
{"Ireland", "Irlande (l')", "IE", "IRL", "372"},
{"Israel", "Israël", "IL", "ISR", "376"},
{"Italy", "Italie (l')", "IT", "ITA", "380"},
{"Côte d'Ivoire", "Côte d'Ivoire (la)", "CI", "CIV", "384"},
{"Jamaica", "Jamaïque (la)", "JM", "JAM", "388"},
{"Japan", "Japon (le)", "JP", "JPN", "392"},
{"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"},
{"Jordan", "Jordanie (la)", "JO", "JOR", "400"},
{"Kenya", "Kenya (le)", "KE", "KEN", "404"},
{"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"},
{"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"},
{"Kuwait", "Koweït (le)", "KW", "KWT", "414"},
{"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"},
{"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"},
{"Lebanon", "Liban (le)", "LB", "LBN", "422"},
{"Lesotho", "Lesotho (le)", "LS", "LSO", "426"},
{"Latvia", "Lettonie (la)", "LV", "LVA", "428"},
{"Liberia", "Libéria (le)", "LR", "LBR", "430"},
{"Libya", "Libye (la)", "LY", "LBY", "434"},
{"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"},
{"Lithuania", "Lituanie (la)", "LT", "LTU", "440"},
{"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"},
{"Macao", "Macao", "MO", "MAC", "446"},
{"Madagascar", "Madagascar", "MG", "MDG", "450"},
{"Malawi", "Malawi (le)", "MW", "MWI", "454"},
{"Malaysia", "Malaisie (la)", "MY", "MYS", "458"},
{"Maldives", "Maldives (les)", "MV", "MDV", "462"},
{"Mali", "Mali (le)", "ML", "MLI", "466"},
{"Malta", "Malte", "MT", "MLT", "470"},
{"Martinique", "Martinique (la)", "MQ", "MTQ", "474"},
{"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"},
{"Mauritius", "Maurice", "MU", "MUS", "480"},
{"Mexico", "Mexique (le)", "MX", "MEX", "484"},
{"Monaco", "Monaco", "MC", "MCO", "492"},
{"Mongolia", "Mongolie (la)", "MN", "MNG", "496"},
{"Moldova (the Republic of)", "Moldova , République de", "MD", "MDA", "498"},
{"Montenegro", "Monténégro (le)", "ME", "MNE", "499"},
{"Montserrat", "Montserrat", "MS", "MSR", "500"},
{"Morocco", "Maroc (le)", "MA", "MAR", "504"},
{"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"},
{"Oman", "Oman", "OM", "OMN", "512"},
{"Namibia", "Namibie (la)", "NA", "NAM", "516"},
{"Nauru", "Nauru", "NR", "NRU", "520"},
{"Nepal", "Népal (le)", "NP", "NPL", "524"},
{"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"},
{"Curaçao", "Curaçao", "CW", "CUW", "531"},
{"Aruba", "Aruba", "AW", "ABW", "533"},
{"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"},
{"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"},
{"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"},
{"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"},
{"New Zealand", "Nouvelle-Zélande (la)", "NZ", "NZL", "554"},
{"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"},
{"Niger (the)", "Niger (le)", "NE", "NER", "562"},
{"Nigeria", "Nigéria (le)", "NG", "NGA", "566"},
{"Niue", "Niue", "NU", "NIU", "570"},
{"Norfolk Island", "Norfolk (l'Île)", "NF", "NFK", "574"},
{"Norway", "Norvège (la)", "NO", "NOR", "578"},
{"Northern Mariana Islands (the)", "Mariannes du Nord (les Îles)", "MP", "MNP", "580"},
{"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"},
{"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"},
{"Marshall Islands (the)", "Marshall (Îles)", "MH", "MHL", "584"},
{"Palau", "Palaos (les)", "PW", "PLW", "585"},
{"Pakistan", "Pakistan (le)", "PK", "PAK", "586"},
{"Panama", "Panama (le)", "PA", "PAN", "591"},
{"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"},
{"Paraguay", "Paraguay (le)", "PY", "PRY", "600"},
{"Peru", "Pérou (le)", "PE", "PER", "604"},
{"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"},
{"Pitcairn", "Pitcairn", "PN", "PCN", "612"},
{"Poland", "Pologne (la)", "PL", "POL", "616"},
{"Portugal", "Portugal (le)", "PT", "PRT", "620"},
{"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"},
{"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"},
{"Puerto Rico", "Porto Rico", "PR", "PRI", "630"},
{"Qatar", "Qatar (le)", "QA", "QAT", "634"},
{"Réunion", "Réunion (La)", "RE", "REU", "638"},
{"Romania", "Roumanie (la)", "RO", "ROU", "642"},
{"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"},
{"Rwanda", "Rwanda (le)", "RW", "RWA", "646"},
{"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"},
{"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"},
{"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"},
{"Anguilla", "Anguilla", "AI", "AIA", "660"},
{"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"},
{"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"},
{"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"},
{"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"},
{"San Marino", "Saint-Marin", "SM", "SMR", "674"},
{"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"},
{"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"},
{"Senegal", "Sénégal (le)", "SN", "SEN", "686"},
{"Serbia", "Serbie (la)", "RS", "SRB", "688"},
{"Seychelles", "Seychelles (les)", "SC", "SYC", "690"},
{"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"},
{"Singapore", "Singapour", "SG", "SGP", "702"},
{"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"},
{"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"},
{"Slovenia", "Slovénie (la)", "SI", "SVN", "705"},
{"Somalia", "Somalie (la)", "SO", "SOM", "706"},
{"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"},
{"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"},
{"Spain", "Espagne (l')", "ES", "ESP", "724"},
{"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"},
{"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"},
{"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"},
{"Suriname", "Suriname (le)", "SR", "SUR", "740"},
{"Svalbard and Jan Mayen", "Svalbard et l'Île Jan Mayen (le)", "SJ", "SJM", "744"},
{"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"},
{"Sweden", "Suède (la)", "SE", "SWE", "752"},
{"Switzerland", "Suisse (la)", "CH", "CHE", "756"},
{"Syrian Arab Republic", "République arabe syrienne (la)", "SY", "SYR", "760"},
{"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"},
{"Thailand", "Thaïlande (la)", "TH", "THA", "764"},
{"Togo", "Togo (le)", "TG", "TGO", "768"},
{"Tokelau", "Tokelau (les)", "TK", "TKL", "772"},
{"Tonga", "Tonga (les)", "TO", "TON", "776"},
{"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"},
{"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"},
{"Tunisia", "Tunisie (la)", "TN", "TUN", "788"},
{"Turkey", "Turquie (la)", "TR", "TUR", "792"},
{"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"},
{"Turks and Caicos Islands (the)", "Turks-et-Caïcos (les Îles)", "TC", "TCA", "796"},
{"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"},
{"Uganda", "Ouganda (l')", "UG", "UGA", "800"},
{"Ukraine", "Ukraine (l')", "UA", "UKR", "804"},
{"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'exRépublique yougoslave de)", "MK", "MKD", "807"},
{"Egypt", "Égypte (l')", "EG", "EGY", "818"},
{"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"},
{"Guernsey", "Guernesey", "GG", "GGY", "831"},
{"Jersey", "Jersey", "JE", "JEY", "832"},
{"Isle of Man", "Île de Man", "IM", "IMN", "833"},
{"Tanzania, United Republic of", "Tanzanie, République-Unie de", "TZ", "TZA", "834"},
{"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"},
{"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"},
{"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"},
{"Uruguay", "Uruguay (l')", "UY", "URY", "858"},
{"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"},
{"Venezuela (Bolivarian Republic of)", "Venezuela (République bolivarienne du)", "VE", "VEN", "862"},
{"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"},
{"Samoa", "Samoa (le)", "WS", "WSM", "882"},
{"Yemen", "Yémen (le)", "YE", "YEM", "887"},
{"Zambia", "Zambie (la)", "ZM", "ZMB", "894"},
}
var ISO4217List = []string{
"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN",
"BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD",
"CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK",
"DJF", "DKK", "DOP", "DZD",
"EGP", "ERN", "ETB", "EUR",
"FJD", "FKP",
"GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD",
"HKD", "HNL", "HRK", "HTG", "HUF",
"IDR", "ILS", "INR", "IQD", "IRR", "ISK",
"JMD", "JOD", "JPY",
"KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT",
"LAK", "LBP", "LKR", "LRD", "LSL", "LYD",
"MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN",
"NAD", "NGN", "NIO", "NOK", "NPR", "NZD",
"OMR",
"PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG",
"QAR",
"RON", "RSD", "RUB", "RWF",
"SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "SVC", "SYP", "SZL",
"THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS",
"UAH", "UGX", "USD", "USN", "UYI", "UYU", "UZS",
"VEF", "VND", "VUV",
"WST",
"XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX",
"YER",
"ZAR", "ZMW", "ZWL",
}
-268
View File
@@ -1,268 +0,0 @@
package govalidator
import (
"errors"
"fmt"
"html"
"math"
"path"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)
// Contains check if the string contains the substring.
func Contains(str, substring string) bool {
return strings.Contains(str, substring)
}
// Matches check if string matches the pattern (pattern is regular expression)
// In case of error return false
func Matches(str, pattern string) bool {
match, _ := regexp.MatchString(pattern, str)
return match
}
// LeftTrim trim characters from the left-side of the input.
// If second argument is empty, it's will be remove leading spaces.
func LeftTrim(str, chars string) string {
pattern := ""
if chars == "" {
pattern = "^\\s+"
} else {
pattern = "^[" + chars + "]+"
}
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// RightTrim trim characters from the right-side of the input.
// If second argument is empty, it's will be remove spaces.
func RightTrim(str, chars string) string {
pattern := ""
if chars == "" {
pattern = "\\s+$"
} else {
pattern = "[" + chars + "]+$"
}
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// Trim trim characters from both sides of the input.
// If second argument is empty, it's will be remove spaces.
func Trim(str, chars string) string {
return LeftTrim(RightTrim(str, chars), chars)
}
// WhiteList remove characters that do not appear in the whitelist.
func WhiteList(str, chars string) string {
pattern := "[^" + chars + "]+"
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// BlackList remove characters that appear in the blacklist.
func BlackList(str, chars string) string {
pattern := "[" + chars + "]+"
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte("")))
}
// StripLow remove characters with a numerical value < 32 and 127, mostly control characters.
// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
func StripLow(str string, keepNewLines bool) string {
chars := ""
if keepNewLines {
chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F"
} else {
chars = "\x00-\x1F\x7F"
}
return BlackList(str, chars)
}
// ReplacePattern replace regular expression pattern in string
func ReplacePattern(str, pattern, replace string) string {
r, _ := regexp.Compile(pattern)
return string(r.ReplaceAll([]byte(str), []byte(replace)))
}
// Escape replace <, >, & and " with HTML entities.
var Escape = html.EscapeString
func addSegment(inrune, segment []rune) []rune {
if len(segment) == 0 {
return inrune
}
if len(inrune) != 0 {
inrune = append(inrune, '_')
}
inrune = append(inrune, segment...)
return inrune
}
// UnderscoreToCamelCase converts from underscore separated form to camel case form.
// Ex.: my_func => MyFunc
func UnderscoreToCamelCase(s string) string {
return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1)
}
// CamelCaseToUnderscore converts from camel case form to underscore separated form.
// Ex.: MyFunc => my_func
func CamelCaseToUnderscore(str string) string {
var output []rune
var segment []rune
for _, r := range str {
if !unicode.IsLower(r) {
output = addSegment(output, segment)
segment = nil
}
segment = append(segment, unicode.ToLower(r))
}
output = addSegment(output, segment)
return string(output)
}
// Reverse return reversed string
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
// GetLines split string by "\n" and return array of lines
func GetLines(s string) []string {
return strings.Split(s, "\n")
}
// GetLine return specified line of multiline string
func GetLine(s string, index int) (string, error) {
lines := GetLines(s)
if index < 0 || index >= len(lines) {
return "", errors.New("line index out of bounds")
}
return lines[index], nil
}
// RemoveTags remove all tags from HTML string
func RemoveTags(s string) string {
return ReplacePattern(s, "<[^>]*>", "")
}
// SafeFileName return safe string that can be used in file names
func SafeFileName(str string) string {
name := strings.ToLower(str)
name = path.Clean(path.Base(name))
name = strings.Trim(name, " ")
separators, err := regexp.Compile(`[ &_=+:]`)
if err == nil {
name = separators.ReplaceAllString(name, "-")
}
legal, err := regexp.Compile(`[^[:alnum:]-.]`)
if err == nil {
name = legal.ReplaceAllString(name, "")
}
for strings.Contains(name, "--") {
name = strings.Replace(name, "--", "-", -1)
}
return name
}
// NormalizeEmail canonicalize an email address.
// The local part of the email address is lowercased for all domains; the hostname is always lowercased and
// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail).
// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and
// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are
// normalized to @gmail.com.
func NormalizeEmail(str string) (string, error) {
if !IsEmail(str) {
return "", fmt.Errorf("%s is not an email", str)
}
parts := strings.Split(str, "@")
parts[0] = strings.ToLower(parts[0])
parts[1] = strings.ToLower(parts[1])
if parts[1] == "gmail.com" || parts[1] == "googlemail.com" {
parts[1] = "gmail.com"
parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0]
}
return strings.Join(parts, "@"), nil
}
// Truncate a string to the closest length without breaking words.
func Truncate(str string, length int, ending string) string {
var aftstr, befstr string
if len(str) > length {
words := strings.Fields(str)
before, present := 0, 0
for i := range words {
befstr = aftstr
before = present
aftstr = aftstr + words[i] + " "
present = len(aftstr)
if present > length && i != 0 {
if (length - before) < (present - length) {
return Trim(befstr, " /\\.,\"'#!?&@+-") + ending
}
return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending
}
}
}
return str
}
// Pad left side of string if size of string is less then indicated pad length
func PadLeft(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, false)
}
// Pad right side of string if size of string is less then indicated pad length
func PadRight(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, false, true)
}
// Pad both sides of string if size of string is less then indicated pad length
func PadBoth(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, true)
}
// Pad string either left, right or both sides, not the padding string can be unicode and more then one
// character
func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string {
// When padded length is less then the current string size
if padLen < utf8.RuneCountInString(str) {
return str
}
padLen -= utf8.RuneCountInString(str)
targetLen := padLen
targetLenLeft := targetLen
targetLenRight := targetLen
if padLeft && padRight {
targetLenLeft = padLen / 2
targetLenRight = padLen - targetLenLeft
}
strToRepeatLen := utf8.RuneCountInString(padStr)
repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen)))
repeatedString := strings.Repeat(padStr, repeatTimes)
leftSide := ""
if padLeft {
leftSide = repeatedString[0:targetLenLeft]
}
rightSide := ""
if padRight {
rightSide = repeatedString[0:targetLenRight]
}
return leftSide + str + rightSide
}
File diff suppressed because it is too large Load Diff
-15
View File
@@ -1,15 +0,0 @@
box: golang
build:
steps:
- setup-go-workspace
- script:
name: go get
code: |
go version
go get -t ./...
- script:
name: go test
code: |
go test -race ./...
-37
View File
@@ -1,37 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# never checkin from the bin file (for now)
bin/*
# Test key files
*.pem
# Cover profiles
*.out
# Editor/IDE specific files.
*.sublime-project
*.sublime-workspace
-19
View File
@@ -1,19 +0,0 @@
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
Brian Bland <brian.bland@docker.com> Brian Bland <brian.t.bland@gmail.com>
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu>
Richard Scothern <richard.scothern@docker.com> Richard <richard.scothern@gmail.com>
Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@gmail.com>
Andrew Meredith <andymeredith@gmail.com> Andrew Meredith <kendru@users.noreply.github.com>
harche <p.harshal@gmail.com> harche <harche@users.noreply.github.com>
Jessie Frazelle <jessie@docker.com> <jfrazelle@users.noreply.github.com>
Sharif Nassar <sharif@mrwacky.com> Sharif Nassar <mrwacky42@users.noreply.github.com>
Sven Dowideit <SvenDowideit@home.org.au> Sven Dowideit <SvenDowideit@users.noreply.github.com>
Vincent Giersch <vincent.giersch@ovh.net> Vincent Giersch <vincent@giersch.fr>
davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com>
Omer Cohen <git@omer.io> Omer Cohen <git@omerc.net>
Eric Yang <windfarer@gmail.com> Eric Yang <Windfarer@users.noreply.github.com>
Nikita Tarasov <nikita@mygento.ru> Nikita <luckyraul@users.noreply.github.com>
Misty Stanley-Jones <misty@docker.com> Misty Stanley-Jones <misty@apache.org>
-182
View File
@@ -1,182 +0,0 @@
Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Schlesinger <aschlesinger@deis.com>
Aaron Vinson <avinson.public@gmail.com>
Adam Duke <adam.v.duke@gmail.com>
Adam Enger <adamenger@gmail.com>
Adrian Mouat <adrian.mouat@gmail.com>
Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
Alex Chan <alex.chan@metaswitch.com>
Alex Elman <aelman@indeed.com>
Alexey Gladkov <gladkov.alexey@gmail.com>
allencloud <allen.sun@daocloud.io>
amitshukla <ashukla73@hotmail.com>
Amy Lindburg <amy.lindburg@docker.com>
Andrew Hsu <andrewhsu@acm.org>
Andrew Meredith <andymeredith@gmail.com>
Andrew T Nguyen <andrew.nguyen@docker.com>
Andrey Kostov <kostov.andrey@gmail.com>
Andy Goldstein <agoldste@redhat.com>
Anis Elleuch <vadmeste@gmail.com>
Antonio Mercado <amercado@thinknode.com>
Antonio Murdaca <runcom@redhat.com>
Anton Tiurin <noxiouz@yandex.ru>
Anusha Ragunathan <anusha@docker.com>
a-palchikov <deemok@gmail.com>
Arien Holthuizen <aholthuizen@schubergphilis.com>
Arnaud Porterie <arnaud.porterie@docker.com>
Arthur Baars <arthur@semmle.com>
Asuka Suzuki <hello@tanksuzuki.com>
Avi Miller <avi.miller@oracle.com>
Ayose Cazorla <ayosec@gmail.com>
BadZen <dave.trombley@gmail.com>
Ben Bodenmiller <bbodenmiller@hotmail.com>
Ben Firshman <ben@firshman.co.uk>
bin liu <liubin0329@gmail.com>
Brian Bland <brian.bland@docker.com>
burnettk <burnettk@gmail.com>
Carson A <ca@carsonoid.net>
Cezar Sa Espinola <cezarsa@gmail.com>
Charles Smith <charles.smith@docker.com>
Chris Dillon <squarism@gmail.com>
cuiwei13 <cuiwei13@pku.edu.cn>
cyli <cyli@twistedmatrix.com>
Daisuke Fujita <dtanshi45@gmail.com>
Daniel Huhn <daniel@danielhuhn.de>
Darren Shepherd <darren@rancher.com>
Dave Trombley <dave.trombley@gmail.com>
Dave Tucker <dt@docker.com>
David Lawrence <david.lawrence@docker.com>
davidli <wenquan.li@hp.com>
David Verhasselt <david@crowdway.com>
David Xia <dxia@spotify.com>
Dejan Golja <dejan@golja.org>
Derek McGowan <derek@mcgstyle.net>
Diogo Mónica <diogo.monica@gmail.com>
DJ Enriquez <dj.enriquez@infospace.com>
Donald Huang <don.hcd@gmail.com>
Doug Davis <dug@us.ibm.com>
Edgar Lee <edgar.lee@docker.com>
Eric Yang <windfarer@gmail.com>
Fabio Berchtold <jamesclonk@jamesclonk.ch>
Fabio Huser <fabio@fh1.ch>
farmerworking <farmerworking@gmail.com>
Felix Yan <felixonmars@archlinux.org>
Florentin Raud <florentin.raud@gmail.com>
Frank Chen <frankchn@gmail.com>
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
gabriell nascimento <gabriell@bluesoft.com.br>
Gleb Schukin <gschukin@ptsecurity.com>
harche <p.harshal@gmail.com>
Henri Gomez <henri.gomez@gmail.com>
Hua Wang <wanghua.humble@gmail.com>
Hu Keping <hukeping@huawei.com>
HuKeping <hukeping@huawei.com>
Ian Babrou <ibobrik@gmail.com>
igayoso <igayoso@gmail.com>
Jack Griffin <jackpg14@gmail.com>
James Findley <jfindley@fastmail.com>
Jason Freidman <jason.freidman@gmail.com>
Jason Heiss <jheiss@aput.net>
Jeff Nickoloff <jeff@allingeek.com>
Jess Frazelle <acidburn@google.com>
Jessie Frazelle <jessie@docker.com>
jhaohai <jhaohai@foxmail.com>
Jianqing Wang <tsing@jianqing.org>
Jihoon Chung <jihoon@gmail.com>
Joao Fernandes <joao.fernandes@docker.com>
John Mulhausen <john@docker.com>
John Starks <jostarks@microsoft.com>
Jonathan Boulle <jonathanboulle@gmail.com>
Jon Johnson <jonjohnson@google.com>
Jon Poler <jonathan.poler@apcera.com>
Jordan Liggitt <jliggitt@redhat.com>
Josh Chorlton <josh.chorlton@docker.com>
Josh Hawn <josh.hawn@docker.com>
Julien Fernandez <julien.fernandez@gmail.com>
Keerthan Mala <kmala@engineyard.com>
Kelsey Hightower <kelsey.hightower@gmail.com>
Kenneth Lim <kennethlimcp@gmail.com>
Kenny Leung <kleung@google.com>
Ke Xu <leonhartx.k@gmail.com>
liuchang0812 <liuchang0812@gmail.com>
Liu Hua <sdu.liu@huawei.com>
Li Yi <denverdino@gmail.com>
Lloyd Ramey <lnr0626@gmail.com>
Louis Kottmann <louis.kottmann@gmail.com>
Luke Carpenter <x@rubynerd.net>
Marcus Martins <marcus@docker.com>
Mary Anthony <mary@docker.com>
Matt Bentley <mbentley@mbentley.net>
Matt Duch <matt@learnmetrics.com>
Matthew Green <greenmr@live.co.uk>
Matt Moore <mattmoor@google.com>
Matt Robenolt <matt@ydekproductions.com>
Michael Prokop <mika@grml.org>
Michal Minar <miminar@redhat.com>
Michal Minář <miminar@redhat.com>
Mike Brown <brownwm@us.ibm.com>
Miquel Sabaté <msabate@suse.com>
Misty Stanley-Jones <misty@docker.com>
Morgan Bauer <mbauer@us.ibm.com>
moxiegirl <mary@docker.com>
Nathan Sullivan <nathan@nightsys.net>
nevermosby <robolwq@qq.com>
Nghia Tran <tcnghia@gmail.com>
Nikita Tarasov <nikita@mygento.ru>
Noah Treuhaft <noah.treuhaft@docker.com>
Nuutti Kotivuori <nuutti.kotivuori@poplatek.fi>
Oilbeater <liumengxinfly@gmail.com>
Olivier Gambier <olivier@docker.com>
Olivier Jacques <olivier.jacques@hp.com>
Omer Cohen <git@omer.io>
Patrick Devine <patrick.devine@docker.com>
Phil Estes <estesp@linux.vnet.ibm.com>
Philip Misiowiec <philip@atlashealth.com>
Pierre-Yves Ritschard <pyr@spootnik.org>
Qiao Anran <qiaoanran@gmail.com>
Randy Barlow <randy@electronsweatshop.com>
Richard Scothern <richard.scothern@docker.com>
Rodolfo Carvalho <rhcarvalho@gmail.com>
Rusty Conover <rusty@luckydinosaur.com>
Sean Boran <Boran@users.noreply.github.com>
Sebastiaan van Stijn <github@gone.nl>
Sebastien Coavoux <s.coavoux@free.fr>
Serge Dubrouski <sergeyfd@gmail.com>
Sharif Nassar <sharif@mrwacky.com>
Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
Shreyas Karnik <karnik.shreyas@gmail.com>
Simon Thulbourn <simon+github@thulbourn.com>
spacexnice <yaoyao.xyy@alibaba-inc.com>
Spencer Rinehart <anubis@overthemonkey.com>
Stan Hu <stanhu@gmail.com>
Stefan Majewsky <stefan.majewsky@sap.com>
Stefan Weil <sw@weilnetz.de>
Stephen J Day <stephen.day@docker.com>
Sungho Moon <sungho.moon@navercorp.com>
Sven Dowideit <SvenDowideit@home.org.au>
Sylvain Baubeau <sbaubeau@redhat.com>
Ted Reed <ted.reed@gmail.com>
tgic <farmer1992@gmail.com>
Thomas Sjögren <konstruktoid@users.noreply.github.com>
Tianon Gravi <admwiggin@gmail.com>
Tibor Vass <teabee89@gmail.com>
Tonis Tiigi <tonistiigi@gmail.com>
Tony Holdstock-Brown <tony@docker.com>
Trevor Pounds <trevor.pounds@gmail.com>
Troels Thomsen <troels@thomsen.io>
Victoria Bialas <victoria.bialas@docker.com>
Victor Vieux <vieux@docker.com>
Vincent Batts <vbatts@redhat.com>
Vincent Demeester <vincent@sbr.pm>
Vincent Giersch <vincent.giersch@ovh.net>
weiyuan.yl <weiyuan.yl@alibaba-inc.com>
W. Trevor King <wking@tremily.us>
xg.song <xg.song@venusource.com>
xiekeyang <xiekeyang@huawei.com>
Yann ROBERT <yann.robert@anantaplex.fr>
yaoyao.xyy <yaoyao.xyy@alibaba-inc.com>
yixi zhang <yixi@memsql.com>
yuexiao-wang <wang.yuexiao@zte.com.cn>
yuzou <zouyu7@huawei.com>
zhouhaibing089 <zhouhaibing089@gmail.com>
姜继忠 <jizhong.jiangjz@alibaba-inc.com>
-119
View File
@@ -1,119 +0,0 @@
# Building the registry source
## Use-case
This is useful if you intend to actively work on the registry.
### Alternatives
Most people should use the [official Registry docker image](https://hub.docker.com/r/library/registry/).
People looking for advanced operational use cases might consider rolling their own image with a custom Dockerfile inheriting `FROM registry:2`.
OS X users who want to run natively can do so following [the instructions here](https://github.com/docker/docker.github.io/blob/master/registry/recipes/osx-setup-guide.md).
### Gotchas
You are expected to know your way around with go & git.
If you are a casual user with no development experience, and no preliminary knowledge of go, building from source is probably not a good solution for you.
## Build the development environment
The first prerequisite of properly building distribution targets is to have a Go
development environment setup. Please follow [How to Write Go Code](https://golang.org/doc/code.html)
for proper setup. If done correctly, you should have a GOROOT and GOPATH set in the
environment.
If a Go development environment is setup, one can use `go get` to install the
`registry` command from the current latest:
go get github.com/docker/distribution/cmd/registry
The above will install the source repository into the `GOPATH`.
Now create the directory for the registry data (this might require you to set permissions properly)
mkdir -p /var/lib/registry
... or alternatively `export REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/somewhere` if you want to store data into another location.
The `registry`
binary can then be run with the following:
$ $GOPATH/bin/registry --version
$GOPATH/bin/registry github.com/docker/distribution v2.0.0-alpha.1+unknown
> __NOTE:__ While you do not need to use `go get` to checkout the distribution
> project, for these build instructions to work, the project must be checked
> out in the correct location in the `GOPATH`. This should almost always be
> `$GOPATH/src/github.com/docker/distribution`.
The registry can be run with the default config using the following
incantation:
$ $GOPATH/bin/registry serve $GOPATH/src/github.com/docker/distribution/cmd/registry/config-example.yml
INFO[0000] endpoint local-5003 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
INFO[0000] endpoint local-8083 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
INFO[0000] listening on :5000 app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
INFO[0000] debug server listening localhost:5001
If it is working, one should see the above log messages.
### Repeatable Builds
For the full development experience, one should `cd` into
`$GOPATH/src/github.com/docker/distribution`. From there, the regular `go`
commands, such as `go test`, should work per package (please see
[Developing](#developing) if they don't work).
A `Makefile` has been provided as a convenience to support repeatable builds.
Please install the following into `GOPATH` for it to work:
go get github.com/tools/godep github.com/golang/lint/golint
**TODO(stevvooe):** Add a `make setup` command to Makefile to run this. Have to think about how to interact with Godeps properly.
Once these commands are available in the `GOPATH`, run `make` to get a full
build:
$ make
+ clean
+ fmt
+ vet
+ lint
+ build
github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar
github.com/Sirupsen/logrus
github.com/docker/libtrust
...
github.com/yvasiyarov/gorelic
github.com/docker/distribution/registry/handlers
github.com/docker/distribution/cmd/registry
+ test
...
ok github.com/docker/distribution/digest 7.875s
ok github.com/docker/distribution/manifest 0.028s
ok github.com/docker/distribution/notifications 17.322s
? github.com/docker/distribution/registry [no test files]
ok github.com/docker/distribution/registry/api/v2 0.101s
? github.com/docker/distribution/registry/auth [no test files]
ok github.com/docker/distribution/registry/auth/silly 0.011s
...
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry-api-descriptor-template
+ binaries
The above provides a repeatable build using the contents of the vendored
Godeps directory. This includes formatting, vetting, linting, building,
testing and generating tagged binaries. We can verify this worked by running
the registry binary generated in the "./bin" directory:
$ ./bin/registry -version
./bin/registry github.com/docker/distribution v2.0.0-alpha.2-80-g16d8b2c.m
### Optional build tags
Optional [build tags](http://golang.org/pkg/go/build/) can be provided using
the environment variable `DOCKER_BUILDTAGS`.
-114
View File
@@ -1,114 +0,0 @@
# Changelog
## 2.6.1 (2017-04-05)
#### Registry
- Fix `Forwarded` header handling, revert use of `X-Forwarded-Port`
- Use driver `Stat` for registry health check
## 2.6.0 (2017-01-18)
#### Storage
- S3: fixed bug in delete due to read-after-write inconsistency
- S3: allow EC2 IAM roles to be used when authorizing region endpoints
- S3: add Object ACL Support
- S3: fix delete method's notion of subpaths
- S3: use multipart upload API in `Move` method for performance
- S3: add v2 signature signing for legacy S3 clones
- Swift: add simple heuristic to detect incomplete DLOs during read ops
- Swift: support different user and tenant domains
- Swift: bulk deletes in chunks
- Aliyun OSS: fix delete method's notion of subpaths
- Aliyun OSS: optimize data copy after upload finishes
- Azure: close leaking response body
- Fix storage drivers dropping non-EOF errors when listing repositories
- Compare path properly when listing repositories in catalog
- Add a foreign layer URL host whitelist
- Improve catalog enumerate runtime
#### Registry
- Export `storage.CreateOptions` in top-level package
- Enable notifications to endpoints that use self-signed certificates
- Properly validate multi-URL foreign layers
- Add control over validation of URLs in pushed manifests
- Proxy mode: fix socket leak when pull is cancelled
- Tag service: properly handle error responses on HEAD request
- Support for custom authentication URL in proxying registry
- Add configuration option to disable access logging
- Add notification filtering by target media type
- Manifest: `References()` returns all children
- Honor `X-Forwarded-Port` and Forwarded headers
- Reference: Preserve tag and digest in With* functions
- Add policy configuration for enforcing repository classes
#### Client
- Changes the client Tags `All()` method to follow links
- Allow registry clients to connect via HTTP2
- Better handling of OAuth errors in client
#### Spec
- Manifest: clarify relationship between urls and foreign layers
- Authorization: add support for repository classes
#### Manifest
- Override media type returned from `Stat()` for existing manifests
- Add plugin mediatype to distribution manifest
#### Docs
- Document `TOOMANYREQUESTS` error code
- Document required Let's Encrypt port
- Improve documentation around implementation of OAuth2
- Improve documentation for configuration
#### Auth
- Add support for registry type in scope
- Add support for using v2 ping challenges for v1
- Add leeway to JWT `nbf` and `exp` checking
- htpasswd: dynamically parse htpasswd file
- Fix missing auth headers with PATCH HTTP request when pushing to default port
#### Dockerfile
- Update to go1.7
- Reorder Dockerfile steps for better layer caching
#### Notes
Documentation has moved to the documentation repository at
`github.com/docker/docker.github.io/tree/master/registry`
The registry is go 1.7 compliant, and passes newer, more restrictive `lint` and `vet` ing.
## 2.5.0 (2016-06-14)
#### Storage
- Ensure uploads directory is cleaned after upload is committed
- Add ability to cap concurrent operations in filesystem driver
- S3: Add 'us-gov-west-1' to the valid region list
- Swift: Handle ceph not returning Last-Modified header for HEAD requests
- Add redirect middleware
#### Registry
- Add support for blobAccessController middleware
- Add support for layers from foreign sources
- Remove signature store
- Add support for Let's Encrypt
- Correct yaml key names in configuration
#### Client
- Add option to get content digest from manifest get
#### Spec
- Update the auth spec scope grammar to reflect the fact that hostnames are optionally supported
- Clarify API documentation around catalog fetch behavior
#### API
- Support returning HTTP 429 (Too Many Requests)
#### Documentation
- Update auth documentation examples to show "expires in" as int
#### Docker Image
- Use Alpine Linux as base image
-140
View File
@@ -1,140 +0,0 @@
# Contributing to the registry
## Before reporting an issue...
### If your problem is with...
- automated builds
- your account on the [Docker Hub](https://hub.docker.com/)
- any other [Docker Hub](https://hub.docker.com/) issue
Then please do not report your issue here - you should instead report it to [https://support.docker.com](https://support.docker.com)
### If you...
- need help setting up your registry
- can't figure out something
- are not sure what's going on or what your problem is
Then please do not open an issue here yet - you should first try one of the following support forums:
- irc: #docker-distribution on freenode
- mailing-list: <distribution@dockerproject.org> or https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution
## Reporting an issue properly
By following these simple rules you will get better and faster feedback on your issue.
- search the bugtracker for an already reported issue
### If you found an issue that describes your problem:
- please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments
- please refrain from adding "same thing here" or "+1" comments
- you don't need to comment on an issue to get notified of updates: just hit the "subscribe" button
- comment if you have some new, technical and relevant information to add to the case
- __DO NOT__ comment on closed issues or merged PRs. If you think you have a related problem, open up a new issue and reference the PR or issue.
### If you have not found an existing issue that describes your problem:
1. create a new issue, with a succinct title that describes your issue:
- bad title: "It doesn't work with my docker"
- good title: "Private registry push fail: 400 error with E_INVALID_DIGEST"
2. copy the output of:
- `docker version`
- `docker info`
- `docker exec <registry-container> registry -version`
3. copy the command line you used to launch your Registry
4. restart your docker daemon in debug mode (add `-D` to the daemon launch arguments)
5. reproduce your problem and get your docker daemon logs showing the error
6. if relevant, copy your registry logs that show the error
7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used)
8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry
## Contributing a patch for a known bug, or a small correction
You should follow the basic GitHub workflow:
1. fork
2. commit a change
3. make sure the tests pass
4. PR
Additionally, you must [sign your commits](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work). It's very simple:
- configure your name with git: `git config user.name "Real Name" && git config user.email mail@example.com`
- sign your commits using `-s`: `git commit -s -m "My commit"`
Some simple rules to ensure quick merge:
- clearly point to the issue(s) you want to fix in your PR comment (e.g., `closes #12345`)
- prefer multiple (smaller) PRs addressing individual issues over a big one trying to address multiple issues at once
- if you need to amend your PR following comments, please squash instead of adding more commits
## Contributing new features
You are heavily encouraged to first discuss what you want to do. You can do so on the irc channel, or by opening an issue that clearly describes the use case you want to fulfill, or the problem you are trying to solve.
If this is a major new feature, you should then submit a proposal that describes your technical solution and reasoning.
If you did discuss it first, this will likely be greenlighted very fast. It's advisable to address all feedback on this proposal before starting actual work.
Then you should submit your implementation, clearly linking to the issue (and possible proposal).
Your PR will be reviewed by the community, then ultimately by the project maintainers, before being merged.
It's mandatory to:
- interact respectfully with other community members and maintainers - more generally, you are expected to abide by the [Docker community rules](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#docker-community-guidelines)
- address maintainers' comments and modify your submission accordingly
- write tests for any new code
Complying to these simple rules will greatly accelerate the review process, and will ensure you have a pleasant experience in contributing code to the Registry.
Have a look at a great, successful contribution: the [Swift driver PR](https://github.com/docker/distribution/pull/493)
## Coding Style
Unless explicitly stated, we follow all coding guidelines from the Go
community. While some of these standards may seem arbitrary, they somehow seem
to result in a solid, consistent codebase.
It is possible that the code base does not currently comply with these
guidelines. We are not looking for a massive PR that fixes this, since that
goes against the spirit of the guidelines. All new contributions should make a
best effort to clean up and make the code base better than they left it.
Obviously, apply your best judgement. Remember, the goal here is to make the
code base easier for humans to navigate and understand. Always keep that in
mind when nudging others to comply.
The rules:
1. All code should be formatted with `gofmt -s`.
2. All code should pass the default levels of
[`golint`](https://github.com/golang/lint).
3. All code should follow the guidelines covered in [Effective
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
4. Comment the code. Tell us the why, the history and the context.
5. Document _all_ declarations and methods, even private ones. Declare
expectations, caveats and anything else that may be important. If a type
gets exported, having the comments already there will ensure it's ready.
6. Variable name length should be proportional to its context and no longer.
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
In practice, short methods will have short variable names and globals will
have longer names.
7. No underscores in package names. If you need a compound name, step back,
and re-examine why you need a compound name. If you still think you need a
compound name, lose the underscore.
8. No utils or helpers packages. If a function is not general enough to
warrant its own package, it has not been written generally enough to be a
part of a util package. Just leave it unexported and well-documented.
9. All tests should run with `go test` and outside tooling should not be
required. No, we don't need another unit testing framework. Assertion
packages are acceptable if they provide _real_ incremental value.
10. Even though we call these "rules" above, they are actually just
guidelines. Since you've read all the rules, you now know that.
If you are having trouble getting into the mood of idiomatic Go, we recommend
reading through [Effective Go](http://golang.org/doc/effective_go.html). The
[Go Blog](http://blog.golang.org/) is also a great resource. Drinking the
kool-aid is a lot easier than going thirsty.
-18
View File
@@ -1,18 +0,0 @@
FROM golang:1.7-alpine
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
ENV DOCKER_BUILDTAGS include_oss include_gcs
RUN set -ex \
&& apk add --no-cache make git
WORKDIR $DISTRIBUTION_DIR
COPY . $DISTRIBUTION_DIR
COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
RUN make PREFIX=/go clean binaries
VOLUME ["/var/lib/registry"]
EXPOSE 5000
ENTRYPOINT ["registry"]
CMD ["serve", "/etc/docker/registry/config.yml"]
-58
View File
@@ -1,58 +0,0 @@
# Distribution maintainers file
#
# This file describes who runs the docker/distribution project and how.
# This is a living document - if you see something out of date or missing, speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant parser.
#
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
people = [
"aaronlehmann",
"dmcgowan",
"dmp42",
"richardscothern",
"shykes",
"stevvooe",
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.aaronlehmann]
Name = "Aaron Lehmann"
Email = "aaron.lehmann@docker.com"
GitHub = "aaronlehmann"
[people.dmcgowan]
Name = "Derek McGowan"
Email = "derek@mcgstyle.net"
GitHub = "dmcgowan"
[people.dmp42]
Name = "Olivier Gambier"
Email = "olivier@docker.com"
GitHub = "dmp42"
[people.richardscothern]
Name = "Richard Scothern"
Email = "richard.scothern@gmail.com"
GitHub = "richardscothern"
[people.shykes]
Name = "Solomon Hykes"
Email = "solomon@docker.com"
GitHub = "shykes"
[people.stevvooe]
Name = "Stephen Day"
Email = "stephen.day@docker.com"
GitHub = "stevvooe"
-109
View File
@@ -1,109 +0,0 @@
# Set an output prefix, which is the local directory if not specified
PREFIX?=$(shell pwd)
# Used to populate version variable in main package.
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
# Allow turning off function inlining and variable registerization
ifeq (${DISABLE_OPTIMIZATION},true)
GO_GCFLAGS=-gcflags "-N -l"
VERSION:="$(VERSION)-noopt"
endif
GO_LDFLAGS=-ldflags "-X `go list ./version`.Version=$(VERSION)"
.PHONY: all build binaries clean dep-restore dep-save dep-validate fmt lint test test-full vet
.DEFAULT: all
all: fmt vet lint build test binaries
AUTHORS: .mailmap .git/HEAD
git log --format='%aN <%aE>' | sort -fu > $@
# This only needs to be generated by hand when cutting full releases.
version/version.go:
./version/version.sh > $@
# Required for go 1.5 to build
GO15VENDOREXPERIMENT := 1
# Go files
GOFILES=$(shell find . -type f -name '*.go')
# Package list
PKGS=$(shell go list -tags "${DOCKER_BUILDTAGS}" ./... | grep -v ^github.com/docker/distribution/vendor/)
# Resolving binary dependencies for specific targets
GOLINT=$(shell which golint || echo '')
GODEP=$(shell which godep || echo '')
${PREFIX}/bin/registry: $(GOFILES)
@echo "+ $@"
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/registry
${PREFIX}/bin/digest: $(GOFILES)
@echo "+ $@"
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/digest
${PREFIX}/bin/registry-api-descriptor-template: $(GOFILES)
@echo "+ $@"
@go build -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/registry-api-descriptor-template
docs/spec/api.md: docs/spec/api.md.tmpl ${PREFIX}/bin/registry-api-descriptor-template
./bin/registry-api-descriptor-template $< > $@
vet:
@echo "+ $@"
@go vet -tags "${DOCKER_BUILDTAGS}" $(PKGS)
fmt:
@echo "+ $@"
@test -z "$$(gofmt -s -l . 2>&1 | grep -v ^vendor/ | tee /dev/stderr)" || \
(echo >&2 "+ please format Go code with 'gofmt -s'" && false)
lint:
@echo "+ $@"
$(if $(GOLINT), , \
$(error Please install golint: `go get -u github.com/golang/lint/golint`))
@test -z "$$($(GOLINT) ./... 2>&1 | grep -v ^vendor/ | tee /dev/stderr)"
build:
@echo "+ $@"
@go build -tags "${DOCKER_BUILDTAGS}" -v ${GO_LDFLAGS} $(PKGS)
test:
@echo "+ $@"
@go test -test.short -tags "${DOCKER_BUILDTAGS}" $(PKGS)
test-full:
@echo "+ $@"
@go test -tags "${DOCKER_BUILDTAGS}" $(PKGS)
binaries: ${PREFIX}/bin/registry ${PREFIX}/bin/digest ${PREFIX}/bin/registry-api-descriptor-template
@echo "+ $@"
clean:
@echo "+ $@"
@rm -rf "${PREFIX}/bin/registry" "${PREFIX}/bin/digest" "${PREFIX}/bin/registry-api-descriptor-template"
dep-save:
@echo "+ $@"
$(if $(GODEP), , \
$(error Please install godep: go get github.com/tools/godep))
@$(GODEP) save $(PKGS)
dep-restore:
@echo "+ $@"
$(if $(GODEP), , \
$(error Please install godep: go get github.com/tools/godep))
@$(GODEP) restore -v
dep-validate: dep-restore
@echo "+ $@"
@rm -Rf .vendor.bak
@mv vendor .vendor.bak
@rm -Rf Godeps
@$(GODEP) save ./...
@test -z "$$(diff -r vendor .vendor.bak 2>&1 | tee /dev/stderr)" || \
(echo >&2 "+ borked dependencies! what you have in Godeps/Godeps.json does not match with what you have in vendor" && false)
@rm -Rf .vendor.bak
-131
View File
@@ -1,131 +0,0 @@
# Distribution
The Docker toolset to pack, ship, store, and deliver content.
This repository's main product is the Docker Registry 2.0 implementation
for storing and distributing Docker images. It supersedes the
[docker/docker-registry](https://github.com/docker/docker-registry)
project with a new API design, focused around security and performance.
<img src="https://www.docker.com/sites/default/files/oyster-registry-3.png" width=200px/>
[![Circle CI](https://circleci.com/gh/docker/distribution/tree/master.svg?style=svg)](https://circleci.com/gh/docker/distribution/tree/master)
[![GoDoc](https://godoc.org/github.com/docker/distribution?status.svg)](https://godoc.org/github.com/docker/distribution)
This repository contains the following components:
|**Component** |Description |
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **registry** | An implementation of the [Docker Registry HTTP API V2](docs/spec/api.md) for use with docker 1.6+. |
| **libraries** | A rich set of libraries for interacting with distribution components. Please see [godoc](https://godoc.org/github.com/docker/distribution) for details. **Note**: These libraries are **unstable**. |
| **specifications** | _Distribution_ related specifications are available in [docs/spec](docs/spec) |
| **documentation** | Docker's full documentation set is available at [docs.docker.com](https://docs.docker.com). This repository [contains the subset](docs/) related just to the registry. |
### How does this integrate with Docker engine?
This project should provide an implementation to a V2 API for use in the [Docker
core project](https://github.com/docker/docker). The API should be embeddable
and simplify the process of securely pulling and pushing content from `docker`
daemons.
### What are the long term goals of the Distribution project?
The _Distribution_ project has the further long term goal of providing a
secure tool chain for distributing content. The specifications, APIs and tools
should be as useful with Docker as they are without.
Our goal is to design a professional grade and extensible content distribution
system that allow users to:
* Enjoy an efficient, secured and reliable way to store, manage, package and
exchange content
* Hack/roll their own on top of healthy open-source components
* Implement their own home made solution through good specs, and solid
extensions mechanism.
## More about Registry 2.0
The new registry implementation provides the following benefits:
- faster push and pull
- new, more efficient implementation
- simplified deployment
- pluggable storage backend
- webhook notifications
For information on upcoming functionality, please see [ROADMAP.md](ROADMAP.md).
### Who needs to deploy a registry?
By default, Docker users pull images from Docker's public registry instance.
[Installing Docker](https://docs.docker.com/engine/installation/) gives users this
ability. Users can also push images to a repository on Docker's public registry,
if they have a [Docker Hub](https://hub.docker.com/) account.
For some users and even companies, this default behavior is sufficient. For
others, it is not.
For example, users with their own software products may want to maintain a
registry for private, company images. Also, you may wish to deploy your own
image repository for images used to test or in continuous integration. For these
use cases and others, [deploying your own registry instance](https://github.com/docker/docker.github.io/blob/master/registry/deploying.md)
may be the better choice.
### Migration to Registry 2.0
For those who have previously deployed their own registry based on the Registry
1.0 implementation and wish to deploy a Registry 2.0 while retaining images,
data migration is required. A tool to assist with migration efforts has been
created. For more information see [docker/migrator]
(https://github.com/docker/migrator).
## Contribute
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
issues, fixes, and patches to this project. If you are contributing code, see
the instructions for [building a development environment](BUILDING.md).
## Support
If any issues are encountered while using the _Distribution_ project, several
avenues are available for support:
<table>
<tr>
<th align="left">
IRC
</th>
<td>
#docker-distribution on FreeNode
</td>
</tr>
<tr>
<th align="left">
Issue Tracker
</th>
<td>
github.com/docker/distribution/issues
</td>
</tr>
<tr>
<th align="left">
Google Groups
</th>
<td>
https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution
</td>
</tr>
<tr>
<th align="left">
Mailing List
</th>
<td>
docker@dockerproject.org
</td>
</tr>
</table>
## License
This project is distributed under [Apache License, Version 2.0](LICENSE).
-36
View File
@@ -1,36 +0,0 @@
## Registry Release Checklist
10. Compile release notes detailing features and since the last release. Update the `CHANGELOG.md` file.
20. Update the version file: `https://github.com/docker/distribution/blob/master/version/version.go`
30. Update the `MAINTAINERS` (if necessary), `AUTHORS` and `.mailmap` files.
```
make AUTHORS
```
40. Create a signed tag.
Distribution uses semantic versioning. Tags are of the format `vx.y.z[-rcn]`
You will need PGP installed and a PGP key which has been added to your Github account. The comment for the tag should include the release notes.
50. Push the signed tag
60. Create a new [release](https://github.com/docker/distribution/releases). In the case of a release candidate, tick the `pre-release` checkbox.
70. Update the registry binary in [distribution library image repo](https://github.com/docker/distribution-library-image) by running the update script and opening a pull request.
80. Update the official image. Add the new version in the [official images repo](https://github.com/docker-library/official-images) by appending a new version to the `registry/registry` file with the git hash pointed to by the signed tag. Update the major version to point to the latest version and the minor version to point to new patch release if necessary.
e.g. to release `2.3.1`
`2.3.1 (new)`
`2.3.0 -> 2.3.0` can be removed
`2 -> 2.3.1`
`2.3 -> 2.3.1`
90. Build a new distribution/registry image on [Docker hub](https://hub.docker.com/u/distribution/dashboard) by adding a new automated build with the new tag and re-building the images.
-267
View File
@@ -1,267 +0,0 @@
# Roadmap
The Distribution Project consists of several components, some of which are
still being defined. This document defines the high-level goals of the
project, identifies the current components, and defines the release-
relationship to the Docker Platform.
* [Distribution Goals](#distribution-goals)
* [Distribution Components](#distribution-components)
* [Project Planning](#project-planning): release-relationship to the Docker Platform.
This road map is a living document, providing an overview of the goals and
considerations made in respect of the future of the project.
## Distribution Goals
- Replace the existing [docker registry](github.com/docker/docker-registry)
implementation as the primary implementation.
- Replace the existing push and pull code in the docker engine with the
distribution package.
- Define a strong data model for distributing docker images
- Provide a flexible distribution tool kit for use in the docker platform
- Unlock new distribution models
## Distribution Components
Components of the Distribution Project are managed via github [milestones](https://github.com/docker/distribution/milestones). Upcoming
features and bugfixes for a component will be added to the relevant milestone. If a feature or
bugfix is not part of a milestone, it is currently unscheduled for
implementation.
* [Registry](#registry)
* [Distribution Package](#distribution-package)
***
### Registry
The new Docker registry is the main portion of the distribution repository.
Registry 2.0 is the first release of the next-generation registry. This was
primarily focused on implementing the [new registry
API](https://github.com/docker/distribution/blob/master/docs/spec/api.md),
with a focus on security and performance.
Following from the Distribution project goals above, we have a set of goals
for registry v2 that we would like to follow in the design. New features
should be compared against these goals.
#### Data Storage and Distribution First
The registry's first goal is to provide a reliable, consistent storage
location for Docker images. The registry should only provide the minimal
amount of indexing required to fetch image data and no more.
This means we should be selective in new features and API additions, including
those that may require expensive, ever growing indexes. Requests should be
servable in "constant time".
#### Content Addressability
All data objects used in the registry API should be content addressable.
Content identifiers should be secure and verifiable. This provides a secure,
reliable base from which to build more advanced content distribution systems.
#### Content Agnostic
In the past, changes to the image format would require large changes in Docker
and the Registry. By decoupling the distribution and image format, we can
allow the formats to progress without having to coordinate between the two.
This means that we should be focused on decoupling Docker from the registry
just as much as decoupling the registry from Docker. Such an approach will
allow us to unlock new distribution models that haven't been possible before.
We can take this further by saying that the new registry should be content
agnostic. The registry provides a model of names, tags, manifests and content
addresses and that model can be used to work with content.
#### Simplicity
The new registry should be closer to a microservice component than its
predecessor. This means it should have a narrower API and a low number of
service dependencies. It should be easy to deploy.
This means that other solutions should be explored before changing the API or
adding extra dependencies. If functionality is required, can it be added as an
extension or companion service.
#### Extensibility
The registry should provide extension points to add functionality. By keeping
the scope narrow, but providing the ability to add functionality.
Features like search, indexing, synchronization and registry explorers fall
into this category. No such feature should be added unless we've found it
impossible to do through an extension.
#### Active Feature Discussions
The following are feature discussions that are currently active.
If you don't see your favorite, unimplemented feature, feel free to contact us
via IRC or the mailing list and we can talk about adding it. The goal here is
to make sure that new features go through a rigid design process before
landing in the registry.
##### Proxying to other Registries
A _pull-through caching_ mode exists for the registry, but is restricted from
within the docker client to only mirror the official Docker Hub. This functionality
can be expanded when image provenance has been specified and implemented in the
distribution project.
##### Metadata storage
Metadata for the registry is currently stored with the manifest and layer data on
the storage backend. While this is a big win for simplicity and reliably maintaining
state, it comes with the cost of consistency and high latency. The mutable registry
metadata operations should be abstracted behind an API which will allow ACID compliant
storage systems to handle metadata.
##### Peer to Peer transfer
Discussion has started here: https://docs.google.com/document/d/1rYDpSpJiQWmCQy8Cuiaa3NH-Co33oK_SC9HeXYo87QA/edit
##### Indexing, Search and Discovery
The original registry provided some implementation of search for use with
private registries. Support has been elided from V2 since we'd like to both
decouple search functionality from the registry. The makes the registry
simpler to deploy, especially in use cases where search is not needed, and
let's us decouple the image format from the registry.
There are explorations into using the catalog API and notification system to
build external indexes. The current line of thought is that we will define a
common search API to index and query docker images. Such a system could be run
as a companion to a registry or set of registries to power discovery.
The main issue with search and discovery is that there are so many ways to
accomplish it. There are two aspects to this project. The first is deciding on
how it will be done, including an API definition that can work with changing
data formats. The second is the process of integrating with `docker search`.
We expect that someone attempts to address the problem with the existing tools
and propose it as a standard search API or uses it to inform a standardization
process. Once this has been explored, we integrate with the docker client.
Please see the following for more detail:
- https://github.com/docker/distribution/issues/206
##### Deletes
> __NOTE:__ Deletes are a much asked for feature. Before requesting this
feature or participating in discussion, we ask that you read this section in
full and understand the problems behind deletes.
While, at first glance, implementing deleting seems simple, there are a number
mitigating factors that make many solutions not ideal or even pathological in
the context of a registry. The following paragraph discuss the background and
approaches that could be applied to arrive at a solution.
The goal of deletes in any system is to remove unused or unneeded data. Only
data requested for deletion should be removed and no other data. Removing
unintended data is worse than _not_ removing data that was requested for
removal but ideally, both are supported. Generally, according to this rule, we
err on holding data longer than needed, ensuring that it is only removed when
we can be certain that it can be removed. With the current behavior, we opt to
hold onto the data forever, ensuring that data cannot be incorrectly removed.
To understand the problems with implementing deletes, one must understand the
data model. All registry data is stored in a filesystem layout, implemented on
a "storage driver", effectively a _virtual file system_ (VFS). The storage
system must assume that this VFS layer will be eventually consistent and has
poor read- after-write consistency, since this is the lower common denominator
among the storage drivers. This is mitigated by writing values in reverse-
dependent order, but makes wider transactional operations unsafe.
Layered on the VFS model is a content-addressable _directed, acyclic graph_
(DAG) made up of blobs. Manifests reference layers. Tags reference manifests.
Since the same data can be referenced by multiple manifests, we only store
data once, even if it is in different repositories. Thus, we have a set of
blobs, referenced by tags and manifests. If we want to delete a blob we need
to be certain that it is no longer referenced by another manifest or tag. When
we delete a manifest, we also can try to delete the referenced blobs. Deciding
whether or not a blob has an active reference is the crux of the problem.
Conceptually, deleting a manifest and its resources is quite simple. Just find
all the manifests, enumerate the referenced blobs and delete the blobs not in
that set. An astute observer will recognize this as a garbage collection
problem. As with garbage collection in programming languages, this is very
simple when one always has a consistent view. When one adds parallelism and an
inconsistent view of data, it becomes very challenging.
A simple example can demonstrate this. Let's say we are deleting a manifest
_A_ in one process. We scan the manifest and decide that all the blobs are
ready for deletion. Concurrently, we have another process accepting a new
manifest _B_ referencing one or more blobs from the manifest _A_. Manifest _B_
is accepted and all the blobs are considered present, so the operation
proceeds. The original process then deletes the referenced blobs, assuming
they were unreferenced. The manifest _B_, which we thought had all of its data
present, can no longer be served by the registry, since the dependent data has
been deleted.
Deleting data from the registry safely requires some way to coordinate this
operation. The following approaches are being considered:
- _Reference Counting_ - Maintain a count of references to each blob. This is
challenging for a number of reasons: 1. maintaining a consistent consensus
of reference counts across a set of Registries and 2. Building the initial
list of reference counts for an existing registry. These challenges can be
met with a consensus protocol like Paxos or Raft in the first case and a
necessary but simple scan in the second..
- _Lock the World GC_ - Halt all writes to the data store. Walk the data store
and find all blob references. Delete all unreferenced blobs. This approach
is very simple but requires disabling writes for a period of time while the
service reads all data. This is slow and expensive but very accurate and
effective.
- _Generational GC_ - Do something similar to above but instead of blocking
writes, writes are sent to another storage backend while reads are broadcast
to the new and old backends. GC is then performed on the read-only portion.
Because writes land in the new backend, the data in the read-only section
can be safely deleted. The main drawbacks of this approach are complexity
and coordination.
- _Centralized Oracle_ - Using a centralized, transactional database, we can
know exactly which data is referenced at any given time. This avoids
coordination problem by managing this data in a single location. We trade
off metadata scalability for simplicity and performance. This is a very good
option for most registry deployments. This would create a bottleneck for
registry metadata. However, metadata is generally not the main bottleneck
when serving images.
Please let us know if other solutions exist that we have yet to enumerate.
Note that for any approach, implementation is a massive consideration. For
example, a mark-sweep based solution may seem simple but the amount of work in
coordination offset the extra work it might take to build a _Centralized
Oracle_. We'll accept proposals for any solution but please coordinate with us
before dropping code.
At this time, we have traded off simplicity and ease of deployment for disk
space. Simplicity and ease of deployment tend to reduce developer involvement,
which is currently the most expensive resource in software engineering. Taking
on any solution for deletes will greatly effect these factors, trading off
very cheap disk space for a complex deployment and operational story.
Please see the following issues for more detail:
- https://github.com/docker/distribution/issues/422
- https://github.com/docker/distribution/issues/461
- https://github.com/docker/distribution/issues/462
### Distribution Package
At its core, the Distribution Project is a set of Go packages that make up
Distribution Components. At this time, most of these packages make up the
Registry implementation.
The package itself is considered unstable. If you're using it, please take care to vendor the dependent version.
For feature additions, please see the Registry section. In the future, we may break out a
separate Roadmap for distribution-specific features that apply to more than
just the registry.
***
### Project Planning
An [Open-Source Planning Process](https://github.com/docker/distribution/wiki/Open-Source-Planning-Process) is used to define the Roadmap. [Project Pages](https://github.com/docker/distribution/wiki) define the goals for each Milestone and identify current progress.
-257
View File
@@ -1,257 +0,0 @@
package distribution
import (
"errors"
"fmt"
"io"
"net/http"
"time"
"github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/reference"
)
var (
// ErrBlobExists returned when blob already exists
ErrBlobExists = errors.New("blob exists")
// ErrBlobDigestUnsupported when blob digest is an unsupported version.
ErrBlobDigestUnsupported = errors.New("unsupported blob digest")
// ErrBlobUnknown when blob is not found.
ErrBlobUnknown = errors.New("unknown blob")
// ErrBlobUploadUnknown returned when upload is not found.
ErrBlobUploadUnknown = errors.New("blob upload unknown")
// ErrBlobInvalidLength returned when the blob has an expected length on
// commit, meaning mismatched with the descriptor or an invalid value.
ErrBlobInvalidLength = errors.New("blob invalid length")
)
// ErrBlobInvalidDigest returned when digest check fails.
type ErrBlobInvalidDigest struct {
Digest digest.Digest
Reason error
}
func (err ErrBlobInvalidDigest) Error() string {
return fmt.Sprintf("invalid digest for referenced layer: %v, %v",
err.Digest, err.Reason)
}
// ErrBlobMounted returned when a blob is mounted from another repository
// instead of initiating an upload session.
type ErrBlobMounted struct {
From reference.Canonical
Descriptor Descriptor
}
func (err ErrBlobMounted) Error() string {
return fmt.Sprintf("blob mounted from: %v to: %v",
err.From, err.Descriptor)
}
// Descriptor describes targeted content. Used in conjunction with a blob
// store, a descriptor can be used to fetch, store and target any kind of
// blob. The struct also describes the wire protocol format. Fields should
// only be added but never changed.
type Descriptor struct {
// MediaType describe the type of the content. All text based formats are
// encoded as utf-8.
MediaType string `json:"mediaType,omitempty"`
// Size in bytes of content.
Size int64 `json:"size,omitempty"`
// Digest uniquely identifies the content. A byte stream can be verified
// against against this digest.
Digest digest.Digest `json:"digest,omitempty"`
// URLs contains the source URLs of this content.
URLs []string `json:"urls,omitempty"`
// NOTE: Before adding a field here, please ensure that all
// other options have been exhausted. Much of the type relationships
// depend on the simplicity of this type.
}
// Descriptor returns the descriptor, to make it satisfy the Describable
// interface. Note that implementations of Describable are generally objects
// which can be described, not simply descriptors; this exception is in place
// to make it more convenient to pass actual descriptors to functions that
// expect Describable objects.
func (d Descriptor) Descriptor() Descriptor {
return d
}
// BlobStatter makes blob descriptors available by digest. The service may
// provide a descriptor of a different digest if the provided digest is not
// canonical.
type BlobStatter interface {
// Stat provides metadata about a blob identified by the digest. If the
// blob is unknown to the describer, ErrBlobUnknown will be returned.
Stat(ctx context.Context, dgst digest.Digest) (Descriptor, error)
}
// BlobDeleter enables deleting blobs from storage.
type BlobDeleter interface {
Delete(ctx context.Context, dgst digest.Digest) error
}
// BlobEnumerator enables iterating over blobs from storage
type BlobEnumerator interface {
Enumerate(ctx context.Context, ingester func(dgst digest.Digest) error) error
}
// BlobDescriptorService manages metadata about a blob by digest. Most
// implementations will not expose such an interface explicitly. Such mappings
// should be maintained by interacting with the BlobIngester. Hence, this is
// left off of BlobService and BlobStore.
type BlobDescriptorService interface {
BlobStatter
// SetDescriptor assigns the descriptor to the digest. The provided digest and
// the digest in the descriptor must map to identical content but they may
// differ on their algorithm. The descriptor must have the canonical
// digest of the content and the digest algorithm must match the
// annotators canonical algorithm.
//
// Such a facility can be used to map blobs between digest domains, with
// the restriction that the algorithm of the descriptor must match the
// canonical algorithm (ie sha256) of the annotator.
SetDescriptor(ctx context.Context, dgst digest.Digest, desc Descriptor) error
// Clear enables descriptors to be unlinked
Clear(ctx context.Context, dgst digest.Digest) error
}
// BlobDescriptorServiceFactory creates middleware for BlobDescriptorService.
type BlobDescriptorServiceFactory interface {
BlobAccessController(svc BlobDescriptorService) BlobDescriptorService
}
// ReadSeekCloser is the primary reader type for blob data, combining
// io.ReadSeeker with io.Closer.
type ReadSeekCloser interface {
io.ReadSeeker
io.Closer
}
// BlobProvider describes operations for getting blob data.
type BlobProvider interface {
// Get returns the entire blob identified by digest along with the descriptor.
Get(ctx context.Context, dgst digest.Digest) ([]byte, error)
// Open provides a ReadSeekCloser to the blob identified by the provided
// descriptor. If the blob is not known to the service, an error will be
// returned.
Open(ctx context.Context, dgst digest.Digest) (ReadSeekCloser, error)
}
// BlobServer can serve blobs via http.
type BlobServer interface {
// ServeBlob attempts to serve the blob, identifed by dgst, via http. The
// service may decide to redirect the client elsewhere or serve the data
// directly.
//
// This handler only issues successful responses, such as 2xx or 3xx,
// meaning it serves data or issues a redirect. If the blob is not
// available, an error will be returned and the caller may still issue a
// response.
//
// The implementation may serve the same blob from a different digest
// domain. The appropriate headers will be set for the blob, unless they
// have already been set by the caller.
ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error
}
// BlobIngester ingests blob data.
type BlobIngester interface {
// Put inserts the content p into the blob service, returning a descriptor
// or an error.
Put(ctx context.Context, mediaType string, p []byte) (Descriptor, error)
// Create allocates a new blob writer to add a blob to this service. The
// returned handle can be written to and later resumed using an opaque
// identifier. With this approach, one can Close and Resume a BlobWriter
// multiple times until the BlobWriter is committed or cancelled.
Create(ctx context.Context, options ...BlobCreateOption) (BlobWriter, error)
// Resume attempts to resume a write to a blob, identified by an id.
Resume(ctx context.Context, id string) (BlobWriter, error)
}
// BlobCreateOption is a general extensible function argument for blob creation
// methods. A BlobIngester may choose to honor any or none of the given
// BlobCreateOptions, which can be specific to the implementation of the
// BlobIngester receiving them.
// TODO (brianbland): unify this with ManifestServiceOption in the future
type BlobCreateOption interface {
Apply(interface{}) error
}
// CreateOptions is a collection of blob creation modifiers relevant to general
// blob storage intended to be configured by the BlobCreateOption.Apply method.
type CreateOptions struct {
Mount struct {
ShouldMount bool
From reference.Canonical
// Stat allows to pass precalculated descriptor to link and return.
// Blob access check will be skipped if set.
Stat *Descriptor
}
}
// BlobWriter provides a handle for inserting data into a blob store.
// Instances should be obtained from BlobWriteService.Writer and
// BlobWriteService.Resume. If supported by the store, a writer can be
// recovered with the id.
type BlobWriter interface {
io.WriteCloser
io.ReaderFrom
// Size returns the number of bytes written to this blob.
Size() int64
// ID returns the identifier for this writer. The ID can be used with the
// Blob service to later resume the write.
ID() string
// StartedAt returns the time this blob write was started.
StartedAt() time.Time
// Commit completes the blob writer process. The content is verified
// against the provided provisional descriptor, which may result in an
// error. Depending on the implementation, written data may be validated
// against the provisional descriptor fields. If MediaType is not present,
// the implementation may reject the commit or assign "application/octet-
// stream" to the blob. The returned descriptor may have a different
// digest depending on the blob store, referred to as the canonical
// descriptor.
Commit(ctx context.Context, provisional Descriptor) (canonical Descriptor, err error)
// Cancel ends the blob write without storing any data and frees any
// associated resources. Any data written thus far will be lost. Cancel
// implementations should allow multiple calls even after a commit that
// result in a no-op. This allows use of Cancel in a defer statement,
// increasing the assurance that it is correctly called.
Cancel(ctx context.Context) error
}
// BlobService combines the operations to access, read and write blobs. This
// can be used to describe remote blob services.
type BlobService interface {
BlobStatter
BlobProvider
BlobIngester
}
// BlobStore represent the entire suite of blob related operations. Such an
// implementation can access, read, write, delete and serve blobs.
type BlobStore interface {
BlobService
BlobServer
BlobDeleter
}
-93
View File
@@ -1,93 +0,0 @@
# Pony-up!
machine:
pre:
# Install gvm
- bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer)
# Install codecov for coverage
- pip install --user codecov
post:
# go
- gvm install go1.7 --prefer-binary --name=stable
environment:
# Convenient shortcuts to "common" locations
CHECKOUT: /home/ubuntu/$CIRCLE_PROJECT_REPONAME
BASE_DIR: src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
# Trick circle brainflat "no absolute path" behavior
BASE_STABLE: ../../../$HOME/.gvm/pkgsets/stable/global/$BASE_DIR
DOCKER_BUILDTAGS: "include_oss include_gcs"
# Workaround Circle parsing dumb bugs and/or YAML wonkyness
CIRCLE_PAIN: "mode: set"
hosts:
# Not used yet
fancy: 127.0.0.1
dependencies:
pre:
# Copy the code to the gopath of all go versions
- >
gvm use stable &&
mkdir -p "$(dirname $BASE_STABLE)" &&
cp -R "$CHECKOUT" "$BASE_STABLE"
override:
# Install dependencies for every copied clone/go version
- gvm use stable && go get github.com/tools/godep:
pwd: $BASE_STABLE
post:
# For the stable go version, additionally install linting tools
- >
gvm use stable &&
go get github.com/axw/gocov/gocov github.com/golang/lint/golint
test:
pre:
# Output the go versions we are going to test
# - gvm use old && go version
- gvm use stable && go version
# todo(richard): replace with a more robust vendoring solution. Removed due to a fundamental disagreement in godep philosophies.
# Ensure validation of dependencies
# - gvm use stable && if test -n "`git diff --stat=1000 master | grep -Ei \"vendor|godeps\"`"; then make dep-validate; fi:
# pwd: $BASE_STABLE
# First thing: build everything. This will catch compile errors, and it's
# also necessary for go vet to work properly (see #807).
- gvm use stable && godep go install $(go list ./... | grep -v "/vendor/"):
pwd: $BASE_STABLE
# FMT
- gvm use stable && make fmt:
pwd: $BASE_STABLE
# VET
- gvm use stable && make vet:
pwd: $BASE_STABLE
# LINT
- gvm use stable && make lint:
pwd: $BASE_STABLE
override:
# Test stable, and report
- gvm use stable; export ROOT_PACKAGE=$(go list .); go list -tags "$DOCKER_BUILDTAGS" ./... | grep -v "/vendor/" | xargs -L 1 -I{} bash -c 'export PACKAGE={}; godep go test -tags "$DOCKER_BUILDTAGS" -test.short -coverprofile=$GOPATH/src/$PACKAGE/coverage.out -coverpkg=$(./coverpkg.sh $PACKAGE $ROOT_PACKAGE) $PACKAGE':
timeout: 1000
pwd: $BASE_STABLE
# Test stable with race
- gvm use stable; export ROOT_PACKAGE=$(go list .); go list -tags "$DOCKER_BUILDTAGS" ./... | grep -v "/vendor/" | grep -v "registry/handlers" | grep -v "registry/storage/driver" | xargs -L 1 -I{} bash -c 'export PACKAGE={}; godep go test -race -tags "$DOCKER_BUILDTAGS" -test.short $PACKAGE':
timeout: 1000
pwd: $BASE_STABLE
post:
# Report to codecov
- bash <(curl -s https://codecov.io/bash):
pwd: $BASE_STABLE
## Notes
# Do we want these as well?
# - go get code.google.com/p/go.tools/cmd/goimports
# - test -z "$(goimports -l -w ./... | tee /dev/stderr)"
# http://labix.org/gocheck
-7
View File
@@ -1,7 +0,0 @@
#!/usr/bin/env bash
# Given a subpackage and the containing package, figures out which packages
# need to be passed to `go test -coverpkg`: this includes all of the
# subpackage's dependencies within the containing package, as well as the
# subpackage itself.
DEPENDENCIES="$(go list -f $'{{range $f := .Deps}}{{$f}}\n{{end}}' ${1} | grep ${2} | grep -v github.com/docker/distribution/vendor)"
echo "${1} ${DEPENDENCIES}" | xargs echo -n | tr ' ' ','
-139
View File
@@ -1,139 +0,0 @@
package digest
import (
"fmt"
"hash"
"io"
"regexp"
"strings"
)
const (
// DigestSha256EmptyTar is the canonical sha256 digest of empty data
DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
)
// Digest allows simple protection of hex formatted digest strings, prefixed
// by their algorithm. Strings of type Digest have some guarantee of being in
// the correct format and it provides quick access to the components of a
// digest string.
//
// The following is an example of the contents of Digest types:
//
// sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
//
// This allows to abstract the digest behind this type and work only in those
// terms.
type Digest string
// NewDigest returns a Digest from alg and a hash.Hash object.
func NewDigest(alg Algorithm, h hash.Hash) Digest {
return NewDigestFromBytes(alg, h.Sum(nil))
}
// NewDigestFromBytes returns a new digest from the byte contents of p.
// Typically, this can come from hash.Hash.Sum(...) or xxx.SumXXX(...)
// functions. This is also useful for rebuilding digests from binary
// serializations.
func NewDigestFromBytes(alg Algorithm, p []byte) Digest {
return Digest(fmt.Sprintf("%s:%x", alg, p))
}
// NewDigestFromHex returns a Digest from alg and a the hex encoded digest.
func NewDigestFromHex(alg, hex string) Digest {
return Digest(fmt.Sprintf("%s:%s", alg, hex))
}
// DigestRegexp matches valid digest types.
var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
// DigestRegexpAnchored matches valid digest types, anchored to the start and end of the match.
var DigestRegexpAnchored = regexp.MustCompile(`^` + DigestRegexp.String() + `$`)
var (
// ErrDigestInvalidFormat returned when digest format invalid.
ErrDigestInvalidFormat = fmt.Errorf("invalid checksum digest format")
// ErrDigestInvalidLength returned when digest has invalid length.
ErrDigestInvalidLength = fmt.Errorf("invalid checksum digest length")
// ErrDigestUnsupported returned when the digest algorithm is unsupported.
ErrDigestUnsupported = fmt.Errorf("unsupported digest algorithm")
)
// ParseDigest parses s and returns the validated digest object. An error will
// be returned if the format is invalid.
func ParseDigest(s string) (Digest, error) {
d := Digest(s)
return d, d.Validate()
}
// FromReader returns the most valid digest for the underlying content using
// the canonical digest algorithm.
func FromReader(rd io.Reader) (Digest, error) {
return Canonical.FromReader(rd)
}
// FromBytes digests the input and returns a Digest.
func FromBytes(p []byte) Digest {
return Canonical.FromBytes(p)
}
// Validate checks that the contents of d is a valid digest, returning an
// error if not.
func (d Digest) Validate() error {
s := string(d)
if !DigestRegexpAnchored.MatchString(s) {
return ErrDigestInvalidFormat
}
i := strings.Index(s, ":")
if i < 0 {
return ErrDigestInvalidFormat
}
// case: "sha256:" with no hex.
if i+1 == len(s) {
return ErrDigestInvalidFormat
}
switch algorithm := Algorithm(s[:i]); algorithm {
case SHA256, SHA384, SHA512:
if algorithm.Size()*2 != len(s[i+1:]) {
return ErrDigestInvalidLength
}
break
default:
return ErrDigestUnsupported
}
return nil
}
// Algorithm returns the algorithm portion of the digest. This will panic if
// the underlying digest is not in a valid format.
func (d Digest) Algorithm() Algorithm {
return Algorithm(d[:d.sepIndex()])
}
// Hex returns the hex digest portion of the digest. This will panic if the
// underlying digest is not in a valid format.
func (d Digest) Hex() string {
return string(d[d.sepIndex()+1:])
}
func (d Digest) String() string {
return string(d)
}
func (d Digest) sepIndex() int {
i := strings.Index(string(d), ":")
if i < 0 {
panic("could not find ':' in digest: " + d)
}
return i
}
-155
View File
@@ -1,155 +0,0 @@
package digest
import (
"crypto"
"fmt"
"hash"
"io"
)
// Algorithm identifies and implementation of a digester by an identifier.
// Note the that this defines both the hash algorithm used and the string
// encoding.
type Algorithm string
// supported digest types
const (
SHA256 Algorithm = "sha256" // sha256 with hex encoding
SHA384 Algorithm = "sha384" // sha384 with hex encoding
SHA512 Algorithm = "sha512" // sha512 with hex encoding
// Canonical is the primary digest algorithm used with the distribution
// project. Other digests may be used but this one is the primary storage
// digest.
Canonical = SHA256
)
var (
// TODO(stevvooe): Follow the pattern of the standard crypto package for
// registration of digests. Effectively, we are a registerable set and
// common symbol access.
// algorithms maps values to hash.Hash implementations. Other algorithms
// may be available but they cannot be calculated by the digest package.
algorithms = map[Algorithm]crypto.Hash{
SHA256: crypto.SHA256,
SHA384: crypto.SHA384,
SHA512: crypto.SHA512,
}
)
// Available returns true if the digest type is available for use. If this
// returns false, New and Hash will return nil.
func (a Algorithm) Available() bool {
h, ok := algorithms[a]
if !ok {
return false
}
// check availability of the hash, as well
return h.Available()
}
func (a Algorithm) String() string {
return string(a)
}
// Size returns number of bytes returned by the hash.
func (a Algorithm) Size() int {
h, ok := algorithms[a]
if !ok {
return 0
}
return h.Size()
}
// Set implemented to allow use of Algorithm as a command line flag.
func (a *Algorithm) Set(value string) error {
if value == "" {
*a = Canonical
} else {
// just do a type conversion, support is queried with Available.
*a = Algorithm(value)
}
return nil
}
// New returns a new digester for the specified algorithm. If the algorithm
// does not have a digester implementation, nil will be returned. This can be
// checked by calling Available before calling New.
func (a Algorithm) New() Digester {
return &digester{
alg: a,
hash: a.Hash(),
}
}
// Hash returns a new hash as used by the algorithm. If not available, the
// method will panic. Check Algorithm.Available() before calling.
func (a Algorithm) Hash() hash.Hash {
if !a.Available() {
// NOTE(stevvooe): A missing hash is usually a programming error that
// must be resolved at compile time. We don't import in the digest
// package to allow users to choose their hash implementation (such as
// when using stevvooe/resumable or a hardware accelerated package).
//
// Applications that may want to resolve the hash at runtime should
// call Algorithm.Available before call Algorithm.Hash().
panic(fmt.Sprintf("%v not available (make sure it is imported)", a))
}
return algorithms[a].New()
}
// FromReader returns the digest of the reader using the algorithm.
func (a Algorithm) FromReader(rd io.Reader) (Digest, error) {
digester := a.New()
if _, err := io.Copy(digester.Hash(), rd); err != nil {
return "", err
}
return digester.Digest(), nil
}
// FromBytes digests the input and returns a Digest.
func (a Algorithm) FromBytes(p []byte) Digest {
digester := a.New()
if _, err := digester.Hash().Write(p); err != nil {
// Writes to a Hash should never fail. None of the existing
// hash implementations in the stdlib or hashes vendored
// here can return errors from Write. Having a panic in this
// condition instead of having FromBytes return an error value
// avoids unnecessary error handling paths in all callers.
panic("write to hash function returned error: " + err.Error())
}
return digester.Digest()
}
// TODO(stevvooe): Allow resolution of verifiers using the digest type and
// this registration system.
// Digester calculates the digest of written data. Writes should go directly
// to the return value of Hash, while calling Digest will return the current
// value of the digest.
type Digester interface {
Hash() hash.Hash // provides direct access to underlying hash instance.
Digest() Digest
}
// digester provides a simple digester definition that embeds a hasher.
type digester struct {
alg Algorithm
hash hash.Hash
}
func (d *digester) Hash() hash.Hash {
return d.hash
}
func (d *digester) Digest() Digest {
return NewDigest(d.alg, d.hash)
}
-42
View File
@@ -1,42 +0,0 @@
// Package digest provides a generalized type to opaquely represent message
// digests and their operations within the registry. The Digest type is
// designed to serve as a flexible identifier in a content-addressable system.
// More importantly, it provides tools and wrappers to work with
// hash.Hash-based digests with little effort.
//
// Basics
//
// The format of a digest is simply a string with two parts, dubbed the
// "algorithm" and the "digest", separated by a colon:
//
// <algorithm>:<digest>
//
// An example of a sha256 digest representation follows:
//
// sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
//
// In this case, the string "sha256" is the algorithm and the hex bytes are
// the "digest".
//
// Because the Digest type is simply a string, once a valid Digest is
// obtained, comparisons are cheap, quick and simple to express with the
// standard equality operator.
//
// Verification
//
// The main benefit of using the Digest type is simple verification against a
// given digest. The Verifier interface, modeled after the stdlib hash.Hash
// interface, provides a common write sink for digest verification. After
// writing is complete, calling the Verifier.Verified method will indicate
// whether or not the stream of bytes matches the target digest.
//
// Missing Features
//
// In addition to the above, we intend to add the following features to this
// package:
//
// 1. A Digester type that supports write sink digest calculation.
//
// 2. Suspend and resume of ongoing digest calculations to support efficient digest verification in the registry.
//
package digest
-245
View File
@@ -1,245 +0,0 @@
package digest
import (
"errors"
"sort"
"strings"
"sync"
)
var (
// ErrDigestNotFound is used when a matching digest
// could not be found in a set.
ErrDigestNotFound = errors.New("digest not found")
// ErrDigestAmbiguous is used when multiple digests
// are found in a set. None of the matching digests
// should be considered valid matches.
ErrDigestAmbiguous = errors.New("ambiguous digest string")
)
// Set is used to hold a unique set of digests which
// may be easily referenced by easily referenced by a string
// representation of the digest as well as short representation.
// The uniqueness of the short representation is based on other
// digests in the set. If digests are omitted from this set,
// collisions in a larger set may not be detected, therefore it
// is important to always do short representation lookups on
// the complete set of digests. To mitigate collisions, an
// appropriately long short code should be used.
type Set struct {
mutex sync.RWMutex
entries digestEntries
}
// NewSet creates an empty set of digests
// which may have digests added.
func NewSet() *Set {
return &Set{
entries: digestEntries{},
}
}
// checkShortMatch checks whether two digests match as either whole
// values or short values. This function does not test equality,
// rather whether the second value could match against the first
// value.
func checkShortMatch(alg Algorithm, hex, shortAlg, shortHex string) bool {
if len(hex) == len(shortHex) {
if hex != shortHex {
return false
}
if len(shortAlg) > 0 && string(alg) != shortAlg {
return false
}
} else if !strings.HasPrefix(hex, shortHex) {
return false
} else if len(shortAlg) > 0 && string(alg) != shortAlg {
return false
}
return true
}
// Lookup looks for a digest matching the given string representation.
// If no digests could be found ErrDigestNotFound will be returned
// with an empty digest value. If multiple matches are found
// ErrDigestAmbiguous will be returned with an empty digest value.
func (dst *Set) Lookup(d string) (Digest, error) {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
if len(dst.entries) == 0 {
return "", ErrDigestNotFound
}
var (
searchFunc func(int) bool
alg Algorithm
hex string
)
dgst, err := ParseDigest(d)
if err == ErrDigestInvalidFormat {
hex = d
searchFunc = func(i int) bool {
return dst.entries[i].val >= d
}
} else {
hex = dgst.Hex()
alg = dgst.Algorithm()
searchFunc = func(i int) bool {
if dst.entries[i].val == hex {
return dst.entries[i].alg >= alg
}
return dst.entries[i].val >= hex
}
}
idx := sort.Search(len(dst.entries), searchFunc)
if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) {
return "", ErrDigestNotFound
}
if dst.entries[idx].alg == alg && dst.entries[idx].val == hex {
return dst.entries[idx].digest, nil
}
if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) {
return "", ErrDigestAmbiguous
}
return dst.entries[idx].digest, nil
}
// Add adds the given digest to the set. An error will be returned
// if the given digest is invalid. If the digest already exists in the
// set, this operation will be a no-op.
func (dst *Set) Add(d Digest) error {
if err := d.Validate(); err != nil {
return err
}
dst.mutex.Lock()
defer dst.mutex.Unlock()
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
searchFunc := func(i int) bool {
if dst.entries[i].val == entry.val {
return dst.entries[i].alg >= entry.alg
}
return dst.entries[i].val >= entry.val
}
idx := sort.Search(len(dst.entries), searchFunc)
if idx == len(dst.entries) {
dst.entries = append(dst.entries, entry)
return nil
} else if dst.entries[idx].digest == d {
return nil
}
entries := append(dst.entries, nil)
copy(entries[idx+1:], entries[idx:len(entries)-1])
entries[idx] = entry
dst.entries = entries
return nil
}
// Remove removes the given digest from the set. An err will be
// returned if the given digest is invalid. If the digest does
// not exist in the set, this operation will be a no-op.
func (dst *Set) Remove(d Digest) error {
if err := d.Validate(); err != nil {
return err
}
dst.mutex.Lock()
defer dst.mutex.Unlock()
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
searchFunc := func(i int) bool {
if dst.entries[i].val == entry.val {
return dst.entries[i].alg >= entry.alg
}
return dst.entries[i].val >= entry.val
}
idx := sort.Search(len(dst.entries), searchFunc)
// Not found if idx is after or value at idx is not digest
if idx == len(dst.entries) || dst.entries[idx].digest != d {
return nil
}
entries := dst.entries
copy(entries[idx:], entries[idx+1:])
entries = entries[:len(entries)-1]
dst.entries = entries
return nil
}
// All returns all the digests in the set
func (dst *Set) All() []Digest {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
retValues := make([]Digest, len(dst.entries))
for i := range dst.entries {
retValues[i] = dst.entries[i].digest
}
return retValues
}
// ShortCodeTable returns a map of Digest to unique short codes. The
// length represents the minimum value, the maximum length may be the
// entire value of digest if uniqueness cannot be achieved without the
// full value. This function will attempt to make short codes as short
// as possible to be unique.
func ShortCodeTable(dst *Set, length int) map[Digest]string {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
m := make(map[Digest]string, len(dst.entries))
l := length
resetIdx := 0
for i := 0; i < len(dst.entries); i++ {
var short string
extended := true
for extended {
extended = false
if len(dst.entries[i].val) <= l {
short = dst.entries[i].digest.String()
} else {
short = dst.entries[i].val[:l]
for j := i + 1; j < len(dst.entries); j++ {
if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) {
if j > resetIdx {
resetIdx = j
}
extended = true
} else {
break
}
}
if extended {
l++
}
}
}
m[dst.entries[i].digest] = short
if i >= resetIdx {
l = length
}
}
return m
}
type digestEntry struct {
alg Algorithm
val string
digest Digest
}
type digestEntries []*digestEntry
func (d digestEntries) Len() int {
return len(d)
}
func (d digestEntries) Less(i, j int) bool {
if d[i].val != d[j].val {
return d[i].val < d[j].val
}
return d[i].alg < d[j].alg
}
func (d digestEntries) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
-44
View File
@@ -1,44 +0,0 @@
package digest
import (
"hash"
"io"
)
// Verifier presents a general verification interface to be used with message
// digests and other byte stream verifications. Users instantiate a Verifier
// from one of the various methods, write the data under test to it then check
// the result with the Verified method.
type Verifier interface {
io.Writer
// Verified will return true if the content written to Verifier matches
// the digest.
Verified() bool
}
// NewDigestVerifier returns a verifier that compares the written bytes
// against a passed in digest.
func NewDigestVerifier(d Digest) (Verifier, error) {
if err := d.Validate(); err != nil {
return nil, err
}
return hashVerifier{
hash: d.Algorithm().Hash(),
digest: d,
}, nil
}
type hashVerifier struct {
digest Digest
hash hash.Hash
}
func (hv hashVerifier) Write(p []byte) (n int, err error) {
return hv.hash.Write(p)
}
func (hv hashVerifier) Verified() bool {
return hv.digest == NewDigest(hv.digest.Algorithm(), hv.hash)
}
-7
View File
@@ -1,7 +0,0 @@
// Package distribution will define the interfaces for the components of
// docker distribution. The goal is to allow users to reliably package, ship
// and store content related to docker images.
//
// This is currently a work in progress. More details are available in the
// README.md.
package distribution
-115
View File
@@ -1,115 +0,0 @@
package distribution
import (
"errors"
"fmt"
"strings"
"github.com/docker/distribution/digest"
)
// ErrAccessDenied is returned when an access to a requested resource is
// denied.
var ErrAccessDenied = errors.New("access denied")
// ErrManifestNotModified is returned when a conditional manifest GetByTag
// returns nil due to the client indicating it has the latest version
var ErrManifestNotModified = errors.New("manifest not modified")
// ErrUnsupported is returned when an unimplemented or unsupported action is
// performed
var ErrUnsupported = errors.New("operation unsupported")
// ErrTagUnknown is returned if the given tag is not known by the tag service
type ErrTagUnknown struct {
Tag string
}
func (err ErrTagUnknown) Error() string {
return fmt.Sprintf("unknown tag=%s", err.Tag)
}
// ErrRepositoryUnknown is returned if the named repository is not known by
// the registry.
type ErrRepositoryUnknown struct {
Name string
}
func (err ErrRepositoryUnknown) Error() string {
return fmt.Sprintf("unknown repository name=%s", err.Name)
}
// ErrRepositoryNameInvalid should be used to denote an invalid repository
// name. Reason may set, indicating the cause of invalidity.
type ErrRepositoryNameInvalid struct {
Name string
Reason error
}
func (err ErrRepositoryNameInvalid) Error() string {
return fmt.Sprintf("repository name %q invalid: %v", err.Name, err.Reason)
}
// ErrManifestUnknown is returned if the manifest is not known by the
// registry.
type ErrManifestUnknown struct {
Name string
Tag string
}
func (err ErrManifestUnknown) Error() string {
return fmt.Sprintf("unknown manifest name=%s tag=%s", err.Name, err.Tag)
}
// ErrManifestUnknownRevision is returned when a manifest cannot be found by
// revision within a repository.
type ErrManifestUnknownRevision struct {
Name string
Revision digest.Digest
}
func (err ErrManifestUnknownRevision) Error() string {
return fmt.Sprintf("unknown manifest name=%s revision=%s", err.Name, err.Revision)
}
// ErrManifestUnverified is returned when the registry is unable to verify
// the manifest.
type ErrManifestUnverified struct{}
func (ErrManifestUnverified) Error() string {
return fmt.Sprintf("unverified manifest")
}
// ErrManifestVerification provides a type to collect errors encountered
// during manifest verification. Currently, it accepts errors of all types,
// but it may be narrowed to those involving manifest verification.
type ErrManifestVerification []error
func (errs ErrManifestVerification) Error() string {
var parts []string
for _, err := range errs {
parts = append(parts, err.Error())
}
return fmt.Sprintf("errors verifying manifest: %v", strings.Join(parts, ","))
}
// ErrManifestBlobUnknown returned when a referenced blob cannot be found.
type ErrManifestBlobUnknown struct {
Digest digest.Digest
}
func (err ErrManifestBlobUnknown) Error() string {
return fmt.Sprintf("unknown blob %v on manifest", err.Digest)
}
// ErrManifestNameInvalid should be used to denote an invalid manifest
// name. Reason may set, indicating the cause of invalidity.
type ErrManifestNameInvalid struct {
Name string
Reason error
}
func (err ErrManifestNameInvalid) Error() string {
return fmt.Sprintf("manifest name %q invalid: %v", err.Name, err.Reason)
}
-125
View File
@@ -1,125 +0,0 @@
package distribution
import (
"fmt"
"mime"
"github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
)
// Manifest represents a registry object specifying a set of
// references and an optional target
type Manifest interface {
// References returns a list of objects which make up this manifest.
// A reference is anything which can be represented by a
// distribution.Descriptor. These can consist of layers, resources or other
// manifests.
//
// While no particular order is required, implementations should return
// them from highest to lowest priority. For example, one might want to
// return the base layer before the top layer.
References() []Descriptor
// Payload provides the serialized format of the manifest, in addition to
// the mediatype.
Payload() (mediatype string, payload []byte, err error)
}
// ManifestBuilder creates a manifest allowing one to include dependencies.
// Instances can be obtained from a version-specific manifest package. Manifest
// specific data is passed into the function which creates the builder.
type ManifestBuilder interface {
// Build creates the manifest from his builder.
Build(ctx context.Context) (Manifest, error)
// References returns a list of objects which have been added to this
// builder. The dependencies are returned in the order they were added,
// which should be from base to head.
References() []Descriptor
// AppendReference includes the given object in the manifest after any
// existing dependencies. If the add fails, such as when adding an
// unsupported dependency, an error may be returned.
//
// The destination of the reference is dependent on the manifest type and
// the dependency type.
AppendReference(dependency Describable) error
}
// ManifestService describes operations on image manifests.
type ManifestService interface {
// Exists returns true if the manifest exists.
Exists(ctx context.Context, dgst digest.Digest) (bool, error)
// Get retrieves the manifest specified by the given digest
Get(ctx context.Context, dgst digest.Digest, options ...ManifestServiceOption) (Manifest, error)
// Put creates or updates the given manifest returning the manifest digest
Put(ctx context.Context, manifest Manifest, options ...ManifestServiceOption) (digest.Digest, error)
// Delete removes the manifest specified by the given digest. Deleting
// a manifest that doesn't exist will return ErrManifestNotFound
Delete(ctx context.Context, dgst digest.Digest) error
}
// ManifestEnumerator enables iterating over manifests
type ManifestEnumerator interface {
// Enumerate calls ingester for each manifest.
Enumerate(ctx context.Context, ingester func(digest.Digest) error) error
}
// Describable is an interface for descriptors
type Describable interface {
Descriptor() Descriptor
}
// ManifestMediaTypes returns the supported media types for manifests.
func ManifestMediaTypes() (mediaTypes []string) {
for t := range mappings {
if t != "" {
mediaTypes = append(mediaTypes, t)
}
}
return
}
// UnmarshalFunc implements manifest unmarshalling a given MediaType
type UnmarshalFunc func([]byte) (Manifest, Descriptor, error)
var mappings = make(map[string]UnmarshalFunc, 0)
// UnmarshalManifest looks up manifest unmarshal functions based on
// MediaType
func UnmarshalManifest(ctHeader string, p []byte) (Manifest, Descriptor, error) {
// Need to look up by the actual media type, not the raw contents of
// the header. Strip semicolons and anything following them.
var mediatype string
if ctHeader != "" {
var err error
mediatype, _, err = mime.ParseMediaType(ctHeader)
if err != nil {
return nil, Descriptor{}, err
}
}
unmarshalFunc, ok := mappings[mediatype]
if !ok {
unmarshalFunc, ok = mappings[""]
if !ok {
return nil, Descriptor{}, fmt.Errorf("unsupported manifest mediatype and no default available: %s", mediatype)
}
}
return unmarshalFunc(p)
}
// RegisterManifestSchema registers an UnmarshalFunc for a given schema type. This
// should be called from specific
func RegisterManifestSchema(mediatype string, u UnmarshalFunc) error {
if _, ok := mappings[mediatype]; ok {
return fmt.Errorf("manifest mediatype registration would overwrite existing: %s", mediatype)
}
mappings[mediatype] = u
return nil
}
-370
View File
@@ -1,370 +0,0 @@
// Package reference provides a general type to represent any way of referencing images within the registry.
// Its main purpose is to abstract tags and digests (content-addressable hash).
//
// Grammar
//
// reference := name [ ":" tag ] [ "@" digest ]
// name := [hostname '/'] component ['/' component]*
// hostname := hostcomponent ['.' hostcomponent]* [':' port-number]
// hostcomponent := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
// port-number := /[0-9]+/
// component := alpha-numeric [separator alpha-numeric]*
// alpha-numeric := /[a-z0-9]+/
// separator := /[_.]|__|[-]*/
//
// tag := /[\w][\w.-]{0,127}/
//
// digest := digest-algorithm ":" digest-hex
// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
// digest-algorithm-separator := /[+.-_]/
// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
package reference
import (
"errors"
"fmt"
"path"
"strings"
"github.com/docker/distribution/digest"
)
const (
// NameTotalLengthMax is the maximum total number of characters in a repository name.
NameTotalLengthMax = 255
)
var (
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
ErrReferenceInvalidFormat = errors.New("invalid reference format")
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
ErrTagInvalidFormat = errors.New("invalid tag format")
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
ErrDigestInvalidFormat = errors.New("invalid digest format")
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
ErrNameContainsUppercase = errors.New("repository name must be lowercase")
// ErrNameEmpty is returned for empty, invalid repository names.
ErrNameEmpty = errors.New("repository name must have at least one component")
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
)
// Reference is an opaque object reference identifier that may include
// modifiers such as a hostname, name, tag, and digest.
type Reference interface {
// String returns the full reference
String() string
}
// Field provides a wrapper type for resolving correct reference types when
// working with encoding.
type Field struct {
reference Reference
}
// AsField wraps a reference in a Field for encoding.
func AsField(reference Reference) Field {
return Field{reference}
}
// Reference unwraps the reference type from the field to
// return the Reference object. This object should be
// of the appropriate type to further check for different
// reference types.
func (f Field) Reference() Reference {
return f.reference
}
// MarshalText serializes the field to byte text which
// is the string of the reference.
func (f Field) MarshalText() (p []byte, err error) {
return []byte(f.reference.String()), nil
}
// UnmarshalText parses text bytes by invoking the
// reference parser to ensure the appropriately
// typed reference object is wrapped by field.
func (f *Field) UnmarshalText(p []byte) error {
r, err := Parse(string(p))
if err != nil {
return err
}
f.reference = r
return nil
}
// Named is an object with a full name
type Named interface {
Reference
Name() string
}
// Tagged is an object which has a tag
type Tagged interface {
Reference
Tag() string
}
// NamedTagged is an object including a name and tag.
type NamedTagged interface {
Named
Tag() string
}
// Digested is an object which has a digest
// in which it can be referenced by
type Digested interface {
Reference
Digest() digest.Digest
}
// Canonical reference is an object with a fully unique
// name including a name with hostname and digest
type Canonical interface {
Named
Digest() digest.Digest
}
// SplitHostname splits a named reference into a
// hostname and name string. If no valid hostname is
// found, the hostname is empty and the full value
// is returned as name
func SplitHostname(named Named) (string, string) {
name := named.Name()
match := anchoredNameRegexp.FindStringSubmatch(name)
if len(match) != 3 {
return "", name
}
return match[1], match[2]
}
// Parse parses s and returns a syntactically valid Reference.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: Parse will not handle short digests.
func Parse(s string) (Reference, error) {
matches := ReferenceRegexp.FindStringSubmatch(s)
if matches == nil {
if s == "" {
return nil, ErrNameEmpty
}
if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
return nil, ErrNameContainsUppercase
}
return nil, ErrReferenceInvalidFormat
}
if len(matches[1]) > NameTotalLengthMax {
return nil, ErrNameTooLong
}
ref := reference{
name: matches[1],
tag: matches[2],
}
if matches[3] != "" {
var err error
ref.digest, err = digest.ParseDigest(matches[3])
if err != nil {
return nil, err
}
}
r := getBestReferenceType(ref)
if r == nil {
return nil, ErrNameEmpty
}
return r, nil
}
// ParseNamed parses s and returns a syntactically valid reference implementing
// the Named interface. The reference must have a name, otherwise an error is
// returned.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: ParseNamed will not handle short digests.
func ParseNamed(s string) (Named, error) {
ref, err := Parse(s)
if err != nil {
return nil, err
}
named, isNamed := ref.(Named)
if !isNamed {
return nil, fmt.Errorf("reference %s has no name", ref.String())
}
return named, nil
}
// WithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
func WithName(name string) (Named, error) {
if len(name) > NameTotalLengthMax {
return nil, ErrNameTooLong
}
if !anchoredNameRegexp.MatchString(name) {
return nil, ErrReferenceInvalidFormat
}
return repository(name), nil
}
// WithTag combines the name from "name" and the tag from "tag" to form a
// reference incorporating both the name and the tag.
func WithTag(name Named, tag string) (NamedTagged, error) {
if !anchoredTagRegexp.MatchString(tag) {
return nil, ErrTagInvalidFormat
}
if canonical, ok := name.(Canonical); ok {
return reference{
name: name.Name(),
tag: tag,
digest: canonical.Digest(),
}, nil
}
return taggedReference{
name: name.Name(),
tag: tag,
}, nil
}
// WithDigest combines the name from "name" and the digest from "digest" to form
// a reference incorporating both the name and the digest.
func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
if !anchoredDigestRegexp.MatchString(digest.String()) {
return nil, ErrDigestInvalidFormat
}
if tagged, ok := name.(Tagged); ok {
return reference{
name: name.Name(),
tag: tagged.Tag(),
digest: digest,
}, nil
}
return canonicalReference{
name: name.Name(),
digest: digest,
}, nil
}
// Match reports whether ref matches the specified pattern.
// See https://godoc.org/path#Match for supported patterns.
func Match(pattern string, ref Reference) (bool, error) {
matched, err := path.Match(pattern, ref.String())
if namedRef, isNamed := ref.(Named); isNamed && !matched {
matched, _ = path.Match(pattern, namedRef.Name())
}
return matched, err
}
// TrimNamed removes any tag or digest from the named reference.
func TrimNamed(ref Named) Named {
return repository(ref.Name())
}
func getBestReferenceType(ref reference) Reference {
if ref.name == "" {
// Allow digest only references
if ref.digest != "" {
return digestReference(ref.digest)
}
return nil
}
if ref.tag == "" {
if ref.digest != "" {
return canonicalReference{
name: ref.name,
digest: ref.digest,
}
}
return repository(ref.name)
}
if ref.digest == "" {
return taggedReference{
name: ref.name,
tag: ref.tag,
}
}
return ref
}
type reference struct {
name string
tag string
digest digest.Digest
}
func (r reference) String() string {
return r.name + ":" + r.tag + "@" + r.digest.String()
}
func (r reference) Name() string {
return r.name
}
func (r reference) Tag() string {
return r.tag
}
func (r reference) Digest() digest.Digest {
return r.digest
}
type repository string
func (r repository) String() string {
return string(r)
}
func (r repository) Name() string {
return string(r)
}
type digestReference digest.Digest
func (d digestReference) String() string {
return d.String()
}
func (d digestReference) Digest() digest.Digest {
return digest.Digest(d)
}
type taggedReference struct {
name string
tag string
}
func (t taggedReference) String() string {
return t.name + ":" + t.tag
}
func (t taggedReference) Name() string {
return t.name
}
func (t taggedReference) Tag() string {
return t.tag
}
type canonicalReference struct {
name string
digest digest.Digest
}
func (c canonicalReference) String() string {
return c.name + "@" + c.digest.String()
}
func (c canonicalReference) Name() string {
return c.name
}
func (c canonicalReference) Digest() digest.Digest {
return c.digest
}
-124
View File
@@ -1,124 +0,0 @@
package reference
import "regexp"
var (
// alphaNumericRegexp defines the alpha numeric atom, typically a
// component of names. This only allows lower case characters and digits.
alphaNumericRegexp = match(`[a-z0-9]+`)
// separatorRegexp defines the separators allowed to be embedded in name
// components. This allow one period, one or two underscore and multiple
// dashes.
separatorRegexp = match(`(?:[._]|__|[-]*)`)
// nameComponentRegexp restricts registry path component names to start
// with at least one letter or number, with following parts able to be
// separated by one period, one or two underscore and multiple dashes.
nameComponentRegexp = expression(
alphaNumericRegexp,
optional(repeated(separatorRegexp, alphaNumericRegexp)))
// hostnameComponentRegexp restricts the registry hostname component of a
// repository name to start with a component as defined by hostnameRegexp
// and followed by an optional port.
hostnameComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
// hostnameRegexp defines the structure of potential hostname components
// that may be part of image names. This is purposely a subset of what is
// allowed by DNS to ensure backwards compatibility with Docker image
// names.
hostnameRegexp = expression(
hostnameComponentRegexp,
optional(repeated(literal(`.`), hostnameComponentRegexp)),
optional(literal(`:`), match(`[0-9]+`)))
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
TagRegexp = match(`[\w][\w.-]{0,127}`)
// anchoredTagRegexp matches valid tag names, anchored at the start and
// end of the matched string.
anchoredTagRegexp = anchored(TagRegexp)
// DigestRegexp matches valid digests.
DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
// anchoredDigestRegexp matches valid digests, anchored at the start and
// end of the matched string.
anchoredDigestRegexp = anchored(DigestRegexp)
// NameRegexp is the format for the name component of references. The
// regexp has capturing groups for the hostname and name part omitting
// the separating forward slash from either.
NameRegexp = expression(
optional(hostnameRegexp, literal(`/`)),
nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp)))
// anchoredNameRegexp is used to parse a name value, capturing the
// hostname and trailing components.
anchoredNameRegexp = anchored(
optional(capture(hostnameRegexp), literal(`/`)),
capture(nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp))))
// ReferenceRegexp is the full supported format of a reference. The regexp
// is anchored and has capturing groups for name, tag, and digest
// components.
ReferenceRegexp = anchored(capture(NameRegexp),
optional(literal(":"), capture(TagRegexp)),
optional(literal("@"), capture(DigestRegexp)))
)
// match compiles the string to a regular expression.
var match = regexp.MustCompile
// literal compiles s into a literal regular expression, escaping any regexp
// reserved characters.
func literal(s string) *regexp.Regexp {
re := match(regexp.QuoteMeta(s))
if _, complete := re.LiteralPrefix(); !complete {
panic("must be a literal")
}
return re
}
// expression defines a full expression, where each regular expression must
// follow the previous.
func expression(res ...*regexp.Regexp) *regexp.Regexp {
var s string
for _, re := range res {
s += re.String()
}
return match(s)
}
// optional wraps the expression in a non-capturing group and makes the
// production optional.
func optional(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `?`)
}
// repeated wraps the regexp in a non-capturing group to get one or more
// matches.
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `+`)
}
// group wraps the regexp in a non-capturing group.
func group(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(?:` + expression(res...).String() + `)`)
}
// capture wraps the expression in a capturing group.
func capture(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(` + expression(res...).String() + `)`)
}
// anchored anchors the regular expression by adding start and end delimiters.
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
return match(`^` + expression(res...).String() + `$`)
}
-97
View File
@@ -1,97 +0,0 @@
package distribution
import (
"github.com/docker/distribution/context"
"github.com/docker/distribution/reference"
)
// Scope defines the set of items that match a namespace.
type Scope interface {
// Contains returns true if the name belongs to the namespace.
Contains(name string) bool
}
type fullScope struct{}
func (f fullScope) Contains(string) bool {
return true
}
// GlobalScope represents the full namespace scope which contains
// all other scopes.
var GlobalScope = Scope(fullScope{})
// Namespace represents a collection of repositories, addressable by name.
// Generally, a namespace is backed by a set of one or more services,
// providing facilities such as registry access, trust, and indexing.
type Namespace interface {
// Scope describes the names that can be used with this Namespace. The
// global namespace will have a scope that matches all names. The scope
// effectively provides an identity for the namespace.
Scope() Scope
// Repository should return a reference to the named repository. The
// registry may or may not have the repository but should always return a
// reference.
Repository(ctx context.Context, name reference.Named) (Repository, error)
// Repositories fills 'repos' with a lexigraphically sorted catalog of repositories
// up to the size of 'repos' and returns the value 'n' for the number of entries
// which were filled. 'last' contains an offset in the catalog, and 'err' will be
// set to io.EOF if there are no more entries to obtain.
Repositories(ctx context.Context, repos []string, last string) (n int, err error)
// Blobs returns a blob enumerator to access all blobs
Blobs() BlobEnumerator
// BlobStatter returns a BlobStatter to control
BlobStatter() BlobStatter
}
// RepositoryEnumerator describes an operation to enumerate repositories
type RepositoryEnumerator interface {
Enumerate(ctx context.Context, ingester func(string) error) error
}
// ManifestServiceOption is a function argument for Manifest Service methods
type ManifestServiceOption interface {
Apply(ManifestService) error
}
// WithTag allows a tag to be passed into Put
func WithTag(tag string) ManifestServiceOption {
return WithTagOption{tag}
}
// WithTagOption holds a tag
type WithTagOption struct{ Tag string }
// Apply conforms to the ManifestServiceOption interface
func (o WithTagOption) Apply(m ManifestService) error {
// no implementation
return nil
}
// Repository is a named collection of manifests and layers.
type Repository interface {
// Named returns the name of the repository.
Named() reference.Named
// Manifests returns a reference to this repository's manifest service.
// with the supplied options applied.
Manifests(ctx context.Context, options ...ManifestServiceOption) (ManifestService, error)
// Blobs returns a reference to this repository's blob service.
Blobs(ctx context.Context) BlobStore
// TODO(stevvooe): The above BlobStore return can probably be relaxed to
// be a BlobService for use with clients. This will allow such
// implementations to avoid implementing ServeBlob.
// Tags returns a reference to this repositories tag service
Tags(ctx context.Context) TagService
}
// TODO(stevvooe): Must add close methods to all these. May want to change the
// way instances are created to better reflect internal dependency
// relationships.
-27
View File
@@ -1,27 +0,0 @@
package distribution
import (
"github.com/docker/distribution/context"
)
// TagService provides access to information about tagged objects.
type TagService interface {
// Get retrieves the descriptor identified by the tag. Some
// implementations may differentiate between "trusted" tags and
// "untrusted" tags. If a tag is "untrusted", the mapping will be returned
// as an ErrTagUntrusted error, with the target descriptor.
Get(ctx context.Context, tag string) (Descriptor, error)
// Tag associates the tag with the provided descriptor, updating the
// current association, if needed.
Tag(ctx context.Context, tag string, desc Descriptor) error
// Untag removes the given tag association
Untag(ctx context.Context, tag string) error
// All returns the set of tags managed by this tag service
All(ctx context.Context) ([]string, error)
// Lookup returns the set of tags referencing the given digest.
Lookup(ctx context.Context, digest Descriptor) ([]string, error)
}
-20
View File
@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Fatih Arslan
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-58
View File
@@ -1,58 +0,0 @@
# CamelCase [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/camelcase) [![Build Status](http://img.shields.io/travis/fatih/camelcase.svg?style=flat-square)](https://travis-ci.org/fatih/camelcase)
CamelCase is a Golang (Go) package to split the words of a camelcase type
string into a slice of words. It can be used to convert a camelcase word (lower
or upper case) into any type of word.
## Splitting rules:
1. If string is not valid UTF-8, return it without splitting as
single item array.
2. Assign all unicode characters into one of 4 sets: lower case
letters, upper case letters, numbers, and all other characters.
3. Iterate through characters of string, introducing splits
between adjacent characters that belong to different sets.
4. Iterate through array of split strings, and if a given string
is upper case:
* if subsequent string is lower case:
* move last character of upper case string to beginning of
lower case string
## Install
```bash
go get github.com/fatih/camelcase
```
## Usage and examples
```go
splitted := camelcase.Split("GolangPackage")
fmt.Println(splitted[0], splitted[1]) // prints: "Golang", "Package"
```
Both lower camel case and upper camel case are supported. For more info please
check: [http://en.wikipedia.org/wiki/CamelCase](http://en.wikipedia.org/wiki/CamelCase)
Below are some example cases:
```
"" => []
"lowercase" => ["lowercase"]
"Class" => ["Class"]
"MyClass" => ["My", "Class"]
"MyC" => ["My", "C"]
"HTML" => ["HTML"]
"PDFLoader" => ["PDF", "Loader"]
"AString" => ["A", "String"]
"SimpleXMLParser" => ["Simple", "XML", "Parser"]
"vimRPCPlugin" => ["vim", "RPC", "Plugin"]
"GL11Version" => ["GL", "11", "Version"]
"99Bottles" => ["99", "Bottles"]
"May5" => ["May", "5"]
"BFG9000" => ["BFG", "9000"]
"BöseÜberraschung" => ["Böse", "Überraschung"]
"Two spaces" => ["Two", " ", "spaces"]
"BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
```
-90
View File
@@ -1,90 +0,0 @@
// Package camelcase is a micro package to split the words of a camelcase type
// string into a slice of words.
package camelcase
import (
"unicode"
"unicode/utf8"
)
// Split splits the camelcase word and returns a list of words. It also
// supports digits. Both lower camel case and upper camel case are supported.
// For more info please check: http://en.wikipedia.org/wiki/CamelCase
//
// Examples
//
// "" => [""]
// "lowercase" => ["lowercase"]
// "Class" => ["Class"]
// "MyClass" => ["My", "Class"]
// "MyC" => ["My", "C"]
// "HTML" => ["HTML"]
// "PDFLoader" => ["PDF", "Loader"]
// "AString" => ["A", "String"]
// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
// "GL11Version" => ["GL", "11", "Version"]
// "99Bottles" => ["99", "Bottles"]
// "May5" => ["May", "5"]
// "BFG9000" => ["BFG", "9000"]
// "BöseÜberraschung" => ["Böse", "Überraschung"]
// "Two spaces" => ["Two", " ", "spaces"]
// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
//
// Splitting rules
//
// 1) If string is not valid UTF-8, return it without splitting as
// single item array.
// 2) Assign all unicode characters into one of 4 sets: lower case
// letters, upper case letters, numbers, and all other characters.
// 3) Iterate through characters of string, introducing splits
// between adjacent characters that belong to different sets.
// 4) Iterate through array of split strings, and if a given string
// is upper case:
// if subsequent string is lower case:
// move last character of upper case string to beginning of
// lower case string
func Split(src string) (entries []string) {
// don't split invalid utf8
if !utf8.ValidString(src) {
return []string{src}
}
entries = []string{}
var runes [][]rune
lastClass := 0
class := 0
// split into fields based on class of unicode character
for _, r := range src {
switch true {
case unicode.IsLower(r):
class = 1
case unicode.IsUpper(r):
class = 2
case unicode.IsDigit(r):
class = 3
default:
class = 4
}
if class == lastClass {
runes[len(runes)-1] = append(runes[len(runes)-1], r)
} else {
runes = append(runes, []rune{r})
}
lastClass = class
}
// handle upper case -> lower case sequences, e.g.
// "PDFL", "oader" -> "PDF", "Loader"
for i := 0; i < len(runes)-1; i++ {
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
runes[i] = runes[i][:len(runes[i])-1]
}
}
// construct []string from results
for _, s := range runes {
if len(s) > 0 {
entries = append(entries, string(s))
}
}
return
}
-3
View File
@@ -1,3 +0,0 @@
secrets.yml
coverage.out
.idea
-74
View File
@@ -1,74 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
-6
View File
@@ -1,6 +0,0 @@
# OpenAPI initiative analysis [![Build Status](https://travis-ci.org/go-openapi/analysis.svg?branch=master)](https://travis-ci.org/go-openapi/analysis) [![codecov](https://codecov.io/gh/go-openapi/analysis/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/analysis) [![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/analysis/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/analysis?status.svg)](http://godoc.org/github.com/go-openapi/analysis)
A foundational library to analyze an OAI specification document for easier reasoning about the content.
-785
View File
@@ -1,785 +0,0 @@
// 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 analysis
import (
"fmt"
slashpath "path"
"strconv"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag"
)
type referenceAnalysis struct {
schemas map[string]spec.Ref
responses map[string]spec.Ref
parameters map[string]spec.Ref
items map[string]spec.Ref
headerItems map[string]spec.Ref
parameterItems map[string]spec.Ref
allRefs map[string]spec.Ref
pathItems map[string]spec.Ref
}
func (r *referenceAnalysis) addRef(key string, ref spec.Ref) {
r.allRefs["#"+key] = ref
}
func (r *referenceAnalysis) addItemsRef(key string, items *spec.Items, location string) {
r.items["#"+key] = items.Ref
r.addRef(key, items.Ref)
if location == "header" {
r.headerItems["#"+key] = items.Ref
} else {
r.parameterItems["#"+key] = items.Ref
}
}
func (r *referenceAnalysis) addSchemaRef(key string, ref SchemaRef) {
r.schemas["#"+key] = ref.Schema.Ref
r.addRef(key, ref.Schema.Ref)
}
func (r *referenceAnalysis) addResponseRef(key string, resp *spec.Response) {
r.responses["#"+key] = resp.Ref
r.addRef(key, resp.Ref)
}
func (r *referenceAnalysis) addParamRef(key string, param *spec.Parameter) {
r.parameters["#"+key] = param.Ref
r.addRef(key, param.Ref)
}
func (r *referenceAnalysis) addPathItemRef(key string, pathItem *spec.PathItem) {
r.pathItems["#"+key] = pathItem.Ref
r.addRef(key, pathItem.Ref)
}
type patternAnalysis struct {
parameters map[string]string
headers map[string]string
items map[string]string
schemas map[string]string
allPatterns map[string]string
}
func (p *patternAnalysis) addPattern(key, pattern string) {
p.allPatterns["#"+key] = pattern
}
func (p *patternAnalysis) addParameterPattern(key, pattern string) {
p.parameters["#"+key] = pattern
p.addPattern(key, pattern)
}
func (p *patternAnalysis) addHeaderPattern(key, pattern string) {
p.headers["#"+key] = pattern
p.addPattern(key, pattern)
}
func (p *patternAnalysis) addItemsPattern(key, pattern string) {
p.items["#"+key] = pattern
p.addPattern(key, pattern)
}
func (p *patternAnalysis) addSchemaPattern(key, pattern string) {
p.schemas["#"+key] = pattern
p.addPattern(key, pattern)
}
// New takes a swagger spec object and returns an analyzed spec document.
// The analyzed document contains a number of indices that make it easier to
// reason about semantics of a swagger specification for use in code generation
// or validation etc.
func New(doc *spec.Swagger) *Spec {
a := &Spec{
spec: doc,
consumes: make(map[string]struct{}, 150),
produces: make(map[string]struct{}, 150),
authSchemes: make(map[string]struct{}, 150),
operations: make(map[string]map[string]*spec.Operation, 150),
allSchemas: make(map[string]SchemaRef, 150),
allOfs: make(map[string]SchemaRef, 150),
references: referenceAnalysis{
schemas: make(map[string]spec.Ref, 150),
pathItems: make(map[string]spec.Ref, 150),
responses: make(map[string]spec.Ref, 150),
parameters: make(map[string]spec.Ref, 150),
items: make(map[string]spec.Ref, 150),
headerItems: make(map[string]spec.Ref, 150),
parameterItems: make(map[string]spec.Ref, 150),
allRefs: make(map[string]spec.Ref, 150),
},
patterns: patternAnalysis{
parameters: make(map[string]string, 150),
headers: make(map[string]string, 150),
items: make(map[string]string, 150),
schemas: make(map[string]string, 150),
allPatterns: make(map[string]string, 150),
},
}
a.initialize()
return a
}
// Spec takes a swagger spec object and turns it into a registry
// with a bunch of utility methods to act on the information in the spec
type Spec struct {
spec *spec.Swagger
consumes map[string]struct{}
produces map[string]struct{}
authSchemes map[string]struct{}
operations map[string]map[string]*spec.Operation
references referenceAnalysis
patterns patternAnalysis
allSchemas map[string]SchemaRef
allOfs map[string]SchemaRef
}
func (s *Spec) reset() {
s.consumes = make(map[string]struct{}, 150)
s.produces = make(map[string]struct{}, 150)
s.authSchemes = make(map[string]struct{}, 150)
s.operations = make(map[string]map[string]*spec.Operation, 150)
s.allSchemas = make(map[string]SchemaRef, 150)
s.allOfs = make(map[string]SchemaRef, 150)
s.references.schemas = make(map[string]spec.Ref, 150)
s.references.pathItems = make(map[string]spec.Ref, 150)
s.references.responses = make(map[string]spec.Ref, 150)
s.references.parameters = make(map[string]spec.Ref, 150)
s.references.items = make(map[string]spec.Ref, 150)
s.references.headerItems = make(map[string]spec.Ref, 150)
s.references.parameterItems = make(map[string]spec.Ref, 150)
s.references.allRefs = make(map[string]spec.Ref, 150)
s.patterns.parameters = make(map[string]string, 150)
s.patterns.headers = make(map[string]string, 150)
s.patterns.items = make(map[string]string, 150)
s.patterns.schemas = make(map[string]string, 150)
s.patterns.allPatterns = make(map[string]string, 150)
}
func (s *Spec) reload() {
s.reset()
s.initialize()
}
func (s *Spec) initialize() {
for _, c := range s.spec.Consumes {
s.consumes[c] = struct{}{}
}
for _, c := range s.spec.Produces {
s.produces[c] = struct{}{}
}
for _, ss := range s.spec.Security {
for k := range ss {
s.authSchemes[k] = struct{}{}
}
}
for path, pathItem := range s.AllPaths() {
s.analyzeOperations(path, &pathItem)
}
for name, parameter := range s.spec.Parameters {
refPref := slashpath.Join("/parameters", jsonpointer.Escape(name))
if parameter.Items != nil {
s.analyzeItems("items", parameter.Items, refPref, "parameter")
}
if parameter.In == "body" && parameter.Schema != nil {
s.analyzeSchema("schema", *parameter.Schema, refPref)
}
if parameter.Pattern != "" {
s.patterns.addParameterPattern(refPref, parameter.Pattern)
}
}
for name, response := range s.spec.Responses {
refPref := slashpath.Join("/responses", jsonpointer.Escape(name))
for k, v := range response.Headers {
hRefPref := slashpath.Join(refPref, "headers", k)
if v.Items != nil {
s.analyzeItems("items", v.Items, hRefPref, "header")
}
if v.Pattern != "" {
s.patterns.addHeaderPattern(hRefPref, v.Pattern)
}
}
if response.Schema != nil {
s.analyzeSchema("schema", *response.Schema, refPref)
}
}
for name, schema := range s.spec.Definitions {
s.analyzeSchema(name, schema, "/definitions")
}
// TODO: after analyzing all things and flattening schemas etc
// resolve all the collected references to their final representations
// best put in a separate method because this could get expensive
}
func (s *Spec) analyzeOperations(path string, pi *spec.PathItem) {
// TODO: resolve refs here?
op := pi
if pi.Ref.String() != "" {
key := slashpath.Join("/paths", jsonpointer.Escape(path))
s.references.addPathItemRef(key, pi)
}
s.analyzeOperation("GET", path, op.Get)
s.analyzeOperation("PUT", path, op.Put)
s.analyzeOperation("POST", path, op.Post)
s.analyzeOperation("PATCH", path, op.Patch)
s.analyzeOperation("DELETE", path, op.Delete)
s.analyzeOperation("HEAD", path, op.Head)
s.analyzeOperation("OPTIONS", path, op.Options)
for i, param := range op.Parameters {
refPref := slashpath.Join("/paths", jsonpointer.Escape(path), "parameters", strconv.Itoa(i))
if param.Ref.String() != "" {
s.references.addParamRef(refPref, &param)
}
if param.Pattern != "" {
s.patterns.addParameterPattern(refPref, param.Pattern)
}
if param.Items != nil {
s.analyzeItems("items", param.Items, refPref, "parameter")
}
if param.Schema != nil {
s.analyzeSchema("schema", *param.Schema, refPref)
}
}
}
func (s *Spec) analyzeItems(name string, items *spec.Items, prefix, location string) {
if items == nil {
return
}
refPref := slashpath.Join(prefix, name)
s.analyzeItems(name, items.Items, refPref, location)
if items.Ref.String() != "" {
s.references.addItemsRef(refPref, items, location)
}
if items.Pattern != "" {
s.patterns.addItemsPattern(refPref, items.Pattern)
}
}
func (s *Spec) analyzeOperation(method, path string, op *spec.Operation) {
if op == nil {
return
}
for _, c := range op.Consumes {
s.consumes[c] = struct{}{}
}
for _, c := range op.Produces {
s.produces[c] = struct{}{}
}
for _, ss := range op.Security {
for k := range ss {
s.authSchemes[k] = struct{}{}
}
}
if _, ok := s.operations[method]; !ok {
s.operations[method] = make(map[string]*spec.Operation)
}
s.operations[method][path] = op
prefix := slashpath.Join("/paths", jsonpointer.Escape(path), strings.ToLower(method))
for i, param := range op.Parameters {
refPref := slashpath.Join(prefix, "parameters", strconv.Itoa(i))
if param.Ref.String() != "" {
s.references.addParamRef(refPref, &param)
}
if param.Pattern != "" {
s.patterns.addParameterPattern(refPref, param.Pattern)
}
s.analyzeItems("items", param.Items, refPref, "parameter")
if param.In == "body" && param.Schema != nil {
s.analyzeSchema("schema", *param.Schema, refPref)
}
}
if op.Responses != nil {
if op.Responses.Default != nil {
refPref := slashpath.Join(prefix, "responses", "default")
if op.Responses.Default.Ref.String() != "" {
s.references.addResponseRef(refPref, op.Responses.Default)
}
for k, v := range op.Responses.Default.Headers {
hRefPref := slashpath.Join(refPref, "headers", k)
s.analyzeItems("items", v.Items, hRefPref, "header")
if v.Pattern != "" {
s.patterns.addHeaderPattern(hRefPref, v.Pattern)
}
}
if op.Responses.Default.Schema != nil {
s.analyzeSchema("schema", *op.Responses.Default.Schema, refPref)
}
}
for k, res := range op.Responses.StatusCodeResponses {
refPref := slashpath.Join(prefix, "responses", strconv.Itoa(k))
if res.Ref.String() != "" {
s.references.addResponseRef(refPref, &res)
}
for k, v := range res.Headers {
hRefPref := slashpath.Join(refPref, "headers", k)
s.analyzeItems("items", v.Items, hRefPref, "header")
if v.Pattern != "" {
s.patterns.addHeaderPattern(hRefPref, v.Pattern)
}
}
if res.Schema != nil {
s.analyzeSchema("schema", *res.Schema, refPref)
}
}
}
}
func (s *Spec) analyzeSchema(name string, schema spec.Schema, prefix string) {
refURI := slashpath.Join(prefix, jsonpointer.Escape(name))
schRef := SchemaRef{
Name: name,
Schema: &schema,
Ref: spec.MustCreateRef("#" + refURI),
TopLevel: prefix == "/definitions",
}
s.allSchemas["#"+refURI] = schRef
if schema.Ref.String() != "" {
s.references.addSchemaRef(refURI, schRef)
}
if schema.Pattern != "" {
s.patterns.addSchemaPattern(refURI, schema.Pattern)
}
for k, v := range schema.Definitions {
s.analyzeSchema(k, v, slashpath.Join(refURI, "definitions"))
}
for k, v := range schema.Properties {
s.analyzeSchema(k, v, slashpath.Join(refURI, "properties"))
}
for k, v := range schema.PatternProperties {
s.analyzeSchema(k, v, slashpath.Join(refURI, "patternProperties"))
}
for i, v := range schema.AllOf {
s.analyzeSchema(strconv.Itoa(i), v, slashpath.Join(refURI, "allOf"))
}
if len(schema.AllOf) > 0 {
s.allOfs["#"+refURI] = schRef
}
for i, v := range schema.AnyOf {
s.analyzeSchema(strconv.Itoa(i), v, slashpath.Join(refURI, "anyOf"))
}
for i, v := range schema.OneOf {
s.analyzeSchema(strconv.Itoa(i), v, slashpath.Join(refURI, "oneOf"))
}
if schema.Not != nil {
s.analyzeSchema("not", *schema.Not, refURI)
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
s.analyzeSchema("additionalProperties", *schema.AdditionalProperties.Schema, refURI)
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
s.analyzeSchema("additionalItems", *schema.AdditionalItems.Schema, refURI)
}
if schema.Items != nil {
if schema.Items.Schema != nil {
s.analyzeSchema("items", *schema.Items.Schema, refURI)
}
for i, sch := range schema.Items.Schemas {
s.analyzeSchema(strconv.Itoa(i), sch, slashpath.Join(refURI, "items"))
}
}
}
// SecurityRequirement is a representation of a security requirement for an operation
type SecurityRequirement struct {
Name string
Scopes []string
}
// SecurityRequirementsFor gets the security requirements for the operation
func (s *Spec) SecurityRequirementsFor(operation *spec.Operation) []SecurityRequirement {
if s.spec.Security == nil && operation.Security == nil {
return nil
}
schemes := s.spec.Security
if operation.Security != nil {
schemes = operation.Security
}
unique := make(map[string]SecurityRequirement)
for _, scheme := range schemes {
for k, v := range scheme {
if _, ok := unique[k]; !ok {
unique[k] = SecurityRequirement{Name: k, Scopes: v}
}
}
}
var result []SecurityRequirement
for _, v := range unique {
result = append(result, v)
}
return result
}
// SecurityDefinitionsFor gets the matching security definitions for a set of requirements
func (s *Spec) SecurityDefinitionsFor(operation *spec.Operation) map[string]spec.SecurityScheme {
requirements := s.SecurityRequirementsFor(operation)
if len(requirements) == 0 {
return nil
}
result := make(map[string]spec.SecurityScheme)
for _, v := range requirements {
if definition, ok := s.spec.SecurityDefinitions[v.Name]; ok {
if definition != nil {
result[v.Name] = *definition
}
}
}
return result
}
// ConsumesFor gets the mediatypes for the operation
func (s *Spec) ConsumesFor(operation *spec.Operation) []string {
if len(operation.Consumes) == 0 {
cons := make(map[string]struct{}, len(s.spec.Consumes))
for _, k := range s.spec.Consumes {
cons[k] = struct{}{}
}
return s.structMapKeys(cons)
}
cons := make(map[string]struct{}, len(operation.Consumes))
for _, c := range operation.Consumes {
cons[c] = struct{}{}
}
return s.structMapKeys(cons)
}
// ProducesFor gets the mediatypes for the operation
func (s *Spec) ProducesFor(operation *spec.Operation) []string {
if len(operation.Produces) == 0 {
prod := make(map[string]struct{}, len(s.spec.Produces))
for _, k := range s.spec.Produces {
prod[k] = struct{}{}
}
return s.structMapKeys(prod)
}
prod := make(map[string]struct{}, len(operation.Produces))
for _, c := range operation.Produces {
prod[c] = struct{}{}
}
return s.structMapKeys(prod)
}
func mapKeyFromParam(param *spec.Parameter) string {
return fmt.Sprintf("%s#%s", param.In, fieldNameFromParam(param))
}
func fieldNameFromParam(param *spec.Parameter) string {
if nm, ok := param.Extensions.GetString("go-name"); ok {
return nm
}
return swag.ToGoName(param.Name)
}
func (s *Spec) paramsAsMap(parameters []spec.Parameter, res map[string]spec.Parameter) {
for _, param := range parameters {
pr := param
if pr.Ref.String() != "" {
obj, _, err := pr.Ref.GetPointer().Get(s.spec)
if err != nil {
panic(err)
}
pr = obj.(spec.Parameter)
}
res[mapKeyFromParam(&pr)] = pr
}
}
// ParametersFor the specified operation id
func (s *Spec) ParametersFor(operationID string) []spec.Parameter {
gatherParams := func(pi *spec.PathItem, op *spec.Operation) []spec.Parameter {
bag := make(map[string]spec.Parameter)
s.paramsAsMap(pi.Parameters, bag)
s.paramsAsMap(op.Parameters, bag)
var res []spec.Parameter
for _, v := range bag {
res = append(res, v)
}
return res
}
for _, pi := range s.spec.Paths.Paths {
if pi.Get != nil && pi.Get.ID == operationID {
return gatherParams(&pi, pi.Get)
}
if pi.Head != nil && pi.Head.ID == operationID {
return gatherParams(&pi, pi.Head)
}
if pi.Options != nil && pi.Options.ID == operationID {
return gatherParams(&pi, pi.Options)
}
if pi.Post != nil && pi.Post.ID == operationID {
return gatherParams(&pi, pi.Post)
}
if pi.Patch != nil && pi.Patch.ID == operationID {
return gatherParams(&pi, pi.Patch)
}
if pi.Put != nil && pi.Put.ID == operationID {
return gatherParams(&pi, pi.Put)
}
if pi.Delete != nil && pi.Delete.ID == operationID {
return gatherParams(&pi, pi.Delete)
}
}
return nil
}
// ParamsFor the specified method and path. Aggregates them with the defaults etc, so it's all the params that
// apply for the method and path.
func (s *Spec) ParamsFor(method, path string) map[string]spec.Parameter {
res := make(map[string]spec.Parameter)
if pi, ok := s.spec.Paths.Paths[path]; ok {
s.paramsAsMap(pi.Parameters, res)
s.paramsAsMap(s.operations[strings.ToUpper(method)][path].Parameters, res)
}
return res
}
// OperationForName gets the operation for the given id
func (s *Spec) OperationForName(operationID string) (string, string, *spec.Operation, bool) {
for method, pathItem := range s.operations {
for path, op := range pathItem {
if operationID == op.ID {
return method, path, op, true
}
}
}
return "", "", nil, false
}
// OperationFor the given method and path
func (s *Spec) OperationFor(method, path string) (*spec.Operation, bool) {
if mp, ok := s.operations[strings.ToUpper(method)]; ok {
op, fn := mp[path]
return op, fn
}
return nil, false
}
// Operations gathers all the operations specified in the spec document
func (s *Spec) Operations() map[string]map[string]*spec.Operation {
return s.operations
}
func (s *Spec) structMapKeys(mp map[string]struct{}) []string {
if len(mp) == 0 {
return nil
}
result := make([]string, 0, len(mp))
for k := range mp {
result = append(result, k)
}
return result
}
// AllPaths returns all the paths in the swagger spec
func (s *Spec) AllPaths() map[string]spec.PathItem {
if s.spec == nil || s.spec.Paths == nil {
return nil
}
return s.spec.Paths.Paths
}
// OperationIDs gets all the operation ids based on method an dpath
func (s *Spec) OperationIDs() []string {
if len(s.operations) == 0 {
return nil
}
result := make([]string, 0, len(s.operations))
for method, v := range s.operations {
for p, o := range v {
if o.ID != "" {
result = append(result, o.ID)
} else {
result = append(result, fmt.Sprintf("%s %s", strings.ToUpper(method), p))
}
}
}
return result
}
// OperationMethodPaths gets all the operation ids based on method an dpath
func (s *Spec) OperationMethodPaths() []string {
if len(s.operations) == 0 {
return nil
}
result := make([]string, 0, len(s.operations))
for method, v := range s.operations {
for p := range v {
result = append(result, fmt.Sprintf("%s %s", strings.ToUpper(method), p))
}
}
return result
}
// RequiredConsumes gets all the distinct consumes that are specified in the specification document
func (s *Spec) RequiredConsumes() []string {
return s.structMapKeys(s.consumes)
}
// RequiredProduces gets all the distinct produces that are specified in the specification document
func (s *Spec) RequiredProduces() []string {
return s.structMapKeys(s.produces)
}
// RequiredSecuritySchemes gets all the distinct security schemes that are specified in the swagger spec
func (s *Spec) RequiredSecuritySchemes() []string {
return s.structMapKeys(s.authSchemes)
}
// SchemaRef is a reference to a schema
type SchemaRef struct {
Name string
Ref spec.Ref
Schema *spec.Schema
TopLevel bool
}
// SchemasWithAllOf returns schema references to all schemas that are defined
// with an allOf key
func (s *Spec) SchemasWithAllOf() (result []SchemaRef) {
for _, v := range s.allOfs {
result = append(result, v)
}
return
}
// AllDefinitions returns schema references for all the definitions that were discovered
func (s *Spec) AllDefinitions() (result []SchemaRef) {
for _, v := range s.allSchemas {
result = append(result, v)
}
return
}
// AllDefinitionReferences returns json refs for all the discovered schemas
func (s *Spec) AllDefinitionReferences() (result []string) {
for _, v := range s.references.schemas {
result = append(result, v.String())
}
return
}
// AllParameterReferences returns json refs for all the discovered parameters
func (s *Spec) AllParameterReferences() (result []string) {
for _, v := range s.references.parameters {
result = append(result, v.String())
}
return
}
// AllResponseReferences returns json refs for all the discovered responses
func (s *Spec) AllResponseReferences() (result []string) {
for _, v := range s.references.responses {
result = append(result, v.String())
}
return
}
// AllPathItemReferences returns the references for all the items
func (s *Spec) AllPathItemReferences() (result []string) {
for _, v := range s.references.pathItems {
result = append(result, v.String())
}
return
}
// AllItemsReferences returns the references for all the items
func (s *Spec) AllItemsReferences() (result []string) {
for _, v := range s.references.items {
result = append(result, v.String())
}
return
}
// AllReferences returns all the references found in the document
func (s *Spec) AllReferences() (result []string) {
for _, v := range s.references.allRefs {
result = append(result, v.String())
}
return
}
// AllRefs returns all the unique references found in the document
func (s *Spec) AllRefs() (result []spec.Ref) {
set := make(map[string]struct{})
for _, v := range s.references.allRefs {
a := v.String()
if a == "" {
continue
}
if _, ok := set[a]; !ok {
set[a] = struct{}{}
result = append(result, v)
}
}
return
}
func cloneStringMap(source map[string]string) map[string]string {
res := make(map[string]string, len(source))
for k, v := range source {
res[k] = v
}
return res
}
// ParameterPatterns returns all the patterns found in parameters
// the map is cloned to avoid accidental changes
func (s *Spec) ParameterPatterns() map[string]string {
return cloneStringMap(s.patterns.parameters)
}
// HeaderPatterns returns all the patterns found in response headers
// the map is cloned to avoid accidental changes
func (s *Spec) HeaderPatterns() map[string]string {
return cloneStringMap(s.patterns.headers)
}
// ItemsPatterns returns all the patterns found in simple array items
// the map is cloned to avoid accidental changes
func (s *Spec) ItemsPatterns() map[string]string {
return cloneStringMap(s.patterns.items)
}
// SchemaPatterns returns all the patterns found in schemas
// the map is cloned to avoid accidental changes
func (s *Spec) SchemaPatterns() map[string]string {
return cloneStringMap(s.patterns.schemas)
}
// AllPatterns returns all the patterns found in the spec
// the map is cloned to avoid accidental changes
func (s *Spec) AllPatterns() map[string]string {
return cloneStringMap(s.patterns.allPatterns)
}
-756
View File
@@ -1,756 +0,0 @@
package analysis
import (
"fmt"
"log"
"net/http"
"path"
"sort"
"strings"
"strconv"
"github.com/go-openapi/jsonpointer"
swspec "github.com/go-openapi/spec"
"github.com/go-openapi/swag"
)
// FlattenOpts configuration for flattening a swagger specification.
type FlattenOpts struct {
Spec *Spec
BasePath string
_ struct{} // require keys
}
// ExpandOpts creates a spec.ExpandOptions to configure expanding a specification document.
func (f *FlattenOpts) ExpandOpts(skipSchemas bool) *swspec.ExpandOptions {
return &swspec.ExpandOptions{RelativeBase: f.BasePath, SkipSchemas: skipSchemas}
}
// Swagger gets the swagger specification for this flatten operation
func (f *FlattenOpts) Swagger() *swspec.Swagger {
return f.Spec.spec
}
// Flatten an analyzed spec.
//
// To flatten a spec means:
//
// Expand the parameters, responses, path items, parameter items and header items.
// Import external (http, file) references so they become internal to the document.
// Move every inline schema to be a definition with an auto-generated name in a depth-first fashion.
// Rewritten schemas get a vendor extension x-go-gen-location so we know in which package they need to be rendered.
func Flatten(opts FlattenOpts) error {
// recursively expand responses, parameters, path items and items
err := swspec.ExpandSpec(opts.Swagger(), opts.ExpandOpts(true))
if err != nil {
return err
}
opts.Spec.reload() // re-analyze
// at this point there are no other references left but schemas
if err := importExternalReferences(&opts); err != nil {
return err
}
opts.Spec.reload() // re-analyze
// rewrite the inline schemas (schemas that aren't simple types or arrays of simple types)
if err := nameInlinedSchemas(&opts); err != nil {
return err
}
opts.Spec.reload() // re-analyze
// TODO: simplifiy known schema patterns to flat objects with properties?
return nil
}
func nameInlinedSchemas(opts *FlattenOpts) error {
namer := &inlineSchemaNamer{Spec: opts.Swagger(), Operations: opRefsByRef(gatherOperations(opts.Spec, nil))}
depthFirst := sortDepthFirst(opts.Spec.allSchemas)
for _, key := range depthFirst {
sch := opts.Spec.allSchemas[key]
if sch.Schema != nil && sch.Schema.Ref.String() == "" && !sch.TopLevel { // inline schema
asch, err := Schema(SchemaOpts{Schema: sch.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})
if err != nil {
return fmt.Errorf("schema analysis [%s]: %v", sch.Ref.String(), err)
}
if !asch.IsSimpleSchema { // complex schemas get moved
if err := namer.Name(key, sch.Schema, asch); err != nil {
return err
}
}
}
}
return nil
}
var depthGroupOrder = []string{"sharedOpParam", "opParam", "codeResponse", "defaultResponse", "definition"}
func sortDepthFirst(data map[string]SchemaRef) (sorted []string) {
// group by category (shared params, op param, statuscode response, default response, definitions)
// sort groups internally by number of parts in the key and lexical names
// flatten groups into a single list of keys
grouped := make(map[string]keys, len(data))
for k := range data {
split := keyParts(k)
var pk string
if split.IsSharedOperationParam() {
pk = "sharedOpParam"
}
if split.IsOperationParam() {
pk = "opParam"
}
if split.IsStatusCodeResponse() {
pk = "codeResponse"
}
if split.IsDefaultResponse() {
pk = "defaultResponse"
}
if split.IsDefinition() {
pk = "definition"
}
grouped[pk] = append(grouped[pk], key{len(split), k})
}
for _, pk := range depthGroupOrder {
res := grouped[pk]
sort.Sort(res)
for _, v := range res {
sorted = append(sorted, v.Key)
}
}
return
}
type key struct {
Segments int
Key string
}
type keys []key
func (k keys) Len() int { return len(k) }
func (k keys) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
func (k keys) Less(i, j int) bool {
return k[i].Segments > k[j].Segments || (k[i].Segments == k[j].Segments && k[i].Key < k[j].Key)
}
type inlineSchemaNamer struct {
Spec *swspec.Swagger
Operations map[string]opRef
}
func opRefsByRef(oprefs map[string]opRef) map[string]opRef {
result := make(map[string]opRef, len(oprefs))
for _, v := range oprefs {
result[v.Ref.String()] = v
}
return result
}
func (isn *inlineSchemaNamer) Name(key string, schema *swspec.Schema, aschema *AnalyzedSchema) error {
if swspec.Debug {
log.Printf("naming inlined schema at %s", key)
}
parts := keyParts(key)
for _, name := range namesFromKey(parts, aschema, isn.Operations) {
if name != "" {
// create unique name
newName := uniqifyName(isn.Spec.Definitions, swag.ToJSONName(name))
// clone schema
sch, err := cloneSchema(schema)
if err != nil {
return err
}
// replace values on schema
if err := rewriteSchemaToRef(isn.Spec, key, swspec.MustCreateRef("#/definitions/"+newName)); err != nil {
return fmt.Errorf("name inlined schema: %v", err)
}
sch.AddExtension("x-go-gen-location", genLocation(parts))
// fmt.Printf("{\n %q,\n \"\",\n spec.MustCreateRef(%q),\n \"\",\n},\n", key, "#/definitions/"+newName)
// save cloned schema to definitions
saveSchema(isn.Spec, newName, sch)
}
}
return nil
}
func genLocation(parts splitKey) string {
if parts.IsOperation() {
return "operations"
}
if parts.IsDefinition() {
return "models"
}
return ""
}
func uniqifyName(definitions swspec.Definitions, name string) string {
if name == "" {
name = "oaiGen"
}
if len(definitions) == 0 {
return name
}
if _, ok := definitions[name]; !ok {
return name
}
name += "OAIGen"
var idx int
unique := name
_, known := definitions[unique]
for known {
idx++
unique = fmt.Sprintf("%s%d", name, idx)
_, known = definitions[unique]
}
return unique
}
func namesFromKey(parts splitKey, aschema *AnalyzedSchema, operations map[string]opRef) []string {
var baseNames [][]string
var startIndex int
if parts.IsOperation() {
// params
if parts.IsOperationParam() || parts.IsSharedOperationParam() {
piref := parts.PathItemRef()
if piref.String() != "" && parts.IsOperationParam() {
if op, ok := operations[piref.String()]; ok {
startIndex = 5
baseNames = append(baseNames, []string{op.ID, "params", "body"})
}
} else if parts.IsSharedOperationParam() {
pref := parts.PathRef()
for k, v := range operations {
if strings.HasPrefix(k, pref.String()) {
startIndex = 4
baseNames = append(baseNames, []string{v.ID, "params", "body"})
}
}
}
}
// responses
if parts.IsOperationResponse() {
piref := parts.PathItemRef()
if piref.String() != "" {
if op, ok := operations[piref.String()]; ok {
startIndex = 6
baseNames = append(baseNames, []string{op.ID, parts.ResponseName(), "body"})
}
}
}
}
// definitions
if parts.IsDefinition() {
nm := parts.DefinitionName()
if nm != "" {
startIndex = 2
baseNames = append(baseNames, []string{parts.DefinitionName()})
}
}
var result []string
for _, segments := range baseNames {
nm := parts.BuildName(segments, startIndex, aschema)
if nm != "" {
result = append(result, nm)
}
}
sort.Strings(result)
return result
}
const (
pths = "paths"
responses = "responses"
parameters = "parameters"
definitions = "definitions"
)
var ignoredKeys map[string]struct{}
func init() {
ignoredKeys = map[string]struct{}{
"schema": {},
"properties": {},
"not": {},
"anyOf": {},
"oneOf": {},
}
}
type splitKey []string
func (s splitKey) IsDefinition() bool {
return len(s) > 1 && s[0] == definitions
}
func (s splitKey) DefinitionName() string {
if !s.IsDefinition() {
return ""
}
return s[1]
}
func (s splitKey) BuildName(segments []string, startIndex int, aschema *AnalyzedSchema) string {
for _, part := range s[startIndex:] {
if _, ignored := ignoredKeys[part]; !ignored {
if part == "items" || part == "additionalItems" {
if aschema.IsTuple || aschema.IsTupleWithExtra {
segments = append(segments, "tuple")
} else {
segments = append(segments, "items")
}
if part == "additionalItems" {
segments = append(segments, part)
}
continue
}
segments = append(segments, part)
}
}
return strings.Join(segments, " ")
}
func (s splitKey) IsOperation() bool {
return len(s) > 1 && s[0] == pths
}
func (s splitKey) IsSharedOperationParam() bool {
return len(s) > 2 && s[0] == pths && s[2] == parameters
}
func (s splitKey) IsOperationParam() bool {
return len(s) > 3 && s[0] == pths && s[3] == parameters
}
func (s splitKey) IsOperationResponse() bool {
return len(s) > 3 && s[0] == pths && s[3] == responses
}
func (s splitKey) IsDefaultResponse() bool {
return len(s) > 4 && s[0] == pths && s[3] == responses && s[4] == "default"
}
func (s splitKey) IsStatusCodeResponse() bool {
isInt := func() bool {
_, err := strconv.Atoi(s[4])
return err == nil
}
return len(s) > 4 && s[0] == pths && s[3] == responses && isInt()
}
func (s splitKey) ResponseName() string {
if s.IsStatusCodeResponse() {
code, _ := strconv.Atoi(s[4])
return http.StatusText(code)
}
if s.IsDefaultResponse() {
return "Default"
}
return ""
}
var validMethods map[string]struct{}
func init() {
validMethods = map[string]struct{}{
"GET": {},
"HEAD": {},
"OPTIONS": {},
"PATCH": {},
"POST": {},
"PUT": {},
"DELETE": {},
}
}
func (s splitKey) PathItemRef() swspec.Ref {
if len(s) < 3 {
return swspec.Ref{}
}
pth, method := s[1], s[2]
if _, validMethod := validMethods[strings.ToUpper(method)]; !validMethod && !strings.HasPrefix(method, "x-") {
return swspec.Ref{}
}
return swspec.MustCreateRef("#" + path.Join("/", pths, jsonpointer.Escape(pth), strings.ToUpper(method)))
}
func (s splitKey) PathRef() swspec.Ref {
if !s.IsOperation() {
return swspec.Ref{}
}
return swspec.MustCreateRef("#" + path.Join("/", pths, jsonpointer.Escape(s[1])))
}
func keyParts(key string) splitKey {
var res []string
for _, part := range strings.Split(key[1:], "/") {
if part != "" {
res = append(res, jsonpointer.Unescape(part))
}
}
return res
}
func rewriteSchemaToRef(spec *swspec.Swagger, key string, ref swspec.Ref) error {
if swspec.Debug {
log.Printf("rewriting schema to ref for %s with %s", key, ref.String())
}
pth := key[1:]
ptr, err := jsonpointer.New(pth)
if err != nil {
return err
}
value, _, err := ptr.Get(spec)
if err != nil {
return err
}
switch refable := value.(type) {
case *swspec.Schema:
return rewriteParentRef(spec, key, ref)
case *swspec.SchemaOrBool:
if refable.Schema != nil {
refable.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
}
case *swspec.SchemaOrArray:
if refable.Schema != nil {
refable.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
}
case swspec.Schema:
return rewriteParentRef(spec, key, ref)
default:
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
}
func rewriteParentRef(spec *swspec.Swagger, key string, ref swspec.Ref) error {
pth := key[1:]
parent, entry := path.Dir(pth), path.Base(pth)
if swspec.Debug {
log.Println("getting schema holder at:", parent)
}
pptr, err := jsonpointer.New(parent)
if err != nil {
return err
}
pvalue, _, err := pptr.Get(spec)
if err != nil {
return fmt.Errorf("can't get parent for %s: %v", parent, err)
}
if swspec.Debug {
log.Printf("rewriting holder for %T", pvalue)
}
switch container := pvalue.(type) {
case swspec.Response:
if err := rewriteParentRef(spec, "#"+parent, ref); err != nil {
return err
}
case *swspec.Response:
container.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case *swspec.Responses:
statusCode, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
resp := container.StatusCodeResponses[statusCode]
resp.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
container.StatusCodeResponses[statusCode] = resp
case map[string]swspec.Response:
resp := container[entry]
resp.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
container[entry] = resp
case swspec.Parameter:
if err := rewriteParentRef(spec, "#"+parent, ref); err != nil {
return err
}
case map[string]swspec.Parameter:
param := container[entry]
param.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
container[entry] = param
case []swspec.Parameter:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
param := container[idx]
param.Schema = &swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
container[idx] = param
case swspec.Definitions:
container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case map[string]swspec.Schema:
container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case []swspec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
container[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case *swspec.SchemaOrArray:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
container.Schemas[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
default:
return fmt.Errorf("unhandled parent schema rewrite %s (%T)", key, pvalue)
}
return nil
}
func cloneSchema(schema *swspec.Schema) (*swspec.Schema, error) {
var sch swspec.Schema
if err := swag.FromDynamicJSON(schema, &sch); err != nil {
return nil, fmt.Errorf("name inlined schema: %v", err)
}
return &sch, nil
}
func importExternalReferences(opts *FlattenOpts) error {
groupedRefs := reverseIndexForSchemaRefs(opts)
for refStr, entry := range groupedRefs {
if !entry.Ref.HasFragmentOnly {
if swspec.Debug {
log.Printf("importing external schema for [%s] from %s", strings.Join(entry.Keys, ", "), refStr)
}
// resolve to actual schema
sch, err := swspec.ResolveRefWithBase(opts.Swagger(), &entry.Ref, opts.ExpandOpts(false))
if err != nil {
return err
}
if sch == nil {
return fmt.Errorf("no schema found at %s for [%s]", refStr, strings.Join(entry.Keys, ", "))
}
if swspec.Debug {
log.Printf("importing external schema for [%s] from %s", strings.Join(entry.Keys, ", "), refStr)
}
// generate a unique name
newName := uniqifyName(opts.Swagger().Definitions, nameFromRef(entry.Ref))
if swspec.Debug {
log.Printf("new name for [%s]: %s", strings.Join(entry.Keys, ", "), newName)
}
// rewrite the external refs to local ones
for _, key := range entry.Keys {
if err := updateRef(opts.Swagger(), key, swspec.MustCreateRef("#"+path.Join("/definitions", newName))); err != nil {
return err
}
}
// add the resolved schema to the definitions
saveSchema(opts.Swagger(), newName, sch)
}
}
return nil
}
type refRevIdx struct {
Ref swspec.Ref
Keys []string
}
func reverseIndexForSchemaRefs(opts *FlattenOpts) map[string]refRevIdx {
collected := make(map[string]refRevIdx)
for key, schRef := range opts.Spec.references.schemas {
if entry, ok := collected[schRef.String()]; ok {
entry.Keys = append(entry.Keys, key)
collected[schRef.String()] = entry
} else {
collected[schRef.String()] = refRevIdx{
Ref: schRef,
Keys: []string{key},
}
}
}
return collected
}
func nameFromRef(ref swspec.Ref) string {
u := ref.GetURL()
if u.Fragment != "" {
return swag.ToJSONName(path.Base(u.Fragment))
}
if u.Path != "" {
bn := path.Base(u.Path)
if bn != "" && bn != "/" {
ext := path.Ext(bn)
if ext != "" {
return swag.ToJSONName(bn[:len(bn)-len(ext)])
}
return swag.ToJSONName(bn)
}
}
return swag.ToJSONName(strings.Replace(u.Host, ".", " ", -1))
}
func saveSchema(spec *swspec.Swagger, name string, schema *swspec.Schema) {
if schema == nil {
return
}
if spec.Definitions == nil {
spec.Definitions = make(map[string]swspec.Schema, 150)
}
spec.Definitions[name] = *schema
}
func updateRef(spec *swspec.Swagger, key string, ref swspec.Ref) error {
if swspec.Debug {
log.Printf("updating ref for %s with %s", key, ref.String())
}
pth := key[1:]
ptr, err := jsonpointer.New(pth)
if err != nil {
return err
}
value, _, err := ptr.Get(spec)
if err != nil {
return err
}
switch refable := value.(type) {
case *swspec.Schema:
refable.Ref = ref
case *swspec.SchemaOrBool:
if refable.Schema != nil {
refable.Schema.Ref = ref
}
case *swspec.SchemaOrArray:
if refable.Schema != nil {
refable.Schema.Ref = ref
}
case swspec.Schema:
parent, entry := path.Dir(pth), path.Base(pth)
if swspec.Debug {
log.Println("getting schema holder at:", parent)
}
pptr, err := jsonpointer.New(parent)
if err != nil {
return err
}
pvalue, _, err := pptr.Get(spec)
if err != nil {
return fmt.Errorf("can't get parent for %s: %v", parent, err)
}
switch container := pvalue.(type) {
case swspec.Definitions:
container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case map[string]swspec.Schema:
container[entry] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case []swspec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
container[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
case *swspec.SchemaOrArray:
idx, err := strconv.Atoi(entry)
if err != nil {
return fmt.Errorf("%s not a number: %v", pth, err)
}
container.Schemas[idx] = swspec.Schema{SchemaProps: swspec.SchemaProps{Ref: ref}}
}
default:
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
}
func containsString(names []string, name string) bool {
for _, nm := range names {
if nm == name {
return true
}
}
return false
}
type opRef struct {
Method string
Path string
Key string
ID string
Op *swspec.Operation
Ref swspec.Ref
}
type opRefs []opRef
func (o opRefs) Len() int { return len(o) }
func (o opRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }
func gatherOperations(specDoc *Spec, operationIDs []string) map[string]opRef {
var oprefs opRefs
for method, pathItem := range specDoc.Operations() {
for pth, operation := range pathItem {
vv := *operation
oprefs = append(oprefs, opRef{
Key: swag.ToGoName(strings.ToLower(method) + " " + pth),
Method: method,
Path: pth,
ID: vv.ID,
Op: &vv,
Ref: swspec.MustCreateRef("#" + path.Join("/paths", jsonpointer.Escape(pth), method)),
})
}
}
sort.Sort(oprefs)
operations := make(map[string]opRef)
for _, opr := range oprefs {
nm := opr.ID
if nm == "" {
nm = opr.Key
}
oo, found := operations[nm]
if found && oo.Method != opr.Method && oo.Path != opr.Path {
nm = opr.Key
}
if len(operationIDs) == 0 || containsString(operationIDs, opr.ID) || containsString(operationIDs, nm) {
opr.ID = nm
opr.Op.ID = nm
operations[nm] = opr
}
}
return operations
}
-199
View File
@@ -1,199 +0,0 @@
package analysis
import (
"fmt"
"github.com/go-openapi/spec"
)
// Mixin modifies the primary swagger spec by adding the paths and
// definitions from the mixin specs. Top level parameters and
// responses from the mixins are also carried over. Operation id
// collisions are avoided by appending "Mixin<N>" but only if
// needed. No other parts of primary are modified. Consider calling
// FixEmptyResponseDescriptions() on the modified primary if you read
// them from storage and they are valid to start with.
//
// Entries in "paths", "definitions", "parameters" and "responses" are
// added to the primary in the order of the given mixins. If the entry
// already exists in primary it is skipped with a warning message.
//
// The count of skipped entries (from collisions) is returned so any
// deviation from the number expected can flag warning in your build
// scripts. Carefully review the collisions before accepting them;
// consider renaming things if possible.
//
// No normalization of any keys takes place (paths, type defs,
// etc). Ensure they are canonical if your downstream tools do
// key normalization of any form.
func Mixin(primary *spec.Swagger, mixins ...*spec.Swagger) []string {
var skipped []string
opIds := getOpIds(primary)
if primary.Paths == nil {
primary.Paths = &spec.Paths{Paths: make(map[string]spec.PathItem)}
}
if primary.Paths.Paths == nil {
primary.Paths.Paths = make(map[string]spec.PathItem)
}
if primary.Definitions == nil {
primary.Definitions = make(spec.Definitions)
}
if primary.Parameters == nil {
primary.Parameters = make(map[string]spec.Parameter)
}
if primary.Responses == nil {
primary.Responses = make(map[string]spec.Response)
}
for i, m := range mixins {
for k, v := range m.Definitions {
// assume name collisions represent IDENTICAL type. careful.
if _, exists := primary.Definitions[k]; exists {
warn := fmt.Sprintf("definitions entry '%v' already exists in primary or higher priority mixin, skipping\n", k)
skipped = append(skipped, warn)
continue
}
primary.Definitions[k] = v
}
if m.Paths != nil {
for k, v := range m.Paths.Paths {
if _, exists := primary.Paths.Paths[k]; exists {
warn := fmt.Sprintf("paths entry '%v' already exists in primary or higher priority mixin, skipping\n", k)
skipped = append(skipped, warn)
continue
}
// Swagger requires that operationIds be
// unique within a spec. If we find a
// collision we append "Mixin0" to the
// operatoinId we are adding, where 0 is mixin
// index. We assume that operationIds with
// all the proivded specs are already unique.
piops := pathItemOps(v)
for _, piop := range piops {
if opIds[piop.ID] {
piop.ID = fmt.Sprintf("%v%v%v", piop.ID, "Mixin", i)
}
opIds[piop.ID] = true
}
primary.Paths.Paths[k] = v
}
}
for k, v := range m.Parameters {
// could try to rename on conflict but would
// have to fix $refs in the mixin. Complain
// for now
if _, exists := primary.Parameters[k]; exists {
warn := fmt.Sprintf("top level parameters entry '%v' already exists in primary or higher priority mixin, skipping\n", k)
skipped = append(skipped, warn)
continue
}
primary.Parameters[k] = v
}
for k, v := range m.Responses {
// could try to rename on conflict but would
// have to fix $refs in the mixin. Complain
// for now
if _, exists := primary.Responses[k]; exists {
warn := fmt.Sprintf("top level responses entry '%v' already exists in primary or higher priority mixin, skipping\n", k)
skipped = append(skipped, warn)
continue
}
primary.Responses[k] = v
}
}
return skipped
}
// FixEmptyResponseDescriptions replaces empty ("") response
// descriptions in the input with "(empty)" to ensure that the
// resulting Swagger is stays valid. The problem appears to arise
// from reading in valid specs that have a explicit response
// description of "" (valid, response.description is required), but
// due to zero values being omitted upon re-serializing (omitempty) we
// lose them unless we stick some chars in there.
func FixEmptyResponseDescriptions(s *spec.Swagger) {
if s.Paths != nil {
for _, v := range s.Paths.Paths {
if v.Get != nil {
FixEmptyDescs(v.Get.Responses)
}
if v.Put != nil {
FixEmptyDescs(v.Put.Responses)
}
if v.Post != nil {
FixEmptyDescs(v.Post.Responses)
}
if v.Delete != nil {
FixEmptyDescs(v.Delete.Responses)
}
if v.Options != nil {
FixEmptyDescs(v.Options.Responses)
}
if v.Head != nil {
FixEmptyDescs(v.Head.Responses)
}
if v.Patch != nil {
FixEmptyDescs(v.Patch.Responses)
}
}
}
for k, v := range s.Responses {
FixEmptyDesc(&v)
s.Responses[k] = v
}
}
// FixEmptyDescs adds "(empty)" as the description for any Response in
// the given Responses object that doesn't already have one.
func FixEmptyDescs(rs *spec.Responses) {
FixEmptyDesc(rs.Default)
for k, v := range rs.StatusCodeResponses {
FixEmptyDesc(&v)
rs.StatusCodeResponses[k] = v
}
}
// FixEmptyDesc adds "(empty)" as the description to the given
// Response object if it doesn't already have one and isn't a
// ref. No-op on nil input.
func FixEmptyDesc(rs *spec.Response) {
if rs == nil || rs.Description != "" || rs.Ref.Ref.GetURL() != nil {
return
}
rs.Description = "(empty)"
}
// getOpIds extracts all the paths.<path>.operationIds from the given
// spec and returns them as the keys in a map with 'true' values.
func getOpIds(s *spec.Swagger) map[string]bool {
rv := make(map[string]bool)
if s.Paths == nil {
return rv
}
for _, v := range s.Paths.Paths {
piops := pathItemOps(v)
for _, op := range piops {
rv[op.ID] = true
}
}
return rv
}
func pathItemOps(p spec.PathItem) []*spec.Operation {
var rv []*spec.Operation
rv = appendOp(rv, p.Get)
rv = appendOp(rv, p.Put)
rv = appendOp(rv, p.Post)
rv = appendOp(rv, p.Delete)
rv = appendOp(rv, p.Head)
rv = appendOp(rv, p.Patch)
return rv
}
func appendOp(ops []*spec.Operation, op *spec.Operation) []*spec.Operation {
if op == nil {
return ops
}
return append(ops, op)
}
-233
View File
@@ -1,233 +0,0 @@
package analysis
import (
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
)
// SchemaOpts configures the schema analyzer
type SchemaOpts struct {
Schema *spec.Schema
Root interface{}
BasePath string
_ struct{}
}
// Schema analysis, will classify the schema according to known
// patterns.
func Schema(opts SchemaOpts) (*AnalyzedSchema, error) {
a := &AnalyzedSchema{
schema: opts.Schema,
root: opts.Root,
basePath: opts.BasePath,
}
a.initializeFlags()
a.inferKnownType()
a.inferEnum()
a.inferBaseType()
if err := a.inferMap(); err != nil {
return nil, err
}
if err := a.inferArray(); err != nil {
return nil, err
}
if err := a.inferTuple(); err != nil {
return nil, err
}
if err := a.inferFromRef(); err != nil {
return nil, err
}
a.inferSimpleSchema()
return a, nil
}
// AnalyzedSchema indicates what the schema represents
type AnalyzedSchema struct {
schema *spec.Schema
root interface{}
basePath string
hasProps bool
hasAllOf bool
hasItems bool
hasAdditionalProps bool
hasAdditionalItems bool
hasRef bool
IsKnownType bool
IsSimpleSchema bool
IsArray bool
IsSimpleArray bool
IsMap bool
IsSimpleMap bool
IsExtendedObject bool
IsTuple bool
IsTupleWithExtra bool
IsBaseType bool
IsEnum bool
}
// Inherits copies value fields from other onto this schema
func (a *AnalyzedSchema) inherits(other *AnalyzedSchema) {
if other == nil {
return
}
a.hasProps = other.hasProps
a.hasAllOf = other.hasAllOf
a.hasItems = other.hasItems
a.hasAdditionalItems = other.hasAdditionalItems
a.hasAdditionalProps = other.hasAdditionalProps
a.hasRef = other.hasRef
a.IsKnownType = other.IsKnownType
a.IsSimpleSchema = other.IsSimpleSchema
a.IsArray = other.IsArray
a.IsSimpleArray = other.IsSimpleArray
a.IsMap = other.IsMap
a.IsSimpleMap = other.IsSimpleMap
a.IsExtendedObject = other.IsExtendedObject
a.IsTuple = other.IsTuple
a.IsTupleWithExtra = other.IsTupleWithExtra
a.IsBaseType = other.IsBaseType
a.IsEnum = other.IsEnum
}
func (a *AnalyzedSchema) inferFromRef() error {
if a.hasRef {
opts := &spec.ExpandOptions{RelativeBase: a.basePath}
sch, err := spec.ResolveRefWithBase(a.root, &a.schema.Ref, opts)
if err != nil {
return err
}
if sch != nil {
rsch, err := Schema(SchemaOpts{
Schema: sch,
Root: a.root,
BasePath: a.basePath,
})
if err != nil {
return err
}
a.inherits(rsch)
}
}
return nil
}
func (a *AnalyzedSchema) inferSimpleSchema() {
a.IsSimpleSchema = a.IsKnownType || a.IsSimpleArray || a.IsSimpleMap
}
func (a *AnalyzedSchema) inferKnownType() {
tpe := a.schema.Type
format := a.schema.Format
a.IsKnownType = tpe.Contains("boolean") ||
tpe.Contains("integer") ||
tpe.Contains("number") ||
tpe.Contains("string") ||
(format != "" && strfmt.Default.ContainsName(format)) ||
(a.isObjectType() && !a.hasProps && !a.hasAllOf && !a.hasAdditionalProps && !a.hasAdditionalItems)
}
func (a *AnalyzedSchema) inferMap() error {
if a.isObjectType() {
hasExtra := a.hasProps || a.hasAllOf
a.IsMap = a.hasAdditionalProps && !hasExtra
a.IsExtendedObject = a.hasAdditionalProps && hasExtra
if a.IsMap {
if a.schema.AdditionalProperties.Schema != nil {
msch, err := Schema(SchemaOpts{
Schema: a.schema.AdditionalProperties.Schema,
Root: a.root,
BasePath: a.basePath,
})
if err != nil {
return err
}
a.IsSimpleMap = msch.IsSimpleSchema
} else if a.schema.AdditionalProperties.Allows {
a.IsSimpleMap = true
}
}
}
return nil
}
func (a *AnalyzedSchema) inferArray() error {
fromValid := a.isArrayType() && (a.schema.Items == nil || a.schema.Items.Len() < 2)
a.IsArray = fromValid || (a.hasItems && a.schema.Items.Len() < 2)
if a.IsArray && a.hasItems {
if a.schema.Items.Schema != nil {
itsch, err := Schema(SchemaOpts{
Schema: a.schema.Items.Schema,
Root: a.root,
BasePath: a.basePath,
})
if err != nil {
return err
}
a.IsSimpleArray = itsch.IsSimpleSchema
}
if len(a.schema.Items.Schemas) > 0 {
itsch, err := Schema(SchemaOpts{
Schema: &a.schema.Items.Schemas[0],
Root: a.root,
BasePath: a.basePath,
})
if err != nil {
return err
}
a.IsSimpleArray = itsch.IsSimpleSchema
}
}
if a.IsArray && !a.hasItems {
a.IsSimpleArray = true
}
return nil
}
func (a *AnalyzedSchema) inferTuple() error {
tuple := a.hasItems && a.schema.Items.Len() > 1
a.IsTuple = tuple && !a.hasAdditionalItems
a.IsTupleWithExtra = tuple && a.hasAdditionalItems
return nil
}
func (a *AnalyzedSchema) inferBaseType() {
if a.isObjectType() {
a.IsBaseType = a.schema.Discriminator != ""
}
}
func (a *AnalyzedSchema) inferEnum() {
a.IsEnum = len(a.schema.Enum) > 0
}
func (a *AnalyzedSchema) initializeFlags() {
a.hasProps = len(a.schema.Properties) > 0
a.hasAllOf = len(a.schema.AllOf) > 0
a.hasRef = a.schema.Ref.String() != ""
a.hasItems = a.schema.Items != nil &&
(a.schema.Items.Schema != nil || len(a.schema.Items.Schemas) > 0)
a.hasAdditionalProps = a.schema.AdditionalProperties != nil &&
(a.schema.AdditionalProperties != nil || a.schema.AdditionalProperties.Allows)
a.hasAdditionalItems = a.schema.AdditionalItems != nil &&
(a.schema.AdditionalItems.Schema != nil || a.schema.AdditionalItems.Allows)
}
func (a *AnalyzedSchema) isObjectType() bool {
return !a.hasRef && (a.schema.Type == nil || a.schema.Type.Contains("") || a.schema.Type.Contains("object"))
}
func (a *AnalyzedSchema) isArrayType() bool {
return !a.hasRef && (a.schema.Type != nil && a.schema.Type.Contains("array"))
}
-2
View File
@@ -1,2 +0,0 @@
secrets.yml
coverage.out
-74
View File
@@ -1,74 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
-5
View File
@@ -1,5 +0,0 @@
# OpenAPI errors [![Build Status](https://travis-ci.org/go-openapi/errors.svg?branch=master)](https://travis-ci.org/go-openapi/errors) [![codecov](https://codecov.io/gh/go-openapi/errors/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/errors) [![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/errors/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/errors?status.svg)](http://godoc.org/github.com/go-openapi/errors)
Shared errors used throughout the various libraries for the go-openapi toolkit
-150
View File
@@ -1,150 +0,0 @@
// 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 errors
import (
"encoding/json"
"fmt"
"net/http"
"strings"
)
// Error represents a error interface all swagger framework errors implement
type Error interface {
error
Code() int32
}
type apiError struct {
code int32
message string
}
func (a *apiError) Error() string {
return a.message
}
func (a *apiError) Code() int32 {
return a.code
}
// New creates a new API error with a code and a message
func New(code int32, message string, args ...interface{}) Error {
if len(args) > 0 {
return &apiError{code, fmt.Sprintf(message, args...)}
}
return &apiError{code, message}
}
// NotFound creates a new not found error
func NotFound(message string, args ...interface{}) Error {
if message == "" {
message = "Not found"
}
return New(http.StatusNotFound, fmt.Sprintf(message, args...))
}
// NotImplemented creates a new not implemented error
func NotImplemented(message string) Error {
return New(http.StatusNotImplemented, message)
}
// MethodNotAllowedError represents an error for when the path matches but the method doesn't
type MethodNotAllowedError struct {
code int32
Allowed []string
message string
}
func (m *MethodNotAllowedError) Error() string {
return m.message
}
// Code the error code
func (m *MethodNotAllowedError) Code() int32 {
return m.code
}
func errorAsJSON(err Error) []byte {
b, _ := json.Marshal(struct {
Code int32 `json:"code"`
Message string `json:"message"`
}{err.Code(), err.Error()})
return b
}
func flattenComposite(errs *CompositeError) *CompositeError {
var res []error
for _, er := range errs.Errors {
switch e := er.(type) {
case *CompositeError:
if len(e.Errors) > 0 {
flat := flattenComposite(e)
if len(flat.Errors) > 0 {
res = append(res, flat.Errors...)
}
}
default:
if e != nil {
res = append(res, e)
}
}
}
return CompositeValidationError(res...)
}
// MethodNotAllowed creates a new method not allowed error
func MethodNotAllowed(requested string, allow []string) Error {
msg := fmt.Sprintf("method %s is not allowed, but [%s] are", requested, strings.Join(allow, ","))
return &MethodNotAllowedError{code: http.StatusMethodNotAllowed, Allowed: allow, message: msg}
}
// ServeError the error handler interface implemenation
func ServeError(rw http.ResponseWriter, r *http.Request, err error) {
rw.Header().Set("Content-Type", "application/json")
switch e := err.(type) {
case *CompositeError:
er := flattenComposite(e)
ServeError(rw, r, er.Errors[0])
case *MethodNotAllowedError:
rw.Header().Add("Allow", strings.Join(err.(*MethodNotAllowedError).Allowed, ","))
rw.WriteHeader(asHTTPCode(int(e.Code())))
if r == nil || r.Method != "HEAD" {
rw.Write(errorAsJSON(e))
}
case Error:
if e == nil {
rw.WriteHeader(http.StatusInternalServerError)
rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
return
}
rw.WriteHeader(asHTTPCode(int(e.Code())))
if r == nil || r.Method != "HEAD" {
rw.Write(errorAsJSON(e))
}
default:
rw.WriteHeader(http.StatusInternalServerError)
if r == nil || r.Method != "HEAD" {
rw.Write(errorAsJSON(New(http.StatusInternalServerError, err.Error())))
}
}
}
func asHTTPCode(input int) int {
if input >= 600 {
return 422
}
return input
}
-20
View File
@@ -1,20 +0,0 @@
// 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 errors
// Unauthenticated returns an unauthenticated error
func Unauthenticated(scheme string) Error {
return New(401, "unauthenticated for %s", scheme)
}
-85
View File
@@ -1,85 +0,0 @@
// 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 errors
import (
"fmt"
"net/http"
)
// Validation represents a failure of a precondition
type Validation struct {
code int32
Name string
In string
Value interface{}
message string
Values []interface{}
}
func (e *Validation) Error() string {
return e.message
}
// Code the error code
func (e *Validation) Code() int32 {
return e.code
}
const (
contentTypeFail = `unsupported media type %q, only %v are allowed`
responseFormatFail = `unsupported media type requested, only %v are available`
)
// InvalidContentType error for an invalid content type
func InvalidContentType(value string, allowed []string) *Validation {
var values []interface{}
for _, v := range allowed {
values = append(values, v)
}
return &Validation{
code: http.StatusUnsupportedMediaType,
Name: "Content-Type",
In: "header",
Value: value,
Values: values,
message: fmt.Sprintf(contentTypeFail, value, allowed),
}
}
// InvalidResponseFormat error for an unacceptable response format request
func InvalidResponseFormat(value string, allowed []string) *Validation {
var values []interface{}
for _, v := range allowed {
values = append(values, v)
}
return &Validation{
code: http.StatusNotAcceptable,
Name: "Accept",
In: "header",
Value: value,
Values: values,
message: fmt.Sprintf(responseFormatFail, allowed),
}
}
// Validate error message name for aliased property
func (e *Validation) ValidateName(name string) *Validation {
if e.Name == "" && name != "" {
e.Name = name
e.message = name+e.message
}
return e
}
-51
View File
@@ -1,51 +0,0 @@
// 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 errors
import (
"bytes"
"fmt"
"strings"
)
// APIVerificationFailed is an error that contains all the missing info for a mismatched section
// between the api registrations and the api spec
type APIVerificationFailed struct {
Section string
MissingSpecification []string
MissingRegistration []string
}
//
func (v *APIVerificationFailed) Error() string {
buf := bytes.NewBuffer(nil)
hasRegMissing := len(v.MissingRegistration) > 0
hasSpecMissing := len(v.MissingSpecification) > 0
if hasRegMissing {
buf.WriteString(fmt.Sprintf("missing [%s] %s registrations", strings.Join(v.MissingRegistration, ", "), v.Section))
}
if hasRegMissing && hasSpecMissing {
buf.WriteString("\n")
}
if hasSpecMissing {
buf.WriteString(fmt.Sprintf("missing from spec file [%s] %s", strings.Join(v.MissingSpecification, ", "), v.Section))
}
return buf.String()
}
-59
View File
@@ -1,59 +0,0 @@
// 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 errors
import "fmt"
// ParseError respresents a parsing error
type ParseError struct {
code int32
Name string
In string
Value string
Reason error
message string
}
func (e *ParseError) Error() string {
return e.message
}
// Code returns the http status code for this error
func (e *ParseError) Code() int32 {
return e.code
}
const (
parseErrorTemplContent = `parsing %s %s from %q failed, because %s`
parseErrorTemplContentNoIn = `parsing %s from %q failed, because %s`
)
// NewParseError creates a new parse error
func NewParseError(name, in, value string, reason error) *ParseError {
var msg string
if in == "" {
msg = fmt.Sprintf(parseErrorTemplContentNoIn, name, value, reason)
} else {
msg = fmt.Sprintf(parseErrorTemplContent, name, in, value, reason)
}
return &ParseError{
code: 400,
Name: name,
In: in,
Value: value,
Reason: reason,
message: msg,
}
}
-548
View File
@@ -1,548 +0,0 @@
// 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 errors
import (
"fmt"
"strings"
)
const (
invalidType = "%s is an invalid type name"
typeFail = "%s in %s must be of type %s"
typeFailWithData = "%s in %s must be of type %s: %q"
typeFailWithError = "%s in %s must be of type %s, because: %s"
requiredFail = "%s in %s is required"
tooLongMessage = "%s in %s should be at most %d chars long"
tooShortMessage = "%s in %s should be at least %d chars long"
patternFail = "%s in %s should match '%s'"
enumFail = "%s in %s should be one of %v"
multipleOfFail = "%s in %s should be a multiple of %v"
maxIncFail = "%s in %s should be less than or equal to %v"
maxExcFail = "%s in %s should be less than %v"
minIncFail = "%s in %s should be greater than or equal to %v"
minExcFail = "%s in %s should be greater than %v"
uniqueFail = "%s in %s shouldn't contain duplicates"
maxItemsFail = "%s in %s should have at most %d items"
minItemsFail = "%s in %s should have at least %d items"
typeFailNoIn = "%s must be of type %s"
typeFailWithDataNoIn = "%s must be of type %s: %q"
typeFailWithErrorNoIn = "%s must be of type %s, because: %s"
requiredFailNoIn = "%s is required"
tooLongMessageNoIn = "%s should be at most %d chars long"
tooShortMessageNoIn = "%s should be at least %d chars long"
patternFailNoIn = "%s should match '%s'"
enumFailNoIn = "%s should be one of %v"
multipleOfFailNoIn = "%s should be a multiple of %v"
maxIncFailNoIn = "%s should be less than or equal to %v"
maxExcFailNoIn = "%s should be less than %v"
minIncFailNoIn = "%s should be greater than or equal to %v"
minExcFailNoIn = "%s should be greater than %v"
uniqueFailNoIn = "%s shouldn't contain duplicates"
maxItemsFailNoIn = "%s should have at most %d items"
minItemsFailNoIn = "%s should have at least %d items"
noAdditionalItems = "%s in %s can't have additional items"
noAdditionalItemsNoIn = "%s can't have additional items"
tooFewProperties = "%s in %s should have at least %d properties"
tooFewPropertiesNoIn = "%s should have at least %d properties"
tooManyProperties = "%s in %s should have at most %d properties"
tooManyPropertiesNoIn = "%s should have at most %d properties"
unallowedProperty = "%s.%s in %s is a forbidden property"
unallowedPropertyNoIn = "%s.%s is a forbidden property"
failedAllPatternProps = "%s.%s in %s failed all pattern properties"
failedAllPatternPropsNoIn = "%s.%s failed all pattern properties"
)
// All code responses can be used to differentiate errors for different handling
// by the consuming program
const (
// CompositeErrorCode remains 422 for backwards-compatibility
// and to separate it from validation errors with cause
CompositeErrorCode = 422
// InvalidTypeCode is used for any subclass of invalid types
InvalidTypeCode = 600 + iota
RequiredFailCode
TooLongFailCode
TooShortFailCode
PatternFailCode
EnumFailCode
MultipleOfFailCode
MaxFailCode
MinFailCode
UniqueFailCode
MaxItemsFailCode
MinItemsFailCode
NoAdditionalItemsCode
TooFewPropertiesCode
TooManyPropertiesCode
UnallowedPropertyCode
FailedAllPatternPropsCode
)
// CompositeError is an error that groups several errors together
type CompositeError struct {
Errors []error
code int32
message string
}
// Code for this error
func (c *CompositeError) Code() int32 {
return c.code
}
func (c *CompositeError) Error() string {
if len(c.Errors) > 0 {
msgs := []string{c.message + ":"}
for _, e := range c.Errors {
msgs = append(msgs, e.Error())
}
return strings.Join(msgs, "\n")
}
return c.message
}
// CompositeValidationError an error to wrap a bunch of other errors
func CompositeValidationError(errors ...error) *CompositeError {
return &CompositeError{
code: CompositeErrorCode,
Errors: append([]error{}, errors...),
message: "validation failure list",
}
}
// FailedAllPatternProperties an error for when the property doesn't match a pattern
func FailedAllPatternProperties(name, in, key string) *Validation {
msg := fmt.Sprintf(failedAllPatternProps, name, key, in)
if in == "" {
msg = fmt.Sprintf(failedAllPatternPropsNoIn, name, key)
}
return &Validation{
code: FailedAllPatternPropsCode,
Name: name,
In: in,
Value: key,
message: msg,
}
}
// PropertyNotAllowed an error for when the property doesn't match a pattern
func PropertyNotAllowed(name, in, key string) *Validation {
msg := fmt.Sprintf(unallowedProperty, name, key, in)
if in == "" {
msg = fmt.Sprintf(unallowedPropertyNoIn, name, key)
}
return &Validation{
code: UnallowedPropertyCode,
Name: name,
In: in,
Value: key,
message: msg,
}
}
// TooFewProperties an error for an object with too few properties
func TooFewProperties(name, in string, n int64) *Validation {
msg := fmt.Sprintf(tooFewProperties, name, in, n)
if in == "" {
msg = fmt.Sprintf(tooFewPropertiesNoIn, name, n)
}
return &Validation{
code: TooFewPropertiesCode,
Name: name,
In: in,
Value: n,
message: msg,
}
}
// TooManyProperties an error for an object with too many properties
func TooManyProperties(name, in string, n int64) *Validation {
msg := fmt.Sprintf(tooManyProperties, name, in, n)
if in == "" {
msg = fmt.Sprintf(tooManyPropertiesNoIn, name, n)
}
return &Validation{
code: TooManyPropertiesCode,
Name: name,
In: in,
Value: n,
message: msg,
}
}
// AdditionalItemsNotAllowed an error for invalid additional items
func AdditionalItemsNotAllowed(name, in string) *Validation {
msg := fmt.Sprintf(noAdditionalItems, name, in)
if in == "" {
msg = fmt.Sprintf(noAdditionalItemsNoIn, name)
}
return &Validation{
code: NoAdditionalItemsCode,
Name: name,
In: in,
message: msg,
}
}
// InvalidCollectionFormat another flavor of invalid type error
func InvalidCollectionFormat(name, in, format string) *Validation {
return &Validation{
code: InvalidTypeCode,
Name: name,
In: in,
Value: format,
message: fmt.Sprintf("the collection format %q is not supported for the %s param %q", format, in, name),
}
}
// InvalidTypeName an error for when the type is invalid
func InvalidTypeName(typeName string) *Validation {
return &Validation{
code: InvalidTypeCode,
Value: typeName,
message: fmt.Sprintf(invalidType, typeName),
}
}
// InvalidType creates an error for when the type is invalid
func InvalidType(name, in, typeName string, value interface{}) *Validation {
var message string
if in != "" {
switch value.(type) {
case string:
message = fmt.Sprintf(typeFailWithData, name, in, typeName, value)
case error:
message = fmt.Sprintf(typeFailWithError, name, in, typeName, value)
default:
message = fmt.Sprintf(typeFail, name, in, typeName)
}
} else {
switch value.(type) {
case string:
message = fmt.Sprintf(typeFailWithDataNoIn, name, typeName, value)
case error:
message = fmt.Sprintf(typeFailWithErrorNoIn, name, typeName, value)
default:
message = fmt.Sprintf(typeFailNoIn, name, typeName)
}
}
return &Validation{
code: InvalidTypeCode,
Name: name,
In: in,
Value: value,
message: message,
}
}
// DuplicateItems error for when an array contains duplicates
func DuplicateItems(name, in string) *Validation {
msg := fmt.Sprintf(uniqueFail, name, in)
if in == "" {
msg = fmt.Sprintf(uniqueFailNoIn, name)
}
return &Validation{
code: UniqueFailCode,
Name: name,
In: in,
message: msg,
}
}
// TooManyItems error for when an array contains too many items
func TooManyItems(name, in string, max int64) *Validation {
msg := fmt.Sprintf(maxItemsFail, name, in, max)
if in == "" {
msg = fmt.Sprintf(maxItemsFailNoIn, name, max)
}
return &Validation{
code: MaxItemsFailCode,
Name: name,
In: in,
message: msg,
}
}
// TooFewItems error for when an array contains too few items
func TooFewItems(name, in string, min int64) *Validation {
msg := fmt.Sprintf(minItemsFail, name, in, min)
if in == "" {
msg = fmt.Sprintf(minItemsFailNoIn, name, min)
}
return &Validation{
code: MinItemsFailCode,
Name: name,
In: in,
message: msg,
}
}
// ExceedsMaximumInt error for when maxinum validation fails
func ExceedsMaximumInt(name, in string, max int64, exclusive bool) *Validation {
var message string
if in == "" {
m := maxIncFailNoIn
if exclusive {
m = maxExcFailNoIn
}
message = fmt.Sprintf(m, name, max)
} else {
m := maxIncFail
if exclusive {
m = maxExcFail
}
message = fmt.Sprintf(m, name, in, max)
}
return &Validation{
code: MaxFailCode,
Name: name,
In: in,
Value: max,
message: message,
}
}
// ExceedsMaximumUint error for when maxinum validation fails
func ExceedsMaximumUint(name, in string, max uint64, exclusive bool) *Validation {
var message string
if in == "" {
m := maxIncFailNoIn
if exclusive {
m = maxExcFailNoIn
}
message = fmt.Sprintf(m, name, max)
} else {
m := maxIncFail
if exclusive {
m = maxExcFail
}
message = fmt.Sprintf(m, name, in, max)
}
return &Validation{
code: MaxFailCode,
Name: name,
In: in,
Value: max,
message: message,
}
}
// ExceedsMaximum error for when maxinum validation fails
func ExceedsMaximum(name, in string, max float64, exclusive bool) *Validation {
var message string
if in == "" {
m := maxIncFailNoIn
if exclusive {
m = maxExcFailNoIn
}
message = fmt.Sprintf(m, name, max)
} else {
m := maxIncFail
if exclusive {
m = maxExcFail
}
message = fmt.Sprintf(m, name, in, max)
}
return &Validation{
code: MaxFailCode,
Name: name,
In: in,
Value: max,
message: message,
}
}
// ExceedsMinimumInt error for when maxinum validation fails
func ExceedsMinimumInt(name, in string, min int64, exclusive bool) *Validation {
var message string
if in == "" {
m := minIncFailNoIn
if exclusive {
m = minExcFailNoIn
}
message = fmt.Sprintf(m, name, min)
} else {
m := minIncFail
if exclusive {
m = minExcFail
}
message = fmt.Sprintf(m, name, in, min)
}
return &Validation{
code: MinFailCode,
Name: name,
In: in,
Value: min,
message: message,
}
}
// ExceedsMinimumUint error for when maxinum validation fails
func ExceedsMinimumUint(name, in string, min uint64, exclusive bool) *Validation {
var message string
if in == "" {
m := minIncFailNoIn
if exclusive {
m = minExcFailNoIn
}
message = fmt.Sprintf(m, name, min)
} else {
m := minIncFail
if exclusive {
m = minExcFail
}
message = fmt.Sprintf(m, name, in, min)
}
return &Validation{
code: MinFailCode,
Name: name,
In: in,
Value: min,
message: message,
}
}
// ExceedsMinimum error for when maxinum validation fails
func ExceedsMinimum(name, in string, min float64, exclusive bool) *Validation {
var message string
if in == "" {
m := minIncFailNoIn
if exclusive {
m = minExcFailNoIn
}
message = fmt.Sprintf(m, name, min)
} else {
m := minIncFail
if exclusive {
m = minExcFail
}
message = fmt.Sprintf(m, name, in, min)
}
return &Validation{
code: MinFailCode,
Name: name,
In: in,
Value: min,
message: message,
}
}
// NotMultipleOf error for when multiple of validation fails
func NotMultipleOf(name, in string, multiple float64) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(multipleOfFailNoIn, name, multiple)
} else {
msg = fmt.Sprintf(multipleOfFail, name, in, multiple)
}
return &Validation{
code: MultipleOfFailCode,
Name: name,
In: in,
Value: multiple,
message: msg,
}
}
// EnumFail error for when an enum validation fails
func EnumFail(name, in string, value interface{}, values []interface{}) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(enumFailNoIn, name, values)
} else {
msg = fmt.Sprintf(enumFail, name, in, values)
}
return &Validation{
code: EnumFailCode,
Name: name,
In: in,
Value: value,
Values: values,
message: msg,
}
}
// Required error for when a value is missing
func Required(name, in string) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(requiredFailNoIn, name)
} else {
msg = fmt.Sprintf(requiredFail, name, in)
}
return &Validation{
code: RequiredFailCode,
Name: name,
In: in,
message: msg,
}
}
// TooLong error for when a string is too long
func TooLong(name, in string, max int64) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(tooLongMessageNoIn, name, max)
} else {
msg = fmt.Sprintf(tooLongMessage, name, in, max)
}
return &Validation{
code: TooLongFailCode,
Name: name,
In: in,
message: msg,
}
}
// TooShort error for when a string is too short
func TooShort(name, in string, min int64) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(tooShortMessageNoIn, name, min)
} else {
msg = fmt.Sprintf(tooShortMessage, name, in, min)
}
return &Validation{
code: TooShortFailCode,
Name: name,
In: in,
message: msg,
}
}
// FailedPattern error for when a string fails a regex pattern match
// the pattern that is returned is the ECMA syntax version of the pattern not the golang version.
func FailedPattern(name, in, pattern string) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(patternFailNoIn, name, pattern)
} else {
msg = fmt.Sprintf(patternFail, name, in, pattern)
}
return &Validation{
code: PatternFailCode,
Name: name,
In: in,
message: msg,
}
}
-1
View File
@@ -1 +0,0 @@
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.xUjixvmMMeampw0Doyr_XLvcV5ICmDgDFmlcWqgmO84O3Hwn6dqbMkwOjpKMOyEJW_98b5Om5ED59IFt2S0T_OarlrsJL8jOK5fqxSMNXy2w8LfI-e5l1URverW41ofAVK8m9wK05q2BSJM_M6PyyODaQeDBiCVK1HreMZBlXpuUDVtBMPILQoga0eSZOsTR3DYEpZIS0A0Rsa5yIhMYR5d5-JMYqbqOCB7tNJ-BM83OzYgL7Hrz0J15kqaJmhQ-GJoMJDzOemSO9KxLCOfSPp11R_G3Mfd48xYnuiRuPOTakbOCLxuYviH6uoGVIOhnMyY9qKiDKbOn4BQUi1-igA.6qjQzq9nzAxRRKV_.z79R5cMFAEuEaAh6U9ykiL8oIqzMbs_I2C-hSFRh3HYRJ4fTB-9LrcbF0uASIOq7bBn4OQzW-0QFwYOs1uaawmrByGngV5d0afiZf_LBKcmTF2vtxRi_A_nxD-EHoPmh3lKBU5WNDe_8kLjEeS89HeyyFPuv5iQbqhzdqPFohHKVigwVqVYYLjB8GWQ4t7tC4c8l5rHanaXf71W0e3op2m8bebpZL0JPGhnULVA1oU27TYeLsO112JkIYtBwZxzvAs--bBFoKeGJWVMFzrKN68UACGZ9RFw0uGJbBmVC4-jRuIc6XpqeEqw3KG-rjFzkeEor3575qW-8kiXYqpub9SFUc3SSZkxJ8hB3SrnMBOuDUSenrXNpAbltmV3KAALzN3_bMBQuihwSRIn0Hg7-Dpni8BieMe44RMDvRu6p_71aeU_KW4V7Umy_h8gpIvQFuKGdTQH2ahsyCXL0ojqjMbVMdoWpDQTQ2_Fy8Qt_p2kJ8BgDo-1Akd4a6BNU2NGqsdnrJmtVKcTqLBadf9ylCwxHdGVrtNYORALSms2T6Q1s-poQnMjIwN8lnUD8ABUBpt4uVtrYkiWPVwrwywLQeiHhR-pboe_53kWDAx4Hy4rpbKsaxanYhy_bEbAYKb3aIUA.75GD4kRBCQdcGFYP1QYdCg
-39
View File
@@ -1,39 +0,0 @@
clone:
path: github.com/go-openapi/loads
matrix:
GO_VERSION:
- "1.6"
build:
integration:
image: golang:$$GO_VERSION
pull: true
environment:
GOCOVMODE: "count"
commands:
- go get -u github.com/axw/gocov/gocov
- go get -u gopkg.in/matm/v1/gocov-html
- go get -u github.com/cee-dub/go-junit-report
- 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/analysis
- go get -u github.com/go-openapi/spec
- ./hack/build-drone.sh
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
View File
@@ -1,26 +0,0 @@
# 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
-4
View File
@@ -1,4 +0,0 @@
secrets.yml
coverage.out
profile.cov
profile.out
-74
View File
@@ -1,74 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
-5
View File
@@ -1,5 +0,0 @@
# 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)
Loading of OAI specification documents from local or remote locations.
-264
View File
@@ -1,264 +0,0 @@
// 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 loads
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"path/filepath"
"github.com/go-openapi/analysis"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag"
)
// JSONDoc loads a json document from either a file or a remote url
func JSONDoc(path string) (json.RawMessage, error) {
data, err := swag.LoadFromFileOrHTTP(path)
if err != nil {
return nil, err
}
return json.RawMessage(data), nil
}
// DocLoader represents a doc loader type
type DocLoader func(string) (json.RawMessage, error)
// DocMatcher represents a predicate to check if a loader matches
type DocMatcher func(string) bool
var (
loaders *loader
defaultLoader *loader
)
func init() {
defaultLoader = &loader{Match: func(_ string) bool { return true }, Fn: JSONDoc}
loaders = defaultLoader
spec.PathLoader = loaders.Fn
AddLoader(swag.YAMLMatcher, swag.YAMLDoc)
}
// AddLoader for a document
func AddLoader(predicate DocMatcher, load DocLoader) {
prev := loaders
loaders = &loader{
Match: predicate,
Fn: load,
Next: prev,
}
spec.PathLoader = loaders.Fn
}
type loader struct {
Fn DocLoader
Match DocMatcher
Next *loader
}
// JSONSpec loads a spec from a json document
func JSONSpec(path string) (*Document, error) {
data, err := JSONDoc(path)
if err != nil {
return nil, err
}
// convert to json
return Analyzed(json.RawMessage(data), "")
}
// Document represents a swagger spec document
type Document struct {
// specAnalyzer
Analyzer *analysis.Spec
spec *spec.Swagger
specFilePath string
origSpec *spec.Swagger
schema *spec.Schema
raw json.RawMessage
}
// Spec loads a new spec document
func Spec(path string) (*Document, error) {
specURL, err := url.Parse(path)
if err != nil {
return nil, err
}
var lastErr error
for l := loaders.Next; l != nil; l = l.Next {
if loaders.Match(specURL.Path) {
b, err2 := loaders.Fn(path)
if err2 != nil {
lastErr = err2
continue
}
doc, err := Analyzed(b, "")
if err != nil {
return nil, err
}
if doc != nil {
doc.specFilePath = path
}
return doc, nil
}
}
if lastErr != nil {
return nil, lastErr
}
b, err := defaultLoader.Fn(path)
if err != nil {
return nil, err
}
document, err := Analyzed(b, "")
if document != nil {
document.specFilePath = path
}
return document, err
}
// Analyzed creates a new analyzed spec document
func Analyzed(data json.RawMessage, version string) (*Document, error) {
if version == "" {
version = "2.0"
}
if version != "2.0" {
return nil, fmt.Errorf("spec version %q is not supported", version)
}
raw := data
trimmed := bytes.TrimSpace(data)
if len(trimmed) > 0 {
if trimmed[0] != '{' && trimmed[0] != '[' {
yml, err := swag.BytesToYAMLDoc(trimmed)
if err != nil {
return nil, fmt.Errorf("analyzed: %v", err)
}
d, err := swag.YAMLToJSON(yml)
if err != nil {
return nil, fmt.Errorf("analyzed: %v", err)
}
raw = d
}
}
swspec := new(spec.Swagger)
if err := json.Unmarshal(raw, swspec); err != nil {
return nil, err
}
origsqspec := new(spec.Swagger)
if err := json.Unmarshal(raw, origsqspec); err != nil {
return nil, err
}
d := &Document{
Analyzer: analysis.New(swspec),
schema: spec.MustLoadSwagger20Schema(),
spec: swspec,
raw: raw,
origSpec: origsqspec,
}
return d, nil
}
// Expanded expands the ref fields in the spec document and returns a new spec document
func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) {
swspec := new(spec.Swagger)
if err := json.Unmarshal(d.raw, swspec); err != nil {
return nil, err
}
var expandOptions *spec.ExpandOptions
if len(options) > 0 {
expandOptions = options[1]
} else {
expandOptions = &spec.ExpandOptions{
RelativeBase: filepath.Dir(d.specFilePath),
}
}
if err := spec.ExpandSpec(swspec, expandOptions); err != nil {
return nil, err
}
dd := &Document{
Analyzer: analysis.New(swspec),
spec: swspec,
schema: spec.MustLoadSwagger20Schema(),
raw: d.raw,
origSpec: d.origSpec,
}
return dd, nil
}
// BasePath the base path for this spec
func (d *Document) BasePath() string {
return d.spec.BasePath
}
// Version returns the version of this spec
func (d *Document) Version() string {
return d.spec.Swagger
}
// Schema returns the swagger 2.0 schema
func (d *Document) Schema() *spec.Schema {
return d.schema
}
// Spec returns the swagger spec object model
func (d *Document) Spec() *spec.Swagger {
return d.spec
}
// Host returns the host for the API
func (d *Document) Host() string {
return d.spec.Host
}
// Raw returns the raw swagger spec as json bytes
func (d *Document) Raw() json.RawMessage {
return d.raw
}
func (d *Document) OrigSpec() *spec.Swagger {
return d.origSpec
}
// ResetDefinitions gives a shallow copy with the models reset
func (d *Document) ResetDefinitions() *Document {
defs := make(map[string]spec.Schema, len(d.origSpec.Definitions))
for k, v := range d.origSpec.Definitions {
defs[k] = v
}
d.spec.Definitions = defs
return d
}
// Pristine creates a new pristine document instance based on the input data
func (d *Document) Pristine() *Document {
dd, _ := Analyzed(d.Raw(), d.Version())
return dd
}
// SpecFilePath returns the file path of the spec if one is defined
func (d *Document) SpecFilePath() string {
return d.specFilePath
}
-26
View File
@@ -1,26 +0,0 @@
# 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
-2
View File
@@ -1,2 +0,0 @@
secrets.yml
coverage.out
-74
View File
@@ -1,74 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
-5
View File
@@ -1,5 +0,0 @@
# Strfmt [![Build Status](https://travis-ci.org/go-openapi/strfmt.svg?branch=master)](https://travis-ci.org/go-openapi/strfmt) [![codecov](https://codecov.io/gh/go-openapi/strfmt/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/strfmt) [![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/strfmt/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/strfmt?status.svg)](http://godoc.org/github.com/go-openapi/strfmt)
strfmt represents a well known string format such as credit card or email. The go toolkit for open api specifications knows how to deal with those.
-124
View File
@@ -1,124 +0,0 @@
// 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 strfmt
import (
"database/sql/driver"
"errors"
"fmt"
"github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
"gopkg.in/mgo.v2/bson"
)
func init() {
var id ObjectId
Default.Add("bsonobjectid", &id, IsBSONObjectID)
}
// IsBSONObjectID returns true when the string is a valid BSON.ObjectId
func IsBSONObjectID(str string) bool {
var id bson.ObjectId
return id.UnmarshalText([]byte(str)) == nil
}
type ObjectId bson.ObjectId
// NewObjectId creates a ObjectId from a Hex String
func NewObjectId(hex string) ObjectId {
return ObjectId(bson.ObjectIdHex(hex))
}
// MarshalText turns this instance into text
func (id *ObjectId) MarshalText() ([]byte, error) {
return []byte(bson.ObjectId(*id).Hex()), nil
}
// UnmarshalText hydrates this instance from text
func (id *ObjectId) UnmarshalText(data []byte) error { // validation is performed later on
var rawID bson.ObjectId
if err := rawID.UnmarshalText(data); err != nil {
return err
}
*id = ObjectId(rawID)
return nil
}
// Scan read a value from a database driver
func (id *ObjectId) Scan(raw interface{}) error {
var data []byte
switch v := raw.(type) {
case []byte:
data = v
case string:
data = []byte(v)
default:
return fmt.Errorf("cannot sql.Scan() strfmt.URI from: %#v", v)
}
return id.UnmarshalText(data)
}
// Value converts a value to a database driver value
func (id *ObjectId) Value() (driver.Value, error) {
return driver.Value(string(*id)), nil
}
func (id *ObjectId) String() string {
return string(*id)
}
func (id *ObjectId) MarshalJSON() ([]byte, error) {
var w jwriter.Writer
id.MarshalEasyJSON(&w)
return w.BuildBytes()
}
func (id *ObjectId) MarshalEasyJSON(w *jwriter.Writer) {
w.String(bson.ObjectId(*id).Hex())
}
func (id *ObjectId) UnmarshalJSON(data []byte) error {
l := jlexer.Lexer{Data: data}
id.UnmarshalEasyJSON(&l)
return l.Error()
}
func (id *ObjectId) UnmarshalEasyJSON(in *jlexer.Lexer) {
if data := in.String(); in.Ok() {
*id = NewObjectId(data)
}
}
func (id *ObjectId) GetBSON() (interface{}, error) {
return bson.M{"data": bson.ObjectId(*id).Hex()}, nil
}
func (id *ObjectId) SetBSON(raw bson.Raw) error {
var m bson.M
if err := raw.Unmarshal(&m); err != nil {
return err
}
if data, ok := m["data"].(string); ok {
*id = NewObjectId(data)
return nil
}
return errors.New("couldn't unmarshal bson raw value as ObjectId")
}
-152
View File
@@ -1,152 +0,0 @@
// 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 strfmt
import (
"database/sql/driver"
"errors"
"fmt"
"regexp"
"time"
"gopkg.in/mgo.v2/bson"
"github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
)
func init() {
d := Date{}
Default.Add("date", &d, IsDate)
}
// IsDate returns true when the string is a valid date
func IsDate(str string) bool {
matches := rxDate.FindAllStringSubmatch(str, -1)
if len(matches) == 0 || len(matches[0]) == 0 {
return false
}
m := matches[0]
return !(m[2] < "01" || m[2] > "12" || m[3] < "01" || m[3] > "31")
}
const (
// RFC3339FullDate represents a full-date as specified by RFC3339
// See: http://goo.gl/xXOvVd
RFC3339FullDate = "2006-01-02"
// DatePattern pattern to match for the date format from http://tools.ietf.org/html/rfc3339#section-5.6
DatePattern = `^([0-9]{4})-([0-9]{2})-([0-9]{2})`
)
var (
rxDate = regexp.MustCompile(DatePattern)
)
// Date represents a date from the API
//
// swagger:strfmt date
type Date time.Time
// String converts this date into a string
func (d Date) String() string {
return time.Time(d).Format(RFC3339FullDate)
}
// UnmarshalText parses a text representation into a date type
func (d *Date) UnmarshalText(text []byte) error {
if len(text) == 0 {
return nil
}
dd, err := time.Parse(RFC3339FullDate, string(text))
if err != nil {
return err
}
*d = Date(dd)
return nil
}
// MarshalText serializes this date type to string
func (d Date) MarshalText() ([]byte, error) {
return []byte(d.String()), nil
}
// Scan scans a Date value from database driver type.
func (d *Date) Scan(raw interface{}) error {
switch v := raw.(type) {
case []byte:
return d.UnmarshalText(v)
case string:
return d.UnmarshalText([]byte(v))
case time.Time:
*d = Date(v)
return nil
case nil:
*d = Date{}
return nil
default:
return fmt.Errorf("cannot sql.Scan() strfmt.Date from: %#v", v)
}
}
// Value converts Date to a primitive value ready to written to a database.
func (d Date) Value() (driver.Value, error) {
return driver.Value(d.String()), nil
}
func (t Date) MarshalJSON() ([]byte, error) {
var w jwriter.Writer
t.MarshalEasyJSON(&w)
return w.BuildBytes()
}
func (t Date) MarshalEasyJSON(w *jwriter.Writer) {
w.String(time.Time(t).Format(RFC3339FullDate))
}
func (t *Date) UnmarshalJSON(data []byte) error {
l := jlexer.Lexer{Data: data}
t.UnmarshalEasyJSON(&l)
return l.Error()
}
func (t *Date) UnmarshalEasyJSON(in *jlexer.Lexer) {
if data := in.String(); in.Ok() {
tt, err := time.Parse(RFC3339FullDate, data)
if err != nil {
in.AddError(err)
return
}
*t = Date(tt)
}
}
func (t *Date) GetBSON() (interface{}, error) {
return bson.M{"data": t.String()}, nil
}
func (t *Date) SetBSON(raw bson.Raw) error {
var m bson.M
if err := raw.Unmarshal(&m); err != nil {
return err
}
if data, ok := m["data"].(string); ok {
rd, err := time.Parse(RFC3339FullDate, data)
*t = Date(rd)
return err
}
return errors.New("couldn't unmarshal bson raw value as Duration")
}
-1697
View File
File diff suppressed because it is too large Load Diff
-194
View File
@@ -1,194 +0,0 @@
// 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 strfmt
import (
"database/sql/driver"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"gopkg.in/mgo.v2/bson"
"github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
)
func init() {
d := Duration(0)
Default.Add("duration", &d, IsDuration)
}
var (
timeUnits = [][]string{
{"ns", "nano"},
{"us", "µs", "micro"},
{"ms", "milli"},
{"s", "sec"},
{"m", "min"},
{"h", "hr", "hour"},
{"d", "day"},
{"w", "wk", "week"},
}
timeMultiplier = map[string]time.Duration{
"ns": time.Nanosecond,
"us": time.Microsecond,
"ms": time.Millisecond,
"s": time.Second,
"m": time.Minute,
"h": time.Hour,
"d": 24 * time.Hour,
"w": 7 * 24 * time.Hour,
}
durationMatcher = regexp.MustCompile(`((\d+)\s*([A-Za-zµ]+))`)
)
// IsDuration returns true if the provided string is a valid duration
func IsDuration(str string) bool {
_, err := ParseDuration(str)
return err == nil
}
// Duration represents a duration
//
// swagger:strfmt duration
type Duration time.Duration
// MarshalText turns this instance into text
func (d Duration) MarshalText() ([]byte, error) {
return []byte(time.Duration(d).String()), nil
}
// UnmarshalText hydrates this instance from text
func (d *Duration) UnmarshalText(data []byte) error { // validation is performed later on
dd, err := ParseDuration(string(data))
if err != nil {
return err
}
*d = Duration(dd)
return nil
}
// ParseDuration parses a duration from a string, compatible with scala duration syntax
func ParseDuration(cand string) (time.Duration, error) {
if dur, err := time.ParseDuration(cand); err == nil {
return dur, nil
}
var dur time.Duration
ok := false
for _, match := range durationMatcher.FindAllStringSubmatch(cand, -1) {
factor, err := strconv.Atoi(match[2]) // converts string to int
if err != nil {
return 0, err
}
unit := strings.ToLower(strings.TrimSpace(match[3]))
for _, variants := range timeUnits {
last := len(variants) - 1
multiplier := timeMultiplier[variants[0]]
for i, variant := range variants {
if (last == i && strings.HasPrefix(unit, variant)) || strings.EqualFold(variant, unit) {
ok = true
dur += (time.Duration(factor) * multiplier)
}
}
}
}
if ok {
return dur, nil
}
return 0, fmt.Errorf("Unable to parse %s as duration", cand)
}
// Scan reads a Duration value from database driver type.
func (d *Duration) Scan(raw interface{}) error {
switch v := raw.(type) {
// TODO: case []byte: // ?
case int64:
*d = Duration(v)
case float64:
*d = Duration(int64(v))
case nil:
*d = Duration(0)
default:
return fmt.Errorf("cannot sql.Scan() strfmt.Duration from: %#v", v)
}
return nil
}
// Value converts Duration to a primitive value ready to written to a database.
func (d Duration) Value() (driver.Value, error) {
return driver.Value(int64(d)), nil
}
// String converts this duration to a string
func (d Duration) String() string {
return time.Duration(d).String()
}
func (d Duration) MarshalJSON() ([]byte, error) {
var w jwriter.Writer
d.MarshalEasyJSON(&w)
return w.BuildBytes()
}
func (d Duration) MarshalEasyJSON(w *jwriter.Writer) {
w.String(time.Duration(d).String())
}
func (d *Duration) UnmarshalJSON(data []byte) error {
l := jlexer.Lexer{Data: data}
d.UnmarshalEasyJSON(&l)
return l.Error()
}
func (d *Duration) UnmarshalEasyJSON(in *jlexer.Lexer) {
if data := in.String(); in.Ok() {
tt, err := ParseDuration(data)
if err != nil {
in.AddError(err)
return
}
*d = Duration(tt)
}
}
func (d *Duration) GetBSON() (interface{}, error) {
return bson.M{"data": int64(*d)}, nil
}
func (d *Duration) SetBSON(raw bson.Raw) error {
var m bson.M
if err := raw.Unmarshal(&m); err != nil {
return err
}
if data, ok := m["data"].(int64); ok {
*d = Duration(data)
return nil
}
return errors.New("couldn't unmarshal bson raw value as Duration")
}
-298
View File
@@ -1,298 +0,0 @@
// 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 strfmt
import (
"encoding"
"reflect"
"strings"
"sync"
"time"
"github.com/go-openapi/errors"
"github.com/mitchellh/mapstructure"
)
// Default is the default formats registry
var Default = NewSeededFormats(nil, nil)
// Validator represents a validator for a string format
type Validator func(string) bool
// Format represents a string format
type Format interface {
String() string
encoding.TextMarshaler
encoding.TextUnmarshaler
}
// Registry is a registry of string formats
type Registry interface {
Add(string, Format, Validator) bool
DelByName(string) bool
GetType(string) (reflect.Type, bool)
ContainsName(string) bool
Validates(string, string) bool
Parse(string, string) (interface{}, error)
MapStructureHookFunc() mapstructure.DecodeHookFunc
}
type knownFormat struct {
Name string
OrigName string
Type reflect.Type
Validator Validator
}
// NameNormalizer is a function that normalizes a format name
type NameNormalizer func(string) string
// DefaultNameNormalizer removes all dashes
func DefaultNameNormalizer(name string) string {
return strings.Replace(name, "-", "", -1)
}
type defaultFormats struct {
sync.Mutex
data []knownFormat
normalizeName NameNormalizer
}
// NewFormats creates a new formats registry seeded with the values from the default
func NewFormats() Registry {
return NewSeededFormats(Default.(*defaultFormats).data, nil)
}
// NewSeededFormats creates a new formats registry
func NewSeededFormats(seeds []knownFormat, normalizer NameNormalizer) Registry {
if normalizer == nil {
normalizer = DefaultNameNormalizer
}
// copy here, don't modify original
d := append([]knownFormat(nil), seeds...)
return &defaultFormats{
data: d,
normalizeName: normalizer,
}
}
// MapStructureHookFunc is a decode hook function for mapstructure
func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc {
return func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
if from.Kind() != reflect.String {
return data, nil
}
for _, v := range f.data {
tpe, _ := f.GetType(v.Name)
if to == tpe {
switch v.Name {
case "date":
d, err := time.Parse(RFC3339FullDate, data.(string))
if err != nil {
return nil, err
}
return Date(d), nil
case "datetime":
return ParseDateTime(data.(string))
case "duration":
dur, err := ParseDuration(data.(string))
if err != nil {
return nil, err
}
return Duration(dur), nil
case "uri":
return URI(data.(string)), nil
case "email":
return Email(data.(string)), nil
case "uuid":
return UUID(data.(string)), nil
case "uuid3":
return UUID3(data.(string)), nil
case "uuid4":
return UUID4(data.(string)), nil
case "uuid5":
return UUID5(data.(string)), nil
case "hostname":
return Hostname(data.(string)), nil
case "ipv4":
return IPv4(data.(string)), nil
case "ipv6":
return IPv6(data.(string)), nil
case "mac":
return MAC(data.(string)), nil
case "isbn":
return ISBN(data.(string)), nil
case "isbn10":
return ISBN10(data.(string)), nil
case "isbn13":
return ISBN13(data.(string)), nil
case "creditcard":
return CreditCard(data.(string)), nil
case "ssn":
return SSN(data.(string)), nil
case "hexcolor":
return HexColor(data.(string)), nil
case "rgbcolor":
return RGBColor(data.(string)), nil
case "byte":
return Base64(data.(string)), nil
case "password":
return Password(data.(string)), nil
default:
return nil, errors.InvalidTypeName(v.Name)
}
}
}
return data, nil
}
}
// Add adds a new format, return true if this was a new item instead of a replacement
func (f *defaultFormats) Add(name string, strfmt Format, validator Validator) bool {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
tpe := reflect.TypeOf(strfmt)
if tpe.Kind() == reflect.Ptr {
tpe = tpe.Elem()
}
for i := range f.data {
v := &f.data[i]
if v.Name == nme {
v.Type = tpe
v.Validator = validator
return false
}
}
// turns out it's new after all
f.data = append(f.data, knownFormat{Name: nme, OrigName: name, Type: tpe, Validator: validator})
return true
}
// GetType gets the type for the specified name
func (f *defaultFormats) GetType(name string) (reflect.Type, bool) {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for _, v := range f.data {
if v.Name == nme {
return v.Type, true
}
}
return nil, false
}
// DelByName removes the format by the specified name, returns true when an item was actually removed
func (f *defaultFormats) DelByName(name string) bool {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for i, v := range f.data {
if v.Name == nme {
f.data[i] = knownFormat{} // release
f.data = append(f.data[:i], f.data[i+1:]...)
return true
}
}
return false
}
// DelByType removes the specified format, returns true when an item was actually removed
func (f *defaultFormats) DelByFormat(strfmt Format) bool {
f.Lock()
defer f.Unlock()
tpe := reflect.TypeOf(strfmt)
if tpe.Kind() == reflect.Ptr {
tpe = tpe.Elem()
}
for i, v := range f.data {
if v.Type == tpe {
f.data[i] = knownFormat{} // release
f.data = append(f.data[:i], f.data[i+1:]...)
return true
}
}
return false
}
// ContainsName returns true if this registry contains the specified name
func (f *defaultFormats) ContainsName(name string) bool {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for _, v := range f.data {
if v.Name == nme {
return true
}
}
return false
}
// ContainsFormat returns true if this registry contains the specified format
func (f *defaultFormats) ContainsFormat(strfmt Format) bool {
f.Lock()
defer f.Unlock()
tpe := reflect.TypeOf(strfmt)
if tpe.Kind() == reflect.Ptr {
tpe = tpe.Elem()
}
for _, v := range f.data {
if v.Type == tpe {
return true
}
}
return false
}
func (f *defaultFormats) Validates(name, data string) bool {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for _, v := range f.data {
if v.Name == nme {
return v.Validator(data)
}
}
return false
}
func (f *defaultFormats) Parse(name, data string) (interface{}, error) {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for _, v := range f.data {
if v.Name == nme {
nw := reflect.New(v.Type).Interface()
if dec, ok := nw.(encoding.TextUnmarshaler); ok {
if err := dec.UnmarshalText([]byte(data)); err != nil {
return nil, err
}
return nw, nil
}
return nil, errors.InvalidTypeName(name)
}
}
return nil, errors.InvalidTypeName(name)
}
-185
View File
@@ -1,185 +0,0 @@
// 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 strfmt
import (
"database/sql/driver"
"errors"
"fmt"
"regexp"
"strings"
"time"
"gopkg.in/mgo.v2/bson"
"github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
)
func init() {
dt := DateTime{}
Default.Add("datetime", &dt, IsDateTime)
}
// IsDateTime returns true when the string is a valid date-time
func IsDateTime(str string) bool {
if len(str) < 4 {
return false
}
s := strings.Split(strings.ToLower(str), "t")
if len(s) < 2 || !IsDate(s[0]) {
return false
}
matches := rxDateTime.FindAllStringSubmatch(s[1], -1)
if len(matches) == 0 || len(matches[0]) == 0 {
return false
}
m := matches[0]
res := m[1] <= "23" && m[2] <= "59" && m[3] <= "59"
return res
}
const (
// RFC3339Millis represents a ISO8601 format to millis instead of to nanos
RFC3339Millis = "2006-01-02T15:04:05.000Z07:00"
// DateTimePattern pattern to match for the date-time format from http://tools.ietf.org/html/rfc3339#section-5.6
DateTimePattern = `^([0-9]{2}):([0-9]{2}):([0-9]{2})(.[0-9]+)?(z|([+-][0-9]{2}:[0-9]{2}))$`
)
var (
dateTimeFormats = []string{RFC3339Millis, time.RFC3339, time.RFC3339Nano}
rxDateTime = regexp.MustCompile(DateTimePattern)
)
// ParseDateTime parses a string that represents an ISO8601 time or a unix epoch
func ParseDateTime(data string) (DateTime, error) {
if data == "" {
return NewDateTime(), nil
}
var lastError error
for _, layout := range dateTimeFormats {
dd, err := time.Parse(layout, data)
if err != nil {
lastError = err
continue
}
lastError = nil
return DateTime(dd), nil
}
return DateTime{}, lastError
}
// DateTime is a time but it serializes to ISO8601 format with millis
// It knows how to read 3 different variations of a RFC3339 date time.
// Most APIs we encounter want either millisecond or second precision times.
// This just tries to make it worry-free.
//
// swagger:strfmt date-time
type DateTime time.Time
// NewDateTime is a representation of zero value for DateTime type
func NewDateTime() DateTime {
return DateTime(time.Unix(0, 0).UTC())
}
func (t DateTime) String() string {
return time.Time(t).Format(RFC3339Millis)
}
// MarshalText implements the text marshaller interface
func (t DateTime) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
// UnmarshalText implements the text unmarshaller interface
func (t *DateTime) UnmarshalText(text []byte) error {
tt, err := ParseDateTime(string(text))
if err != nil {
return err
}
*t = tt
return nil
}
// Scan scans a DateTime value from database driver type.
func (t *DateTime) Scan(raw interface{}) error {
// TODO: case int64: and case float64: ?
switch v := raw.(type) {
case []byte:
return t.UnmarshalText(v)
case string:
return t.UnmarshalText([]byte(v))
case time.Time:
*t = DateTime(v)
case nil:
*t = DateTime{}
default:
return fmt.Errorf("cannot sql.Scan() strfmt.DateTime from: %#v", v)
}
return nil
}
// Value converts DateTime to a primitive value ready to written to a database.
func (t DateTime) Value() (driver.Value, error) {
return driver.Value(t.String()), nil
}
func (t DateTime) MarshalJSON() ([]byte, error) {
var w jwriter.Writer
t.MarshalEasyJSON(&w)
return w.BuildBytes()
}
func (t DateTime) MarshalEasyJSON(w *jwriter.Writer) {
w.String(time.Time(t).Format(RFC3339Millis))
}
func (t *DateTime) UnmarshalJSON(data []byte) error {
l := jlexer.Lexer{Data: data}
t.UnmarshalEasyJSON(&l)
return l.Error()
}
func (t *DateTime) UnmarshalEasyJSON(in *jlexer.Lexer) {
if data := in.String(); in.Ok() {
tt, err := ParseDateTime(data)
if err != nil {
in.AddError(err)
return
}
*t = tt
}
}
func (t *DateTime) GetBSON() (interface{}, error) {
return bson.M{"data": t.String()}, nil
}
func (t *DateTime) SetBSON(raw bson.Raw) error {
var m bson.M
if err := raw.Unmarshal(&m); err != nil {
return err
}
if data, ok := m["data"].(string); ok {
var err error
*t, err = ParseDateTime(data)
return err
}
return errors.New("couldn't unmarshal bson raw value as Duration")
}
+144
View File
@@ -0,0 +1,144 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: google/protobuf/duration.proto
/*
Package duration is a generated protocol buffer package.
It is generated from these files:
google/protobuf/duration.proto
It has these top-level messages:
Duration
*/
package duration
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// A Duration represents a signed, fixed-length span of time represented
// as a count of seconds and fractions of seconds at nanosecond
// resolution. It is independent of any calendar and concepts like "day"
// or "month". It is related to Timestamp in that the difference between
// two Timestamp values is a Duration and it can be added or subtracted
// from a Timestamp. Range is approximately +-10,000 years.
//
// # Examples
//
// Example 1: Compute Duration from two Timestamps in pseudo code.
//
// Timestamp start = ...;
// Timestamp end = ...;
// Duration duration = ...;
//
// duration.seconds = end.seconds - start.seconds;
// duration.nanos = end.nanos - start.nanos;
//
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
//
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
//
// Timestamp start = ...;
// Duration duration = ...;
// Timestamp end = ...;
//
// end.seconds = start.seconds + duration.seconds;
// end.nanos = start.nanos + duration.nanos;
//
// if (end.nanos < 0) {
// end.seconds -= 1;
// end.nanos += 1000000000;
// } else if (end.nanos >= 1000000000) {
// end.seconds += 1;
// end.nanos -= 1000000000;
// }
//
// Example 3: Compute Duration from datetime.timedelta in Python.
//
// td = datetime.timedelta(days=3, minutes=10)
// duration = Duration()
// duration.FromTimedelta(td)
//
// # JSON Mapping
//
// In JSON format, the Duration type is encoded as a string rather than an
// object, where the string ends in the suffix "s" (indicating seconds) and
// is preceded by the number of seconds, with nanoseconds expressed as
// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
// microsecond should be expressed in JSON format as "3.000001s".
//
//
type Duration struct {
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"`
// Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"`
}
func (m *Duration) Reset() { *m = Duration{} }
func (m *Duration) String() string { return proto.CompactTextString(m) }
func (*Duration) ProtoMessage() {}
func (*Duration) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (*Duration) XXX_WellKnownType() string { return "Duration" }
func (m *Duration) GetSeconds() int64 {
if m != nil {
return m.Seconds
}
return 0
}
func (m *Duration) GetNanos() int32 {
if m != nil {
return m.Nanos
}
return 0
}
func init() {
proto.RegisterType((*Duration)(nil), "google.protobuf.Duration")
}
func init() { proto.RegisterFile("google/protobuf/duration.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 190 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0xcf, 0xcf, 0x4f,
0xcf, 0x49, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x4f, 0x29, 0x2d, 0x4a,
0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x03, 0x8b, 0x08, 0xf1, 0x43, 0xe4, 0xf5, 0x60, 0xf2, 0x4a, 0x56,
0x5c, 0x1c, 0x2e, 0x50, 0x25, 0x42, 0x12, 0x5c, 0xec, 0xc5, 0xa9, 0xc9, 0xf9, 0x79, 0x29, 0xc5,
0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xcc, 0x41, 0x30, 0xae, 0x90, 0x08, 0x17, 0x6b, 0x5e, 0x62, 0x5e,
0x7e, 0xb1, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x6b, 0x10, 0x84, 0xe3, 0x54, 0xc3, 0x25, 0x9c, 0x9c,
0x9f, 0xab, 0x87, 0x66, 0xa4, 0x13, 0x2f, 0xcc, 0xc0, 0x00, 0x90, 0x48, 0x00, 0x63, 0x94, 0x56,
0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x7a, 0x7e, 0x4e, 0x62, 0x5e,
0x3a, 0xc2, 0x7d, 0x05, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x70, 0x67, 0xfe, 0x60, 0x64, 0x5c, 0xc4,
0xc4, 0xec, 0x1e, 0xe0, 0xb4, 0x8a, 0x49, 0xce, 0x1d, 0x62, 0x6e, 0x00, 0x54, 0xa9, 0x5e, 0x78,
0x6a, 0x4e, 0x8e, 0x77, 0x5e, 0x7e, 0x79, 0x5e, 0x08, 0x48, 0x4b, 0x12, 0x1b, 0xd8, 0x0c, 0x63,
0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x84, 0x30, 0xff, 0xf3, 0x00, 0x00, 0x00,
}
+117
View File
@@ -0,0 +1,117 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option go_package = "github.com/golang/protobuf/ptypes/duration";
option java_package = "com.google.protobuf";
option java_outer_classname = "DurationProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// A Duration represents a signed, fixed-length span of time represented
// as a count of seconds and fractions of seconds at nanosecond
// resolution. It is independent of any calendar and concepts like "day"
// or "month". It is related to Timestamp in that the difference between
// two Timestamp values is a Duration and it can be added or subtracted
// from a Timestamp. Range is approximately +-10,000 years.
//
// # Examples
//
// Example 1: Compute Duration from two Timestamps in pseudo code.
//
// Timestamp start = ...;
// Timestamp end = ...;
// Duration duration = ...;
//
// duration.seconds = end.seconds - start.seconds;
// duration.nanos = end.nanos - start.nanos;
//
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
//
// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
//
// Timestamp start = ...;
// Duration duration = ...;
// Timestamp end = ...;
//
// end.seconds = start.seconds + duration.seconds;
// end.nanos = start.nanos + duration.nanos;
//
// if (end.nanos < 0) {
// end.seconds -= 1;
// end.nanos += 1000000000;
// } else if (end.nanos >= 1000000000) {
// end.seconds += 1;
// end.nanos -= 1000000000;
// }
//
// Example 3: Compute Duration from datetime.timedelta in Python.
//
// td = datetime.timedelta(days=3, minutes=10)
// duration = Duration()
// duration.FromTimedelta(td)
//
// # JSON Mapping
//
// In JSON format, the Duration type is encoded as a string rather than an
// object, where the string ends in the suffix "s" (indicating seconds) and
// is preceded by the number of seconds, with nanoseconds expressed as
// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
// microsecond should be expressed in JSON format as "3.000001s".
//
//
message Duration {
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
int64 seconds = 1;
// Signed fractions of a second at nanosecond resolution of the span
// of time. Durations less than one second are represented with a 0
// `seconds` field and a positive or negative `nanos` field. For durations
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
int32 nanos = 2;
}
+160
View File
@@ -0,0 +1,160 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: google/protobuf/timestamp.proto
/*
Package timestamp is a generated protocol buffer package.
It is generated from these files:
google/protobuf/timestamp.proto
It has these top-level messages:
Timestamp
*/
package timestamp
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// A Timestamp represents a point in time independent of any time zone
// or calendar, represented as seconds and fractions of seconds at
// nanosecond resolution in UTC Epoch time. It is encoded using the
// Proleptic Gregorian Calendar which extends the Gregorian calendar
// backwards to year one. It is encoded assuming all minutes are 60
// seconds long, i.e. leap seconds are "smeared" so that no leap second
// table is needed for interpretation. Range is from
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.
// By restricting to that range, we ensure that we can convert to
// and from RFC 3339 date strings.
// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt).
//
// # Examples
//
// Example 1: Compute Timestamp from POSIX `time()`.
//
// Timestamp timestamp;
// timestamp.set_seconds(time(NULL));
// timestamp.set_nanos(0);
//
// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
//
// struct timeval tv;
// gettimeofday(&tv, NULL);
//
// Timestamp timestamp;
// timestamp.set_seconds(tv.tv_sec);
// timestamp.set_nanos(tv.tv_usec * 1000);
//
// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
//
// FILETIME ft;
// GetSystemTimeAsFileTime(&ft);
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
//
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
// Timestamp timestamp;
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
//
// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
//
// long millis = System.currentTimeMillis();
//
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
// .setNanos((int) ((millis % 1000) * 1000000)).build();
//
//
// Example 5: Compute Timestamp from current time in Python.
//
// timestamp = Timestamp()
// timestamp.GetCurrentTime()
//
// # JSON Mapping
//
// In JSON format, the Timestamp type is encoded as a string in the
// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
// where {year} is always expressed using four digits while {month}, {day},
// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
// is required, though only UTC (as indicated by "Z") is presently supported.
//
// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
// 01:30 UTC on January 15, 2017.
//
// In JavaScript, one can convert a Date object to this format using the
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString]
// method. In Python, a standard `datetime.datetime` object can be converted
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime())
// to obtain a formatter capable of generating timestamps in this format.
//
//
type Timestamp struct {
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"`
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"`
}
func (m *Timestamp) Reset() { *m = Timestamp{} }
func (m *Timestamp) String() string { return proto.CompactTextString(m) }
func (*Timestamp) ProtoMessage() {}
func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (*Timestamp) XXX_WellKnownType() string { return "Timestamp" }
func (m *Timestamp) GetSeconds() int64 {
if m != nil {
return m.Seconds
}
return 0
}
func (m *Timestamp) GetNanos() int32 {
if m != nil {
return m.Nanos
}
return 0
}
func init() {
proto.RegisterType((*Timestamp)(nil), "google.protobuf.Timestamp")
}
func init() { proto.RegisterFile("google/protobuf/timestamp.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 191 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0xcf, 0xcf, 0x4f,
0xcf, 0x49, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x2f, 0xc9, 0xcc, 0x4d,
0x2d, 0x2e, 0x49, 0xcc, 0x2d, 0xd0, 0x03, 0x0b, 0x09, 0xf1, 0x43, 0x14, 0xe8, 0xc1, 0x14, 0x28,
0x59, 0x73, 0x71, 0x86, 0xc0, 0xd4, 0x08, 0x49, 0x70, 0xb1, 0x17, 0xa7, 0x26, 0xe7, 0xe7, 0xa5,
0x14, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x30, 0x07, 0xc1, 0xb8, 0x42, 0x22, 0x5c, 0xac, 0x79, 0x89,
0x79, 0xf9, 0xc5, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xac, 0x41, 0x10, 0x8e, 0x53, 0x1d, 0x97, 0x70,
0x72, 0x7e, 0xae, 0x1e, 0x9a, 0x99, 0x4e, 0x7c, 0x70, 0x13, 0x03, 0x40, 0x42, 0x01, 0x8c, 0x51,
0xda, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0xe9, 0xf9, 0x39, 0x89,
0x79, 0xe9, 0x08, 0x27, 0x16, 0x94, 0x54, 0x16, 0xa4, 0x16, 0x23, 0x5c, 0xfa, 0x83, 0x91, 0x71,
0x11, 0x13, 0xb3, 0x7b, 0x80, 0xd3, 0x2a, 0x26, 0x39, 0x77, 0x88, 0xc9, 0x01, 0x50, 0xb5, 0x7a,
0xe1, 0xa9, 0x39, 0x39, 0xde, 0x79, 0xf9, 0xe5, 0x79, 0x21, 0x20, 0x3d, 0x49, 0x6c, 0x60, 0x43,
0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x77, 0x4a, 0x07, 0xf7, 0x00, 0x00, 0x00,
}
+133
View File
@@ -0,0 +1,133 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option go_package = "github.com/golang/protobuf/ptypes/timestamp";
option java_package = "com.google.protobuf";
option java_outer_classname = "TimestampProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// A Timestamp represents a point in time independent of any time zone
// or calendar, represented as seconds and fractions of seconds at
// nanosecond resolution in UTC Epoch time. It is encoded using the
// Proleptic Gregorian Calendar which extends the Gregorian calendar
// backwards to year one. It is encoded assuming all minutes are 60
// seconds long, i.e. leap seconds are "smeared" so that no leap second
// table is needed for interpretation. Range is from
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.
// By restricting to that range, we ensure that we can convert to
// and from RFC 3339 date strings.
// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt).
//
// # Examples
//
// Example 1: Compute Timestamp from POSIX `time()`.
//
// Timestamp timestamp;
// timestamp.set_seconds(time(NULL));
// timestamp.set_nanos(0);
//
// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
//
// struct timeval tv;
// gettimeofday(&tv, NULL);
//
// Timestamp timestamp;
// timestamp.set_seconds(tv.tv_sec);
// timestamp.set_nanos(tv.tv_usec * 1000);
//
// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
//
// FILETIME ft;
// GetSystemTimeAsFileTime(&ft);
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
//
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
// Timestamp timestamp;
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
//
// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
//
// long millis = System.currentTimeMillis();
//
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
// .setNanos((int) ((millis % 1000) * 1000000)).build();
//
//
// Example 5: Compute Timestamp from current time in Python.
//
// timestamp = Timestamp()
// timestamp.GetCurrentTime()
//
// # JSON Mapping
//
// In JSON format, the Timestamp type is encoded as a string in the
// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
// where {year} is always expressed using four digits while {month}, {day},
// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
// is required, though only UTC (as indicated by "Z") is presently supported.
//
// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
// 01:30 UTC on January 15, 2017.
//
// In JavaScript, one can convert a Date object to this format using the
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString]
// method. In Python, a standard `datetime.datetime` object can be converted
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime())
// to obtain a formatter capable of generating timestamps in this format.
//
//
message Timestamp {
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
int64 seconds = 1;
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// inclusive.
int32 nanos = 2;
}
+12
View File
@@ -0,0 +1,12 @@
# BTree implementation for Go
![Travis CI Build Status](https://api.travis-ci.org/google/btree.svg?branch=master)
This package provides an in-memory B-Tree implementation for Go, useful as
an ordered, mutable data structure.
The API is based off of the wonderful
http://godoc.org/github.com/petar/GoLLRB/llrb, and is meant to allow btree to
act as a drop-in replacement for gollrb trees.
See http://godoc.org/github.com/google/btree for documentation.
+821
View File
@@ -0,0 +1,821 @@
// Copyright 2014 Google Inc.
//
// 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 btree implements in-memory B-Trees of arbitrary degree.
//
// btree implements an in-memory B-Tree for use as an ordered data structure.
// It is not meant for persistent storage solutions.
//
// It has a flatter structure than an equivalent red-black or other binary tree,
// which in some cases yields better memory usage and/or performance.
// See some discussion on the matter here:
// http://google-opensource.blogspot.com/2013/01/c-containers-that-save-memory-and-time.html
// Note, though, that this project is in no way related to the C++ B-Tree
// implementation written about there.
//
// Within this tree, each node contains a slice of items and a (possibly nil)
// slice of children. For basic numeric values or raw structs, this can cause
// efficiency differences when compared to equivalent C++ template code that
// stores values in arrays within the node:
// * Due to the overhead of storing values as interfaces (each
// value needs to be stored as the value itself, then 2 words for the
// interface pointing to that value and its type), resulting in higher
// memory use.
// * Since interfaces can point to values anywhere in memory, values are
// most likely not stored in contiguous blocks, resulting in a higher
// number of cache misses.
// These issues don't tend to matter, though, when working with strings or other
// heap-allocated structures, since C++-equivalent structures also must store
// pointers and also distribute their values across the heap.
//
// This implementation is designed to be a drop-in replacement to gollrb.LLRB
// trees, (http://github.com/petar/gollrb), an excellent and probably the most
// widely used ordered tree implementation in the Go ecosystem currently.
// Its functions, therefore, exactly mirror those of
// llrb.LLRB where possible. Unlike gollrb, though, we currently don't
// support storing multiple equivalent values.
package btree
import (
"fmt"
"io"
"sort"
"strings"
"sync"
)
// Item represents a single object in the tree.
type Item interface {
// Less tests whether the current item is less than the given argument.
//
// This must provide a strict weak ordering.
// If !a.Less(b) && !b.Less(a), we treat this to mean a == b (i.e. we can only
// hold one of either a or b in the tree).
Less(than Item) bool
}
const (
DefaultFreeListSize = 32
)
var (
nilItems = make(items, 16)
nilChildren = make(children, 16)
)
// FreeList represents a free list of btree nodes. By default each
// BTree has its own FreeList, but multiple BTrees can share the same
// FreeList.
// Two Btrees using the same freelist are safe for concurrent write access.
type FreeList struct {
mu sync.Mutex
freelist []*node
}
// NewFreeList creates a new free list.
// size is the maximum size of the returned free list.
func NewFreeList(size int) *FreeList {
return &FreeList{freelist: make([]*node, 0, size)}
}
func (f *FreeList) newNode() (n *node) {
f.mu.Lock()
index := len(f.freelist) - 1
if index < 0 {
f.mu.Unlock()
return new(node)
}
n = f.freelist[index]
f.freelist[index] = nil
f.freelist = f.freelist[:index]
f.mu.Unlock()
return
}
func (f *FreeList) freeNode(n *node) {
f.mu.Lock()
if len(f.freelist) < cap(f.freelist) {
f.freelist = append(f.freelist, n)
}
f.mu.Unlock()
}
// ItemIterator allows callers of Ascend* to iterate in-order over portions of
// the tree. When this function returns false, iteration will stop and the
// associated Ascend* function will immediately return.
type ItemIterator func(i Item) bool
// New creates a new B-Tree with the given degree.
//
// New(2), for example, will create a 2-3-4 tree (each node contains 1-3 items
// and 2-4 children).
func New(degree int) *BTree {
return NewWithFreeList(degree, NewFreeList(DefaultFreeListSize))
}
// NewWithFreeList creates a new B-Tree that uses the given node free list.
func NewWithFreeList(degree int, f *FreeList) *BTree {
if degree <= 1 {
panic("bad degree")
}
return &BTree{
degree: degree,
cow: &copyOnWriteContext{freelist: f},
}
}
// items stores items in a node.
type items []Item
// insertAt inserts a value into the given index, pushing all subsequent values
// forward.
func (s *items) insertAt(index int, item Item) {
*s = append(*s, nil)
if index < len(*s) {
copy((*s)[index+1:], (*s)[index:])
}
(*s)[index] = item
}
// removeAt removes a value at a given index, pulling all subsequent values
// back.
func (s *items) removeAt(index int) Item {
item := (*s)[index]
copy((*s)[index:], (*s)[index+1:])
(*s)[len(*s)-1] = nil
*s = (*s)[:len(*s)-1]
return item
}
// pop removes and returns the last element in the list.
func (s *items) pop() (out Item) {
index := len(*s) - 1
out = (*s)[index]
(*s)[index] = nil
*s = (*s)[:index]
return
}
// truncate truncates this instance at index so that it contains only the
// first index items. index must be less than or equal to length.
func (s *items) truncate(index int) {
var toClear items
*s, toClear = (*s)[:index], (*s)[index:]
for len(toClear) > 0 {
toClear = toClear[copy(toClear, nilItems):]
}
}
// find returns the index where the given item should be inserted into this
// list. 'found' is true if the item already exists in the list at the given
// index.
func (s items) find(item Item) (index int, found bool) {
i := sort.Search(len(s), func(i int) bool {
return item.Less(s[i])
})
if i > 0 && !s[i-1].Less(item) {
return i - 1, true
}
return i, false
}
// children stores child nodes in a node.
type children []*node
// insertAt inserts a value into the given index, pushing all subsequent values
// forward.
func (s *children) insertAt(index int, n *node) {
*s = append(*s, nil)
if index < len(*s) {
copy((*s)[index+1:], (*s)[index:])
}
(*s)[index] = n
}
// removeAt removes a value at a given index, pulling all subsequent values
// back.
func (s *children) removeAt(index int) *node {
n := (*s)[index]
copy((*s)[index:], (*s)[index+1:])
(*s)[len(*s)-1] = nil
*s = (*s)[:len(*s)-1]
return n
}
// pop removes and returns the last element in the list.
func (s *children) pop() (out *node) {
index := len(*s) - 1
out = (*s)[index]
(*s)[index] = nil
*s = (*s)[:index]
return
}
// truncate truncates this instance at index so that it contains only the
// first index children. index must be less than or equal to length.
func (s *children) truncate(index int) {
var toClear children
*s, toClear = (*s)[:index], (*s)[index:]
for len(toClear) > 0 {
toClear = toClear[copy(toClear, nilChildren):]
}
}
// node is an internal node in a tree.
//
// It must at all times maintain the invariant that either
// * len(children) == 0, len(items) unconstrained
// * len(children) == len(items) + 1
type node struct {
items items
children children
cow *copyOnWriteContext
}
func (n *node) mutableFor(cow *copyOnWriteContext) *node {
if n.cow == cow {
return n
}
out := cow.newNode()
if cap(out.items) >= len(n.items) {
out.items = out.items[:len(n.items)]
} else {
out.items = make(items, len(n.items), cap(n.items))
}
copy(out.items, n.items)
// Copy children
if cap(out.children) >= len(n.children) {
out.children = out.children[:len(n.children)]
} else {
out.children = make(children, len(n.children), cap(n.children))
}
copy(out.children, n.children)
return out
}
func (n *node) mutableChild(i int) *node {
c := n.children[i].mutableFor(n.cow)
n.children[i] = c
return c
}
// split splits the given node at the given index. The current node shrinks,
// and this function returns the item that existed at that index and a new node
// containing all items/children after it.
func (n *node) split(i int) (Item, *node) {
item := n.items[i]
next := n.cow.newNode()
next.items = append(next.items, n.items[i+1:]...)
n.items.truncate(i)
if len(n.children) > 0 {
next.children = append(next.children, n.children[i+1:]...)
n.children.truncate(i + 1)
}
return item, next
}
// maybeSplitChild checks if a child should be split, and if so splits it.
// Returns whether or not a split occurred.
func (n *node) maybeSplitChild(i, maxItems int) bool {
if len(n.children[i].items) < maxItems {
return false
}
first := n.mutableChild(i)
item, second := first.split(maxItems / 2)
n.items.insertAt(i, item)
n.children.insertAt(i+1, second)
return true
}
// insert inserts an item into the subtree rooted at this node, making sure
// no nodes in the subtree exceed maxItems items. Should an equivalent item be
// be found/replaced by insert, it will be returned.
func (n *node) insert(item Item, maxItems int) Item {
i, found := n.items.find(item)
if found {
out := n.items[i]
n.items[i] = item
return out
}
if len(n.children) == 0 {
n.items.insertAt(i, item)
return nil
}
if n.maybeSplitChild(i, maxItems) {
inTree := n.items[i]
switch {
case item.Less(inTree):
// no change, we want first split node
case inTree.Less(item):
i++ // we want second split node
default:
out := n.items[i]
n.items[i] = item
return out
}
}
return n.mutableChild(i).insert(item, maxItems)
}
// get finds the given key in the subtree and returns it.
func (n *node) get(key Item) Item {
i, found := n.items.find(key)
if found {
return n.items[i]
} else if len(n.children) > 0 {
return n.children[i].get(key)
}
return nil
}
// min returns the first item in the subtree.
func min(n *node) Item {
if n == nil {
return nil
}
for len(n.children) > 0 {
n = n.children[0]
}
if len(n.items) == 0 {
return nil
}
return n.items[0]
}
// max returns the last item in the subtree.
func max(n *node) Item {
if n == nil {
return nil
}
for len(n.children) > 0 {
n = n.children[len(n.children)-1]
}
if len(n.items) == 0 {
return nil
}
return n.items[len(n.items)-1]
}
// toRemove details what item to remove in a node.remove call.
type toRemove int
const (
removeItem toRemove = iota // removes the given item
removeMin // removes smallest item in the subtree
removeMax // removes largest item in the subtree
)
// remove removes an item from the subtree rooted at this node.
func (n *node) remove(item Item, minItems int, typ toRemove) Item {
var i int
var found bool
switch typ {
case removeMax:
if len(n.children) == 0 {
return n.items.pop()
}
i = len(n.items)
case removeMin:
if len(n.children) == 0 {
return n.items.removeAt(0)
}
i = 0
case removeItem:
i, found = n.items.find(item)
if len(n.children) == 0 {
if found {
return n.items.removeAt(i)
}
return nil
}
default:
panic("invalid type")
}
// If we get to here, we have children.
if len(n.children[i].items) <= minItems {
return n.growChildAndRemove(i, item, minItems, typ)
}
child := n.mutableChild(i)
// Either we had enough items to begin with, or we've done some
// merging/stealing, because we've got enough now and we're ready to return
// stuff.
if found {
// The item exists at index 'i', and the child we've selected can give us a
// predecessor, since if we've gotten here it's got > minItems items in it.
out := n.items[i]
// We use our special-case 'remove' call with typ=maxItem to pull the
// predecessor of item i (the rightmost leaf of our immediate left child)
// and set it into where we pulled the item from.
n.items[i] = child.remove(nil, minItems, removeMax)
return out
}
// Final recursive call. Once we're here, we know that the item isn't in this
// node and that the child is big enough to remove from.
return child.remove(item, minItems, typ)
}
// growChildAndRemove grows child 'i' to make sure it's possible to remove an
// item from it while keeping it at minItems, then calls remove to actually
// remove it.
//
// Most documentation says we have to do two sets of special casing:
// 1) item is in this node
// 2) item is in child
// In both cases, we need to handle the two subcases:
// A) node has enough values that it can spare one
// B) node doesn't have enough values
// For the latter, we have to check:
// a) left sibling has node to spare
// b) right sibling has node to spare
// c) we must merge
// To simplify our code here, we handle cases #1 and #2 the same:
// If a node doesn't have enough items, we make sure it does (using a,b,c).
// We then simply redo our remove call, and the second time (regardless of
// whether we're in case 1 or 2), we'll have enough items and can guarantee
// that we hit case A.
func (n *node) growChildAndRemove(i int, item Item, minItems int, typ toRemove) Item {
if i > 0 && len(n.children[i-1].items) > minItems {
// Steal from left child
child := n.mutableChild(i)
stealFrom := n.mutableChild(i - 1)
stolenItem := stealFrom.items.pop()
child.items.insertAt(0, n.items[i-1])
n.items[i-1] = stolenItem
if len(stealFrom.children) > 0 {
child.children.insertAt(0, stealFrom.children.pop())
}
} else if i < len(n.items) && len(n.children[i+1].items) > minItems {
// steal from right child
child := n.mutableChild(i)
stealFrom := n.mutableChild(i + 1)
stolenItem := stealFrom.items.removeAt(0)
child.items = append(child.items, n.items[i])
n.items[i] = stolenItem
if len(stealFrom.children) > 0 {
child.children = append(child.children, stealFrom.children.removeAt(0))
}
} else {
if i >= len(n.items) {
i--
}
child := n.mutableChild(i)
// merge with right child
mergeItem := n.items.removeAt(i)
mergeChild := n.children.removeAt(i + 1)
child.items = append(child.items, mergeItem)
child.items = append(child.items, mergeChild.items...)
child.children = append(child.children, mergeChild.children...)
n.cow.freeNode(mergeChild)
}
return n.remove(item, minItems, typ)
}
type direction int
const (
descend = direction(-1)
ascend = direction(+1)
)
// iterate provides a simple method for iterating over elements in the tree.
//
// When ascending, the 'start' should be less than 'stop' and when descending,
// the 'start' should be greater than 'stop'. Setting 'includeStart' to true
// will force the iterator to include the first item when it equals 'start',
// thus creating a "greaterOrEqual" or "lessThanEqual" rather than just a
// "greaterThan" or "lessThan" queries.
func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit bool, iter ItemIterator) (bool, bool) {
var ok bool
switch dir {
case ascend:
for i := 0; i < len(n.items); i++ {
if start != nil && n.items[i].Less(start) {
continue
}
if len(n.children) > 0 {
if hit, ok = n.children[i].iterate(dir, start, stop, includeStart, hit, iter); !ok {
return hit, false
}
}
if !includeStart && !hit && start != nil && !start.Less(n.items[i]) {
hit = true
continue
}
hit = true
if stop != nil && !n.items[i].Less(stop) {
return hit, false
}
if !iter(n.items[i]) {
return hit, false
}
}
if len(n.children) > 0 {
if hit, ok = n.children[len(n.children)-1].iterate(dir, start, stop, includeStart, hit, iter); !ok {
return hit, false
}
}
case descend:
for i := len(n.items) - 1; i >= 0; i-- {
if start != nil && !n.items[i].Less(start) {
if !includeStart || hit || start.Less(n.items[i]) {
continue
}
}
if len(n.children) > 0 {
if hit, ok = n.children[i+1].iterate(dir, start, stop, includeStart, hit, iter); !ok {
return hit, false
}
}
if stop != nil && !stop.Less(n.items[i]) {
return hit, false // continue
}
hit = true
if !iter(n.items[i]) {
return hit, false
}
}
if len(n.children) > 0 {
if hit, ok = n.children[0].iterate(dir, start, stop, includeStart, hit, iter); !ok {
return hit, false
}
}
}
return hit, true
}
// Used for testing/debugging purposes.
func (n *node) print(w io.Writer, level int) {
fmt.Fprintf(w, "%sNODE:%v\n", strings.Repeat(" ", level), n.items)
for _, c := range n.children {
c.print(w, level+1)
}
}
// BTree is an implementation of a B-Tree.
//
// BTree stores Item instances in an ordered structure, allowing easy insertion,
// removal, and iteration.
//
// Write operations are not safe for concurrent mutation by multiple
// goroutines, but Read operations are.
type BTree struct {
degree int
length int
root *node
cow *copyOnWriteContext
}
// copyOnWriteContext pointers determine node ownership... a tree with a write
// context equivalent to a node's write context is allowed to modify that node.
// A tree whose write context does not match a node's is not allowed to modify
// it, and must create a new, writable copy (IE: it's a Clone).
//
// When doing any write operation, we maintain the invariant that the current
// node's context is equal to the context of the tree that requested the write.
// We do this by, before we descend into any node, creating a copy with the
// correct context if the contexts don't match.
//
// Since the node we're currently visiting on any write has the requesting
// tree's context, that node is modifiable in place. Children of that node may
// not share context, but before we descend into them, we'll make a mutable
// copy.
type copyOnWriteContext struct {
freelist *FreeList
}
// Clone clones the btree, lazily. Clone should not be called concurrently,
// but the original tree (t) and the new tree (t2) can be used concurrently
// once the Clone call completes.
//
// The internal tree structure of b is marked read-only and shared between t and
// t2. Writes to both t and t2 use copy-on-write logic, creating new nodes
// whenever one of b's original nodes would have been modified. Read operations
// should have no performance degredation. Write operations for both t and t2
// will initially experience minor slow-downs caused by additional allocs and
// copies due to the aforementioned copy-on-write logic, but should converge to
// the original performance characteristics of the original tree.
func (t *BTree) Clone() (t2 *BTree) {
// Create two entirely new copy-on-write contexts.
// This operation effectively creates three trees:
// the original, shared nodes (old b.cow)
// the new b.cow nodes
// the new out.cow nodes
cow1, cow2 := *t.cow, *t.cow
out := *t
t.cow = &cow1
out.cow = &cow2
return &out
}
// maxItems returns the max number of items to allow per node.
func (t *BTree) maxItems() int {
return t.degree*2 - 1
}
// minItems returns the min number of items to allow per node (ignored for the
// root node).
func (t *BTree) minItems() int {
return t.degree - 1
}
func (c *copyOnWriteContext) newNode() (n *node) {
n = c.freelist.newNode()
n.cow = c
return
}
func (c *copyOnWriteContext) freeNode(n *node) {
if n.cow == c {
// clear to allow GC
n.items.truncate(0)
n.children.truncate(0)
n.cow = nil
c.freelist.freeNode(n)
}
}
// ReplaceOrInsert adds the given item to the tree. If an item in the tree
// already equals the given one, it is removed from the tree and returned.
// Otherwise, nil is returned.
//
// nil cannot be added to the tree (will panic).
func (t *BTree) ReplaceOrInsert(item Item) Item {
if item == nil {
panic("nil item being added to BTree")
}
if t.root == nil {
t.root = t.cow.newNode()
t.root.items = append(t.root.items, item)
t.length++
return nil
} else {
t.root = t.root.mutableFor(t.cow)
if len(t.root.items) >= t.maxItems() {
item2, second := t.root.split(t.maxItems() / 2)
oldroot := t.root
t.root = t.cow.newNode()
t.root.items = append(t.root.items, item2)
t.root.children = append(t.root.children, oldroot, second)
}
}
out := t.root.insert(item, t.maxItems())
if out == nil {
t.length++
}
return out
}
// Delete removes an item equal to the passed in item from the tree, returning
// it. If no such item exists, returns nil.
func (t *BTree) Delete(item Item) Item {
return t.deleteItem(item, removeItem)
}
// DeleteMin removes the smallest item in the tree and returns it.
// If no such item exists, returns nil.
func (t *BTree) DeleteMin() Item {
return t.deleteItem(nil, removeMin)
}
// DeleteMax removes the largest item in the tree and returns it.
// If no such item exists, returns nil.
func (t *BTree) DeleteMax() Item {
return t.deleteItem(nil, removeMax)
}
func (t *BTree) deleteItem(item Item, typ toRemove) Item {
if t.root == nil || len(t.root.items) == 0 {
return nil
}
t.root = t.root.mutableFor(t.cow)
out := t.root.remove(item, t.minItems(), typ)
if len(t.root.items) == 0 && len(t.root.children) > 0 {
oldroot := t.root
t.root = t.root.children[0]
t.cow.freeNode(oldroot)
}
if out != nil {
t.length--
}
return out
}
// AscendRange calls the iterator for every value in the tree within the range
// [greaterOrEqual, lessThan), until iterator returns false.
func (t *BTree) AscendRange(greaterOrEqual, lessThan Item, iterator ItemIterator) {
if t.root == nil {
return
}
t.root.iterate(ascend, greaterOrEqual, lessThan, true, false, iterator)
}
// AscendLessThan calls the iterator for every value in the tree within the range
// [first, pivot), until iterator returns false.
func (t *BTree) AscendLessThan(pivot Item, iterator ItemIterator) {
if t.root == nil {
return
}
t.root.iterate(ascend, nil, pivot, false, false, iterator)
}
// AscendGreaterOrEqual calls the iterator for every value in the tree within
// the range [pivot, last], until iterator returns false.
func (t *BTree) AscendGreaterOrEqual(pivot Item, iterator ItemIterator) {
if t.root == nil {
return
}
t.root.iterate(ascend, pivot, nil, true, false, iterator)
}
// Ascend calls the iterator for every value in the tree within the range
// [first, last], until iterator returns false.
func (t *BTree) Ascend(iterator ItemIterator) {
if t.root == nil {
return
}
t.root.iterate(ascend, nil, nil, false, false, iterator)
}
// DescendRange calls the iterator for every value in the tree within the range
// [lessOrEqual, greaterThan), until iterator returns false.
func (t *BTree) DescendRange(lessOrEqual, greaterThan Item, iterator ItemIterator) {
if t.root == nil {
return
}
t.root.iterate(descend, lessOrEqual, greaterThan, true, false, iterator)
}
// DescendLessOrEqual calls the iterator for every value in the tree within the range
// [pivot, first], until iterator returns false.
func (t *BTree) DescendLessOrEqual(pivot Item, iterator ItemIterator) {
if t.root == nil {
return
}
t.root.iterate(descend, pivot, nil, true, false, iterator)
}
// DescendGreaterThan calls the iterator for every value in the tree within
// the range (pivot, last], until iterator returns false.
func (t *BTree) DescendGreaterThan(pivot Item, iterator ItemIterator) {
if t.root == nil {
return
}
t.root.iterate(descend, nil, pivot, false, false, iterator)
}
// Descend calls the iterator for every value in the tree within the range
// [last, first], until iterator returns false.
func (t *BTree) Descend(iterator ItemIterator) {
if t.root == nil {
return
}
t.root.iterate(descend, nil, nil, false, false, iterator)
}
// Get looks for the key item in the tree, returning it. It returns nil if
// unable to find that item.
func (t *BTree) Get(key Item) Item {
if t.root == nil {
return nil
}
return t.root.get(key)
}
// Min returns the smallest item in the tree, or nil if the tree is empty.
func (t *BTree) Min() Item {
return min(t.root)
}
// Max returns the largest item in the tree, or nil if the tree is empty.
func (t *BTree) Max() Item {
return max(t.root)
}
// Has returns true if the given key is in the tree.
func (t *BTree) Has(key Item) bool {
return t.Get(key) != nil
}
// Len returns the number of items currently in the tree.
func (t *BTree) Len() int {
return t.length
}
// Int implements the Item interface for integers.
type Int int
// Less returns true if int(a) < int(b).
func (a Int) Less(b Item) bool {
return a < b.(Int)
}
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2014 Google Inc.
//
// 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.
// +build ignore
// This binary compares memory usage between btree and gollrb.
package main
import (
"flag"
"fmt"
"math/rand"
"runtime"
"time"
"github.com/google/btree"
"github.com/petar/GoLLRB/llrb"
)
var (
size = flag.Int("size", 1000000, "size of the tree to build")
degree = flag.Int("degree", 8, "degree of btree")
gollrb = flag.Bool("llrb", false, "use llrb instead of btree")
)
func main() {
flag.Parse()
vals := rand.Perm(*size)
var t, v interface{}
v = vals
var stats runtime.MemStats
for i := 0; i < 10; i++ {
runtime.GC()
}
fmt.Println("-------- BEFORE ----------")
runtime.ReadMemStats(&stats)
fmt.Printf("%+v\n", stats)
start := time.Now()
if *gollrb {
tr := llrb.New()
for _, v := range vals {
tr.ReplaceOrInsert(llrb.Int(v))
}
t = tr // keep it around
} else {
tr := btree.New(*degree)
for _, v := range vals {
tr.ReplaceOrInsert(btree.Int(v))
}
t = tr // keep it around
}
fmt.Printf("%v inserts in %v\n", *size, time.Since(start))
fmt.Println("-------- AFTER ----------")
runtime.ReadMemStats(&stats)
fmt.Printf("%+v\n", stats)
for i := 0; i < 10; i++ {
runtime.GC()
}
fmt.Println("-------- AFTER GC ----------")
runtime.ReadMemStats(&stats)
fmt.Printf("%+v\n", stats)
if t == v {
fmt.Println("to make sure vals and tree aren't GC'd")
}
}
+14
View File
@@ -0,0 +1,14 @@
# Eclipse
.checkstyle
.project
.settings
# Swift
.build
Packages
# vi
*.swp
# vscode
.vscode
.DS_Store
*~
Package.resolved
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
#
# Install dependencies that aren't available as Ubuntu packages.
#
# Everything goes into $HOME/local.
#
# Scripts should add
# - $HOME/local/bin to PATH
# - $HOME/local/lib to LD_LIBRARY_PATH
#
cd
mkdir -p local
# Install swift
SWIFT_URL=https://swift.org/builds/swift-4.0-branch/ubuntu1404/swift-4.0-DEVELOPMENT-SNAPSHOT-2017-09-01-a/swift-4.0-DEVELOPMENT-SNAPSHOT-2017-09-01-a-ubuntu14.04.tar.gz
echo $SWIFT_URL
curl -fSsL $SWIFT_URL -o swift.tar.gz
tar -xzf swift.tar.gz --strip-components=2 --directory=local
# Install protoc
PROTOC_URL=https://github.com/google/protobuf/releases/download/v3.4.0/protoc-3.4.0-linux-x86_64.zip
echo $PROTOC_URL
curl -fSsL $PROTOC_URL -o protoc.zip
unzip protoc.zip -d local
# Verify installation
find local
+34
View File
@@ -0,0 +1,34 @@
#!/bin/sh
#
# Copyright 2016 Google Inc. All Rights Reserved.
#
# 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.
#
go get github.com/golang/protobuf/protoc-gen-go
protoc \
--go_out=Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. \
OpenAPIv2/OpenAPIv2.proto
protoc \
--go_out=:. \
plugins/plugin.proto
protoc \
--go_out=Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. \
OpenAPIv3/OpenAPIv3.proto
protoc \
--go_out=Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. \
discovery/discovery.proto
+35
View File
@@ -0,0 +1,35 @@
# How to become a contributor and submit your own code
## Contributor License Agreements
We'd love to accept your sample apps and patches! Before we can take them, we
have to jump a couple of legal hurdles.
Please fill out either the individual or corporate Contributor License Agreement
(CLA).
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual CLA]
(https://developers.google.com/open-source/cla/individual).
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate CLA]
(https://developers.google.com/open-source/cla/corporate).
Follow either of the two links above to access the appropriate CLA and
instructions for how to sign and return it. Once we receive it, we'll be able to
accept your pull requests.
## Contributing A Patch
1. Submit an issue describing your proposed change to the repo in question.
1. The repo owner will respond to your issue promptly.
1. If your proposed change is accepted, and you haven't already done so, sign a
Contributor License Agreement (see details above).
1. Fork the desired repo, develop and test your code changes.
1. Ensure that your code adheres to the existing style in the sample to which
you are contributing. Refer to the
[Google Cloud Platform Samples Style Guide]
(https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the
recommended coding standards for this organization.
1. Ensure that your code has an appropriate set of unit tests which all pass.
1. Submit a pull request.
@@ -1,4 +1,5 @@
Apache License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -178,7 +179,7 @@ Apache License
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
@@ -186,7 +187,7 @@ Apache License
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+16
View File
@@ -0,0 +1,16 @@
build:
go get
go install
cd generate-gnostic; go get; go install
cd apps/disco; go get; go install
cd apps/report; go get; go install
cd apps/petstore-builder; go get; go install
cd plugins/gnostic-summary; go get; go install
cd plugins/gnostic-analyze; go get; go install
cd plugins/gnostic-go-generator; go get; go install
rm -f $(GOPATH)/bin/gnostic-go-client $(GOPATH)/bin/gnostic-go-server
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-client
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-server
cd extensions/sample; make
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+663
View File
@@ -0,0 +1,663 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.
// THIS FILE IS AUTOMATICALLY GENERATED.
syntax = "proto3";
package openapi.v2;
import "google/protobuf/any.proto";
// This option lets the proto compiler generate Java code inside the package
// name (see below) instead of inside an outer class. It creates a simpler
// developer experience by reducing one-level of name nesting and be
// consistent with most programming languages that don't support outer classes.
option java_multiple_files = true;
// The Java outer classname should be the filename in UpperCamelCase. This
// class is only used to hold proto descriptor, so developers don't need to
// work with it directly.
option java_outer_classname = "OpenAPIProto";
// The Java package name must be proto package name with proper prefix.
option java_package = "org.openapi_v2";
// A reasonable prefix for the Objective-C symbols generated from the package.
// It should at a minimum be 3 characters long, all uppercase, and convention
// is to use an abbreviation of the package name. Something short, but
// hopefully unique enough to not conflict with things that may come along in
// the future. 'GPB' is reserved for the protocol buffer implementation itself.
option objc_class_prefix = "OAS";
message AdditionalPropertiesItem {
oneof oneof {
Schema schema = 1;
bool boolean = 2;
}
}
message Any {
google.protobuf.Any value = 1;
string yaml = 2;
}
message ApiKeySecurity {
string type = 1;
string name = 2;
string in = 3;
string description = 4;
repeated NamedAny vendor_extension = 5;
}
message BasicAuthenticationSecurity {
string type = 1;
string description = 2;
repeated NamedAny vendor_extension = 3;
}
message BodyParameter {
// A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.
string description = 1;
// The name of the parameter.
string name = 2;
// Determines the location of the parameter.
string in = 3;
// Determines whether or not this parameter is required or optional.
bool required = 4;
Schema schema = 5;
repeated NamedAny vendor_extension = 6;
}
// Contact information for the owners of the API.
message Contact {
// The identifying name of the contact person/organization.
string name = 1;
// The URL pointing to the contact information.
string url = 2;
// The email address of the contact person/organization.
string email = 3;
repeated NamedAny vendor_extension = 4;
}
message Default {
repeated NamedAny additional_properties = 1;
}
// One or more JSON objects describing the schemas being consumed and produced by the API.
message Definitions {
repeated NamedSchema additional_properties = 1;
}
message Document {
// The Swagger version of this document.
string swagger = 1;
Info info = 2;
// The host (name or ip) of the API. Example: 'swagger.io'
string host = 3;
// The base path to the API. Example: '/api'.
string base_path = 4;
// The transfer protocol of the API.
repeated string schemes = 5;
// A list of MIME types accepted by the API.
repeated string consumes = 6;
// A list of MIME types the API can produce.
repeated string produces = 7;
Paths paths = 8;
Definitions definitions = 9;
ParameterDefinitions parameters = 10;
ResponseDefinitions responses = 11;
repeated SecurityRequirement security = 12;
SecurityDefinitions security_definitions = 13;
repeated Tag tags = 14;
ExternalDocs external_docs = 15;
repeated NamedAny vendor_extension = 16;
}
message Examples {
repeated NamedAny additional_properties = 1;
}
// information about external documentation
message ExternalDocs {
string description = 1;
string url = 2;
repeated NamedAny vendor_extension = 3;
}
// A deterministic version of a JSON Schema object.
message FileSchema {
string format = 1;
string title = 2;
string description = 3;
Any default = 4;
repeated string required = 5;
string type = 6;
bool read_only = 7;
ExternalDocs external_docs = 8;
Any example = 9;
repeated NamedAny vendor_extension = 10;
}
message FormDataParameterSubSchema {
// Determines whether or not this parameter is required or optional.
bool required = 1;
// Determines the location of the parameter.
string in = 2;
// A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.
string description = 3;
// The name of the parameter.
string name = 4;
// allows sending a parameter by name only or with an empty value.
bool allow_empty_value = 5;
string type = 6;
string format = 7;
PrimitivesItems items = 8;
string collection_format = 9;
Any default = 10;
double maximum = 11;
bool exclusive_maximum = 12;
double minimum = 13;
bool exclusive_minimum = 14;
int64 max_length = 15;
int64 min_length = 16;
string pattern = 17;
int64 max_items = 18;
int64 min_items = 19;
bool unique_items = 20;
repeated Any enum = 21;
double multiple_of = 22;
repeated NamedAny vendor_extension = 23;
}
message Header {
string type = 1;
string format = 2;
PrimitivesItems items = 3;
string collection_format = 4;
Any default = 5;
double maximum = 6;
bool exclusive_maximum = 7;
double minimum = 8;
bool exclusive_minimum = 9;
int64 max_length = 10;
int64 min_length = 11;
string pattern = 12;
int64 max_items = 13;
int64 min_items = 14;
bool unique_items = 15;
repeated Any enum = 16;
double multiple_of = 17;
string description = 18;
repeated NamedAny vendor_extension = 19;
}
message HeaderParameterSubSchema {
// Determines whether or not this parameter is required or optional.
bool required = 1;
// Determines the location of the parameter.
string in = 2;
// A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.
string description = 3;
// The name of the parameter.
string name = 4;
string type = 5;
string format = 6;
PrimitivesItems items = 7;
string collection_format = 8;
Any default = 9;
double maximum = 10;
bool exclusive_maximum = 11;
double minimum = 12;
bool exclusive_minimum = 13;
int64 max_length = 14;
int64 min_length = 15;
string pattern = 16;
int64 max_items = 17;
int64 min_items = 18;
bool unique_items = 19;
repeated Any enum = 20;
double multiple_of = 21;
repeated NamedAny vendor_extension = 22;
}
message Headers {
repeated NamedHeader additional_properties = 1;
}
// General information about the API.
message Info {
// A unique and precise title of the API.
string title = 1;
// A semantic version number of the API.
string version = 2;
// A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed.
string description = 3;
// The terms of service for the API.
string terms_of_service = 4;
Contact contact = 5;
License license = 6;
repeated NamedAny vendor_extension = 7;
}
message ItemsItem {
repeated Schema schema = 1;
}
message JsonReference {
string _ref = 1;
string description = 2;
}
message License {
// The name of the license type. It's encouraged to use an OSI compatible license.
string name = 1;
// The URL pointing to the license.
string url = 2;
repeated NamedAny vendor_extension = 3;
}
// Automatically-generated message used to represent maps of Any as ordered (name,value) pairs.
message NamedAny {
// Map key
string name = 1;
// Mapped value
Any value = 2;
}
// Automatically-generated message used to represent maps of Header as ordered (name,value) pairs.
message NamedHeader {
// Map key
string name = 1;
// Mapped value
Header value = 2;
}
// Automatically-generated message used to represent maps of Parameter as ordered (name,value) pairs.
message NamedParameter {
// Map key
string name = 1;
// Mapped value
Parameter value = 2;
}
// Automatically-generated message used to represent maps of PathItem as ordered (name,value) pairs.
message NamedPathItem {
// Map key
string name = 1;
// Mapped value
PathItem value = 2;
}
// Automatically-generated message used to represent maps of Response as ordered (name,value) pairs.
message NamedResponse {
// Map key
string name = 1;
// Mapped value
Response value = 2;
}
// Automatically-generated message used to represent maps of ResponseValue as ordered (name,value) pairs.
message NamedResponseValue {
// Map key
string name = 1;
// Mapped value
ResponseValue value = 2;
}
// Automatically-generated message used to represent maps of Schema as ordered (name,value) pairs.
message NamedSchema {
// Map key
string name = 1;
// Mapped value
Schema value = 2;
}
// Automatically-generated message used to represent maps of SecurityDefinitionsItem as ordered (name,value) pairs.
message NamedSecurityDefinitionsItem {
// Map key
string name = 1;
// Mapped value
SecurityDefinitionsItem value = 2;
}
// Automatically-generated message used to represent maps of string as ordered (name,value) pairs.
message NamedString {
// Map key
string name = 1;
// Mapped value
string value = 2;
}
// Automatically-generated message used to represent maps of StringArray as ordered (name,value) pairs.
message NamedStringArray {
// Map key
string name = 1;
// Mapped value
StringArray value = 2;
}
message NonBodyParameter {
oneof oneof {
HeaderParameterSubSchema header_parameter_sub_schema = 1;
FormDataParameterSubSchema form_data_parameter_sub_schema = 2;
QueryParameterSubSchema query_parameter_sub_schema = 3;
PathParameterSubSchema path_parameter_sub_schema = 4;
}
}
message Oauth2AccessCodeSecurity {
string type = 1;
string flow = 2;
Oauth2Scopes scopes = 3;
string authorization_url = 4;
string token_url = 5;
string description = 6;
repeated NamedAny vendor_extension = 7;
}
message Oauth2ApplicationSecurity {
string type = 1;
string flow = 2;
Oauth2Scopes scopes = 3;
string token_url = 4;
string description = 5;
repeated NamedAny vendor_extension = 6;
}
message Oauth2ImplicitSecurity {
string type = 1;
string flow = 2;
Oauth2Scopes scopes = 3;
string authorization_url = 4;
string description = 5;
repeated NamedAny vendor_extension = 6;
}
message Oauth2PasswordSecurity {
string type = 1;
string flow = 2;
Oauth2Scopes scopes = 3;
string token_url = 4;
string description = 5;
repeated NamedAny vendor_extension = 6;
}
message Oauth2Scopes {
repeated NamedString additional_properties = 1;
}
message Operation {
repeated string tags = 1;
// A brief summary of the operation.
string summary = 2;
// A longer description of the operation, GitHub Flavored Markdown is allowed.
string description = 3;
ExternalDocs external_docs = 4;
// A unique identifier of the operation.
string operation_id = 5;
// A list of MIME types the API can produce.
repeated string produces = 6;
// A list of MIME types the API can consume.
repeated string consumes = 7;
// The parameters needed to send a valid API call.
repeated ParametersItem parameters = 8;
Responses responses = 9;
// The transfer protocol of the API.
repeated string schemes = 10;
bool deprecated = 11;
repeated SecurityRequirement security = 12;
repeated NamedAny vendor_extension = 13;
}
message Parameter {
oneof oneof {
BodyParameter body_parameter = 1;
NonBodyParameter non_body_parameter = 2;
}
}
// One or more JSON representations for parameters
message ParameterDefinitions {
repeated NamedParameter additional_properties = 1;
}
message ParametersItem {
oneof oneof {
Parameter parameter = 1;
JsonReference json_reference = 2;
}
}
message PathItem {
string _ref = 1;
Operation get = 2;
Operation put = 3;
Operation post = 4;
Operation delete = 5;
Operation options = 6;
Operation head = 7;
Operation patch = 8;
// The parameters needed to send a valid API call.
repeated ParametersItem parameters = 9;
repeated NamedAny vendor_extension = 10;
}
message PathParameterSubSchema {
// Determines whether or not this parameter is required or optional.
bool required = 1;
// Determines the location of the parameter.
string in = 2;
// A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.
string description = 3;
// The name of the parameter.
string name = 4;
string type = 5;
string format = 6;
PrimitivesItems items = 7;
string collection_format = 8;
Any default = 9;
double maximum = 10;
bool exclusive_maximum = 11;
double minimum = 12;
bool exclusive_minimum = 13;
int64 max_length = 14;
int64 min_length = 15;
string pattern = 16;
int64 max_items = 17;
int64 min_items = 18;
bool unique_items = 19;
repeated Any enum = 20;
double multiple_of = 21;
repeated NamedAny vendor_extension = 22;
}
// Relative paths to the individual endpoints. They must be relative to the 'basePath'.
message Paths {
repeated NamedAny vendor_extension = 1;
repeated NamedPathItem path = 2;
}
message PrimitivesItems {
string type = 1;
string format = 2;
PrimitivesItems items = 3;
string collection_format = 4;
Any default = 5;
double maximum = 6;
bool exclusive_maximum = 7;
double minimum = 8;
bool exclusive_minimum = 9;
int64 max_length = 10;
int64 min_length = 11;
string pattern = 12;
int64 max_items = 13;
int64 min_items = 14;
bool unique_items = 15;
repeated Any enum = 16;
double multiple_of = 17;
repeated NamedAny vendor_extension = 18;
}
message Properties {
repeated NamedSchema additional_properties = 1;
}
message QueryParameterSubSchema {
// Determines whether or not this parameter is required or optional.
bool required = 1;
// Determines the location of the parameter.
string in = 2;
// A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed.
string description = 3;
// The name of the parameter.
string name = 4;
// allows sending a parameter by name only or with an empty value.
bool allow_empty_value = 5;
string type = 6;
string format = 7;
PrimitivesItems items = 8;
string collection_format = 9;
Any default = 10;
double maximum = 11;
bool exclusive_maximum = 12;
double minimum = 13;
bool exclusive_minimum = 14;
int64 max_length = 15;
int64 min_length = 16;
string pattern = 17;
int64 max_items = 18;
int64 min_items = 19;
bool unique_items = 20;
repeated Any enum = 21;
double multiple_of = 22;
repeated NamedAny vendor_extension = 23;
}
message Response {
string description = 1;
SchemaItem schema = 2;
Headers headers = 3;
Examples examples = 4;
repeated NamedAny vendor_extension = 5;
}
// One or more JSON representations for parameters
message ResponseDefinitions {
repeated NamedResponse additional_properties = 1;
}
message ResponseValue {
oneof oneof {
Response response = 1;
JsonReference json_reference = 2;
}
}
// Response objects names can either be any valid HTTP status code or 'default'.
message Responses {
repeated NamedResponseValue response_code = 1;
repeated NamedAny vendor_extension = 2;
}
// A deterministic version of a JSON Schema object.
message Schema {
string _ref = 1;
string format = 2;
string title = 3;
string description = 4;
Any default = 5;
double multiple_of = 6;
double maximum = 7;
bool exclusive_maximum = 8;
double minimum = 9;
bool exclusive_minimum = 10;
int64 max_length = 11;
int64 min_length = 12;
string pattern = 13;
int64 max_items = 14;
int64 min_items = 15;
bool unique_items = 16;
int64 max_properties = 17;
int64 min_properties = 18;
repeated string required = 19;
repeated Any enum = 20;
AdditionalPropertiesItem additional_properties = 21;
TypeItem type = 22;
ItemsItem items = 23;
repeated Schema all_of = 24;
Properties properties = 25;
string discriminator = 26;
bool read_only = 27;
Xml xml = 28;
ExternalDocs external_docs = 29;
Any example = 30;
repeated NamedAny vendor_extension = 31;
}
message SchemaItem {
oneof oneof {
Schema schema = 1;
FileSchema file_schema = 2;
}
}
message SecurityDefinitions {
repeated NamedSecurityDefinitionsItem additional_properties = 1;
}
message SecurityDefinitionsItem {
oneof oneof {
BasicAuthenticationSecurity basic_authentication_security = 1;
ApiKeySecurity api_key_security = 2;
Oauth2ImplicitSecurity oauth2_implicit_security = 3;
Oauth2PasswordSecurity oauth2_password_security = 4;
Oauth2ApplicationSecurity oauth2_application_security = 5;
Oauth2AccessCodeSecurity oauth2_access_code_security = 6;
}
}
message SecurityRequirement {
repeated NamedStringArray additional_properties = 1;
}
message StringArray {
repeated string value = 1;
}
message Tag {
string name = 1;
string description = 2;
ExternalDocs external_docs = 3;
repeated NamedAny vendor_extension = 4;
}
message TypeItem {
repeated string value = 1;
}
// Any property starting with x- is valid.
message VendorExtension {
repeated NamedAny additional_properties = 1;
}
message Xml {
string name = 1;
string namespace = 2;
string prefix = 3;
bool attribute = 4;
bool wrapped = 5;
repeated NamedAny vendor_extension = 6;
}
+16
View File
@@ -0,0 +1,16 @@
# OpenAPI v2 Protocol Buffer Models
This directory contains a Protocol Buffer-language model
and related code for supporting OpenAPI v2.
Gnostic applications and plugins can use OpenAPIv2.proto
to generate Protocol Buffer support code for their preferred languages.
OpenAPIv2.go is used by Gnostic to read JSON and YAML OpenAPI
descriptions into the Protocol Buffer-based datastructures
generated from OpenAPIv2.proto.
OpenAPIv2.proto and OpenAPIv2.go are generated by the Gnostic
compiler generator, and OpenAPIv2.pb.go is generated by
protoc, the Protocol Buffer compiler, and protoc-gen-go, the
Protocol Buffer Go code generation plugin.
File diff suppressed because it is too large Load Diff
+103
View File
@@ -0,0 +1,103 @@
[![Build Status](https://travis-ci.org/googleapis/gnostic.svg?branch=master)](https://travis-ci.org/googleapis/gnostic)
# ⨁ gnostic
This repository contains a Go command line tool which converts
JSON and YAML [OpenAPI](https://github.com/OAI/OpenAPI-Specification)
descriptions to and from equivalent Protocol Buffer representations.
[Protocol Buffers](https://developers.google.com/protocol-buffers/)
provide a language-neutral, platform-neutral, extensible mechanism
for serializing structured data.
**gnostic**'s Protocol Buffer models for the OpenAPI Specification
can be used to generate code that includes data structures with
explicit fields for the elements of an OpenAPI description.
This makes it possible for developers to work with OpenAPI
descriptions in type-safe ways, which is particularly useful
in strongly-typed languages like Go and Swift.
**gnostic** reads OpenAPI descriptions into
these generated data structures, reports errors,
resolves internal dependencies, and writes the results
in a binary form that can be used in any language that is
supported by the Protocol Buffer tools.
A plugin interface simplifies integration with API
tools written in a variety of different languages,
and when necessary, Protocol Buffer OpenAPI descriptions
can be reexported as JSON or YAML.
**gnostic** compilation code and OpenAPI Protocol Buffer
models are automatically generated from an
[OpenAPI JSON Schema](https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v2.0/schema.json).
Source code for the generator is in the [generate-gnostic](generate-gnostic) directory.
## Disclaimer
This is prerelease software and work in progress. Feedback and
contributions are welcome, but we currently make no guarantees of
function or stability.
## Requirements
**gnostic** can be run in any environment that supports [Go](http://golang.org)
and the [Google Protocol Buffer Compiler](https://github.com/google/protobuf).
## Installation
1. Get this package by downloading it with `go get`.
go get github.com/googleapis/gnostic
2. [Optional] Build and run the compiler generator.
This uses the OpenAPI JSON schema to generate a Protocol Buffer language file
that describes the OpenAPI specification and a Go-language file of code that
will read a JSON or YAML OpenAPI representation into the generated protocol
buffers. Pre-generated versions of these files are in the OpenAPIv2 directory.
cd $GOPATH/src/github.com/googleapis/gnostic/generate-gnostic
go install
cd ..
generate-gnostic --v2
3. [Optional] Generate Protocol Buffer support code.
A pre-generated version of this file is checked into the OpenAPIv2 directory.
This step requires a local installation of protoc, the Protocol Buffer Compiler.
You can get protoc [here](https://github.com/google/protobuf).
./COMPILE-PROTOS.sh
4. [Optional] Rebuild **gnostic**. This is only necessary if you've performed steps
2 or 3 above.
go install github.com/googleapis/gnostic
5. Run **gnostic**. This will create a file in the current directory named "petstore.pb" that contains a binary
Protocol Buffer description of a sample API.
gnostic --pb-out=. examples/petstore.json
6. You can also compile files that you specify with a URL. Here's another way to compile the previous
example. This time we're creating "petstore.text", which contains a textual representation of the
Protocol Buffer description. This is mainly for use in testing and debugging.
gnostic --text-out=petstore.text https://raw.githubusercontent.com/googleapis/gnostic/master/examples/petstore.json
7. For a sample application, see apps/report.
go install github.com/googleapis/gnostic/apps/report
report petstore.pb
8. **gnostic** supports plugins. This builds and runs a sample plugin
that reports some basic information about an API. The "-" causes the plugin to
write its output to stdout.
go install github.com/googleapis/gnostic/plugins/gnostic-go-sample
gnostic examples/petstore.json --go-sample-out=-
## Copyright
Copyright 2017, Google Inc.
## License
Released under the Apache 2.0 license.
+3
View File
@@ -0,0 +1,3 @@
# Compiler support code
This directory contains compiler support code used by Gnostic and Gnostic extensions.
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 compiler
// Context contains state of the compiler as it traverses a document.
type Context struct {
Parent *Context
Name string
ExtensionHandlers *[]ExtensionHandler
}
// NewContextWithExtensions returns a new object representing the compiler state
func NewContextWithExtensions(name string, parent *Context, extensionHandlers *[]ExtensionHandler) *Context {
return &Context{Name: name, Parent: parent, ExtensionHandlers: extensionHandlers}
}
// NewContext returns a new object representing the compiler state
func NewContext(name string, parent *Context) *Context {
if parent != nil {
return &Context{Name: name, Parent: parent, ExtensionHandlers: parent.ExtensionHandlers}
}
return &Context{Name: name, Parent: parent, ExtensionHandlers: nil}
}
// Description returns a text description of the compiler state
func (context *Context) Description() string {
if context.Parent != nil {
return context.Parent.Description() + "." + context.Name
}
return context.Name
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 compiler
// Error represents compiler errors and their location in the document.
type Error struct {
Context *Context
Message string
}
// NewError creates an Error.
func NewError(context *Context, message string) *Error {
return &Error{Context: context, Message: message}
}
// Error returns the string value of an Error.
func (err *Error) Error() string {
if err.Context == nil {
return "ERROR " + err.Message
}
return "ERROR " + err.Context.Description() + " " + err.Message
}
// ErrorGroup is a container for groups of Error values.
type ErrorGroup struct {
Errors []error
}
// NewErrorGroupOrNil returns a new ErrorGroup for a slice of errors or nil if the slice is empty.
func NewErrorGroupOrNil(errors []error) error {
if len(errors) == 0 {
return nil
} else if len(errors) == 1 {
return errors[0]
} else {
return &ErrorGroup{Errors: errors}
}
}
func (group *ErrorGroup) Error() string {
result := ""
for i, err := range group.Errors {
if i > 0 {
result += "\n"
}
result += err.Error()
}
return result
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 compiler
import (
"bytes"
"fmt"
"os/exec"
"strings"
"errors"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/any"
ext_plugin "github.com/googleapis/gnostic/extensions"
yaml "gopkg.in/yaml.v2"
)
// ExtensionHandler describes a binary that is called by the compiler to handle specification extensions.
type ExtensionHandler struct {
Name string
}
// HandleExtension calls a binary extension handler.
func HandleExtension(context *Context, in interface{}, extensionName string) (bool, *any.Any, error) {
handled := false
var errFromPlugin error
var outFromPlugin *any.Any
if context != nil && context.ExtensionHandlers != nil && len(*(context.ExtensionHandlers)) != 0 {
for _, customAnyProtoGenerator := range *(context.ExtensionHandlers) {
outFromPlugin, errFromPlugin = customAnyProtoGenerator.handle(in, extensionName)
if outFromPlugin == nil {
continue
} else {
handled = true
break
}
}
}
return handled, outFromPlugin, errFromPlugin
}
func (extensionHandlers *ExtensionHandler) handle(in interface{}, extensionName string) (*any.Any, error) {
if extensionHandlers.Name != "" {
binary, _ := yaml.Marshal(in)
request := &ext_plugin.ExtensionHandlerRequest{}
version := &ext_plugin.Version{}
version.Major = 0
version.Minor = 1
version.Patch = 0
request.CompilerVersion = version
request.Wrapper = &ext_plugin.Wrapper{}
request.Wrapper.Version = "v2"
request.Wrapper.Yaml = string(binary)
request.Wrapper.ExtensionName = extensionName
requestBytes, _ := proto.Marshal(request)
cmd := exec.Command(extensionHandlers.Name)
cmd.Stdin = bytes.NewReader(requestBytes)
output, err := cmd.Output()
if err != nil {
fmt.Printf("Error: %+v\n", err)
return nil, err
}
response := &ext_plugin.ExtensionHandlerResponse{}
err = proto.Unmarshal(output, response)
if err != nil {
fmt.Printf("Error: %+v\n", err)
fmt.Printf("%s\n", string(output))
return nil, err
}
if !response.Handled {
return nil, nil
}
if len(response.Error) != 0 {
message := fmt.Sprintf("Errors when parsing: %+v for field %s by vendor extension handler %s. Details %+v", in, extensionName, extensionHandlers.Name, strings.Join(response.Error, ","))
return nil, errors.New(message)
}
return response.Value, nil
}
return nil, nil
}
+197
View File
@@ -0,0 +1,197 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 compiler
import (
"fmt"
"gopkg.in/yaml.v2"
"regexp"
"sort"
"strconv"
)
// compiler helper functions, usually called from generated code
// UnpackMap gets a yaml.MapSlice if possible.
func UnpackMap(in interface{}) (yaml.MapSlice, bool) {
m, ok := in.(yaml.MapSlice)
if ok {
return m, true
}
// do we have an empty array?
a, ok := in.([]interface{})
if ok && len(a) == 0 {
// if so, return an empty map
return yaml.MapSlice{}, true
}
return nil, false
}
// SortedKeysForMap returns the sorted keys of a yaml.MapSlice.
func SortedKeysForMap(m yaml.MapSlice) []string {
keys := make([]string, 0)
for _, item := range m {
keys = append(keys, item.Key.(string))
}
sort.Strings(keys)
return keys
}
// MapHasKey returns true if a yaml.MapSlice contains a specified key.
func MapHasKey(m yaml.MapSlice, key string) bool {
for _, item := range m {
itemKey, ok := item.Key.(string)
if ok && key == itemKey {
return true
}
}
return false
}
// MapValueForKey gets the value of a map value for a specified key.
func MapValueForKey(m yaml.MapSlice, key string) interface{} {
for _, item := range m {
itemKey, ok := item.Key.(string)
if ok && key == itemKey {
return item.Value
}
}
return nil
}
// ConvertInterfaceArrayToStringArray converts an array of interfaces to an array of strings, if possible.
func ConvertInterfaceArrayToStringArray(interfaceArray []interface{}) []string {
stringArray := make([]string, 0)
for _, item := range interfaceArray {
v, ok := item.(string)
if ok {
stringArray = append(stringArray, v)
}
}
return stringArray
}
// MissingKeysInMap identifies which keys from a list of required keys are not in a map.
func MissingKeysInMap(m yaml.MapSlice, requiredKeys []string) []string {
missingKeys := make([]string, 0)
for _, k := range requiredKeys {
if !MapHasKey(m, k) {
missingKeys = append(missingKeys, k)
}
}
return missingKeys
}
// InvalidKeysInMap returns keys in a map that don't match a list of allowed keys and patterns.
func InvalidKeysInMap(m yaml.MapSlice, allowedKeys []string, allowedPatterns []*regexp.Regexp) []string {
invalidKeys := make([]string, 0)
for _, item := range m {
itemKey, ok := item.Key.(string)
if ok {
key := itemKey
found := false
// does the key match an allowed key?
for _, allowedKey := range allowedKeys {
if key == allowedKey {
found = true
break
}
}
if !found {
// does the key match an allowed pattern?
for _, allowedPattern := range allowedPatterns {
if allowedPattern.MatchString(key) {
found = true
break
}
}
if !found {
invalidKeys = append(invalidKeys, key)
}
}
}
}
return invalidKeys
}
// DescribeMap describes a map (for debugging purposes).
func DescribeMap(in interface{}, indent string) string {
description := ""
m, ok := in.(map[string]interface{})
if ok {
keys := make([]string, 0)
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := m[k]
description += fmt.Sprintf("%s%s:\n", indent, k)
description += DescribeMap(v, indent+" ")
}
return description
}
a, ok := in.([]interface{})
if ok {
for i, v := range a {
description += fmt.Sprintf("%s%d:\n", indent, i)
description += DescribeMap(v, indent+" ")
}
return description
}
description += fmt.Sprintf("%s%+v\n", indent, in)
return description
}
// PluralProperties returns the string "properties" pluralized.
func PluralProperties(count int) string {
if count == 1 {
return "property"
}
return "properties"
}
// StringArrayContainsValue returns true if a string array contains a specified value.
func StringArrayContainsValue(array []string, value string) bool {
for _, item := range array {
if item == value {
return true
}
}
return false
}
// StringArrayContainsValues returns true if a string array contains all of a list of specified values.
func StringArrayContainsValues(array []string, values []string) bool {
for _, value := range values {
if !StringArrayContainsValue(array, value) {
return false
}
}
return true
}
// StringValue returns the string value of an item.
func StringValue(item interface{}) (value string, ok bool) {
value, ok = item.(string)
if ok {
return value, ok
}
intValue, ok := item.(int)
if ok {
return strconv.Itoa(intValue), true
}
return "", false
}

Some files were not shown because too many files have changed in this diff Show More