mirror of https://github.com/docker/cli.git
Bump kubernetes dependencies to 1.11
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
parent
601131634e
commit
c8f0e211b9
25
vendor.conf
25
vendor.conf
|
@ -17,8 +17,6 @@ github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
||||||
github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18
|
github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18
|
||||||
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
|
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
|
||||||
github.com/docker/swarmkit edd5641391926a50bc5f7040e20b7efc05003c26
|
github.com/docker/swarmkit edd5641391926a50bc5f7040e20b7efc05003c26
|
||||||
github.com/emicklei/go-restful ff4f55a206334ef123e4f79bbf348980da81ca46
|
|
||||||
github.com/emicklei/go-restful-swagger12 dcef7f55730566d41eae5db10e7d6981829720f6
|
|
||||||
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
|
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
|
||||||
github.com/ghodss/yaml 0ca9ea5df5451ffdf184b4428c902747c2c11cd7
|
github.com/ghodss/yaml 0ca9ea5df5451ffdf184b4428c902747c2c11cd7
|
||||||
github.com/gogo/protobuf v1.0.0
|
github.com/gogo/protobuf v1.0.0
|
||||||
|
@ -31,26 +29,21 @@ github.com/googleapis/gnostic e4f56557df6250e1945ee6854f181ce4e1c2c646
|
||||||
github.com/gorilla/context v1.1
|
github.com/gorilla/context v1.1
|
||||||
github.com/gorilla/mux v1.1
|
github.com/gorilla/mux v1.1
|
||||||
gotest.tools v2.1.0
|
gotest.tools v2.1.0
|
||||||
github.com/go-openapi/jsonpointer 46af16f9f7b149af66e5d1bd010e3574dc06de98
|
|
||||||
github.com/go-openapi/jsonreference 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272
|
|
||||||
github.com/go-openapi/spec 6aced65f8501fe1217321abf0749d354824ba2ff
|
|
||||||
github.com/go-openapi/swag 1d0bd113de87027671077d3c71eb3ac5d7dbba72
|
|
||||||
github.com/gregjones/httpcache c1f8028e62adb3d518b823a2f8e6a95c38bdd3aa
|
github.com/gregjones/httpcache c1f8028e62adb3d518b823a2f8e6a95c38bdd3aa
|
||||||
github.com/grpc-ecosystem/grpc-gateway 1a03ca3bad1e1ebadaedd3abb76bc58d4ac8143b
|
github.com/grpc-ecosystem/grpc-gateway 1a03ca3bad1e1ebadaedd3abb76bc58d4ac8143b
|
||||||
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
|
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
|
||||||
github.com/hashicorp/golang-lru a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4
|
github.com/hashicorp/golang-lru a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4
|
||||||
github.com/howeyc/gopass 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d
|
|
||||||
github.com/imdario/mergo v0.3.5
|
github.com/imdario/mergo v0.3.5
|
||||||
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||||
github.com/juju/ratelimit 5b9ff866471762aa2ab2dced63c9fb6f53921342
|
github.com/json-iterator/go ab8a2e0c74be9d3be70b3184d9acc634935ded82 # 1.1.4
|
||||||
github.com/json-iterator/go 6240e1e7983a85228f7fd9c3e1b6932d46ec58e2
|
|
||||||
github.com/mailru/easyjson d5b7844b561a7bc640052f1b935f7b800330d7e0
|
|
||||||
github.com/mattn/go-shellwords v1.0.3
|
github.com/mattn/go-shellwords v1.0.3
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.0
|
github.com/matttproud/golang_protobuf_extensions v1.0.0
|
||||||
github.com/Microsoft/go-winio v0.4.8
|
github.com/Microsoft/go-winio v0.4.8
|
||||||
github.com/miekg/pkcs11 5f6e0d0dad6f472df908c8e968a98ef00c9224bb
|
github.com/miekg/pkcs11 5f6e0d0dad6f472df908c8e968a98ef00c9224bb
|
||||||
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
|
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
|
||||||
github.com/moby/buildkit 9acf51e49185b348608e0096b2903dd72907adcb
|
github.com/moby/buildkit 9acf51e49185b348608e0096b2903dd72907adcb
|
||||||
|
github.com/modern-go/concurrent bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94 # 1.0.3
|
||||||
|
github.com/modern-go/reflect2 4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd # 1.0.1
|
||||||
github.com/morikuni/aec 39771216ff4c63d11f5e604076f9c45e8be1067b
|
github.com/morikuni/aec 39771216ff4c63d11f5e604076f9c45e8be1067b
|
||||||
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
|
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||||
|
@ -63,8 +56,6 @@ github.com/prometheus/client_golang 52437c81da6b127a9925d17eb3a382a2e5fd395e
|
||||||
github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
|
github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
|
||||||
github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8
|
github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8
|
||||||
github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5
|
github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5
|
||||||
github.com/PuerkitoBio/purell 8a290539e2e8629dbc4e6bad948158f790ec31f4
|
|
||||||
github.com/PuerkitoBio/urlesc 5bd2802263f21d8788851d5305584c82a5c75d7e
|
|
||||||
github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438
|
github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438
|
||||||
github.com/shurcooL/sanitized_anchor_name 10ef21a441db47d8b13ebcc5fd2310f636973c77
|
github.com/shurcooL/sanitized_anchor_name 10ef21a441db47d8b13ebcc5fd2310f636973c77
|
||||||
github.com/sirupsen/logrus v1.0.3
|
github.com/sirupsen/logrus v1.0.3
|
||||||
|
@ -85,11 +76,11 @@ google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9
|
||||||
google.golang.org/grpc v1.12.0
|
google.golang.org/grpc v1.12.0
|
||||||
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
|
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
|
||||||
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
|
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
|
||||||
k8s.io/api kubernetes-1.8.14
|
k8s.io/api kubernetes-1.11.0
|
||||||
k8s.io/apimachinery kubernetes-1.8.14
|
k8s.io/apimachinery kubernetes-1.11.0
|
||||||
k8s.io/client-go kubernetes-1.8.14
|
k8s.io/client-go kubernetes-1.11.0
|
||||||
k8s.io/kubernetes v1.8.14
|
k8s.io/kube-openapi d8ea2fe547a448256204cfc68dfee7b26c720acb
|
||||||
k8s.io/kube-openapi 0c329704159e3b051aafac400b15baacf2a94a04
|
k8s.io/kubernetes v1.11.0
|
||||||
vbom.ml/util 928aaa586d7718c70f4090ddf83f2b34c16fdc8d
|
vbom.ml/util 928aaa586d7718c70f4090ddf83f2b34c16fdc8d
|
||||||
github.com/containerd/console cb7008ab3d8359b78c5f464cb7cf160107ad5925
|
github.com/containerd/console cb7008ab3d8359b78c5f464cb7cf160107ad5925
|
||||||
github.com/tonistiigi/units 29de085e9400559bd68aea2e7bc21566e7b8281d
|
github.com/tonistiigi/units 29de085e9400559bd68aea2e7bc21566e7b8281d
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
Copyright (c) 2012, Martin Angers
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
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 the author 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 HOLDER 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.
|
|
|
@ -1,185 +0,0 @@
|
||||||
# Purell
|
|
||||||
|
|
||||||
Purell is a tiny Go library to normalize URLs. It returns a pure URL. Pure-ell. Sanitizer and all. Yeah, I know...
|
|
||||||
|
|
||||||
Based on the [wikipedia paper][wiki] and the [RFC 3986 document][rfc].
|
|
||||||
|
|
||||||
[![build status](https://secure.travis-ci.org/PuerkitoBio/purell.png)](http://travis-ci.org/PuerkitoBio/purell)
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
`go get github.com/PuerkitoBio/purell`
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
* **2016-07-27 (v1.0.0)** : Normalize IDN to ASCII (thanks to @zenovich).
|
|
||||||
* **2015-02-08** : Add fix for relative paths issue ([PR #5][pr5]) and add fix for unnecessary encoding of reserved characters ([see issue #7][iss7]).
|
|
||||||
* **v0.2.0** : Add benchmarks, Attempt IDN support.
|
|
||||||
* **v0.1.0** : Initial release.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
From `example_test.go` (note that in your code, you would import "github.com/PuerkitoBio/purell", and would prefix references to its methods and constants with "purell."):
|
|
||||||
|
|
||||||
```go
|
|
||||||
package purell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleNormalizeURLString() {
|
|
||||||
if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/",
|
|
||||||
FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
fmt.Print(normalized)
|
|
||||||
}
|
|
||||||
// Output: http://somewebsite.com:80/Amazing%3F/url/
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleMustNormalizeURLString() {
|
|
||||||
normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/",
|
|
||||||
FlagsUnsafeGreedy)
|
|
||||||
fmt.Print(normalized)
|
|
||||||
|
|
||||||
// Output: http://somewebsite.com/Amazing%FA/url
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleNormalizeURL() {
|
|
||||||
if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment)
|
|
||||||
fmt.Print(normalized)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
As seen in the examples above, purell offers three methods, `NormalizeURLString(string, NormalizationFlags) (string, error)`, `MustNormalizeURLString(string, NormalizationFlags) (string)` and `NormalizeURL(*url.URL, NormalizationFlags) (string)`. They all normalize the provided URL based on the specified flags. Here are the available flags:
|
|
||||||
|
|
||||||
```go
|
|
||||||
const (
|
|
||||||
// Safe normalizations
|
|
||||||
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
|
|
||||||
FlagLowercaseHost // http://HOST -> http://host
|
|
||||||
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
|
|
||||||
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
|
|
||||||
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
|
|
||||||
FlagRemoveDefaultPort // http://host:80 -> http://host
|
|
||||||
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
|
|
||||||
|
|
||||||
// Usually safe normalizations
|
|
||||||
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
|
|
||||||
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
|
|
||||||
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
|
|
||||||
|
|
||||||
// Unsafe normalizations
|
|
||||||
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
|
|
||||||
FlagRemoveFragment // http://host/path#fragment -> http://host/path
|
|
||||||
FlagForceHTTP // https://host -> http://host
|
|
||||||
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
|
|
||||||
FlagRemoveWWW // http://www.host/ -> http://host/
|
|
||||||
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
|
|
||||||
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
|
|
||||||
|
|
||||||
// Normalizations not in the wikipedia article, required to cover tests cases
|
|
||||||
// submitted by jehiah
|
|
||||||
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
|
|
||||||
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
|
|
||||||
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
|
|
||||||
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
|
|
||||||
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
|
|
||||||
|
|
||||||
// Convenience set of safe normalizations
|
|
||||||
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
|
|
||||||
|
|
||||||
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
|
|
||||||
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
|
|
||||||
|
|
||||||
// Convenience set of usually safe normalizations (includes FlagsSafe)
|
|
||||||
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
|
|
||||||
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
|
|
||||||
|
|
||||||
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
|
|
||||||
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
|
|
||||||
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
|
|
||||||
|
|
||||||
// Convenience set of all available flags
|
|
||||||
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
|
||||||
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
For convenience, the set of flags `FlagsSafe`, `FlagsUsuallySafe[Greedy|NonGreedy]`, `FlagsUnsafe[Greedy|NonGreedy]` and `FlagsAll[Greedy|NonGreedy]` are provided for the similarly grouped normalizations on [wikipedia's URL normalization page][wiki]. You can add (using the bitwise OR `|` operator) or remove (using the bitwise AND NOT `&^` operator) individual flags from the sets if required, to build your own custom set.
|
|
||||||
|
|
||||||
The [full godoc reference is available on gopkgdoc][godoc].
|
|
||||||
|
|
||||||
Some things to note:
|
|
||||||
|
|
||||||
* `FlagDecodeUnnecessaryEscapes`, `FlagEncodeNecessaryEscapes`, `FlagUppercaseEscapes` and `FlagRemoveEmptyQuerySeparator` are always implicitly set, because internally, the URL string is parsed as an URL object, which automatically decodes unnecessary escapes, uppercases and encodes necessary ones, and removes empty query separators (an unnecessary `?` at the end of the url). So this operation cannot **not** be done. For this reason, `FlagRemoveEmptyQuerySeparator` (as well as the other three) has been included in the `FlagsSafe` convenience set, instead of `FlagsUnsafe`, where Wikipedia puts it.
|
|
||||||
|
|
||||||
* The `FlagDecodeUnnecessaryEscapes` decodes the following escapes (*from -> to*):
|
|
||||||
- %24 -> $
|
|
||||||
- %26 -> &
|
|
||||||
- %2B-%3B -> +,-./0123456789:;
|
|
||||||
- %3D -> =
|
|
||||||
- %40-%5A -> @ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
- %5F -> _
|
|
||||||
- %61-%7A -> abcdefghijklmnopqrstuvwxyz
|
|
||||||
- %7E -> ~
|
|
||||||
|
|
||||||
|
|
||||||
* When the `NormalizeURL` function is used (passing an URL object), this source URL object is modified (that is, after the call, the URL object will be modified to reflect the normalization).
|
|
||||||
|
|
||||||
* The *replace IP with domain name* normalization (`http://208.77.188.166/ → http://www.example.com/`) is obviously not possible for a library without making some network requests. This is not implemented in purell.
|
|
||||||
|
|
||||||
* The *remove unused query string parameters* and *remove default query parameters* are also not implemented, since this is a very case-specific normalization, and it is quite trivial to do with an URL object.
|
|
||||||
|
|
||||||
### Safe vs Usually Safe vs Unsafe
|
|
||||||
|
|
||||||
Purell allows you to control the level of risk you take while normalizing an URL. You can aggressively normalize, play it totally safe, or anything in between.
|
|
||||||
|
|
||||||
Consider the following URL:
|
|
||||||
|
|
||||||
`HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
|
|
||||||
|
|
||||||
Normalizing with the `FlagsSafe` gives:
|
|
||||||
|
|
||||||
`https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
|
|
||||||
|
|
||||||
With the `FlagsUsuallySafeGreedy`:
|
|
||||||
|
|
||||||
`https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid`
|
|
||||||
|
|
||||||
And with `FlagsUnsafeGreedy`:
|
|
||||||
|
|
||||||
`http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3`
|
|
||||||
|
|
||||||
## TODOs
|
|
||||||
|
|
||||||
* Add a class/default instance to allow specifying custom directory index names? At the moment, removing directory index removes `(^|/)((?:default|index)\.\w{1,4})$`.
|
|
||||||
|
|
||||||
## Thanks / Contributions
|
|
||||||
|
|
||||||
@rogpeppe
|
|
||||||
@jehiah
|
|
||||||
@opennota
|
|
||||||
@pchristopher1275
|
|
||||||
@zenovich
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
The [BSD 3-Clause license][bsd].
|
|
||||||
|
|
||||||
[bsd]: http://opensource.org/licenses/BSD-3-Clause
|
|
||||||
[wiki]: http://en.wikipedia.org/wiki/URL_normalization
|
|
||||||
[rfc]: http://tools.ietf.org/html/rfc3986#section-6
|
|
||||||
[godoc]: http://go.pkgdoc.org/github.com/PuerkitoBio/purell
|
|
||||||
[pr5]: https://github.com/PuerkitoBio/purell/pull/5
|
|
||||||
[iss7]: https://github.com/PuerkitoBio/purell/issues/7
|
|
|
@ -1,375 +0,0 @@
|
||||||
/*
|
|
||||||
Package purell offers URL normalization as described on the wikipedia page:
|
|
||||||
http://en.wikipedia.org/wiki/URL_normalization
|
|
||||||
*/
|
|
||||||
package purell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/urlesc"
|
|
||||||
"golang.org/x/net/idna"
|
|
||||||
"golang.org/x/text/secure/precis"
|
|
||||||
"golang.org/x/text/unicode/norm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A set of normalization flags determines how a URL will
|
|
||||||
// be normalized.
|
|
||||||
type NormalizationFlags uint
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Safe normalizations
|
|
||||||
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
|
|
||||||
FlagLowercaseHost // http://HOST -> http://host
|
|
||||||
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
|
|
||||||
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
|
|
||||||
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
|
|
||||||
FlagRemoveDefaultPort // http://host:80 -> http://host
|
|
||||||
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
|
|
||||||
|
|
||||||
// Usually safe normalizations
|
|
||||||
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
|
|
||||||
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
|
|
||||||
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
|
|
||||||
|
|
||||||
// Unsafe normalizations
|
|
||||||
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
|
|
||||||
FlagRemoveFragment // http://host/path#fragment -> http://host/path
|
|
||||||
FlagForceHTTP // https://host -> http://host
|
|
||||||
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
|
|
||||||
FlagRemoveWWW // http://www.host/ -> http://host/
|
|
||||||
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
|
|
||||||
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
|
|
||||||
|
|
||||||
// Normalizations not in the wikipedia article, required to cover tests cases
|
|
||||||
// submitted by jehiah
|
|
||||||
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
|
|
||||||
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
|
|
||||||
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
|
|
||||||
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
|
|
||||||
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
|
|
||||||
|
|
||||||
// Convenience set of safe normalizations
|
|
||||||
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
|
|
||||||
|
|
||||||
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
|
|
||||||
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
|
|
||||||
|
|
||||||
// Convenience set of usually safe normalizations (includes FlagsSafe)
|
|
||||||
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
|
|
||||||
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
|
|
||||||
|
|
||||||
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
|
|
||||||
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
|
|
||||||
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
|
|
||||||
|
|
||||||
// Convenience set of all available flags
|
|
||||||
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
|
||||||
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultHttpPort = ":80"
|
|
||||||
defaultHttpsPort = ":443"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Regular expressions used by the normalizations
|
|
||||||
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
|
|
||||||
var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`)
|
|
||||||
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
|
|
||||||
var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`)
|
|
||||||
var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`)
|
|
||||||
var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`)
|
|
||||||
var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`)
|
|
||||||
var rxEmptyPort = regexp.MustCompile(`:+$`)
|
|
||||||
|
|
||||||
// Map of flags to implementation function.
|
|
||||||
// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically
|
|
||||||
// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator.
|
|
||||||
|
|
||||||
// Since maps have undefined traversing order, make a slice of ordered keys
|
|
||||||
var flagsOrder = []NormalizationFlags{
|
|
||||||
FlagLowercaseScheme,
|
|
||||||
FlagLowercaseHost,
|
|
||||||
FlagRemoveDefaultPort,
|
|
||||||
FlagRemoveDirectoryIndex,
|
|
||||||
FlagRemoveDotSegments,
|
|
||||||
FlagRemoveFragment,
|
|
||||||
FlagForceHTTP, // Must be after remove default port (because https=443/http=80)
|
|
||||||
FlagRemoveDuplicateSlashes,
|
|
||||||
FlagRemoveWWW,
|
|
||||||
FlagAddWWW,
|
|
||||||
FlagSortQuery,
|
|
||||||
FlagDecodeDWORDHost,
|
|
||||||
FlagDecodeOctalHost,
|
|
||||||
FlagDecodeHexHost,
|
|
||||||
FlagRemoveUnnecessaryHostDots,
|
|
||||||
FlagRemoveEmptyPortSeparator,
|
|
||||||
FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last
|
|
||||||
FlagAddTrailingSlash,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... and then the map, where order is unimportant
|
|
||||||
var flags = map[NormalizationFlags]func(*url.URL){
|
|
||||||
FlagLowercaseScheme: lowercaseScheme,
|
|
||||||
FlagLowercaseHost: lowercaseHost,
|
|
||||||
FlagRemoveDefaultPort: removeDefaultPort,
|
|
||||||
FlagRemoveDirectoryIndex: removeDirectoryIndex,
|
|
||||||
FlagRemoveDotSegments: removeDotSegments,
|
|
||||||
FlagRemoveFragment: removeFragment,
|
|
||||||
FlagForceHTTP: forceHTTP,
|
|
||||||
FlagRemoveDuplicateSlashes: removeDuplicateSlashes,
|
|
||||||
FlagRemoveWWW: removeWWW,
|
|
||||||
FlagAddWWW: addWWW,
|
|
||||||
FlagSortQuery: sortQuery,
|
|
||||||
FlagDecodeDWORDHost: decodeDWORDHost,
|
|
||||||
FlagDecodeOctalHost: decodeOctalHost,
|
|
||||||
FlagDecodeHexHost: decodeHexHost,
|
|
||||||
FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots,
|
|
||||||
FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator,
|
|
||||||
FlagRemoveTrailingSlash: removeTrailingSlash,
|
|
||||||
FlagAddTrailingSlash: addTrailingSlash,
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustNormalizeURLString returns the normalized string, and panics if an error occurs.
|
|
||||||
// It takes an URL string as input, as well as the normalization flags.
|
|
||||||
func MustNormalizeURLString(u string, f NormalizationFlags) string {
|
|
||||||
result, e := NormalizeURLString(u, f)
|
|
||||||
if e != nil {
|
|
||||||
panic(e)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object.
|
|
||||||
// It takes an URL string as input, as well as the normalization flags.
|
|
||||||
func NormalizeURLString(u string, f NormalizationFlags) (string, error) {
|
|
||||||
if parsed, e := url.Parse(u); e != nil {
|
|
||||||
return "", e
|
|
||||||
} else {
|
|
||||||
options := make([]precis.Option, 1, 3)
|
|
||||||
options[0] = precis.IgnoreCase
|
|
||||||
if f&FlagLowercaseHost == FlagLowercaseHost {
|
|
||||||
options = append(options, precis.FoldCase())
|
|
||||||
}
|
|
||||||
options = append(options, precis.Norm(norm.NFC))
|
|
||||||
profile := precis.NewFreeform(options...)
|
|
||||||
if parsed.Host, e = idna.ToASCII(profile.NewTransformer().String(parsed.Host)); e != nil {
|
|
||||||
return "", e
|
|
||||||
}
|
|
||||||
return NormalizeURL(parsed, f), nil
|
|
||||||
}
|
|
||||||
panic("Unreachable code.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NormalizeURL returns the normalized string.
|
|
||||||
// It takes a parsed URL object as input, as well as the normalization flags.
|
|
||||||
func NormalizeURL(u *url.URL, f NormalizationFlags) string {
|
|
||||||
for _, k := range flagsOrder {
|
|
||||||
if f&k == k {
|
|
||||||
flags[k](u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return urlesc.Escape(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lowercaseScheme(u *url.URL) {
|
|
||||||
if len(u.Scheme) > 0 {
|
|
||||||
u.Scheme = strings.ToLower(u.Scheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lowercaseHost(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
u.Host = strings.ToLower(u.Host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDefaultPort(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
scheme := strings.ToLower(u.Scheme)
|
|
||||||
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
|
|
||||||
if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeTrailingSlash(u *url.URL) {
|
|
||||||
if l := len(u.Path); l > 0 {
|
|
||||||
if strings.HasSuffix(u.Path, "/") {
|
|
||||||
u.Path = u.Path[:l-1]
|
|
||||||
}
|
|
||||||
} else if l = len(u.Host); l > 0 {
|
|
||||||
if strings.HasSuffix(u.Host, "/") {
|
|
||||||
u.Host = u.Host[:l-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addTrailingSlash(u *url.URL) {
|
|
||||||
if l := len(u.Path); l > 0 {
|
|
||||||
if !strings.HasSuffix(u.Path, "/") {
|
|
||||||
u.Path += "/"
|
|
||||||
}
|
|
||||||
} else if l = len(u.Host); l > 0 {
|
|
||||||
if !strings.HasSuffix(u.Host, "/") {
|
|
||||||
u.Host += "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDotSegments(u *url.URL) {
|
|
||||||
if len(u.Path) > 0 {
|
|
||||||
var dotFree []string
|
|
||||||
var lastIsDot bool
|
|
||||||
|
|
||||||
sections := strings.Split(u.Path, "/")
|
|
||||||
for _, s := range sections {
|
|
||||||
if s == ".." {
|
|
||||||
if len(dotFree) > 0 {
|
|
||||||
dotFree = dotFree[:len(dotFree)-1]
|
|
||||||
}
|
|
||||||
} else if s != "." {
|
|
||||||
dotFree = append(dotFree, s)
|
|
||||||
}
|
|
||||||
lastIsDot = (s == "." || s == "..")
|
|
||||||
}
|
|
||||||
// Special case if host does not end with / and new path does not begin with /
|
|
||||||
u.Path = strings.Join(dotFree, "/")
|
|
||||||
if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") {
|
|
||||||
u.Path = "/" + u.Path
|
|
||||||
}
|
|
||||||
// Special case if the last segment was a dot, make sure the path ends with a slash
|
|
||||||
if lastIsDot && !strings.HasSuffix(u.Path, "/") {
|
|
||||||
u.Path += "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDirectoryIndex(u *url.URL) {
|
|
||||||
if len(u.Path) > 0 {
|
|
||||||
u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeFragment(u *url.URL) {
|
|
||||||
u.Fragment = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func forceHTTP(u *url.URL) {
|
|
||||||
if strings.ToLower(u.Scheme) == "https" {
|
|
||||||
u.Scheme = "http"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDuplicateSlashes(u *url.URL) {
|
|
||||||
if len(u.Path) > 0 {
|
|
||||||
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeWWW(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") {
|
|
||||||
u.Host = u.Host[4:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addWWW(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") {
|
|
||||||
u.Host = "www." + u.Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortQuery(u *url.URL) {
|
|
||||||
q := u.Query()
|
|
||||||
|
|
||||||
if len(q) > 0 {
|
|
||||||
arKeys := make([]string, len(q))
|
|
||||||
i := 0
|
|
||||||
for k, _ := range q {
|
|
||||||
arKeys[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(arKeys)
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
for _, k := range arKeys {
|
|
||||||
sort.Strings(q[k])
|
|
||||||
for _, v := range q[k] {
|
|
||||||
if buf.Len() > 0 {
|
|
||||||
buf.WriteRune('&')
|
|
||||||
}
|
|
||||||
buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild the raw query string
|
|
||||||
u.RawQuery = buf.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeDWORDHost(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 {
|
|
||||||
var parts [4]int64
|
|
||||||
|
|
||||||
dword, _ := strconv.ParseInt(matches[1], 10, 0)
|
|
||||||
for i, shift := range []uint{24, 16, 8, 0} {
|
|
||||||
parts[i] = dword >> shift & 0xFF
|
|
||||||
}
|
|
||||||
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeOctalHost(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 {
|
|
||||||
var parts [4]int64
|
|
||||||
|
|
||||||
for i := 1; i <= 4; i++ {
|
|
||||||
parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0)
|
|
||||||
}
|
|
||||||
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeHexHost(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 {
|
|
||||||
// Conversion is safe because of regex validation
|
|
||||||
parsed, _ := strconv.ParseInt(matches[1], 16, 0)
|
|
||||||
// Set host as DWORD (base 10) encoded host
|
|
||||||
u.Host = fmt.Sprintf("%d%s", parsed, matches[2])
|
|
||||||
// The rest is the same as decoding a DWORD host
|
|
||||||
decodeDWORDHost(u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeUnncessaryHostDots(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 {
|
|
||||||
// Trim the leading and trailing dots
|
|
||||||
u.Host = strings.Trim(matches[1], ".")
|
|
||||||
if len(matches) > 2 {
|
|
||||||
u.Host += matches[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeEmptyPortSeparator(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
u.Host = rxEmptyPort.ReplaceAllString(u.Host, "")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,16 +0,0 @@
|
||||||
urlesc [![Build Status](https://travis-ci.org/PuerkitoBio/urlesc.png?branch=master)](https://travis-ci.org/PuerkitoBio/urlesc) [![GoDoc](http://godoc.org/github.com/PuerkitoBio/urlesc?status.svg)](http://godoc.org/github.com/PuerkitoBio/urlesc)
|
|
||||||
======
|
|
||||||
|
|
||||||
Package urlesc implements query escaping as per RFC 3986.
|
|
||||||
|
|
||||||
It contains some parts of the net/url package, modified so as to allow
|
|
||||||
some reserved characters incorrectly escaped by net/url (see [issue 5684](https://github.com/golang/go/issues/5684)).
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
go get github.com/PuerkitoBio/urlesc
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Go license (BSD-3-Clause)
|
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package urlesc implements query escaping as per RFC 3986.
|
|
||||||
// It contains some parts of the net/url package, modified so as to allow
|
|
||||||
// some reserved characters incorrectly escaped by net/url.
|
|
||||||
// See https://github.com/golang/go/issues/5684
|
|
||||||
package urlesc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type encoding int
|
|
||||||
|
|
||||||
const (
|
|
||||||
encodePath encoding = 1 + iota
|
|
||||||
encodeUserPassword
|
|
||||||
encodeQueryComponent
|
|
||||||
encodeFragment
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return true if the specified character should be escaped when
|
|
||||||
// appearing in a URL string, according to RFC 3986.
|
|
||||||
func shouldEscape(c byte, mode encoding) bool {
|
|
||||||
// §2.3 Unreserved characters (alphanum)
|
|
||||||
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c {
|
|
||||||
case '-', '.', '_', '~': // §2.3 Unreserved characters (mark)
|
|
||||||
return false
|
|
||||||
|
|
||||||
// §2.2 Reserved characters (reserved)
|
|
||||||
case ':', '/', '?', '#', '[', ']', '@', // gen-delims
|
|
||||||
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims
|
|
||||||
// Different sections of the URL allow a few of
|
|
||||||
// the reserved characters to appear unescaped.
|
|
||||||
switch mode {
|
|
||||||
case encodePath: // §3.3
|
|
||||||
// The RFC allows sub-delims and : @.
|
|
||||||
// '/', '[' and ']' can be used to assign meaning to individual path
|
|
||||||
// segments. This package only manipulates the path as a whole,
|
|
||||||
// so we allow those as well. That leaves only ? and # to escape.
|
|
||||||
return c == '?' || c == '#'
|
|
||||||
|
|
||||||
case encodeUserPassword: // §3.2.1
|
|
||||||
// The RFC allows : and sub-delims in
|
|
||||||
// userinfo. The parsing of userinfo treats ':' as special so we must escape
|
|
||||||
// all the gen-delims.
|
|
||||||
return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@'
|
|
||||||
|
|
||||||
case encodeQueryComponent: // §3.4
|
|
||||||
// The RFC allows / and ?.
|
|
||||||
return c != '/' && c != '?'
|
|
||||||
|
|
||||||
case encodeFragment: // §4.1
|
|
||||||
// The RFC text is silent but the grammar allows
|
|
||||||
// everything, so escape nothing but #
|
|
||||||
return c == '#'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything else must be escaped.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEscape escapes the string so it can be safely placed
|
|
||||||
// inside a URL query.
|
|
||||||
func QueryEscape(s string) string {
|
|
||||||
return escape(s, encodeQueryComponent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func escape(s string, mode encoding) string {
|
|
||||||
spaceCount, hexCount := 0, 0
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
c := s[i]
|
|
||||||
if shouldEscape(c, mode) {
|
|
||||||
if c == ' ' && mode == encodeQueryComponent {
|
|
||||||
spaceCount++
|
|
||||||
} else {
|
|
||||||
hexCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if spaceCount == 0 && hexCount == 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
t := make([]byte, len(s)+2*hexCount)
|
|
||||||
j := 0
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
switch c := s[i]; {
|
|
||||||
case c == ' ' && mode == encodeQueryComponent:
|
|
||||||
t[j] = '+'
|
|
||||||
j++
|
|
||||||
case shouldEscape(c, mode):
|
|
||||||
t[j] = '%'
|
|
||||||
t[j+1] = "0123456789ABCDEF"[c>>4]
|
|
||||||
t[j+2] = "0123456789ABCDEF"[c&15]
|
|
||||||
j += 3
|
|
||||||
default:
|
|
||||||
t[j] = s[i]
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
var uiReplacer = strings.NewReplacer(
|
|
||||||
"%21", "!",
|
|
||||||
"%27", "'",
|
|
||||||
"%28", "(",
|
|
||||||
"%29", ")",
|
|
||||||
"%2A", "*",
|
|
||||||
)
|
|
||||||
|
|
||||||
// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986.
|
|
||||||
func unescapeUserinfo(s string) string {
|
|
||||||
return uiReplacer.Replace(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escape reassembles the URL into a valid URL string.
|
|
||||||
// The general form of the result is one of:
|
|
||||||
//
|
|
||||||
// scheme:opaque
|
|
||||||
// scheme://userinfo@host/path?query#fragment
|
|
||||||
//
|
|
||||||
// If u.Opaque is non-empty, String uses the first form;
|
|
||||||
// otherwise it uses the second form.
|
|
||||||
//
|
|
||||||
// In the second form, the following rules apply:
|
|
||||||
// - if u.Scheme is empty, scheme: is omitted.
|
|
||||||
// - if u.User is nil, userinfo@ is omitted.
|
|
||||||
// - if u.Host is empty, host/ is omitted.
|
|
||||||
// - if u.Scheme and u.Host are empty and u.User is nil,
|
|
||||||
// the entire scheme://userinfo@host/ is omitted.
|
|
||||||
// - if u.Host is non-empty and u.Path begins with a /,
|
|
||||||
// the form host/path does not add its own /.
|
|
||||||
// - if u.RawQuery is empty, ?query is omitted.
|
|
||||||
// - if u.Fragment is empty, #fragment is omitted.
|
|
||||||
func Escape(u *url.URL) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if u.Scheme != "" {
|
|
||||||
buf.WriteString(u.Scheme)
|
|
||||||
buf.WriteByte(':')
|
|
||||||
}
|
|
||||||
if u.Opaque != "" {
|
|
||||||
buf.WriteString(u.Opaque)
|
|
||||||
} else {
|
|
||||||
if u.Scheme != "" || u.Host != "" || u.User != nil {
|
|
||||||
buf.WriteString("//")
|
|
||||||
if ui := u.User; ui != nil {
|
|
||||||
buf.WriteString(unescapeUserinfo(ui.String()))
|
|
||||||
buf.WriteByte('@')
|
|
||||||
}
|
|
||||||
if h := u.Host; h != "" {
|
|
||||||
buf.WriteString(h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
|
|
||||||
buf.WriteByte('/')
|
|
||||||
}
|
|
||||||
buf.WriteString(escape(u.Path, encodePath))
|
|
||||||
}
|
|
||||||
if u.RawQuery != "" {
|
|
||||||
buf.WriteByte('?')
|
|
||||||
buf.WriteString(u.RawQuery)
|
|
||||||
}
|
|
||||||
if u.Fragment != "" {
|
|
||||||
buf.WriteByte('#')
|
|
||||||
buf.WriteString(escape(u.Fragment, encodeFragment))
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
Copyright (c) 2017 Ernest Micklei
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,83 +0,0 @@
|
||||||
# go-restful-swagger12
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/emicklei/go-restful-swagger12.png)](https://travis-ci.org/emicklei/go-restful-swagger12)
|
|
||||||
[![GoDoc](https://godoc.org/github.com/emicklei/go-restful-swagger12?status.svg)](https://godoc.org/github.com/emicklei/go-restful-swagger12)
|
|
||||||
|
|
||||||
How to use Swagger UI with go-restful
|
|
||||||
=
|
|
||||||
|
|
||||||
Get the Swagger UI sources (version 1.2 only)
|
|
||||||
|
|
||||||
git clone https://github.com/wordnik/swagger-ui.git
|
|
||||||
|
|
||||||
The project contains a "dist" folder.
|
|
||||||
Its contents has all the Swagger UI files you need.
|
|
||||||
|
|
||||||
The `index.html` has an `url` set to `http://petstore.swagger.wordnik.com/api/api-docs`.
|
|
||||||
You need to change that to match your WebService JSON endpoint e.g. `http://localhost:8080/apidocs.json`
|
|
||||||
|
|
||||||
Now, you can install the Swagger WebService for serving the Swagger specification in JSON.
|
|
||||||
|
|
||||||
config := swagger.Config{
|
|
||||||
WebServices: restful.RegisteredWebServices(),
|
|
||||||
ApiPath: "/apidocs.json",
|
|
||||||
SwaggerPath: "/apidocs/",
|
|
||||||
SwaggerFilePath: "/Users/emicklei/Projects/swagger-ui/dist"}
|
|
||||||
swagger.InstallSwaggerService(config)
|
|
||||||
|
|
||||||
|
|
||||||
Documenting Structs
|
|
||||||
--
|
|
||||||
|
|
||||||
Currently there are 2 ways to document your structs in the go-restful Swagger.
|
|
||||||
|
|
||||||
###### By using struct tags
|
|
||||||
- Use tag "description" to annotate a struct field with a description to show in the UI
|
|
||||||
- Use tag "modelDescription" to annotate the struct itself with a description to show in the UI. The tag can be added in an field of the struct and in case that there are multiple definition, they will be appended with an empty line.
|
|
||||||
|
|
||||||
###### By using the SwaggerDoc method
|
|
||||||
Here is an example with an `Address` struct and the documentation for each of the fields. The `""` is a special entry for **documenting the struct itself**.
|
|
||||||
|
|
||||||
type Address struct {
|
|
||||||
Country string `json:"country,omitempty"`
|
|
||||||
PostCode int `json:"postcode,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Address) SwaggerDoc() map[string]string {
|
|
||||||
return map[string]string{
|
|
||||||
"": "Address doc",
|
|
||||||
"country": "Country doc",
|
|
||||||
"postcode": "PostCode doc",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
This example will generate a JSON like this
|
|
||||||
|
|
||||||
{
|
|
||||||
"Address": {
|
|
||||||
"id": "Address",
|
|
||||||
"description": "Address doc",
|
|
||||||
"properties": {
|
|
||||||
"country": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Country doc"
|
|
||||||
},
|
|
||||||
"postcode": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32",
|
|
||||||
"description": "PostCode doc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
**Very Important Notes:**
|
|
||||||
- `SwaggerDoc()` is using a **NON-Pointer** receiver (e.g. func (Address) and not func (*Address))
|
|
||||||
- The returned map should use as key the name of the field as defined in the JSON parameter (e.g. `"postcode"` and not `"PostCode"`)
|
|
||||||
|
|
||||||
Notes
|
|
||||||
--
|
|
||||||
- The Nickname of an Operation is automatically set by finding the name of the function. You can override it using RouteBuilder.Operation(..)
|
|
||||||
- The WebServices field of swagger.Config can be used to control which service you want to expose and document ; you can have multiple configs and therefore multiple endpoints.
|
|
||||||
|
|
||||||
© 2017, ernestmicklei.com. MIT License. Contributions welcome.
|
|
|
@ -1,64 +0,0 @@
|
||||||
package swagger
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ApiDeclarationList maintains an ordered list of ApiDeclaration.
|
|
||||||
type ApiDeclarationList struct {
|
|
||||||
List []ApiDeclaration
|
|
||||||
}
|
|
||||||
|
|
||||||
// At returns the ApiDeclaration by its path unless absent, then ok is false
|
|
||||||
func (l *ApiDeclarationList) At(path string) (a ApiDeclaration, ok bool) {
|
|
||||||
for _, each := range l.List {
|
|
||||||
if each.ResourcePath == path {
|
|
||||||
return each, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put adds or replaces a ApiDeclaration with this name
|
|
||||||
func (l *ApiDeclarationList) Put(path string, a ApiDeclaration) {
|
|
||||||
// maybe replace existing
|
|
||||||
for i, each := range l.List {
|
|
||||||
if each.ResourcePath == path {
|
|
||||||
// replace
|
|
||||||
l.List[i] = a
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// add
|
|
||||||
l.List = append(l.List, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do enumerates all the properties, each with its assigned name
|
|
||||||
func (l *ApiDeclarationList) Do(block func(path string, decl ApiDeclaration)) {
|
|
||||||
for _, each := range l.List {
|
|
||||||
block(each.ResourcePath, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty
|
|
||||||
func (l ApiDeclarationList) MarshalJSON() ([]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
encoder := json.NewEncoder(&buf)
|
|
||||||
buf.WriteString("{\n")
|
|
||||||
for i, each := range l.List {
|
|
||||||
buf.WriteString("\"")
|
|
||||||
buf.WriteString(each.ResourcePath)
|
|
||||||
buf.WriteString("\": ")
|
|
||||||
encoder.Encode(each)
|
|
||||||
if i < len(l.List)-1 {
|
|
||||||
buf.WriteString(",\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.WriteString("}")
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package swagger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PostBuildDeclarationMapFunc can be used to modify the api declaration map.
|
|
||||||
type PostBuildDeclarationMapFunc func(apiDeclarationMap *ApiDeclarationList)
|
|
||||||
|
|
||||||
// MapSchemaFormatFunc can be used to modify typeName at definition time.
|
|
||||||
type MapSchemaFormatFunc func(typeName string) string
|
|
||||||
|
|
||||||
// MapModelTypeNameFunc can be used to return the desired typeName for a given
|
|
||||||
// type. It will return false if the default name should be used.
|
|
||||||
type MapModelTypeNameFunc func(t reflect.Type) (string, bool)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
// url where the services are available, e.g. http://localhost:8080
|
|
||||||
// if left empty then the basePath of Swagger is taken from the actual request
|
|
||||||
WebServicesUrl string
|
|
||||||
// path where the JSON api is avaiable , e.g. /apidocs
|
|
||||||
ApiPath string
|
|
||||||
// [optional] path where the swagger UI will be served, e.g. /swagger
|
|
||||||
SwaggerPath string
|
|
||||||
// [optional] location of folder containing Swagger HTML5 application index.html
|
|
||||||
SwaggerFilePath string
|
|
||||||
// api listing is constructed from this list of restful WebServices.
|
|
||||||
WebServices []*restful.WebService
|
|
||||||
// will serve all static content (scripts,pages,images)
|
|
||||||
StaticHandler http.Handler
|
|
||||||
// [optional] on default CORS (Cross-Origin-Resource-Sharing) is enabled.
|
|
||||||
DisableCORS bool
|
|
||||||
// Top-level API version. Is reflected in the resource listing.
|
|
||||||
ApiVersion string
|
|
||||||
// If set then call this handler after building the complete ApiDeclaration Map
|
|
||||||
PostBuildHandler PostBuildDeclarationMapFunc
|
|
||||||
// Swagger global info struct
|
|
||||||
Info Info
|
|
||||||
// [optional] If set, model builder should call this handler to get addition typename-to-swagger-format-field conversion.
|
|
||||||
SchemaFormatHandler MapSchemaFormatFunc
|
|
||||||
// [optional] If set, model builder should call this handler to retrieve the name for a given type.
|
|
||||||
ModelTypeNameHandler MapModelTypeNameFunc
|
|
||||||
}
|
|
|
@ -1,467 +0,0 @@
|
||||||
package swagger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ModelBuildable is used for extending Structs that need more control over
|
|
||||||
// how the Model appears in the Swagger api declaration.
|
|
||||||
type ModelBuildable interface {
|
|
||||||
PostBuildModel(m *Model) *Model
|
|
||||||
}
|
|
||||||
|
|
||||||
type modelBuilder struct {
|
|
||||||
Models *ModelList
|
|
||||||
Config *Config
|
|
||||||
}
|
|
||||||
|
|
||||||
type documentable interface {
|
|
||||||
SwaggerDoc() map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this structure has a method with signature func (<theModel>) SwaggerDoc() map[string]string
|
|
||||||
// If it exists, retrive the documentation and overwrite all struct tag descriptions
|
|
||||||
func getDocFromMethodSwaggerDoc2(model reflect.Type) map[string]string {
|
|
||||||
if docable, ok := reflect.New(model).Elem().Interface().(documentable); ok {
|
|
||||||
return docable.SwaggerDoc()
|
|
||||||
}
|
|
||||||
return make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addModelFrom creates and adds a Model to the builder and detects and calls
|
|
||||||
// the post build hook for customizations
|
|
||||||
func (b modelBuilder) addModelFrom(sample interface{}) {
|
|
||||||
if modelOrNil := b.addModel(reflect.TypeOf(sample), ""); modelOrNil != nil {
|
|
||||||
// allow customizations
|
|
||||||
if buildable, ok := sample.(ModelBuildable); ok {
|
|
||||||
modelOrNil = buildable.PostBuildModel(modelOrNil)
|
|
||||||
b.Models.Put(modelOrNil.Id, *modelOrNil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model {
|
|
||||||
// Turn pointers into simpler types so further checks are
|
|
||||||
// correct.
|
|
||||||
if st.Kind() == reflect.Ptr {
|
|
||||||
st = st.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
modelName := b.keyFrom(st)
|
|
||||||
if nameOverride != "" {
|
|
||||||
modelName = nameOverride
|
|
||||||
}
|
|
||||||
// no models needed for primitive types
|
|
||||||
if b.isPrimitiveType(modelName) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// golang encoding/json packages says array and slice values encode as
|
|
||||||
// JSON arrays, except that []byte encodes as a base64-encoded string.
|
|
||||||
// If we see a []byte here, treat it at as a primitive type (string)
|
|
||||||
// and deal with it in buildArrayTypeProperty.
|
|
||||||
if (st.Kind() == reflect.Slice || st.Kind() == reflect.Array) &&
|
|
||||||
st.Elem().Kind() == reflect.Uint8 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// see if we already have visited this model
|
|
||||||
if _, ok := b.Models.At(modelName); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sm := Model{
|
|
||||||
Id: modelName,
|
|
||||||
Required: []string{},
|
|
||||||
Properties: ModelPropertyList{}}
|
|
||||||
|
|
||||||
// reference the model before further initializing (enables recursive structs)
|
|
||||||
b.Models.Put(modelName, sm)
|
|
||||||
|
|
||||||
// check for slice or array
|
|
||||||
if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
|
|
||||||
b.addModel(st.Elem(), "")
|
|
||||||
return &sm
|
|
||||||
}
|
|
||||||
// check for structure or primitive type
|
|
||||||
if st.Kind() != reflect.Struct {
|
|
||||||
return &sm
|
|
||||||
}
|
|
||||||
|
|
||||||
fullDoc := getDocFromMethodSwaggerDoc2(st)
|
|
||||||
modelDescriptions := []string{}
|
|
||||||
|
|
||||||
for i := 0; i < st.NumField(); i++ {
|
|
||||||
field := st.Field(i)
|
|
||||||
jsonName, modelDescription, prop := b.buildProperty(field, &sm, modelName)
|
|
||||||
if len(modelDescription) > 0 {
|
|
||||||
modelDescriptions = append(modelDescriptions, modelDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
// add if not omitted
|
|
||||||
if len(jsonName) != 0 {
|
|
||||||
// update description
|
|
||||||
if fieldDoc, ok := fullDoc[jsonName]; ok {
|
|
||||||
prop.Description = fieldDoc
|
|
||||||
}
|
|
||||||
// update Required
|
|
||||||
if b.isPropertyRequired(field) {
|
|
||||||
sm.Required = append(sm.Required, jsonName)
|
|
||||||
}
|
|
||||||
sm.Properties.Put(jsonName, prop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We always overwrite documentation if SwaggerDoc method exists
|
|
||||||
// "" is special for documenting the struct itself
|
|
||||||
if modelDoc, ok := fullDoc[""]; ok {
|
|
||||||
sm.Description = modelDoc
|
|
||||||
} else if len(modelDescriptions) != 0 {
|
|
||||||
sm.Description = strings.Join(modelDescriptions, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// update model builder with completed model
|
|
||||||
b.Models.Put(modelName, sm)
|
|
||||||
|
|
||||||
return &sm
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b modelBuilder) isPropertyRequired(field reflect.StructField) bool {
|
|
||||||
required := true
|
|
||||||
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
|
||||||
s := strings.Split(jsonTag, ",")
|
|
||||||
if len(s) > 1 && s[1] == "omitempty" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return required
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, modelName string) (jsonName, modelDescription string, prop ModelProperty) {
|
|
||||||
jsonName = b.jsonNameOfField(field)
|
|
||||||
if len(jsonName) == 0 {
|
|
||||||
// empty name signals skip property
|
|
||||||
return "", "", prop
|
|
||||||
}
|
|
||||||
|
|
||||||
if field.Name == "XMLName" && field.Type.String() == "xml.Name" {
|
|
||||||
// property is metadata for the xml.Name attribute, can be skipped
|
|
||||||
return "", "", prop
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag := field.Tag.Get("modelDescription"); tag != "" {
|
|
||||||
modelDescription = tag
|
|
||||||
}
|
|
||||||
|
|
||||||
prop.setPropertyMetadata(field)
|
|
||||||
if prop.Type != nil {
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
}
|
|
||||||
fieldType := field.Type
|
|
||||||
|
|
||||||
// check if type is doing its own marshalling
|
|
||||||
marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
|
|
||||||
if fieldType.Implements(marshalerType) {
|
|
||||||
var pType = "string"
|
|
||||||
if prop.Type == nil {
|
|
||||||
prop.Type = &pType
|
|
||||||
}
|
|
||||||
if prop.Format == "" {
|
|
||||||
prop.Format = b.jsonSchemaFormat(b.keyFrom(fieldType))
|
|
||||||
}
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if annotation says it is a string
|
|
||||||
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
|
||||||
s := strings.Split(jsonTag, ",")
|
|
||||||
if len(s) > 1 && s[1] == "string" {
|
|
||||||
stringt := "string"
|
|
||||||
prop.Type = &stringt
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldKind := fieldType.Kind()
|
|
||||||
switch {
|
|
||||||
case fieldKind == reflect.Struct:
|
|
||||||
jsonName, prop := b.buildStructTypeProperty(field, jsonName, model)
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
case fieldKind == reflect.Slice || fieldKind == reflect.Array:
|
|
||||||
jsonName, prop := b.buildArrayTypeProperty(field, jsonName, modelName)
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
case fieldKind == reflect.Ptr:
|
|
||||||
jsonName, prop := b.buildPointerTypeProperty(field, jsonName, modelName)
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
case fieldKind == reflect.String:
|
|
||||||
stringt := "string"
|
|
||||||
prop.Type = &stringt
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
case fieldKind == reflect.Map:
|
|
||||||
// if it's a map, it's unstructured, and swagger 1.2 can't handle it
|
|
||||||
objectType := "object"
|
|
||||||
prop.Type = &objectType
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldTypeName := b.keyFrom(fieldType)
|
|
||||||
if b.isPrimitiveType(fieldTypeName) {
|
|
||||||
mapped := b.jsonSchemaType(fieldTypeName)
|
|
||||||
prop.Type = &mapped
|
|
||||||
prop.Format = b.jsonSchemaFormat(fieldTypeName)
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
}
|
|
||||||
modelType := b.keyFrom(fieldType)
|
|
||||||
prop.Ref = &modelType
|
|
||||||
|
|
||||||
if fieldType.Name() == "" { // override type of anonymous structs
|
|
||||||
nestedTypeName := modelName + "." + jsonName
|
|
||||||
prop.Ref = &nestedTypeName
|
|
||||||
b.addModel(fieldType, nestedTypeName)
|
|
||||||
}
|
|
||||||
return jsonName, modelDescription, prop
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasNamedJSONTag(field reflect.StructField) bool {
|
|
||||||
parts := strings.Split(field.Tag.Get("json"), ",")
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, s := range parts[1:] {
|
|
||||||
if s == "inline" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(parts[0]) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonName string, model *Model) (nameJson string, prop ModelProperty) {
|
|
||||||
prop.setPropertyMetadata(field)
|
|
||||||
// Check for type override in tag
|
|
||||||
if prop.Type != nil {
|
|
||||||
return jsonName, prop
|
|
||||||
}
|
|
||||||
fieldType := field.Type
|
|
||||||
// check for anonymous
|
|
||||||
if len(fieldType.Name()) == 0 {
|
|
||||||
// anonymous
|
|
||||||
anonType := model.Id + "." + jsonName
|
|
||||||
b.addModel(fieldType, anonType)
|
|
||||||
prop.Ref = &anonType
|
|
||||||
return jsonName, prop
|
|
||||||
}
|
|
||||||
|
|
||||||
if field.Name == fieldType.Name() && field.Anonymous && !hasNamedJSONTag(field) {
|
|
||||||
// embedded struct
|
|
||||||
sub := modelBuilder{new(ModelList), b.Config}
|
|
||||||
sub.addModel(fieldType, "")
|
|
||||||
subKey := sub.keyFrom(fieldType)
|
|
||||||
// merge properties from sub
|
|
||||||
subModel, _ := sub.Models.At(subKey)
|
|
||||||
subModel.Properties.Do(func(k string, v ModelProperty) {
|
|
||||||
model.Properties.Put(k, v)
|
|
||||||
// if subModel says this property is required then include it
|
|
||||||
required := false
|
|
||||||
for _, each := range subModel.Required {
|
|
||||||
if k == each {
|
|
||||||
required = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if required {
|
|
||||||
model.Required = append(model.Required, k)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// add all new referenced models
|
|
||||||
sub.Models.Do(func(key string, sub Model) {
|
|
||||||
if key != subKey {
|
|
||||||
if _, ok := b.Models.At(key); !ok {
|
|
||||||
b.Models.Put(key, sub)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// empty name signals skip property
|
|
||||||
return "", prop
|
|
||||||
}
|
|
||||||
// simple struct
|
|
||||||
b.addModel(fieldType, "")
|
|
||||||
var pType = b.keyFrom(fieldType)
|
|
||||||
prop.Ref = &pType
|
|
||||||
return jsonName, prop
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
|
|
||||||
// check for type override in tags
|
|
||||||
prop.setPropertyMetadata(field)
|
|
||||||
if prop.Type != nil {
|
|
||||||
return jsonName, prop
|
|
||||||
}
|
|
||||||
fieldType := field.Type
|
|
||||||
if fieldType.Elem().Kind() == reflect.Uint8 {
|
|
||||||
stringt := "string"
|
|
||||||
prop.Type = &stringt
|
|
||||||
return jsonName, prop
|
|
||||||
}
|
|
||||||
var pType = "array"
|
|
||||||
prop.Type = &pType
|
|
||||||
isPrimitive := b.isPrimitiveType(fieldType.Elem().Name())
|
|
||||||
elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem())
|
|
||||||
prop.Items = new(Item)
|
|
||||||
if isPrimitive {
|
|
||||||
mapped := b.jsonSchemaType(elemTypeName)
|
|
||||||
prop.Items.Type = &mapped
|
|
||||||
} else {
|
|
||||||
prop.Items.Ref = &elemTypeName
|
|
||||||
}
|
|
||||||
// add|overwrite model for element type
|
|
||||||
if fieldType.Elem().Kind() == reflect.Ptr {
|
|
||||||
fieldType = fieldType.Elem()
|
|
||||||
}
|
|
||||||
if !isPrimitive {
|
|
||||||
b.addModel(fieldType.Elem(), elemTypeName)
|
|
||||||
}
|
|
||||||
return jsonName, prop
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
|
|
||||||
prop.setPropertyMetadata(field)
|
|
||||||
// Check for type override in tags
|
|
||||||
if prop.Type != nil {
|
|
||||||
return jsonName, prop
|
|
||||||
}
|
|
||||||
fieldType := field.Type
|
|
||||||
|
|
||||||
// override type of pointer to list-likes
|
|
||||||
if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array {
|
|
||||||
var pType = "array"
|
|
||||||
prop.Type = &pType
|
|
||||||
isPrimitive := b.isPrimitiveType(fieldType.Elem().Elem().Name())
|
|
||||||
elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem())
|
|
||||||
if isPrimitive {
|
|
||||||
primName := b.jsonSchemaType(elemName)
|
|
||||||
prop.Items = &Item{Ref: &primName}
|
|
||||||
} else {
|
|
||||||
prop.Items = &Item{Ref: &elemName}
|
|
||||||
}
|
|
||||||
if !isPrimitive {
|
|
||||||
// add|overwrite model for element type
|
|
||||||
b.addModel(fieldType.Elem().Elem(), elemName)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// non-array, pointer type
|
|
||||||
fieldTypeName := b.keyFrom(fieldType.Elem())
|
|
||||||
var pType = b.jsonSchemaType(fieldTypeName) // no star, include pkg path
|
|
||||||
if b.isPrimitiveType(fieldTypeName) {
|
|
||||||
prop.Type = &pType
|
|
||||||
prop.Format = b.jsonSchemaFormat(fieldTypeName)
|
|
||||||
return jsonName, prop
|
|
||||||
}
|
|
||||||
prop.Ref = &pType
|
|
||||||
elemName := ""
|
|
||||||
if fieldType.Elem().Name() == "" {
|
|
||||||
elemName = modelName + "." + jsonName
|
|
||||||
prop.Ref = &elemName
|
|
||||||
}
|
|
||||||
b.addModel(fieldType.Elem(), elemName)
|
|
||||||
}
|
|
||||||
return jsonName, prop
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string {
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
if t.Name() == "" {
|
|
||||||
return modelName + "." + jsonName
|
|
||||||
}
|
|
||||||
return b.keyFrom(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b modelBuilder) keyFrom(st reflect.Type) string {
|
|
||||||
key := st.String()
|
|
||||||
if b.Config != nil && b.Config.ModelTypeNameHandler != nil {
|
|
||||||
if name, ok := b.Config.ModelTypeNameHandler(st); ok {
|
|
||||||
key = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(st.Name()) == 0 { // unnamed type
|
|
||||||
// Swagger UI has special meaning for [
|
|
||||||
key = strings.Replace(key, "[]", "||", -1)
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
// see also https://golang.org/ref/spec#Numeric_types
|
|
||||||
func (b modelBuilder) isPrimitiveType(modelName string) bool {
|
|
||||||
if len(modelName) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return strings.Contains("uint uint8 uint16 uint32 uint64 int int8 int16 int32 int64 float32 float64 bool string byte rune time.Time", modelName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// jsonNameOfField returns the name of the field as it should appear in JSON format
|
|
||||||
// An empty string indicates that this field is not part of the JSON representation
|
|
||||||
func (b modelBuilder) jsonNameOfField(field reflect.StructField) string {
|
|
||||||
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
|
|
||||||
s := strings.Split(jsonTag, ",")
|
|
||||||
if s[0] == "-" {
|
|
||||||
// empty name signals skip property
|
|
||||||
return ""
|
|
||||||
} else if s[0] != "" {
|
|
||||||
return s[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return field.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// see also http://json-schema.org/latest/json-schema-core.html#anchor8
|
|
||||||
func (b modelBuilder) jsonSchemaType(modelName string) string {
|
|
||||||
schemaMap := map[string]string{
|
|
||||||
"uint": "integer",
|
|
||||||
"uint8": "integer",
|
|
||||||
"uint16": "integer",
|
|
||||||
"uint32": "integer",
|
|
||||||
"uint64": "integer",
|
|
||||||
|
|
||||||
"int": "integer",
|
|
||||||
"int8": "integer",
|
|
||||||
"int16": "integer",
|
|
||||||
"int32": "integer",
|
|
||||||
"int64": "integer",
|
|
||||||
|
|
||||||
"byte": "integer",
|
|
||||||
"float64": "number",
|
|
||||||
"float32": "number",
|
|
||||||
"bool": "boolean",
|
|
||||||
"time.Time": "string",
|
|
||||||
}
|
|
||||||
mapped, ok := schemaMap[modelName]
|
|
||||||
if !ok {
|
|
||||||
return modelName // use as is (custom or struct)
|
|
||||||
}
|
|
||||||
return mapped
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b modelBuilder) jsonSchemaFormat(modelName string) string {
|
|
||||||
if b.Config != nil && b.Config.SchemaFormatHandler != nil {
|
|
||||||
if mapped := b.Config.SchemaFormatHandler(modelName); mapped != "" {
|
|
||||||
return mapped
|
|
||||||
}
|
|
||||||
}
|
|
||||||
schemaMap := map[string]string{
|
|
||||||
"int": "int32",
|
|
||||||
"int32": "int32",
|
|
||||||
"int64": "int64",
|
|
||||||
"byte": "byte",
|
|
||||||
"uint": "integer",
|
|
||||||
"uint8": "byte",
|
|
||||||
"float64": "double",
|
|
||||||
"float32": "float",
|
|
||||||
"time.Time": "date-time",
|
|
||||||
"*time.Time": "date-time",
|
|
||||||
}
|
|
||||||
mapped, ok := schemaMap[modelName]
|
|
||||||
if !ok {
|
|
||||||
return "" // no format
|
|
||||||
}
|
|
||||||
return mapped
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
package swagger
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NamedModel associates a name with a Model (not using its Id)
|
|
||||||
type NamedModel struct {
|
|
||||||
Name string
|
|
||||||
Model Model
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModelList encapsulates a list of NamedModel (association)
|
|
||||||
type ModelList struct {
|
|
||||||
List []NamedModel
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put adds or replaces a Model by its name
|
|
||||||
func (l *ModelList) Put(name string, model Model) {
|
|
||||||
for i, each := range l.List {
|
|
||||||
if each.Name == name {
|
|
||||||
// replace
|
|
||||||
l.List[i] = NamedModel{name, model}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// add
|
|
||||||
l.List = append(l.List, NamedModel{name, model})
|
|
||||||
}
|
|
||||||
|
|
||||||
// At returns a Model by its name, ok is false if absent
|
|
||||||
func (l *ModelList) At(name string) (m Model, ok bool) {
|
|
||||||
for _, each := range l.List {
|
|
||||||
if each.Name == name {
|
|
||||||
return each.Model, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do enumerates all the models, each with its assigned name
|
|
||||||
func (l *ModelList) Do(block func(name string, value Model)) {
|
|
||||||
for _, each := range l.List {
|
|
||||||
block(each.Name, each.Model)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON writes the ModelList as if it was a map[string]Model
|
|
||||||
func (l ModelList) MarshalJSON() ([]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
encoder := json.NewEncoder(&buf)
|
|
||||||
buf.WriteString("{\n")
|
|
||||||
for i, each := range l.List {
|
|
||||||
buf.WriteString("\"")
|
|
||||||
buf.WriteString(each.Name)
|
|
||||||
buf.WriteString("\": ")
|
|
||||||
encoder.Encode(each.Model)
|
|
||||||
if i < len(l.List)-1 {
|
|
||||||
buf.WriteString(",\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.WriteString("}")
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON reads back a ModelList. This is an expensive operation.
|
|
||||||
func (l *ModelList) UnmarshalJSON(data []byte) error {
|
|
||||||
raw := map[string]interface{}{}
|
|
||||||
json.NewDecoder(bytes.NewReader(data)).Decode(&raw)
|
|
||||||
for k, v := range raw {
|
|
||||||
// produces JSON bytes for each value
|
|
||||||
data, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var m Model
|
|
||||||
json.NewDecoder(bytes.NewReader(data)).Decode(&m)
|
|
||||||
l.Put(k, m)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
package swagger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (prop *ModelProperty) setDescription(field reflect.StructField) {
|
|
||||||
if tag := field.Tag.Get("description"); tag != "" {
|
|
||||||
prop.Description = tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (prop *ModelProperty) setDefaultValue(field reflect.StructField) {
|
|
||||||
if tag := field.Tag.Get("default"); tag != "" {
|
|
||||||
prop.DefaultValue = Special(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (prop *ModelProperty) setEnumValues(field reflect.StructField) {
|
|
||||||
// We use | to separate the enum values. This value is chosen
|
|
||||||
// since its unlikely to be useful in actual enumeration values.
|
|
||||||
if tag := field.Tag.Get("enum"); tag != "" {
|
|
||||||
prop.Enum = strings.Split(tag, "|")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (prop *ModelProperty) setMaximum(field reflect.StructField) {
|
|
||||||
if tag := field.Tag.Get("maximum"); tag != "" {
|
|
||||||
prop.Maximum = tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (prop *ModelProperty) setType(field reflect.StructField) {
|
|
||||||
if tag := field.Tag.Get("type"); tag != "" {
|
|
||||||
// Check if the first two characters of the type tag are
|
|
||||||
// intended to emulate slice/array behaviour.
|
|
||||||
//
|
|
||||||
// If type is intended to be a slice/array then add the
|
|
||||||
// overriden type to the array item instead of the main property
|
|
||||||
if len(tag) > 2 && tag[0:2] == "[]" {
|
|
||||||
pType := "array"
|
|
||||||
prop.Type = &pType
|
|
||||||
prop.Items = new(Item)
|
|
||||||
|
|
||||||
iType := tag[2:]
|
|
||||||
prop.Items.Type = &iType
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
prop.Type = &tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (prop *ModelProperty) setMinimum(field reflect.StructField) {
|
|
||||||
if tag := field.Tag.Get("minimum"); tag != "" {
|
|
||||||
prop.Minimum = tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (prop *ModelProperty) setUniqueItems(field reflect.StructField) {
|
|
||||||
tag := field.Tag.Get("unique")
|
|
||||||
switch tag {
|
|
||||||
case "true":
|
|
||||||
v := true
|
|
||||||
prop.UniqueItems = &v
|
|
||||||
case "false":
|
|
||||||
v := false
|
|
||||||
prop.UniqueItems = &v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (prop *ModelProperty) setPropertyMetadata(field reflect.StructField) {
|
|
||||||
prop.setDescription(field)
|
|
||||||
prop.setEnumValues(field)
|
|
||||||
prop.setMinimum(field)
|
|
||||||
prop.setMaximum(field)
|
|
||||||
prop.setUniqueItems(field)
|
|
||||||
prop.setDefaultValue(field)
|
|
||||||
prop.setType(field)
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
package swagger
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NamedModelProperty associates a name to a ModelProperty
|
|
||||||
type NamedModelProperty struct {
|
|
||||||
Name string
|
|
||||||
Property ModelProperty
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModelPropertyList encapsulates a list of NamedModelProperty (association)
|
|
||||||
type ModelPropertyList struct {
|
|
||||||
List []NamedModelProperty
|
|
||||||
}
|
|
||||||
|
|
||||||
// At returns the ModelPropety by its name unless absent, then ok is false
|
|
||||||
func (l *ModelPropertyList) At(name string) (p ModelProperty, ok bool) {
|
|
||||||
for _, each := range l.List {
|
|
||||||
if each.Name == name {
|
|
||||||
return each.Property, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put adds or replaces a ModelProperty with this name
|
|
||||||
func (l *ModelPropertyList) Put(name string, prop ModelProperty) {
|
|
||||||
// maybe replace existing
|
|
||||||
for i, each := range l.List {
|
|
||||||
if each.Name == name {
|
|
||||||
// replace
|
|
||||||
l.List[i] = NamedModelProperty{Name: name, Property: prop}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// add
|
|
||||||
l.List = append(l.List, NamedModelProperty{Name: name, Property: prop})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do enumerates all the properties, each with its assigned name
|
|
||||||
func (l *ModelPropertyList) Do(block func(name string, value ModelProperty)) {
|
|
||||||
for _, each := range l.List {
|
|
||||||
block(each.Name, each.Property)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty
|
|
||||||
func (l ModelPropertyList) MarshalJSON() ([]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
encoder := json.NewEncoder(&buf)
|
|
||||||
buf.WriteString("{\n")
|
|
||||||
for i, each := range l.List {
|
|
||||||
buf.WriteString("\"")
|
|
||||||
buf.WriteString(each.Name)
|
|
||||||
buf.WriteString("\": ")
|
|
||||||
encoder.Encode(each.Property)
|
|
||||||
if i < len(l.List)-1 {
|
|
||||||
buf.WriteString(",\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.WriteString("}")
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON reads back a ModelPropertyList. This is an expensive operation.
|
|
||||||
func (l *ModelPropertyList) UnmarshalJSON(data []byte) error {
|
|
||||||
raw := map[string]interface{}{}
|
|
||||||
json.NewDecoder(bytes.NewReader(data)).Decode(&raw)
|
|
||||||
for k, v := range raw {
|
|
||||||
// produces JSON bytes for each value
|
|
||||||
data, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var m ModelProperty
|
|
||||||
json.NewDecoder(bytes.NewReader(data)).Decode(&m)
|
|
||||||
l.Put(k, m)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package swagger
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import "github.com/emicklei/go-restful"
|
|
||||||
|
|
||||||
type orderedRouteMap struct {
|
|
||||||
elements map[string][]restful.Route
|
|
||||||
keys []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newOrderedRouteMap() *orderedRouteMap {
|
|
||||||
return &orderedRouteMap{
|
|
||||||
elements: map[string][]restful.Route{},
|
|
||||||
keys: []string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *orderedRouteMap) Add(key string, route restful.Route) {
|
|
||||||
routes, ok := o.elements[key]
|
|
||||||
if ok {
|
|
||||||
routes = append(routes, route)
|
|
||||||
o.elements[key] = routes
|
|
||||||
return
|
|
||||||
}
|
|
||||||
o.elements[key] = []restful.Route{route}
|
|
||||||
o.keys = append(o.keys, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *orderedRouteMap) Do(block func(key string, routes []restful.Route)) {
|
|
||||||
for _, k := range o.keys {
|
|
||||||
block(k, o.elements[k])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
// Package swagger implements the structures of the Swagger
|
|
||||||
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md
|
|
||||||
package swagger
|
|
||||||
|
|
||||||
const swaggerVersion = "1.2"
|
|
||||||
|
|
||||||
// 4.3.3 Data Type Fields
|
|
||||||
type DataTypeFields struct {
|
|
||||||
Type *string `json:"type,omitempty"` // if Ref not used
|
|
||||||
Ref *string `json:"$ref,omitempty"` // if Type not used
|
|
||||||
Format string `json:"format,omitempty"`
|
|
||||||
DefaultValue Special `json:"defaultValue,omitempty"`
|
|
||||||
Enum []string `json:"enum,omitempty"`
|
|
||||||
Minimum string `json:"minimum,omitempty"`
|
|
||||||
Maximum string `json:"maximum,omitempty"`
|
|
||||||
Items *Item `json:"items,omitempty"`
|
|
||||||
UniqueItems *bool `json:"uniqueItems,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Special string
|
|
||||||
|
|
||||||
// 4.3.4 Items Object
|
|
||||||
type Item struct {
|
|
||||||
Type *string `json:"type,omitempty"`
|
|
||||||
Ref *string `json:"$ref,omitempty"`
|
|
||||||
Format string `json:"format,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1 Resource Listing
|
|
||||||
type ResourceListing struct {
|
|
||||||
SwaggerVersion string `json:"swaggerVersion"` // e.g 1.2
|
|
||||||
Apis []Resource `json:"apis"`
|
|
||||||
ApiVersion string `json:"apiVersion"`
|
|
||||||
Info Info `json:"info"`
|
|
||||||
Authorizations []Authorization `json:"authorizations,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.2 Resource Object
|
|
||||||
type Resource struct {
|
|
||||||
Path string `json:"path"` // relative or absolute, must start with /
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.3 Info Object
|
|
||||||
type Info struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
TermsOfServiceUrl string `json:"termsOfServiceUrl,omitempty"`
|
|
||||||
Contact string `json:"contact,omitempty"`
|
|
||||||
License string `json:"license,omitempty"`
|
|
||||||
LicenseUrl string `json:"licenseUrl,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.5
|
|
||||||
type Authorization struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
PassAs string `json:"passAs"`
|
|
||||||
Keyname string `json:"keyname"`
|
|
||||||
Scopes []Scope `json:"scopes"`
|
|
||||||
GrantTypes []GrantType `json:"grandTypes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.6, 5.2.11
|
|
||||||
type Scope struct {
|
|
||||||
// Required. The name of the scope.
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
// Recommended. A short description of the scope.
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.7
|
|
||||||
type GrantType struct {
|
|
||||||
Implicit Implicit `json:"implicit"`
|
|
||||||
AuthorizationCode AuthorizationCode `json:"authorization_code"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.8 Implicit Object
|
|
||||||
type Implicit struct {
|
|
||||||
// Required. The login endpoint definition.
|
|
||||||
loginEndpoint LoginEndpoint `json:"loginEndpoint"`
|
|
||||||
// An optional alternative name to standard "access_token" OAuth2 parameter.
|
|
||||||
TokenName string `json:"tokenName"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.9 Authorization Code Object
|
|
||||||
type AuthorizationCode struct {
|
|
||||||
TokenRequestEndpoint TokenRequestEndpoint `json:"tokenRequestEndpoint"`
|
|
||||||
TokenEndpoint TokenEndpoint `json:"tokenEndpoint"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.10 Login Endpoint Object
|
|
||||||
type LoginEndpoint struct {
|
|
||||||
// Required. The URL of the authorization endpoint for the implicit grant flow. The value SHOULD be in a URL format.
|
|
||||||
Url string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.11 Token Request Endpoint Object
|
|
||||||
type TokenRequestEndpoint struct {
|
|
||||||
// Required. The URL of the authorization endpoint for the authentication code grant flow. The value SHOULD be in a URL format.
|
|
||||||
Url string `json:"url"`
|
|
||||||
// An optional alternative name to standard "client_id" OAuth2 parameter.
|
|
||||||
ClientIdName string `json:"clientIdName"`
|
|
||||||
// An optional alternative name to the standard "client_secret" OAuth2 parameter.
|
|
||||||
ClientSecretName string `json:"clientSecretName"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.1.12 Token Endpoint Object
|
|
||||||
type TokenEndpoint struct {
|
|
||||||
// Required. The URL of the token endpoint for the authentication code grant flow. The value SHOULD be in a URL format.
|
|
||||||
Url string `json:"url"`
|
|
||||||
// An optional alternative name to standard "access_token" OAuth2 parameter.
|
|
||||||
TokenName string `json:"tokenName"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.2 API Declaration
|
|
||||||
type ApiDeclaration struct {
|
|
||||||
SwaggerVersion string `json:"swaggerVersion"`
|
|
||||||
ApiVersion string `json:"apiVersion"`
|
|
||||||
BasePath string `json:"basePath"`
|
|
||||||
ResourcePath string `json:"resourcePath"` // must start with /
|
|
||||||
Info Info `json:"info"`
|
|
||||||
Apis []Api `json:"apis,omitempty"`
|
|
||||||
Models ModelList `json:"models,omitempty"`
|
|
||||||
Produces []string `json:"produces,omitempty"`
|
|
||||||
Consumes []string `json:"consumes,omitempty"`
|
|
||||||
Authorizations []Authorization `json:"authorizations,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.2.2 API Object
|
|
||||||
type Api struct {
|
|
||||||
Path string `json:"path"` // relative or absolute, must start with /
|
|
||||||
Description string `json:"description"`
|
|
||||||
Operations []Operation `json:"operations,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.2.3 Operation Object
|
|
||||||
type Operation struct {
|
|
||||||
DataTypeFields
|
|
||||||
Method string `json:"method"`
|
|
||||||
Summary string `json:"summary,omitempty"`
|
|
||||||
Notes string `json:"notes,omitempty"`
|
|
||||||
Nickname string `json:"nickname"`
|
|
||||||
Authorizations []Authorization `json:"authorizations,omitempty"`
|
|
||||||
Parameters []Parameter `json:"parameters"`
|
|
||||||
ResponseMessages []ResponseMessage `json:"responseMessages,omitempty"` // optional
|
|
||||||
Produces []string `json:"produces,omitempty"`
|
|
||||||
Consumes []string `json:"consumes,omitempty"`
|
|
||||||
Deprecated string `json:"deprecated,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.2.4 Parameter Object
|
|
||||||
type Parameter struct {
|
|
||||||
DataTypeFields
|
|
||||||
ParamType string `json:"paramType"` // path,query,body,header,form
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Required bool `json:"required"`
|
|
||||||
AllowMultiple bool `json:"allowMultiple"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.2.5 Response Message Object
|
|
||||||
type ResponseMessage struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
ResponseModel string `json:"responseModel,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.2.6, 5.2.7 Models Object
|
|
||||||
type Model struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Required []string `json:"required,omitempty"`
|
|
||||||
Properties ModelPropertyList `json:"properties"`
|
|
||||||
SubTypes []string `json:"subTypes,omitempty"`
|
|
||||||
Discriminator string `json:"discriminator,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.2.8 Properties Object
|
|
||||||
type ModelProperty struct {
|
|
||||||
DataTypeFields
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5.2.10
|
|
||||||
type Authorizations map[string]Authorization
|
|
|
@ -1,21 +0,0 @@
|
||||||
package swagger
|
|
||||||
|
|
||||||
type SwaggerBuilder struct {
|
|
||||||
SwaggerService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSwaggerBuilder(config Config) *SwaggerBuilder {
|
|
||||||
return &SwaggerBuilder{*newSwaggerService(config)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb SwaggerBuilder) ProduceListing() ResourceListing {
|
|
||||||
return sb.SwaggerService.produceListing()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb SwaggerBuilder) ProduceAllDeclarations() map[string]ApiDeclaration {
|
|
||||||
return sb.SwaggerService.produceAllDeclarations()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb SwaggerBuilder) ProduceDeclarations(route string) (*ApiDeclaration, bool) {
|
|
||||||
return sb.SwaggerService.produceDeclarations(route)
|
|
||||||
}
|
|
|
@ -1,443 +0,0 @@
|
||||||
package swagger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
// "github.com/emicklei/hopwatch"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SwaggerService struct {
|
|
||||||
config Config
|
|
||||||
apiDeclarationMap *ApiDeclarationList
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSwaggerService(config Config) *SwaggerService {
|
|
||||||
sws := &SwaggerService{
|
|
||||||
config: config,
|
|
||||||
apiDeclarationMap: new(ApiDeclarationList)}
|
|
||||||
|
|
||||||
// Build all ApiDeclarations
|
|
||||||
for _, each := range config.WebServices {
|
|
||||||
rootPath := each.RootPath()
|
|
||||||
// skip the api service itself
|
|
||||||
if rootPath != config.ApiPath {
|
|
||||||
if rootPath == "" || rootPath == "/" {
|
|
||||||
// use routes
|
|
||||||
for _, route := range each.Routes() {
|
|
||||||
entry := staticPathFromRoute(route)
|
|
||||||
_, exists := sws.apiDeclarationMap.At(entry)
|
|
||||||
if !exists {
|
|
||||||
sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // use root path
|
|
||||||
sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if specified then call the PostBuilderHandler
|
|
||||||
if config.PostBuildHandler != nil {
|
|
||||||
config.PostBuildHandler(sws.apiDeclarationMap)
|
|
||||||
}
|
|
||||||
return sws
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogInfo is the function that is called when this package needs to log. It defaults to log.Printf
|
|
||||||
var LogInfo = func(format string, v ...interface{}) {
|
|
||||||
// use the restful package-wide logger
|
|
||||||
log.Printf(format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallSwaggerService add the WebService that provides the API documentation of all services
|
|
||||||
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
|
|
||||||
func InstallSwaggerService(aSwaggerConfig Config) {
|
|
||||||
RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterSwaggerService add the WebService that provides the API documentation of all services
|
|
||||||
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
|
|
||||||
func RegisterSwaggerService(config Config, wsContainer *restful.Container) {
|
|
||||||
sws := newSwaggerService(config)
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path(config.ApiPath)
|
|
||||||
ws.Produces(restful.MIME_JSON)
|
|
||||||
if config.DisableCORS {
|
|
||||||
ws.Filter(enableCORS)
|
|
||||||
}
|
|
||||||
ws.Route(ws.GET("/").To(sws.getListing))
|
|
||||||
ws.Route(ws.GET("/{a}").To(sws.getDeclarations))
|
|
||||||
ws.Route(ws.GET("/{a}/{b}").To(sws.getDeclarations))
|
|
||||||
ws.Route(ws.GET("/{a}/{b}/{c}").To(sws.getDeclarations))
|
|
||||||
ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(sws.getDeclarations))
|
|
||||||
ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(sws.getDeclarations))
|
|
||||||
ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(sws.getDeclarations))
|
|
||||||
ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(sws.getDeclarations))
|
|
||||||
LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
|
|
||||||
wsContainer.Add(ws)
|
|
||||||
|
|
||||||
// Check paths for UI serving
|
|
||||||
if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
|
|
||||||
swaggerPathSlash := config.SwaggerPath
|
|
||||||
// path must end with slash /
|
|
||||||
if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
|
|
||||||
LogInfo("[restful/swagger] use corrected SwaggerPath ; must end with slash (/)")
|
|
||||||
swaggerPathSlash += "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
LogInfo("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, swaggerPathSlash, config.SwaggerFilePath)
|
|
||||||
wsContainer.Handle(swaggerPathSlash, http.StripPrefix(swaggerPathSlash, http.FileServer(http.Dir(config.SwaggerFilePath))))
|
|
||||||
|
|
||||||
//if we define a custom static handler use it
|
|
||||||
} else if config.StaticHandler != nil && config.SwaggerPath != "" {
|
|
||||||
swaggerPathSlash := config.SwaggerPath
|
|
||||||
// path must end with slash /
|
|
||||||
if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
|
|
||||||
LogInfo("[restful/swagger] use corrected SwaggerFilePath ; must end with slash (/)")
|
|
||||||
swaggerPathSlash += "/"
|
|
||||||
|
|
||||||
}
|
|
||||||
LogInfo("[restful/swagger] %v%v is mapped to custom Handler %T", config.WebServicesUrl, swaggerPathSlash, config.StaticHandler)
|
|
||||||
wsContainer.Handle(swaggerPathSlash, config.StaticHandler)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
LogInfo("[restful/swagger] Swagger(File)Path is empty ; no UI is served")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func staticPathFromRoute(r restful.Route) string {
|
|
||||||
static := r.Path
|
|
||||||
bracket := strings.Index(static, "{")
|
|
||||||
if bracket <= 1 { // result cannot be empty
|
|
||||||
return static
|
|
||||||
}
|
|
||||||
if bracket != -1 {
|
|
||||||
static = r.Path[:bracket]
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(static, "/") {
|
|
||||||
return static[:len(static)-1]
|
|
||||||
} else {
|
|
||||||
return static
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
if origin := req.HeaderParameter(restful.HEADER_Origin); origin != "" {
|
|
||||||
// prevent duplicate header
|
|
||||||
if len(resp.Header().Get(restful.HEADER_AccessControlAllowOrigin)) == 0 {
|
|
||||||
resp.AddHeader(restful.HEADER_AccessControlAllowOrigin, origin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) {
|
|
||||||
listing := sws.produceListing()
|
|
||||||
resp.WriteAsJson(listing)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sws SwaggerService) produceListing() ResourceListing {
|
|
||||||
listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion, Info: sws.config.Info}
|
|
||||||
sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
|
|
||||||
ref := Resource{Path: k}
|
|
||||||
if len(v.Apis) > 0 { // use description of first (could still be empty)
|
|
||||||
ref.Description = v.Apis[0].Description
|
|
||||||
}
|
|
||||||
listing.Apis = append(listing.Apis, ref)
|
|
||||||
})
|
|
||||||
return listing
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) {
|
|
||||||
decl, ok := sws.produceDeclarations(composeRootPath(req))
|
|
||||||
if !ok {
|
|
||||||
resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// unless WebServicesUrl is given
|
|
||||||
if len(sws.config.WebServicesUrl) == 0 {
|
|
||||||
// update base path from the actual request
|
|
||||||
// TODO how to detect https? assume http for now
|
|
||||||
var host string
|
|
||||||
// X-Forwarded-Host or Host or Request.Host
|
|
||||||
hostvalues, ok := req.Request.Header["X-Forwarded-Host"] // apache specific?
|
|
||||||
if !ok || len(hostvalues) == 0 {
|
|
||||||
forwarded, ok := req.Request.Header["Host"] // without reverse-proxy
|
|
||||||
if !ok || len(forwarded) == 0 {
|
|
||||||
// fallback to Host field
|
|
||||||
host = req.Request.Host
|
|
||||||
} else {
|
|
||||||
host = forwarded[0]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
host = hostvalues[0]
|
|
||||||
}
|
|
||||||
// inspect Referer for the scheme (http vs https)
|
|
||||||
scheme := "http"
|
|
||||||
if referer := req.Request.Header["Referer"]; len(referer) > 0 {
|
|
||||||
if strings.HasPrefix(referer[0], "https") {
|
|
||||||
scheme = "https"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
decl.BasePath = fmt.Sprintf("%s://%s", scheme, host)
|
|
||||||
}
|
|
||||||
resp.WriteAsJson(decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sws SwaggerService) produceAllDeclarations() map[string]ApiDeclaration {
|
|
||||||
decls := map[string]ApiDeclaration{}
|
|
||||||
sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
|
|
||||||
decls[k] = v
|
|
||||||
})
|
|
||||||
return decls
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sws SwaggerService) produceDeclarations(route string) (*ApiDeclaration, bool) {
|
|
||||||
decl, ok := sws.apiDeclarationMap.At(route)
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
decl.BasePath = sws.config.WebServicesUrl
|
|
||||||
return &decl, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// composeDeclaration uses all routes and parameters to create a ApiDeclaration
|
|
||||||
func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration {
|
|
||||||
decl := ApiDeclaration{
|
|
||||||
SwaggerVersion: swaggerVersion,
|
|
||||||
BasePath: sws.config.WebServicesUrl,
|
|
||||||
ResourcePath: pathPrefix,
|
|
||||||
Models: ModelList{},
|
|
||||||
ApiVersion: ws.Version()}
|
|
||||||
|
|
||||||
// collect any path parameters
|
|
||||||
rootParams := []Parameter{}
|
|
||||||
for _, param := range ws.PathParameters() {
|
|
||||||
rootParams = append(rootParams, asSwaggerParameter(param.Data()))
|
|
||||||
}
|
|
||||||
// aggregate by path
|
|
||||||
pathToRoutes := newOrderedRouteMap()
|
|
||||||
for _, other := range ws.Routes() {
|
|
||||||
if strings.HasPrefix(other.Path, pathPrefix) {
|
|
||||||
if len(pathPrefix) > 1 && len(other.Path) > len(pathPrefix) && other.Path[len(pathPrefix)] != '/' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pathToRoutes.Add(other.Path, other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pathToRoutes.Do(func(path string, routes []restful.Route) {
|
|
||||||
api := Api{Path: strings.TrimSuffix(withoutWildcard(path), "/"), Description: ws.Documentation()}
|
|
||||||
voidString := "void"
|
|
||||||
for _, route := range routes {
|
|
||||||
operation := Operation{
|
|
||||||
Method: route.Method,
|
|
||||||
Summary: route.Doc,
|
|
||||||
Notes: route.Notes,
|
|
||||||
// Type gets overwritten if there is a write sample
|
|
||||||
DataTypeFields: DataTypeFields{Type: &voidString},
|
|
||||||
Parameters: []Parameter{},
|
|
||||||
Nickname: route.Operation,
|
|
||||||
ResponseMessages: composeResponseMessages(route, &decl, &sws.config)}
|
|
||||||
|
|
||||||
operation.Consumes = route.Consumes
|
|
||||||
operation.Produces = route.Produces
|
|
||||||
|
|
||||||
// share root params if any
|
|
||||||
for _, swparam := range rootParams {
|
|
||||||
operation.Parameters = append(operation.Parameters, swparam)
|
|
||||||
}
|
|
||||||
// route specific params
|
|
||||||
for _, param := range route.ParameterDocs {
|
|
||||||
operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data()))
|
|
||||||
}
|
|
||||||
|
|
||||||
sws.addModelsFromRouteTo(&operation, route, &decl)
|
|
||||||
api.Operations = append(api.Operations, operation)
|
|
||||||
}
|
|
||||||
decl.Apis = append(decl.Apis, api)
|
|
||||||
})
|
|
||||||
return decl
|
|
||||||
}
|
|
||||||
|
|
||||||
func withoutWildcard(path string) string {
|
|
||||||
if strings.HasSuffix(path, ":*}") {
|
|
||||||
return path[0:len(path)-3] + "}"
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them.
|
|
||||||
func composeResponseMessages(route restful.Route, decl *ApiDeclaration, config *Config) (messages []ResponseMessage) {
|
|
||||||
if route.ResponseErrors == nil {
|
|
||||||
return messages
|
|
||||||
}
|
|
||||||
// sort by code
|
|
||||||
codes := sort.IntSlice{}
|
|
||||||
for code := range route.ResponseErrors {
|
|
||||||
codes = append(codes, code)
|
|
||||||
}
|
|
||||||
codes.Sort()
|
|
||||||
for _, code := range codes {
|
|
||||||
each := route.ResponseErrors[code]
|
|
||||||
message := ResponseMessage{
|
|
||||||
Code: code,
|
|
||||||
Message: each.Message,
|
|
||||||
}
|
|
||||||
if each.Model != nil {
|
|
||||||
st := reflect.TypeOf(each.Model)
|
|
||||||
isCollection, st := detectCollectionType(st)
|
|
||||||
// collection cannot be in responsemodel
|
|
||||||
if !isCollection {
|
|
||||||
modelName := modelBuilder{}.keyFrom(st)
|
|
||||||
modelBuilder{Models: &decl.Models, Config: config}.addModel(st, "")
|
|
||||||
message.ResponseModel = modelName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
messages = append(messages, message)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it.
|
|
||||||
func (sws SwaggerService) addModelsFromRouteTo(operation *Operation, route restful.Route, decl *ApiDeclaration) {
|
|
||||||
if route.ReadSample != nil {
|
|
||||||
sws.addModelFromSampleTo(operation, false, route.ReadSample, &decl.Models)
|
|
||||||
}
|
|
||||||
if route.WriteSample != nil {
|
|
||||||
sws.addModelFromSampleTo(operation, true, route.WriteSample, &decl.Models)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectCollectionType(st reflect.Type) (bool, reflect.Type) {
|
|
||||||
isCollection := false
|
|
||||||
if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
|
|
||||||
st = st.Elem()
|
|
||||||
isCollection = true
|
|
||||||
} else {
|
|
||||||
if st.Kind() == reflect.Ptr {
|
|
||||||
if st.Elem().Kind() == reflect.Slice || st.Elem().Kind() == reflect.Array {
|
|
||||||
st = st.Elem().Elem()
|
|
||||||
isCollection = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isCollection, st
|
|
||||||
}
|
|
||||||
|
|
||||||
// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
|
|
||||||
func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) {
|
|
||||||
mb := modelBuilder{Models: models, Config: &sws.config}
|
|
||||||
if isResponse {
|
|
||||||
sampleType, items := asDataType(sample, &sws.config)
|
|
||||||
operation.Type = sampleType
|
|
||||||
operation.Items = items
|
|
||||||
}
|
|
||||||
mb.addModelFrom(sample)
|
|
||||||
}
|
|
||||||
|
|
||||||
func asSwaggerParameter(param restful.ParameterData) Parameter {
|
|
||||||
return Parameter{
|
|
||||||
DataTypeFields: DataTypeFields{
|
|
||||||
Type: ¶m.DataType,
|
|
||||||
Format: asFormat(param.DataType, param.DataFormat),
|
|
||||||
DefaultValue: Special(param.DefaultValue),
|
|
||||||
},
|
|
||||||
Name: param.Name,
|
|
||||||
Description: param.Description,
|
|
||||||
ParamType: asParamType(param.Kind),
|
|
||||||
|
|
||||||
Required: param.Required}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Between 1..7 path parameters is supported
|
|
||||||
func composeRootPath(req *restful.Request) string {
|
|
||||||
path := "/" + req.PathParameter("a")
|
|
||||||
b := req.PathParameter("b")
|
|
||||||
if b == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
path = path + "/" + b
|
|
||||||
c := req.PathParameter("c")
|
|
||||||
if c == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
path = path + "/" + c
|
|
||||||
d := req.PathParameter("d")
|
|
||||||
if d == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
path = path + "/" + d
|
|
||||||
e := req.PathParameter("e")
|
|
||||||
if e == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
path = path + "/" + e
|
|
||||||
f := req.PathParameter("f")
|
|
||||||
if f == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
path = path + "/" + f
|
|
||||||
g := req.PathParameter("g")
|
|
||||||
if g == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
return path + "/" + g
|
|
||||||
}
|
|
||||||
|
|
||||||
func asFormat(dataType string, dataFormat string) string {
|
|
||||||
if dataFormat != "" {
|
|
||||||
return dataFormat
|
|
||||||
}
|
|
||||||
return "" // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
func asParamType(kind int) string {
|
|
||||||
switch {
|
|
||||||
case kind == restful.PathParameterKind:
|
|
||||||
return "path"
|
|
||||||
case kind == restful.QueryParameterKind:
|
|
||||||
return "query"
|
|
||||||
case kind == restful.BodyParameterKind:
|
|
||||||
return "body"
|
|
||||||
case kind == restful.HeaderParameterKind:
|
|
||||||
return "header"
|
|
||||||
case kind == restful.FormParameterKind:
|
|
||||||
return "form"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func asDataType(any interface{}, config *Config) (*string, *Item) {
|
|
||||||
// If it's not a collection, return the suggested model name
|
|
||||||
st := reflect.TypeOf(any)
|
|
||||||
isCollection, st := detectCollectionType(st)
|
|
||||||
modelName := modelBuilder{}.keyFrom(st)
|
|
||||||
// if it's not a collection we are done
|
|
||||||
if !isCollection {
|
|
||||||
return &modelName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: This is not very elegant
|
|
||||||
// We create an Item object referring to the given model
|
|
||||||
models := ModelList{}
|
|
||||||
mb := modelBuilder{Models: &models, Config: config}
|
|
||||||
mb.addModelFrom(any)
|
|
||||||
|
|
||||||
elemTypeName := mb.getElementTypeName(modelName, "", st)
|
|
||||||
item := new(Item)
|
|
||||||
if mb.isPrimitiveType(elemTypeName) {
|
|
||||||
mapped := mb.jsonSchemaType(elemTypeName)
|
|
||||||
item.Type = &mapped
|
|
||||||
} else {
|
|
||||||
item.Ref = &elemTypeName
|
|
||||||
}
|
|
||||||
tmp := "array"
|
|
||||||
return &tmp, item
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
Copyright (c) 2012,2013 Ernest Micklei
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,74 +0,0 @@
|
||||||
go-restful
|
|
||||||
==========
|
|
||||||
package for building REST-style Web Services using Google Go
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/emicklei/go-restful.png)](https://travis-ci.org/emicklei/go-restful)
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/emicklei/go-restful)](https://goreportcard.com/report/github.com/emicklei/go-restful)
|
|
||||||
[![GoDoc](https://godoc.org/github.com/emicklei/go-restful?status.svg)](https://godoc.org/github.com/emicklei/go-restful)
|
|
||||||
|
|
||||||
- [Code examples](https://github.com/emicklei/go-restful/tree/master/examples)
|
|
||||||
|
|
||||||
REST asks developers to use HTTP methods explicitly and in a way that's consistent with the protocol definition. This basic REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods. According to this mapping:
|
|
||||||
|
|
||||||
- GET = Retrieve a representation of a resource
|
|
||||||
- POST = Create if you are sending content to the server to create a subordinate of the specified resource collection, using some server-side algorithm.
|
|
||||||
- PUT = Create if you are sending the full content of the specified resource (URI).
|
|
||||||
- PUT = Update if you are updating the full content of the specified resource.
|
|
||||||
- DELETE = Delete if you are requesting the server to delete the resource
|
|
||||||
- PATCH = Update partial content of a resource
|
|
||||||
- OPTIONS = Get information about the communication options for the request URI
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```Go
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser).
|
|
||||||
Doc("get a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
|
|
||||||
Writes(User{}))
|
|
||||||
...
|
|
||||||
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[Full API of a UserResource](https://github.com/emicklei/go-restful/tree/master/examples/restful-user-resource.go)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Routes for request → function mapping with path parameter (e.g. {id}) support
|
|
||||||
- Configurable router:
|
|
||||||
- (default) Fast routing algorithm that allows static elements, regular expressions and dynamic parameters in the URL path (e.g. /meetings/{id} or /static/{subpath:*}
|
|
||||||
- Routing algorithm after [JSR311](http://jsr311.java.net/nonav/releases/1.1/spec/spec.html) that is implemented using (but does **not** accept) regular expressions
|
|
||||||
- Request API for reading structs from JSON/XML and accesing parameters (path,query,header)
|
|
||||||
- Response API for writing structs to JSON/XML and setting headers
|
|
||||||
- Customizable encoding using EntityReaderWriter registration
|
|
||||||
- Filters for intercepting the request → response flow on Service or Route level
|
|
||||||
- Request-scoped variables using attributes
|
|
||||||
- Containers for WebServices on different HTTP endpoints
|
|
||||||
- Content encoding (gzip,deflate) of request and response payloads
|
|
||||||
- Automatic responses on OPTIONS (using a filter)
|
|
||||||
- Automatic CORS request handling (using a filter)
|
|
||||||
- API declaration for Swagger UI (see [go-restful-swagger12](https://github.com/emicklei/go-restful-swagger12),[go-restful-openapi](https://github.com/emicklei/go-restful-openapi))
|
|
||||||
- Panic recovery to produce HTTP 500, customizable using RecoverHandler(...)
|
|
||||||
- Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...)
|
|
||||||
- Configurable (trace) logging
|
|
||||||
- Customizable gzip/deflate readers and writers using CompressorProvider registration
|
|
||||||
|
|
||||||
### Resources
|
|
||||||
|
|
||||||
- [Example posted on blog](http://ernestmicklei.com/2012/11/go-restful-first-working-example/)
|
|
||||||
- [Design explained on blog](http://ernestmicklei.com/2012/11/go-restful-api-design/)
|
|
||||||
- [sourcegraph](https://sourcegraph.com/github.com/emicklei/go-restful)
|
|
||||||
- [showcase: Mora - MongoDB REST Api server](https://github.com/emicklei/mora)
|
|
||||||
|
|
||||||
Type ```git shortlog -s``` for a full list of contributors.
|
|
||||||
|
|
||||||
© 2012 - 2017, http://ernestmicklei.com. MIT License. Contributions are welcome.
|
|
|
@ -1,123 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OBSOLETE : use restful.DefaultContainer.EnableContentEncoding(true) to change this setting.
|
|
||||||
var EnableContentEncoding = false
|
|
||||||
|
|
||||||
// CompressingResponseWriter is a http.ResponseWriter that can perform content encoding (gzip and zlib)
|
|
||||||
type CompressingResponseWriter struct {
|
|
||||||
writer http.ResponseWriter
|
|
||||||
compressor io.WriteCloser
|
|
||||||
encoding string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header is part of http.ResponseWriter interface
|
|
||||||
func (c *CompressingResponseWriter) Header() http.Header {
|
|
||||||
return c.writer.Header()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader is part of http.ResponseWriter interface
|
|
||||||
func (c *CompressingResponseWriter) WriteHeader(status int) {
|
|
||||||
c.writer.WriteHeader(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write is part of http.ResponseWriter interface
|
|
||||||
// It is passed through the compressor
|
|
||||||
func (c *CompressingResponseWriter) Write(bytes []byte) (int, error) {
|
|
||||||
if c.isCompressorClosed() {
|
|
||||||
return -1, errors.New("Compressing error: tried to write data using closed compressor")
|
|
||||||
}
|
|
||||||
return c.compressor.Write(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseNotify is part of http.CloseNotifier interface
|
|
||||||
func (c *CompressingResponseWriter) CloseNotify() <-chan bool {
|
|
||||||
return c.writer.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the underlying compressor
|
|
||||||
func (c *CompressingResponseWriter) Close() error {
|
|
||||||
if c.isCompressorClosed() {
|
|
||||||
return errors.New("Compressing error: tried to close already closed compressor")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.compressor.Close()
|
|
||||||
if ENCODING_GZIP == c.encoding {
|
|
||||||
currentCompressorProvider.ReleaseGzipWriter(c.compressor.(*gzip.Writer))
|
|
||||||
}
|
|
||||||
if ENCODING_DEFLATE == c.encoding {
|
|
||||||
currentCompressorProvider.ReleaseZlibWriter(c.compressor.(*zlib.Writer))
|
|
||||||
}
|
|
||||||
// gc hint needed?
|
|
||||||
c.compressor = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompressingResponseWriter) isCompressorClosed() bool {
|
|
||||||
return nil == c.compressor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hijack implements the Hijacker interface
|
|
||||||
// This is especially useful when combining Container.EnabledContentEncoding
|
|
||||||
// in combination with websockets (for instance gorilla/websocket)
|
|
||||||
func (c *CompressingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
hijacker, ok := c.writer.(http.Hijacker)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, errors.New("ResponseWriter doesn't support Hijacker interface")
|
|
||||||
}
|
|
||||||
return hijacker.Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested.
|
|
||||||
func wantsCompressedResponse(httpRequest *http.Request) (bool, string) {
|
|
||||||
header := httpRequest.Header.Get(HEADER_AcceptEncoding)
|
|
||||||
gi := strings.Index(header, ENCODING_GZIP)
|
|
||||||
zi := strings.Index(header, ENCODING_DEFLATE)
|
|
||||||
// use in order of appearance
|
|
||||||
if gi == -1 {
|
|
||||||
return zi != -1, ENCODING_DEFLATE
|
|
||||||
} else if zi == -1 {
|
|
||||||
return gi != -1, ENCODING_GZIP
|
|
||||||
} else {
|
|
||||||
if gi < zi {
|
|
||||||
return true, ENCODING_GZIP
|
|
||||||
}
|
|
||||||
return true, ENCODING_DEFLATE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCompressingResponseWriter create a CompressingResponseWriter for a known encoding = {gzip,deflate}
|
|
||||||
func NewCompressingResponseWriter(httpWriter http.ResponseWriter, encoding string) (*CompressingResponseWriter, error) {
|
|
||||||
httpWriter.Header().Set(HEADER_ContentEncoding, encoding)
|
|
||||||
c := new(CompressingResponseWriter)
|
|
||||||
c.writer = httpWriter
|
|
||||||
var err error
|
|
||||||
if ENCODING_GZIP == encoding {
|
|
||||||
w := currentCompressorProvider.AcquireGzipWriter()
|
|
||||||
w.Reset(httpWriter)
|
|
||||||
c.compressor = w
|
|
||||||
c.encoding = ENCODING_GZIP
|
|
||||||
} else if ENCODING_DEFLATE == encoding {
|
|
||||||
w := currentCompressorProvider.AcquireZlibWriter()
|
|
||||||
w.Reset(httpWriter)
|
|
||||||
c.compressor = w
|
|
||||||
c.encoding = ENCODING_DEFLATE
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("Unknown encoding:" + encoding)
|
|
||||||
}
|
|
||||||
return c, err
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BoundedCachedCompressors is a CompressorProvider that uses a cache with a fixed amount
|
|
||||||
// of writers and readers (resources).
|
|
||||||
// If a new resource is acquired and all are in use, it will return a new unmanaged resource.
|
|
||||||
type BoundedCachedCompressors struct {
|
|
||||||
gzipWriters chan *gzip.Writer
|
|
||||||
gzipReaders chan *gzip.Reader
|
|
||||||
zlibWriters chan *zlib.Writer
|
|
||||||
writersCapacity int
|
|
||||||
readersCapacity int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBoundedCachedCompressors returns a new, with filled cache, BoundedCachedCompressors.
|
|
||||||
func NewBoundedCachedCompressors(writersCapacity, readersCapacity int) *BoundedCachedCompressors {
|
|
||||||
b := &BoundedCachedCompressors{
|
|
||||||
gzipWriters: make(chan *gzip.Writer, writersCapacity),
|
|
||||||
gzipReaders: make(chan *gzip.Reader, readersCapacity),
|
|
||||||
zlibWriters: make(chan *zlib.Writer, writersCapacity),
|
|
||||||
writersCapacity: writersCapacity,
|
|
||||||
readersCapacity: readersCapacity,
|
|
||||||
}
|
|
||||||
for ix := 0; ix < writersCapacity; ix++ {
|
|
||||||
b.gzipWriters <- newGzipWriter()
|
|
||||||
b.zlibWriters <- newZlibWriter()
|
|
||||||
}
|
|
||||||
for ix := 0; ix < readersCapacity; ix++ {
|
|
||||||
b.gzipReaders <- newGzipReader()
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcquireGzipWriter returns an resettable *gzip.Writer. Needs to be released.
|
|
||||||
func (b *BoundedCachedCompressors) AcquireGzipWriter() *gzip.Writer {
|
|
||||||
var writer *gzip.Writer
|
|
||||||
select {
|
|
||||||
case writer, _ = <-b.gzipWriters:
|
|
||||||
default:
|
|
||||||
// return a new unmanaged one
|
|
||||||
writer = newGzipWriter()
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseGzipWriter accepts a writer (does not have to be one that was cached)
|
|
||||||
// only when the cache has room for it. It will ignore it otherwise.
|
|
||||||
func (b *BoundedCachedCompressors) ReleaseGzipWriter(w *gzip.Writer) {
|
|
||||||
// forget the unmanaged ones
|
|
||||||
if len(b.gzipWriters) < b.writersCapacity {
|
|
||||||
b.gzipWriters <- w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcquireGzipReader returns a *gzip.Reader. Needs to be released.
|
|
||||||
func (b *BoundedCachedCompressors) AcquireGzipReader() *gzip.Reader {
|
|
||||||
var reader *gzip.Reader
|
|
||||||
select {
|
|
||||||
case reader, _ = <-b.gzipReaders:
|
|
||||||
default:
|
|
||||||
// return a new unmanaged one
|
|
||||||
reader = newGzipReader()
|
|
||||||
}
|
|
||||||
return reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseGzipReader accepts a reader (does not have to be one that was cached)
|
|
||||||
// only when the cache has room for it. It will ignore it otherwise.
|
|
||||||
func (b *BoundedCachedCompressors) ReleaseGzipReader(r *gzip.Reader) {
|
|
||||||
// forget the unmanaged ones
|
|
||||||
if len(b.gzipReaders) < b.readersCapacity {
|
|
||||||
b.gzipReaders <- r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcquireZlibWriter returns an resettable *zlib.Writer. Needs to be released.
|
|
||||||
func (b *BoundedCachedCompressors) AcquireZlibWriter() *zlib.Writer {
|
|
||||||
var writer *zlib.Writer
|
|
||||||
select {
|
|
||||||
case writer, _ = <-b.zlibWriters:
|
|
||||||
default:
|
|
||||||
// return a new unmanaged one
|
|
||||||
writer = newZlibWriter()
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseZlibWriter accepts a writer (does not have to be one that was cached)
|
|
||||||
// only when the cache has room for it. It will ignore it otherwise.
|
|
||||||
func (b *BoundedCachedCompressors) ReleaseZlibWriter(w *zlib.Writer) {
|
|
||||||
// forget the unmanaged ones
|
|
||||||
if len(b.zlibWriters) < b.writersCapacity {
|
|
||||||
b.zlibWriters <- w
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SyncPoolCompessors is a CompressorProvider that use the standard sync.Pool.
|
|
||||||
type SyncPoolCompessors struct {
|
|
||||||
GzipWriterPool *sync.Pool
|
|
||||||
GzipReaderPool *sync.Pool
|
|
||||||
ZlibWriterPool *sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSyncPoolCompessors returns a new ("empty") SyncPoolCompessors.
|
|
||||||
func NewSyncPoolCompessors() *SyncPoolCompessors {
|
|
||||||
return &SyncPoolCompessors{
|
|
||||||
GzipWriterPool: &sync.Pool{
|
|
||||||
New: func() interface{} { return newGzipWriter() },
|
|
||||||
},
|
|
||||||
GzipReaderPool: &sync.Pool{
|
|
||||||
New: func() interface{} { return newGzipReader() },
|
|
||||||
},
|
|
||||||
ZlibWriterPool: &sync.Pool{
|
|
||||||
New: func() interface{} { return newZlibWriter() },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) AcquireGzipWriter() *gzip.Writer {
|
|
||||||
return s.GzipWriterPool.Get().(*gzip.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) ReleaseGzipWriter(w *gzip.Writer) {
|
|
||||||
s.GzipWriterPool.Put(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) AcquireGzipReader() *gzip.Reader {
|
|
||||||
return s.GzipReaderPool.Get().(*gzip.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) ReleaseGzipReader(r *gzip.Reader) {
|
|
||||||
s.GzipReaderPool.Put(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) AcquireZlibWriter() *zlib.Writer {
|
|
||||||
return s.ZlibWriterPool.Get().(*zlib.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) ReleaseZlibWriter(w *zlib.Writer) {
|
|
||||||
s.ZlibWriterPool.Put(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGzipWriter() *gzip.Writer {
|
|
||||||
// create with an empty bytes writer; it will be replaced before using the gzipWriter
|
|
||||||
writer, err := gzip.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGzipReader() *gzip.Reader {
|
|
||||||
// create with an empty reader (but with GZIP header); it will be replaced before using the gzipReader
|
|
||||||
// we can safely use currentCompressProvider because it is set on package initialization.
|
|
||||||
w := currentCompressorProvider.AcquireGzipWriter()
|
|
||||||
defer currentCompressorProvider.ReleaseGzipWriter(w)
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
w.Reset(b)
|
|
||||||
w.Flush()
|
|
||||||
w.Close()
|
|
||||||
reader, err := gzip.NewReader(bytes.NewReader(b.Bytes()))
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func newZlibWriter() *zlib.Writer {
|
|
||||||
writer, err := zlib.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CompressorProvider describes a component that can provider compressors for the std methods.
|
|
||||||
type CompressorProvider interface {
|
|
||||||
// Returns a *gzip.Writer which needs to be released later.
|
|
||||||
// Before using it, call Reset().
|
|
||||||
AcquireGzipWriter() *gzip.Writer
|
|
||||||
|
|
||||||
// Releases an aqcuired *gzip.Writer.
|
|
||||||
ReleaseGzipWriter(w *gzip.Writer)
|
|
||||||
|
|
||||||
// Returns a *gzip.Reader which needs to be released later.
|
|
||||||
AcquireGzipReader() *gzip.Reader
|
|
||||||
|
|
||||||
// Releases an aqcuired *gzip.Reader.
|
|
||||||
ReleaseGzipReader(w *gzip.Reader)
|
|
||||||
|
|
||||||
// Returns a *zlib.Writer which needs to be released later.
|
|
||||||
// Before using it, call Reset().
|
|
||||||
AcquireZlibWriter() *zlib.Writer
|
|
||||||
|
|
||||||
// Releases an aqcuired *zlib.Writer.
|
|
||||||
ReleaseZlibWriter(w *zlib.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultCompressorProvider is the actual provider of compressors (zlib or gzip).
|
|
||||||
var currentCompressorProvider CompressorProvider
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
currentCompressorProvider = NewSyncPoolCompessors()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentCompressorProvider returns the current CompressorProvider.
|
|
||||||
// It is initialized using a SyncPoolCompessors.
|
|
||||||
func CurrentCompressorProvider() CompressorProvider {
|
|
||||||
return currentCompressorProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompressorProvider sets the actual provider of compressors (zlib or gzip).
|
|
||||||
func SetCompressorProvider(p CompressorProvider) {
|
|
||||||
if p == nil {
|
|
||||||
panic("cannot set compressor provider to nil")
|
|
||||||
}
|
|
||||||
currentCompressorProvider = p
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
const (
|
|
||||||
MIME_XML = "application/xml" // Accept or Content-Type used in Consumes() and/or Produces()
|
|
||||||
MIME_JSON = "application/json" // Accept or Content-Type used in Consumes() and/or Produces()
|
|
||||||
MIME_OCTET = "application/octet-stream" // If Content-Type is not present in request, use the default
|
|
||||||
|
|
||||||
HEADER_Allow = "Allow"
|
|
||||||
HEADER_Accept = "Accept"
|
|
||||||
HEADER_Origin = "Origin"
|
|
||||||
HEADER_ContentType = "Content-Type"
|
|
||||||
HEADER_LastModified = "Last-Modified"
|
|
||||||
HEADER_AcceptEncoding = "Accept-Encoding"
|
|
||||||
HEADER_ContentEncoding = "Content-Encoding"
|
|
||||||
HEADER_AccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
|
||||||
HEADER_AccessControlRequestMethod = "Access-Control-Request-Method"
|
|
||||||
HEADER_AccessControlRequestHeaders = "Access-Control-Request-Headers"
|
|
||||||
HEADER_AccessControlAllowMethods = "Access-Control-Allow-Methods"
|
|
||||||
HEADER_AccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
|
||||||
HEADER_AccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
|
||||||
HEADER_AccessControlAllowHeaders = "Access-Control-Allow-Headers"
|
|
||||||
HEADER_AccessControlMaxAge = "Access-Control-Max-Age"
|
|
||||||
|
|
||||||
ENCODING_GZIP = "gzip"
|
|
||||||
ENCODING_DEFLATE = "deflate"
|
|
||||||
)
|
|
|
@ -1,366 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Container holds a collection of WebServices and a http.ServeMux to dispatch http requests.
|
|
||||||
// The requests are further dispatched to routes of WebServices using a RouteSelector
|
|
||||||
type Container struct {
|
|
||||||
webServicesLock sync.RWMutex
|
|
||||||
webServices []*WebService
|
|
||||||
ServeMux *http.ServeMux
|
|
||||||
isRegisteredOnRoot bool
|
|
||||||
containerFilters []FilterFunction
|
|
||||||
doNotRecover bool // default is true
|
|
||||||
recoverHandleFunc RecoverHandleFunction
|
|
||||||
serviceErrorHandleFunc ServiceErrorHandleFunction
|
|
||||||
router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
|
|
||||||
contentEncodingEnabled bool // default is false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter)
|
|
||||||
func NewContainer() *Container {
|
|
||||||
return &Container{
|
|
||||||
webServices: []*WebService{},
|
|
||||||
ServeMux: http.NewServeMux(),
|
|
||||||
isRegisteredOnRoot: false,
|
|
||||||
containerFilters: []FilterFunction{},
|
|
||||||
doNotRecover: true,
|
|
||||||
recoverHandleFunc: logStackOnRecover,
|
|
||||||
serviceErrorHandleFunc: writeServiceError,
|
|
||||||
router: CurlyRouter{},
|
|
||||||
contentEncodingEnabled: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecoverHandleFunction declares functions that can be used to handle a panic situation.
|
|
||||||
// The first argument is what recover() returns. The second must be used to communicate an error response.
|
|
||||||
type RecoverHandleFunction func(interface{}, http.ResponseWriter)
|
|
||||||
|
|
||||||
// RecoverHandler changes the default function (logStackOnRecover) to be called
|
|
||||||
// when a panic is detected. DoNotRecover must be have its default value (=false).
|
|
||||||
func (c *Container) RecoverHandler(handler RecoverHandleFunction) {
|
|
||||||
c.recoverHandleFunc = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceErrorHandleFunction declares functions that can be used to handle a service error situation.
|
|
||||||
// The first argument is the service error, the second is the request that resulted in the error and
|
|
||||||
// the third must be used to communicate an error response.
|
|
||||||
type ServiceErrorHandleFunction func(ServiceError, *Request, *Response)
|
|
||||||
|
|
||||||
// ServiceErrorHandler changes the default function (writeServiceError) to be called
|
|
||||||
// when a ServiceError is detected.
|
|
||||||
func (c *Container) ServiceErrorHandler(handler ServiceErrorHandleFunction) {
|
|
||||||
c.serviceErrorHandleFunc = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoNotRecover controls whether panics will be caught to return HTTP 500.
|
|
||||||
// If set to true, Route functions are responsible for handling any error situation.
|
|
||||||
// Default value is true.
|
|
||||||
func (c *Container) DoNotRecover(doNot bool) {
|
|
||||||
c.doNotRecover = doNot
|
|
||||||
}
|
|
||||||
|
|
||||||
// Router changes the default Router (currently CurlyRouter)
|
|
||||||
func (c *Container) Router(aRouter RouteSelector) {
|
|
||||||
c.router = aRouter
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses.
|
|
||||||
func (c *Container) EnableContentEncoding(enabled bool) {
|
|
||||||
c.contentEncodingEnabled = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
|
|
||||||
func (c *Container) Add(service *WebService) *Container {
|
|
||||||
c.webServicesLock.Lock()
|
|
||||||
defer c.webServicesLock.Unlock()
|
|
||||||
|
|
||||||
// if rootPath was not set then lazy initialize it
|
|
||||||
if len(service.rootPath) == 0 {
|
|
||||||
service.Path("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cannot have duplicate root paths
|
|
||||||
for _, each := range c.webServices {
|
|
||||||
if each.RootPath() == service.RootPath() {
|
|
||||||
log.Printf("[restful] WebService with duplicate root path detected:['%v']", each)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not registered on root then add specific mapping
|
|
||||||
if !c.isRegisteredOnRoot {
|
|
||||||
c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
|
|
||||||
}
|
|
||||||
c.webServices = append(c.webServices, service)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// addHandler may set a new HandleFunc for the serveMux
|
|
||||||
// this function must run inside the critical region protected by the webServicesLock.
|
|
||||||
// returns true if the function was registered on root ("/")
|
|
||||||
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
|
|
||||||
pattern := fixedPrefixPath(service.RootPath())
|
|
||||||
// check if root path registration is needed
|
|
||||||
if "/" == pattern || "" == pattern {
|
|
||||||
serveMux.HandleFunc("/", c.dispatch)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// detect if registration already exists
|
|
||||||
alreadyMapped := false
|
|
||||||
for _, each := range c.webServices {
|
|
||||||
if each.RootPath() == service.RootPath() {
|
|
||||||
alreadyMapped = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !alreadyMapped {
|
|
||||||
serveMux.HandleFunc(pattern, c.dispatch)
|
|
||||||
if !strings.HasSuffix(pattern, "/") {
|
|
||||||
serveMux.HandleFunc(pattern+"/", c.dispatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Remove(ws *WebService) error {
|
|
||||||
if c.ServeMux == http.DefaultServeMux {
|
|
||||||
errMsg := fmt.Sprintf("[restful] cannot remove a WebService from a Container using the DefaultServeMux: ['%v']", ws)
|
|
||||||
log.Printf(errMsg)
|
|
||||||
return errors.New(errMsg)
|
|
||||||
}
|
|
||||||
c.webServicesLock.Lock()
|
|
||||||
defer c.webServicesLock.Unlock()
|
|
||||||
// build a new ServeMux and re-register all WebServices
|
|
||||||
newServeMux := http.NewServeMux()
|
|
||||||
newServices := []*WebService{}
|
|
||||||
newIsRegisteredOnRoot := false
|
|
||||||
for _, each := range c.webServices {
|
|
||||||
if each.rootPath != ws.rootPath {
|
|
||||||
// If not registered on root then add specific mapping
|
|
||||||
if !newIsRegisteredOnRoot {
|
|
||||||
newIsRegisteredOnRoot = c.addHandler(each, newServeMux)
|
|
||||||
}
|
|
||||||
newServices = append(newServices, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.webServices, c.ServeMux, c.isRegisteredOnRoot = newServices, newServeMux, newIsRegisteredOnRoot
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// logStackOnRecover is the default RecoverHandleFunction and is called
|
|
||||||
// when DoNotRecover is false and the recoverHandleFunc is not set for the container.
|
|
||||||
// Default implementation logs the stacktrace and writes the stacktrace on the response.
|
|
||||||
// This may be a security issue as it exposes sourcecode information.
|
|
||||||
func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
buffer.WriteString(fmt.Sprintf("[restful] recover from panic situation: - %v\r\n", panicReason))
|
|
||||||
for i := 2; ; i += 1 {
|
|
||||||
_, file, line, ok := runtime.Caller(i)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
|
|
||||||
}
|
|
||||||
log.Print(buffer.String())
|
|
||||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
|
||||||
httpWriter.Write(buffer.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeServiceError is the default ServiceErrorHandleFunction and is called
|
|
||||||
// when a ServiceError is returned during route selection. Default implementation
|
|
||||||
// calls resp.WriteErrorString(err.Code, err.Message)
|
|
||||||
func writeServiceError(err ServiceError, req *Request, resp *Response) {
|
|
||||||
resp.WriteErrorString(err.Code, err.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the incoming Http Request to a matching WebService.
|
|
||||||
func (c *Container) Dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
if httpWriter == nil {
|
|
||||||
panic("httpWriter cannot be nil")
|
|
||||||
}
|
|
||||||
if httpRequest == nil {
|
|
||||||
panic("httpRequest cannot be nil")
|
|
||||||
}
|
|
||||||
c.dispatch(httpWriter, httpRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the incoming Http Request to a matching WebService.
|
|
||||||
func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
writer := httpWriter
|
|
||||||
|
|
||||||
// CompressingResponseWriter should be closed after all operations are done
|
|
||||||
defer func() {
|
|
||||||
if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
|
|
||||||
compressWriter.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Instal panic recovery unless told otherwise
|
|
||||||
if !c.doNotRecover { // catch all for 500 response
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
c.recoverHandleFunc(r, writer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect if compression is needed
|
|
||||||
// assume without compression, test for override
|
|
||||||
if c.contentEncodingEnabled {
|
|
||||||
doCompress, encoding := wantsCompressedResponse(httpRequest)
|
|
||||||
if doCompress {
|
|
||||||
var err error
|
|
||||||
writer, err = NewCompressingResponseWriter(httpWriter, encoding)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("[restful] unable to install compressor: ", err)
|
|
||||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Find best match Route ; err is non nil if no match was found
|
|
||||||
var webService *WebService
|
|
||||||
var route *Route
|
|
||||||
var err error
|
|
||||||
func() {
|
|
||||||
c.webServicesLock.RLock()
|
|
||||||
defer c.webServicesLock.RUnlock()
|
|
||||||
webService, route, err = c.router.SelectRoute(
|
|
||||||
c.webServices,
|
|
||||||
httpRequest)
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
// a non-200 response has already been written
|
|
||||||
// run container filters anyway ; they should not touch the response...
|
|
||||||
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
|
|
||||||
switch err.(type) {
|
|
||||||
case ServiceError:
|
|
||||||
ser := err.(ServiceError)
|
|
||||||
c.serviceErrorHandleFunc(ser, req, resp)
|
|
||||||
}
|
|
||||||
// TODO
|
|
||||||
}}
|
|
||||||
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest)
|
|
||||||
// pass through filters (if any)
|
|
||||||
if len(c.containerFilters)+len(webService.filters)+len(route.Filters) > 0 {
|
|
||||||
// compose filter chain
|
|
||||||
allFilters := []FilterFunction{}
|
|
||||||
allFilters = append(allFilters, c.containerFilters...)
|
|
||||||
allFilters = append(allFilters, webService.filters...)
|
|
||||||
allFilters = append(allFilters, route.Filters...)
|
|
||||||
chain := FilterChain{Filters: allFilters, Target: func(req *Request, resp *Response) {
|
|
||||||
// handle request by route after passing all filters
|
|
||||||
route.Function(wrappedRequest, wrappedResponse)
|
|
||||||
}}
|
|
||||||
chain.ProcessFilter(wrappedRequest, wrappedResponse)
|
|
||||||
} else {
|
|
||||||
// no filters, handle request by route
|
|
||||||
route.Function(wrappedRequest, wrappedResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {}
|
|
||||||
func fixedPrefixPath(pathspec string) string {
|
|
||||||
varBegin := strings.Index(pathspec, "{")
|
|
||||||
if -1 == varBegin {
|
|
||||||
return pathspec
|
|
||||||
}
|
|
||||||
return pathspec[:varBegin]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server
|
|
||||||
func (c *Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
c.ServeMux.ServeHTTP(httpwriter, httpRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
|
|
||||||
func (c *Container) Handle(pattern string, handler http.Handler) {
|
|
||||||
c.ServeMux.Handle(pattern, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleWithFilter registers the handler for the given pattern.
|
|
||||||
// Container's filter chain is applied for handler.
|
|
||||||
// If a handler already exists for pattern, HandleWithFilter panics.
|
|
||||||
func (c *Container) HandleWithFilter(pattern string, handler http.Handler) {
|
|
||||||
f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
if len(c.containerFilters) == 0 {
|
|
||||||
handler.ServeHTTP(httpResponse, httpRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
|
|
||||||
handler.ServeHTTP(httpResponse, httpRequest)
|
|
||||||
}}
|
|
||||||
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Handle(pattern, http.HandlerFunc(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter appends a container FilterFunction. These are called before dispatching
|
|
||||||
// a http.Request to a WebService from the container
|
|
||||||
func (c *Container) Filter(filter FilterFunction) {
|
|
||||||
c.containerFilters = append(c.containerFilters, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisteredWebServices returns the collections of added WebServices
|
|
||||||
func (c *Container) RegisteredWebServices() []*WebService {
|
|
||||||
c.webServicesLock.RLock()
|
|
||||||
defer c.webServicesLock.RUnlock()
|
|
||||||
result := make([]*WebService, len(c.webServices))
|
|
||||||
for ix := range c.webServices {
|
|
||||||
result[ix] = c.webServices[ix]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeAllowedMethods returns a list of HTTP methods that are valid for a Request
|
|
||||||
func (c *Container) computeAllowedMethods(req *Request) []string {
|
|
||||||
// Go through all RegisteredWebServices() and all its Routes to collect the options
|
|
||||||
methods := []string{}
|
|
||||||
requestPath := req.Request.URL.Path
|
|
||||||
for _, ws := range c.RegisteredWebServices() {
|
|
||||||
matches := ws.pathExpr.Matcher.FindStringSubmatch(requestPath)
|
|
||||||
if matches != nil {
|
|
||||||
finalMatch := matches[len(matches)-1]
|
|
||||||
for _, rt := range ws.Routes() {
|
|
||||||
matches := rt.pathExpr.Matcher.FindStringSubmatch(finalMatch)
|
|
||||||
if matches != nil {
|
|
||||||
lastMatch := matches[len(matches)-1]
|
|
||||||
if lastMatch == "" || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
|
|
||||||
methods = append(methods, rt.Method)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// methods = append(methods, "OPTIONS") not sure about this
|
|
||||||
return methods
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBasicRequestResponse creates a pair of Request,Response from its http versions.
|
|
||||||
// It is basic because no parameter or (produces) content-type information is given.
|
|
||||||
func newBasicRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
|
|
||||||
resp := NewResponse(httpWriter)
|
|
||||||
resp.requestAccept = httpRequest.Header.Get(HEADER_Accept)
|
|
||||||
return NewRequest(httpRequest), resp
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CrossOriginResourceSharing is used to create a Container Filter that implements CORS.
|
|
||||||
// Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page
|
|
||||||
// to make XMLHttpRequests to another domain, not the domain the JavaScript originated from.
|
|
||||||
//
|
|
||||||
// http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
|
|
||||||
// http://enable-cors.org/server.html
|
|
||||||
// http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
|
|
||||||
type CrossOriginResourceSharing struct {
|
|
||||||
ExposeHeaders []string // list of Header names
|
|
||||||
AllowedHeaders []string // list of Header names
|
|
||||||
AllowedDomains []string // list of allowed values for Http Origin. An allowed value can be a regular expression to support subdomain matching. If empty all are allowed.
|
|
||||||
AllowedMethods []string
|
|
||||||
MaxAge int // number of seconds before requiring new Options request
|
|
||||||
CookiesAllowed bool
|
|
||||||
Container *Container
|
|
||||||
|
|
||||||
allowedOriginPatterns []*regexp.Regexp // internal field for origin regexp check.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter is a filter function that implements the CORS flow as documented on http://enable-cors.org/server.html
|
|
||||||
// and http://www.html5rocks.com/static/images/cors_server_flowchart.png
|
|
||||||
func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
origin := req.Request.Header.Get(HEADER_Origin)
|
|
||||||
if len(origin) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Print("no Http header Origin set")
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !c.isOriginAllowed(origin) { // check whether this origin is allowed
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("HTTP Origin:%s is not part of %v, neither matches any part of %v", origin, c.AllowedDomains, c.allowedOriginPatterns)
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if req.Request.Method != "OPTIONS" {
|
|
||||||
c.doActualRequest(req, resp)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod); acrm != "" {
|
|
||||||
c.doPreflightRequest(req, resp)
|
|
||||||
} else {
|
|
||||||
c.doActualRequest(req, resp)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) doActualRequest(req *Request, resp *Response) {
|
|
||||||
c.setOptionsHeaders(req, resp)
|
|
||||||
// continue processing the response
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) {
|
|
||||||
if len(c.AllowedMethods) == 0 {
|
|
||||||
if c.Container == nil {
|
|
||||||
c.AllowedMethods = DefaultContainer.computeAllowedMethods(req)
|
|
||||||
} else {
|
|
||||||
c.AllowedMethods = c.Container.computeAllowedMethods(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod)
|
|
||||||
if !c.isValidAccessControlRequestMethod(acrm, c.AllowedMethods) {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("Http header %s:%s is not in %v",
|
|
||||||
HEADER_AccessControlRequestMethod,
|
|
||||||
acrm,
|
|
||||||
c.AllowedMethods)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
acrhs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders)
|
|
||||||
if len(acrhs) > 0 {
|
|
||||||
for _, each := range strings.Split(acrhs, ",") {
|
|
||||||
if !c.isValidAccessControlRequestHeader(strings.Trim(each, " ")) {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("Http header %s:%s is not in %v",
|
|
||||||
HEADER_AccessControlRequestHeaders,
|
|
||||||
acrhs,
|
|
||||||
c.AllowedHeaders)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowMethods, strings.Join(c.AllowedMethods, ","))
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowHeaders, acrhs)
|
|
||||||
c.setOptionsHeaders(req, resp)
|
|
||||||
|
|
||||||
// return http 200 response, no body
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) setOptionsHeaders(req *Request, resp *Response) {
|
|
||||||
c.checkAndSetExposeHeaders(resp)
|
|
||||||
c.setAllowOriginHeader(req, resp)
|
|
||||||
c.checkAndSetAllowCredentials(resp)
|
|
||||||
if c.MaxAge > 0 {
|
|
||||||
resp.AddHeader(HEADER_AccessControlMaxAge, strconv.Itoa(c.MaxAge))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) isOriginAllowed(origin string) bool {
|
|
||||||
if len(origin) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(c.AllowedDomains) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
allowed := false
|
|
||||||
for _, domain := range c.AllowedDomains {
|
|
||||||
if domain == origin {
|
|
||||||
allowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowed {
|
|
||||||
if len(c.allowedOriginPatterns) == 0 {
|
|
||||||
// compile allowed domains to allowed origin patterns
|
|
||||||
allowedOriginRegexps, err := compileRegexps(c.AllowedDomains)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
c.allowedOriginPatterns = allowedOriginRegexps
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pattern := range c.allowedOriginPatterns {
|
|
||||||
if allowed = pattern.MatchString(origin); allowed {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allowed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) setAllowOriginHeader(req *Request, resp *Response) {
|
|
||||||
origin := req.Request.Header.Get(HEADER_Origin)
|
|
||||||
if c.isOriginAllowed(origin) {
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowOrigin, origin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) checkAndSetExposeHeaders(resp *Response) {
|
|
||||||
if len(c.ExposeHeaders) > 0 {
|
|
||||||
resp.AddHeader(HEADER_AccessControlExposeHeaders, strings.Join(c.ExposeHeaders, ","))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) checkAndSetAllowCredentials(resp *Response) {
|
|
||||||
if c.CookiesAllowed {
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowCredentials, "true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) isValidAccessControlRequestMethod(method string, allowedMethods []string) bool {
|
|
||||||
for _, each := range allowedMethods {
|
|
||||||
if each == method {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) isValidAccessControlRequestHeader(header string) bool {
|
|
||||||
for _, each := range c.AllowedHeaders {
|
|
||||||
if strings.ToLower(each) == strings.ToLower(header) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take a list of strings and compile them into a list of regular expressions.
|
|
||||||
func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
|
|
||||||
regexps := []*regexp.Regexp{}
|
|
||||||
for _, regexpStr := range regexpStrings {
|
|
||||||
r, err := regexp.Compile(regexpStr)
|
|
||||||
if err != nil {
|
|
||||||
return regexps, err
|
|
||||||
}
|
|
||||||
regexps = append(regexps, r)
|
|
||||||
}
|
|
||||||
return regexps, nil
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
|
|
||||||
type CurlyRouter struct{}
|
|
||||||
|
|
||||||
// SelectRoute is part of the Router interface and returns the best match
|
|
||||||
// for the WebService and its Route for the given Request.
|
|
||||||
func (c CurlyRouter) SelectRoute(
|
|
||||||
webServices []*WebService,
|
|
||||||
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
|
|
||||||
|
|
||||||
requestTokens := tokenizePath(httpRequest.URL.Path)
|
|
||||||
|
|
||||||
detectedService := c.detectWebService(requestTokens, webServices)
|
|
||||||
if detectedService == nil {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
|
|
||||||
}
|
|
||||||
return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
|
||||||
}
|
|
||||||
candidateRoutes := c.selectRoutes(detectedService, requestTokens)
|
|
||||||
if len(candidateRoutes) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
|
|
||||||
}
|
|
||||||
return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
|
||||||
}
|
|
||||||
selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
|
|
||||||
if selectedRoute == nil {
|
|
||||||
return detectedService, nil, err
|
|
||||||
}
|
|
||||||
return detectedService, selectedRoute, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectRoutes return a collection of Route from a WebService that matches the path tokens from the request.
|
|
||||||
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
|
|
||||||
candidates := sortableCurlyRoutes{}
|
|
||||||
for _, each := range ws.routes {
|
|
||||||
matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens)
|
|
||||||
if matches {
|
|
||||||
candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(candidates))
|
|
||||||
return candidates
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are.
|
|
||||||
func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string) (matches bool, paramCount int, staticCount int) {
|
|
||||||
if len(routeTokens) < len(requestTokens) {
|
|
||||||
// proceed in matching only if last routeToken is wildcard
|
|
||||||
count := len(routeTokens)
|
|
||||||
if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
// proceed
|
|
||||||
}
|
|
||||||
for i, routeToken := range routeTokens {
|
|
||||||
if i == len(requestTokens) {
|
|
||||||
// reached end of request path
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
requestToken := requestTokens[i]
|
|
||||||
if strings.HasPrefix(routeToken, "{") {
|
|
||||||
paramCount++
|
|
||||||
if colon := strings.Index(routeToken, ":"); colon != -1 {
|
|
||||||
// match by regex
|
|
||||||
matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
|
|
||||||
if !matchesToken {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
if matchesRemainder {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // no { prefix
|
|
||||||
if requestToken != routeToken {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
staticCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, paramCount, staticCount
|
|
||||||
}
|
|
||||||
|
|
||||||
// regularMatchesPathToken tests whether the regular expression part of routeToken matches the requestToken or all remaining tokens
|
|
||||||
// format routeToken is {someVar:someExpression}, e.g. {zipcode:[\d][\d][\d][\d][A-Z][A-Z]}
|
|
||||||
func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, requestToken string) (matchesToken bool, matchesRemainder bool) {
|
|
||||||
regPart := routeToken[colon+1 : len(routeToken)-1]
|
|
||||||
if regPart == "*" {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("wildcard parameter detected in route token %s that matches %s\n", routeToken, requestToken)
|
|
||||||
}
|
|
||||||
return true, true
|
|
||||||
}
|
|
||||||
matched, err := regexp.MatchString(regPart, requestToken)
|
|
||||||
return (matched && err == nil), false
|
|
||||||
}
|
|
||||||
|
|
||||||
var jsr311Router = RouterJSR311{}
|
|
||||||
|
|
||||||
// detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type
|
|
||||||
// headers of the Request. See also RouterJSR311 in jsr311.go
|
|
||||||
func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
|
|
||||||
// tracing is done inside detectRoute
|
|
||||||
return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectWebService returns the best matching webService given the list of path tokens.
|
|
||||||
// see also computeWebserviceScore
|
|
||||||
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
|
|
||||||
var best *WebService
|
|
||||||
score := -1
|
|
||||||
for _, each := range webServices {
|
|
||||||
matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
|
|
||||||
if matches && (eachScore > score) {
|
|
||||||
best = each
|
|
||||||
score = eachScore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeWebserviceScore returns whether tokens match and
|
|
||||||
// the weighted score of the longest matching consecutive tokens from the beginning.
|
|
||||||
func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
|
|
||||||
if len(tokens) > len(requestTokens) {
|
|
||||||
return false, 0
|
|
||||||
}
|
|
||||||
score := 0
|
|
||||||
for i := 0; i < len(tokens); i++ {
|
|
||||||
each := requestTokens[i]
|
|
||||||
other := tokens[i]
|
|
||||||
if len(each) == 0 && len(other) == 0 {
|
|
||||||
score++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(other) > 0 && strings.HasPrefix(other, "{") {
|
|
||||||
// no empty match
|
|
||||||
if len(each) == 0 {
|
|
||||||
return false, score
|
|
||||||
}
|
|
||||||
score += 1
|
|
||||||
} else {
|
|
||||||
// not a parameter
|
|
||||||
if each != other {
|
|
||||||
return false, score
|
|
||||||
}
|
|
||||||
score += (len(tokens) - i) * 10 //fuzzy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, score
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// curlyRoute exits for sorting Routes by the CurlyRouter based on number of parameters and number of static path elements.
|
|
||||||
type curlyRoute struct {
|
|
||||||
route Route
|
|
||||||
paramCount int
|
|
||||||
staticCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortableCurlyRoutes []curlyRoute
|
|
||||||
|
|
||||||
func (s *sortableCurlyRoutes) add(route curlyRoute) {
|
|
||||||
*s = append(*s, route)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sortableCurlyRoutes) routes() (routes []Route) {
|
|
||||||
for _, each := range s {
|
|
||||||
routes = append(routes, each.route) // TODO change return type
|
|
||||||
}
|
|
||||||
return routes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sortableCurlyRoutes) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
func (s sortableCurlyRoutes) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
func (s sortableCurlyRoutes) Less(i, j int) bool {
|
|
||||||
ci := s[i]
|
|
||||||
cj := s[j]
|
|
||||||
|
|
||||||
// primary key
|
|
||||||
if ci.staticCount < cj.staticCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.staticCount > cj.staticCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// secundary key
|
|
||||||
if ci.paramCount < cj.paramCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.paramCount > cj.paramCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return ci.route.Path < cj.route.Path
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
/*
|
|
||||||
Package restful , a lean package for creating REST-style WebServices without magic.
|
|
||||||
|
|
||||||
WebServices and Routes
|
|
||||||
|
|
||||||
A WebService has a collection of Route objects that dispatch incoming Http Requests to a function calls.
|
|
||||||
Typically, a WebService has a root path (e.g. /users) and defines common MIME types for its routes.
|
|
||||||
WebServices must be added to a container (see below) in order to handler Http requests from a server.
|
|
||||||
|
|
||||||
A Route is defined by a HTTP method, an URL path and (optionally) the MIME types it consumes (Content-Type) and produces (Accept).
|
|
||||||
This package has the logic to find the best matching Route and if found, call its Function.
|
|
||||||
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_JSON, restful.MIME_XML).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser)) // u is a UserResource
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users/1
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
The (*Request, *Response) arguments provide functions for reading information from the request and writing information back to the response.
|
|
||||||
|
|
||||||
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-user-resource.go with a full implementation.
|
|
||||||
|
|
||||||
Regular expression matching Routes
|
|
||||||
|
|
||||||
A Route parameter can be specified using the format "uri/{var[:regexp]}" or the special version "uri/{var:*}" for matching the tail of the path.
|
|
||||||
For example, /persons/{name:[A-Z][A-Z]} can be used to restrict values for the parameter "name" to only contain capital alphabetic characters.
|
|
||||||
Regular expressions must use the standard Go syntax as described in the regexp package. (https://code.google.com/p/re2/wiki/Syntax)
|
|
||||||
This feature requires the use of a CurlyRouter.
|
|
||||||
|
|
||||||
Containers
|
|
||||||
|
|
||||||
A Container holds a collection of WebServices, Filters and a http.ServeMux for multiplexing http requests.
|
|
||||||
Using the statements "restful.Add(...) and restful.Filter(...)" will register WebServices and Filters to the Default Container.
|
|
||||||
The Default container of go-restful uses the http.DefaultServeMux.
|
|
||||||
You can create your own Container and create a new http.Server for that particular container.
|
|
||||||
|
|
||||||
container := restful.NewContainer()
|
|
||||||
server := &http.Server{Addr: ":8081", Handler: container}
|
|
||||||
|
|
||||||
Filters
|
|
||||||
|
|
||||||
A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses.
|
|
||||||
You can use filters to perform generic logging, measurement, authentication, redirect, set response headers etc.
|
|
||||||
In the restful package there are three hooks into the request,response flow where filters can be added.
|
|
||||||
Each filter must define a FilterFunction:
|
|
||||||
|
|
||||||
func (req *restful.Request, resp *restful.Response, chain *restful.FilterChain)
|
|
||||||
|
|
||||||
Use the following statement to pass the request,response pair to the next filter or RouteFunction
|
|
||||||
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
|
|
||||||
Container Filters
|
|
||||||
|
|
||||||
These are processed before any registered WebService.
|
|
||||||
|
|
||||||
// install a (global) filter for the default container (processed before any webservice)
|
|
||||||
restful.Filter(globalLogging)
|
|
||||||
|
|
||||||
WebService Filters
|
|
||||||
|
|
||||||
These are processed before any Route of a WebService.
|
|
||||||
|
|
||||||
// install a webservice filter (processed before any route)
|
|
||||||
ws.Filter(webserviceLogging).Filter(measureTime)
|
|
||||||
|
|
||||||
|
|
||||||
Route Filters
|
|
||||||
|
|
||||||
These are processed before calling the function associated with the Route.
|
|
||||||
|
|
||||||
// install 2 chained route filters (processed before calling findUser)
|
|
||||||
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser))
|
|
||||||
|
|
||||||
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-filters.go with full implementations.
|
|
||||||
|
|
||||||
Response Encoding
|
|
||||||
|
|
||||||
Two encodings are supported: gzip and deflate. To enable this for all responses:
|
|
||||||
|
|
||||||
restful.DefaultContainer.EnableContentEncoding(true)
|
|
||||||
|
|
||||||
If a Http request includes the Accept-Encoding header then the response content will be compressed using the specified encoding.
|
|
||||||
Alternatively, you can create a Filter that performs the encoding and install it per WebService or Route.
|
|
||||||
|
|
||||||
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-encoding-filter.go
|
|
||||||
|
|
||||||
OPTIONS support
|
|
||||||
|
|
||||||
By installing a pre-defined container filter, your Webservice(s) can respond to the OPTIONS Http request.
|
|
||||||
|
|
||||||
Filter(OPTIONSFilter())
|
|
||||||
|
|
||||||
CORS
|
|
||||||
|
|
||||||
By installing the filter of a CrossOriginResourceSharing (CORS), your WebService(s) can handle CORS requests.
|
|
||||||
|
|
||||||
cors := CrossOriginResourceSharing{ExposeHeaders: []string{"X-My-Header"}, CookiesAllowed: false, Container: DefaultContainer}
|
|
||||||
Filter(cors.Filter)
|
|
||||||
|
|
||||||
Error Handling
|
|
||||||
|
|
||||||
Unexpected things happen. If a request cannot be processed because of a failure, your service needs to tell via the response what happened and why.
|
|
||||||
For this reason HTTP status codes exist and it is important to use the correct code in every exceptional situation.
|
|
||||||
|
|
||||||
400: Bad Request
|
|
||||||
|
|
||||||
If path or query parameters are not valid (content or type) then use http.StatusBadRequest.
|
|
||||||
|
|
||||||
404: Not Found
|
|
||||||
|
|
||||||
Despite a valid URI, the resource requested may not be available
|
|
||||||
|
|
||||||
500: Internal Server Error
|
|
||||||
|
|
||||||
If the application logic could not process the request (or write the response) then use http.StatusInternalServerError.
|
|
||||||
|
|
||||||
405: Method Not Allowed
|
|
||||||
|
|
||||||
The request has a valid URL but the method (GET,PUT,POST,...) is not allowed.
|
|
||||||
|
|
||||||
406: Not Acceptable
|
|
||||||
|
|
||||||
The request does not have or has an unknown Accept Header set for this operation.
|
|
||||||
|
|
||||||
415: Unsupported Media Type
|
|
||||||
|
|
||||||
The request does not have or has an unknown Content-Type Header set for this operation.
|
|
||||||
|
|
||||||
ServiceError
|
|
||||||
|
|
||||||
In addition to setting the correct (error) Http status code, you can choose to write a ServiceError message on the response.
|
|
||||||
|
|
||||||
Performance options
|
|
||||||
|
|
||||||
This package has several options that affect the performance of your service. It is important to understand them and how you can change it.
|
|
||||||
|
|
||||||
restful.DefaultContainer.DoNotRecover(false)
|
|
||||||
|
|
||||||
DoNotRecover controls whether panics will be caught to return HTTP 500.
|
|
||||||
If set to false, the container will recover from panics.
|
|
||||||
Default value is true
|
|
||||||
|
|
||||||
restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20))
|
|
||||||
|
|
||||||
If content encoding is enabled then the default strategy for getting new gzip/zlib writers and readers is to use a sync.Pool.
|
|
||||||
Because writers are expensive structures, performance is even more improved when using a preloaded cache. You can also inject your own implementation.
|
|
||||||
|
|
||||||
Trouble shooting
|
|
||||||
|
|
||||||
This package has the means to produce detail logging of the complete Http request matching process and filter invocation.
|
|
||||||
Enabling this feature requires you to set an implementation of restful.StdLogger (e.g. log.Logger) instance such as:
|
|
||||||
|
|
||||||
restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile))
|
|
||||||
|
|
||||||
Logging
|
|
||||||
|
|
||||||
The restful.SetLogger() method allows you to override the logger used by the package. By default restful
|
|
||||||
uses the standard library `log` package and logs to stdout. Different logging packages are supported as
|
|
||||||
long as they conform to `StdLogger` interface defined in the `log` sub-package, writing an adapter for your
|
|
||||||
preferred package is simple.
|
|
||||||
|
|
||||||
Resources
|
|
||||||
|
|
||||||
[project]: https://github.com/emicklei/go-restful
|
|
||||||
|
|
||||||
[examples]: https://github.com/emicklei/go-restful/blob/master/examples
|
|
||||||
|
|
||||||
[design]: http://ernestmicklei.com/2012/11/11/go-restful-api-design/
|
|
||||||
|
|
||||||
[showcases]: https://github.com/emicklei/mora, https://github.com/emicklei/landskape
|
|
||||||
|
|
||||||
(c) 2012-2015, http://ernestmicklei.com. MIT License
|
|
||||||
*/
|
|
||||||
package restful
|
|
|
@ -1,163 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EntityReaderWriter can read and write values using an encoding such as JSON,XML.
|
|
||||||
type EntityReaderWriter interface {
|
|
||||||
// Read a serialized version of the value from the request.
|
|
||||||
// The Request may have a decompressing reader. Depends on Content-Encoding.
|
|
||||||
Read(req *Request, v interface{}) error
|
|
||||||
|
|
||||||
// Write a serialized version of the value on the response.
|
|
||||||
// The Response may have a compressing writer. Depends on Accept-Encoding.
|
|
||||||
// status should be a valid Http Status code
|
|
||||||
Write(resp *Response, status int, v interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityAccessRegistry is a singleton
|
|
||||||
var entityAccessRegistry = &entityReaderWriters{
|
|
||||||
protection: new(sync.RWMutex),
|
|
||||||
accessors: map[string]EntityReaderWriter{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityReaderWriters associates MIME to an EntityReaderWriter
|
|
||||||
type entityReaderWriters struct {
|
|
||||||
protection *sync.RWMutex
|
|
||||||
accessors map[string]EntityReaderWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterEntityAccessor(MIME_JSON, NewEntityAccessorJSON(MIME_JSON))
|
|
||||||
RegisterEntityAccessor(MIME_XML, NewEntityAccessorXML(MIME_XML))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type.
|
|
||||||
func RegisterEntityAccessor(mime string, erw EntityReaderWriter) {
|
|
||||||
entityAccessRegistry.protection.Lock()
|
|
||||||
defer entityAccessRegistry.protection.Unlock()
|
|
||||||
entityAccessRegistry.accessors[mime] = erw
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEntityAccessorJSON returns a new EntityReaderWriter for accessing JSON content.
|
|
||||||
// This package is already initialized with such an accessor using the MIME_JSON contentType.
|
|
||||||
func NewEntityAccessorJSON(contentType string) EntityReaderWriter {
|
|
||||||
return entityJSONAccess{ContentType: contentType}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEntityAccessorXML returns a new EntityReaderWriter for accessing XML content.
|
|
||||||
// This package is already initialized with such an accessor using the MIME_XML contentType.
|
|
||||||
func NewEntityAccessorXML(contentType string) EntityReaderWriter {
|
|
||||||
return entityXMLAccess{ContentType: contentType}
|
|
||||||
}
|
|
||||||
|
|
||||||
// accessorAt returns the registered ReaderWriter for this MIME type.
|
|
||||||
func (r *entityReaderWriters) accessorAt(mime string) (EntityReaderWriter, bool) {
|
|
||||||
r.protection.RLock()
|
|
||||||
defer r.protection.RUnlock()
|
|
||||||
er, ok := r.accessors[mime]
|
|
||||||
if !ok {
|
|
||||||
// retry with reverse lookup
|
|
||||||
// more expensive but we are in an exceptional situation anyway
|
|
||||||
for k, v := range r.accessors {
|
|
||||||
if strings.Contains(mime, k) {
|
|
||||||
return v, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return er, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityXMLAccess is a EntityReaderWriter for XML encoding
|
|
||||||
type entityXMLAccess struct {
|
|
||||||
// This is used for setting the Content-Type header when writing
|
|
||||||
ContentType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read unmarshalls the value from XML
|
|
||||||
func (e entityXMLAccess) Read(req *Request, v interface{}) error {
|
|
||||||
return xml.NewDecoder(req.Request.Body).Decode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func (e entityXMLAccess) Write(resp *Response, status int, v interface{}) error {
|
|
||||||
return writeXML(resp, status, e.ContentType, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeXML marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func writeXML(resp *Response, status int, contentType string, v interface{}) error {
|
|
||||||
if v == nil {
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
// do not write a nil representation
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if resp.prettyPrint {
|
|
||||||
// pretty output must be created and written explicitly
|
|
||||||
output, err := xml.MarshalIndent(v, " ", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
_, err = resp.Write([]byte(xml.Header))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = resp.Write(output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// not-so-pretty
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
return xml.NewEncoder(resp).Encode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityJSONAccess is a EntityReaderWriter for JSON encoding
|
|
||||||
type entityJSONAccess struct {
|
|
||||||
// This is used for setting the Content-Type header when writing
|
|
||||||
ContentType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read unmarshalls the value from JSON
|
|
||||||
func (e entityJSONAccess) Read(req *Request, v interface{}) error {
|
|
||||||
decoder := json.NewDecoder(req.Request.Body)
|
|
||||||
decoder.UseNumber()
|
|
||||||
return decoder.Decode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func (e entityJSONAccess) Write(resp *Response, status int, v interface{}) error {
|
|
||||||
return writeJSON(resp, status, e.ContentType, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func writeJSON(resp *Response, status int, contentType string, v interface{}) error {
|
|
||||||
if v == nil {
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
// do not write a nil representation
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if resp.prettyPrint {
|
|
||||||
// pretty output must be created and written explicitly
|
|
||||||
output, err := json.MarshalIndent(v, " ", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
_, err = resp.Write(output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// not-so-pretty
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
return json.NewEncoder(resp).Encode(v)
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// FilterChain is a request scoped object to process one or more filters before calling the target RouteFunction.
|
|
||||||
type FilterChain struct {
|
|
||||||
Filters []FilterFunction // ordered list of FilterFunction
|
|
||||||
Index int // index into filters that is currently in progress
|
|
||||||
Target RouteFunction // function to call after passing all filters
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessFilter passes the request,response pair through the next of Filters.
|
|
||||||
// Each filter can decide to proceed to the next Filter or handle the Response itself.
|
|
||||||
func (f *FilterChain) ProcessFilter(request *Request, response *Response) {
|
|
||||||
if f.Index < len(f.Filters) {
|
|
||||||
f.Index++
|
|
||||||
f.Filters[f.Index-1](request, response, f)
|
|
||||||
} else {
|
|
||||||
f.Target(request, response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction
|
|
||||||
type FilterFunction func(*Request, *Response, *FilterChain)
|
|
||||||
|
|
||||||
// NoBrowserCacheFilter is a filter function to set HTTP headers that disable browser caching
|
|
||||||
// See examples/restful-no-cache-filter.go for usage
|
|
||||||
func NoBrowserCacheFilter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
resp.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") // HTTP 1.1.
|
|
||||||
resp.Header().Set("Pragma", "no-cache") // HTTP 1.0.
|
|
||||||
resp.Header().Set("Expires", "0") // Proxies.
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
|
@ -1,248 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions)
|
|
||||||
// as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html.
|
|
||||||
// RouterJSR311 implements the Router interface.
|
|
||||||
// Concept of locators is not implemented.
|
|
||||||
type RouterJSR311 struct{}
|
|
||||||
|
|
||||||
// SelectRoute is part of the Router interface and returns the best match
|
|
||||||
// for the WebService and its Route for the given Request.
|
|
||||||
func (r RouterJSR311) SelectRoute(
|
|
||||||
webServices []*WebService,
|
|
||||||
httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) {
|
|
||||||
|
|
||||||
// Identify the root resource class (WebService)
|
|
||||||
dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, NewError(http.StatusNotFound, "")
|
|
||||||
}
|
|
||||||
// Obtain the set of candidate methods (Routes)
|
|
||||||
routes := r.selectRoutes(dispatcher, finalMatch)
|
|
||||||
if len(routes) == 0 {
|
|
||||||
return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identify the method (Route) that will handle the request
|
|
||||||
route, ok := r.detectRoute(routes, httpRequest)
|
|
||||||
return dispatcher, route, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
|
||||||
func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
|
|
||||||
// http method
|
|
||||||
methodOk := []Route{}
|
|
||||||
for _, each := range routes {
|
|
||||||
if httpRequest.Method == each.Method {
|
|
||||||
methodOk = append(methodOk, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(methodOk) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(routes), httpRequest.Method)
|
|
||||||
}
|
|
||||||
return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed")
|
|
||||||
}
|
|
||||||
inputMediaOk := methodOk
|
|
||||||
|
|
||||||
// content-type
|
|
||||||
contentType := httpRequest.Header.Get(HEADER_ContentType)
|
|
||||||
inputMediaOk = []Route{}
|
|
||||||
for _, each := range methodOk {
|
|
||||||
if each.matchesContentType(contentType) {
|
|
||||||
inputMediaOk = append(inputMediaOk, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(inputMediaOk) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(methodOk), contentType)
|
|
||||||
}
|
|
||||||
return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// accept
|
|
||||||
outputMediaOk := []Route{}
|
|
||||||
accept := httpRequest.Header.Get(HEADER_Accept)
|
|
||||||
if len(accept) == 0 {
|
|
||||||
accept = "*/*"
|
|
||||||
}
|
|
||||||
for _, each := range inputMediaOk {
|
|
||||||
if each.matchesAccept(accept) {
|
|
||||||
outputMediaOk = append(outputMediaOk, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(outputMediaOk) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(inputMediaOk), accept)
|
|
||||||
}
|
|
||||||
return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable")
|
|
||||||
}
|
|
||||||
// return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil
|
|
||||||
return &outputMediaOk[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
|
||||||
// n/m > n/* > */*
|
|
||||||
func (r RouterJSR311) bestMatchByMedia(routes []Route, contentType string, accept string) *Route {
|
|
||||||
// TODO
|
|
||||||
return &routes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 2)
|
|
||||||
func (r RouterJSR311) selectRoutes(dispatcher *WebService, pathRemainder string) []Route {
|
|
||||||
filtered := &sortableRouteCandidates{}
|
|
||||||
for _, each := range dispatcher.Routes() {
|
|
||||||
pathExpr := each.pathExpr
|
|
||||||
matches := pathExpr.Matcher.FindStringSubmatch(pathRemainder)
|
|
||||||
if matches != nil {
|
|
||||||
lastMatch := matches[len(matches)-1]
|
|
||||||
if len(lastMatch) == 0 || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
|
|
||||||
filtered.candidates = append(filtered.candidates,
|
|
||||||
routeCandidate{each, len(matches) - 1, pathExpr.LiteralCount, pathExpr.VarCount})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(filtered.candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("WebService on path %s has no routes that match URL path remainder:%s\n", dispatcher.rootPath, pathRemainder)
|
|
||||||
}
|
|
||||||
return []Route{}
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(filtered))
|
|
||||||
|
|
||||||
// select other routes from candidates whoes expression matches rmatch
|
|
||||||
matchingRoutes := []Route{filtered.candidates[0].route}
|
|
||||||
for c := 1; c < len(filtered.candidates); c++ {
|
|
||||||
each := filtered.candidates[c]
|
|
||||||
if each.route.pathExpr.Matcher.MatchString(pathRemainder) {
|
|
||||||
matchingRoutes = append(matchingRoutes, each.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matchingRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1)
|
|
||||||
func (r RouterJSR311) detectDispatcher(requestPath string, dispatchers []*WebService) (*WebService, string, error) {
|
|
||||||
filtered := &sortableDispatcherCandidates{}
|
|
||||||
for _, each := range dispatchers {
|
|
||||||
matches := each.pathExpr.Matcher.FindStringSubmatch(requestPath)
|
|
||||||
if matches != nil {
|
|
||||||
filtered.candidates = append(filtered.candidates,
|
|
||||||
dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(filtered.candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no WebService was found to match URL path:%s\n", requestPath)
|
|
||||||
}
|
|
||||||
return nil, "", errors.New("not found")
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(filtered))
|
|
||||||
return filtered.candidates[0].dispatcher, filtered.candidates[0].finalMatch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types and functions to support the sorting of Routes
|
|
||||||
|
|
||||||
type routeCandidate struct {
|
|
||||||
route Route
|
|
||||||
matchesCount int // the number of capturing groups
|
|
||||||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
|
||||||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r routeCandidate) expressionToMatch() string {
|
|
||||||
return r.route.pathExpr.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r routeCandidate) String() string {
|
|
||||||
return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortableRouteCandidates struct {
|
|
||||||
candidates []routeCandidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rcs *sortableRouteCandidates) Len() int {
|
|
||||||
return len(rcs.candidates)
|
|
||||||
}
|
|
||||||
func (rcs *sortableRouteCandidates) Swap(i, j int) {
|
|
||||||
rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i]
|
|
||||||
}
|
|
||||||
func (rcs *sortableRouteCandidates) Less(i, j int) bool {
|
|
||||||
ci := rcs.candidates[i]
|
|
||||||
cj := rcs.candidates[j]
|
|
||||||
// primary key
|
|
||||||
if ci.literalCount < cj.literalCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.literalCount > cj.literalCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// secundary key
|
|
||||||
if ci.matchesCount < cj.matchesCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.matchesCount > cj.matchesCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// tertiary key
|
|
||||||
if ci.nonDefaultCount < cj.nonDefaultCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.nonDefaultCount > cj.nonDefaultCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// quaternary key ("source" is interpreted as Path)
|
|
||||||
return ci.route.Path < cj.route.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types and functions to support the sorting of Dispatchers
|
|
||||||
|
|
||||||
type dispatcherCandidate struct {
|
|
||||||
dispatcher *WebService
|
|
||||||
finalMatch string
|
|
||||||
matchesCount int // the number of capturing groups
|
|
||||||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
|
||||||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
|
||||||
}
|
|
||||||
type sortableDispatcherCandidates struct {
|
|
||||||
candidates []dispatcherCandidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *sortableDispatcherCandidates) Len() int {
|
|
||||||
return len(dc.candidates)
|
|
||||||
}
|
|
||||||
func (dc *sortableDispatcherCandidates) Swap(i, j int) {
|
|
||||||
dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i]
|
|
||||||
}
|
|
||||||
func (dc *sortableDispatcherCandidates) Less(i, j int) bool {
|
|
||||||
ci := dc.candidates[i]
|
|
||||||
cj := dc.candidates[j]
|
|
||||||
// primary key
|
|
||||||
if ci.matchesCount < cj.matchesCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.matchesCount > cj.matchesCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// secundary key
|
|
||||||
if ci.literalCount < cj.literalCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.literalCount > cj.literalCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// tertiary key
|
|
||||||
return ci.nonDefaultCount < cj.nonDefaultCount
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
stdlog "log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StdLogger corresponds to a minimal subset of the interface satisfied by stdlib log.Logger
|
|
||||||
type StdLogger interface {
|
|
||||||
Print(v ...interface{})
|
|
||||||
Printf(format string, v ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
var Logger StdLogger
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// default Logger
|
|
||||||
SetLogger(stdlog.New(os.Stderr, "[restful] ", stdlog.LstdFlags|stdlog.Lshortfile))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLogger sets the logger for this package
|
|
||||||
func SetLogger(customLogger StdLogger) {
|
|
||||||
Logger = customLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print delegates to the Logger
|
|
||||||
func Print(v ...interface{}) {
|
|
||||||
Logger.Print(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf delegates to the Logger
|
|
||||||
func Printf(format string, v ...interface{}) {
|
|
||||||
Logger.Printf(format, v...)
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2014 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var trace bool = false
|
|
||||||
var traceLogger log.StdLogger
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
traceLogger = log.Logger // use the package logger by default
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceLogger enables detailed logging of Http request matching and filter invocation. Default no logger is set.
|
|
||||||
// You may call EnableTracing() directly to enable trace logging to the package-wide logger.
|
|
||||||
func TraceLogger(logger log.StdLogger) {
|
|
||||||
traceLogger = logger
|
|
||||||
EnableTracing(logger != nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// expose the setter for the global logger on the top-level package
|
|
||||||
func SetLogger(customLogger log.StdLogger) {
|
|
||||||
log.SetLogger(customLogger)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableTracing can be used to Trace logging on and off.
|
|
||||||
func EnableTracing(enabled bool) {
|
|
||||||
trace = enabled
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mime struct {
|
|
||||||
media string
|
|
||||||
quality float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// insertMime adds a mime to a list and keeps it sorted by quality.
|
|
||||||
func insertMime(l []mime, e mime) []mime {
|
|
||||||
for i, each := range l {
|
|
||||||
// if current mime has lower quality then insert before
|
|
||||||
if e.quality > each.quality {
|
|
||||||
left := append([]mime{}, l[0:i]...)
|
|
||||||
return append(append(left, e), l[i:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return append(l, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortedMimes returns a list of mime sorted (desc) by its specified quality.
|
|
||||||
func sortedMimes(accept string) (sorted []mime) {
|
|
||||||
for _, each := range strings.Split(accept, ",") {
|
|
||||||
typeAndQuality := strings.Split(strings.Trim(each, " "), ";")
|
|
||||||
if len(typeAndQuality) == 1 {
|
|
||||||
sorted = insertMime(sorted, mime{typeAndQuality[0], 1.0})
|
|
||||||
} else {
|
|
||||||
// take factor
|
|
||||||
parts := strings.Split(typeAndQuality[1], "=")
|
|
||||||
if len(parts) == 2 {
|
|
||||||
f, err := strconv.ParseFloat(parts[1], 64)
|
|
||||||
if err != nil {
|
|
||||||
traceLogger.Printf("unable to parse quality in %s, %v", each, err)
|
|
||||||
} else {
|
|
||||||
sorted = insertMime(sorted, mime{typeAndQuality[0], f})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
|
||||||
// and provides the response with a set of allowed methods for the request URL Path.
|
|
||||||
// As for any filter, you can also install it for a particular WebService within a Container.
|
|
||||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
|
||||||
func (c *Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
if "OPTIONS" != req.Request.Method {
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp.AddHeader(HEADER_Allow, strings.Join(c.computeAllowedMethods(req), ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
|
||||||
// and provides the response with a set of allowed methods for the request URL Path.
|
|
||||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
|
||||||
func OPTIONSFilter() FilterFunction {
|
|
||||||
return DefaultContainer.OPTIONSFilter
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PathParameterKind = indicator of Request parameter type "path"
|
|
||||||
PathParameterKind = iota
|
|
||||||
|
|
||||||
// QueryParameterKind = indicator of Request parameter type "query"
|
|
||||||
QueryParameterKind
|
|
||||||
|
|
||||||
// BodyParameterKind = indicator of Request parameter type "body"
|
|
||||||
BodyParameterKind
|
|
||||||
|
|
||||||
// HeaderParameterKind = indicator of Request parameter type "header"
|
|
||||||
HeaderParameterKind
|
|
||||||
|
|
||||||
// FormParameterKind = indicator of Request parameter type "form"
|
|
||||||
FormParameterKind
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parameter is for documententing the parameter used in a Http Request
|
|
||||||
// ParameterData kinds are Path,Query and Body
|
|
||||||
type Parameter struct {
|
|
||||||
data *ParameterData
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParameterData represents the state of a Parameter.
|
|
||||||
// It is made public to make it accessible to e.g. the Swagger package.
|
|
||||||
type ParameterData struct {
|
|
||||||
Name, Description, DataType, DataFormat string
|
|
||||||
Kind int
|
|
||||||
Required bool
|
|
||||||
AllowableValues map[string]string
|
|
||||||
AllowMultiple bool
|
|
||||||
DefaultValue string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data returns the state of the Parameter
|
|
||||||
func (p *Parameter) Data() ParameterData {
|
|
||||||
return *p.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kind returns the parameter type indicator (see const for valid values)
|
|
||||||
func (p *Parameter) Kind() int {
|
|
||||||
return p.data.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) bePath() *Parameter {
|
|
||||||
p.data.Kind = PathParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
func (p *Parameter) beQuery() *Parameter {
|
|
||||||
p.data.Kind = QueryParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
func (p *Parameter) beBody() *Parameter {
|
|
||||||
p.data.Kind = BodyParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) beHeader() *Parameter {
|
|
||||||
p.data.Kind = HeaderParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) beForm() *Parameter {
|
|
||||||
p.data.Kind = FormParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required sets the required field and returns the receiver
|
|
||||||
func (p *Parameter) Required(required bool) *Parameter {
|
|
||||||
p.data.Required = required
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowMultiple sets the allowMultiple field and returns the receiver
|
|
||||||
func (p *Parameter) AllowMultiple(multiple bool) *Parameter {
|
|
||||||
p.data.AllowMultiple = multiple
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowableValues sets the allowableValues field and returns the receiver
|
|
||||||
func (p *Parameter) AllowableValues(values map[string]string) *Parameter {
|
|
||||||
p.data.AllowableValues = values
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataType sets the dataType field and returns the receiver
|
|
||||||
func (p *Parameter) DataType(typeName string) *Parameter {
|
|
||||||
p.data.DataType = typeName
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataFormat sets the dataFormat field for Swagger UI
|
|
||||||
func (p *Parameter) DataFormat(formatName string) *Parameter {
|
|
||||||
p.data.DataFormat = formatName
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultValue sets the default value field and returns the receiver
|
|
||||||
func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter {
|
|
||||||
p.data.DefaultValue = stringRepresentation
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Description sets the description value field and returns the receiver
|
|
||||||
func (p *Parameter) Description(doc string) *Parameter {
|
|
||||||
p.data.Description = doc
|
|
||||||
return p
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PathExpression holds a compiled path expression (RegExp) needed to match against
|
|
||||||
// Http request paths and to extract path parameter values.
|
|
||||||
type pathExpression struct {
|
|
||||||
LiteralCount int // the number of literal characters (means those not resulting from template variable substitution)
|
|
||||||
VarCount int // the number of named parameters (enclosed by {}) in the path
|
|
||||||
Matcher *regexp.Regexp
|
|
||||||
Source string // Path as defined by the RouteBuilder
|
|
||||||
tokens []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPathExpression creates a PathExpression from the input URL path.
|
|
||||||
// Returns an error if the path is invalid.
|
|
||||||
func newPathExpression(path string) (*pathExpression, error) {
|
|
||||||
expression, literalCount, varCount, tokens := templateToRegularExpression(path)
|
|
||||||
compiled, err := regexp.Compile(expression)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &pathExpression{literalCount, varCount, compiled, expression, tokens}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-370003.7.3
|
|
||||||
func templateToRegularExpression(template string) (expression string, literalCount int, varCount int, tokens []string) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
buffer.WriteString("^")
|
|
||||||
//tokens = strings.Split(template, "/")
|
|
||||||
tokens = tokenizePath(template)
|
|
||||||
for _, each := range tokens {
|
|
||||||
if each == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buffer.WriteString("/")
|
|
||||||
if strings.HasPrefix(each, "{") {
|
|
||||||
// check for regular expression in variable
|
|
||||||
colon := strings.Index(each, ":")
|
|
||||||
if colon != -1 {
|
|
||||||
// extract expression
|
|
||||||
paramExpr := strings.TrimSpace(each[colon+1 : len(each)-1])
|
|
||||||
if paramExpr == "*" { // special case
|
|
||||||
buffer.WriteString("(.*)")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf("(%s)", paramExpr)) // between colon and closing moustache
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// plain var
|
|
||||||
buffer.WriteString("([^/]+?)")
|
|
||||||
}
|
|
||||||
varCount += 1
|
|
||||||
} else {
|
|
||||||
literalCount += len(each)
|
|
||||||
encoded := each // TODO URI encode
|
|
||||||
buffer.WriteString(regexp.QuoteMeta(encoded))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.TrimRight(buffer.String(), "/") + "(/.*)?$", literalCount, varCount, tokens
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/zlib"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultRequestContentType string
|
|
||||||
|
|
||||||
// Request is a wrapper for a http Request that provides convenience methods
|
|
||||||
type Request struct {
|
|
||||||
Request *http.Request
|
|
||||||
pathParameters map[string]string
|
|
||||||
attributes map[string]interface{} // for storing request-scoped values
|
|
||||||
selectedRoutePath string // root path + route path that matched the request, e.g. /meetings/{id}/attendees
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequest(httpRequest *http.Request) *Request {
|
|
||||||
return &Request{
|
|
||||||
Request: httpRequest,
|
|
||||||
pathParameters: map[string]string{},
|
|
||||||
attributes: map[string]interface{}{},
|
|
||||||
} // empty parameters, attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
// If ContentType is missing or */* is given then fall back to this type, otherwise
|
|
||||||
// a "Unable to unmarshal content of type:" response is returned.
|
|
||||||
// Valid values are restful.MIME_JSON and restful.MIME_XML
|
|
||||||
// Example:
|
|
||||||
// restful.DefaultRequestContentType(restful.MIME_JSON)
|
|
||||||
func DefaultRequestContentType(mime string) {
|
|
||||||
defaultRequestContentType = mime
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameter accesses the Path parameter value by its name
|
|
||||||
func (r *Request) PathParameter(name string) string {
|
|
||||||
return r.pathParameters[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameters accesses the Path parameter values
|
|
||||||
func (r *Request) PathParameters() map[string]string {
|
|
||||||
return r.pathParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryParameter returns the (first) Query parameter value by its name
|
|
||||||
func (r *Request) QueryParameter(name string) string {
|
|
||||||
return r.Request.FormValue(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BodyParameter parses the body of the request (once for typically a POST or a PUT) and returns the value of the given name or an error.
|
|
||||||
func (r *Request) BodyParameter(name string) (string, error) {
|
|
||||||
err := r.Request.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return r.Request.PostFormValue(name), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderParameter returns the HTTP Header value of a Header name or empty if missing
|
|
||||||
func (r *Request) HeaderParameter(name string) string {
|
|
||||||
return r.Request.Header.Get(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadEntity checks the Accept header and reads the content into the entityPointer.
|
|
||||||
func (r *Request) ReadEntity(entityPointer interface{}) (err error) {
|
|
||||||
contentType := r.Request.Header.Get(HEADER_ContentType)
|
|
||||||
contentEncoding := r.Request.Header.Get(HEADER_ContentEncoding)
|
|
||||||
|
|
||||||
// check if the request body needs decompression
|
|
||||||
if ENCODING_GZIP == contentEncoding {
|
|
||||||
gzipReader := currentCompressorProvider.AcquireGzipReader()
|
|
||||||
defer currentCompressorProvider.ReleaseGzipReader(gzipReader)
|
|
||||||
gzipReader.Reset(r.Request.Body)
|
|
||||||
r.Request.Body = gzipReader
|
|
||||||
} else if ENCODING_DEFLATE == contentEncoding {
|
|
||||||
zlibReader, err := zlib.NewReader(r.Request.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Request.Body = zlibReader
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup the EntityReader, use defaultRequestContentType if needed and provided
|
|
||||||
entityReader, ok := entityAccessRegistry.accessorAt(contentType)
|
|
||||||
if !ok {
|
|
||||||
if len(defaultRequestContentType) != 0 {
|
|
||||||
entityReader, ok = entityAccessRegistry.accessorAt(defaultRequestContentType)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entityReader.Read(r, entityPointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAttribute adds or replaces the attribute with the given value.
|
|
||||||
func (r *Request) SetAttribute(name string, value interface{}) {
|
|
||||||
r.attributes[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute returns the value associated to the given name. Returns nil if absent.
|
|
||||||
func (r Request) Attribute(name string) interface{} {
|
|
||||||
return r.attributes[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectedRoutePath root path + route path that matched the request, e.g. /meetings/{id}/attendees
|
|
||||||
func (r Request) SelectedRoutePath() string {
|
|
||||||
return r.selectedRoutePath
|
|
||||||
}
|
|
|
@ -1,236 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultResponseMimeType is DEPRECATED, use DefaultResponseContentType(mime)
|
|
||||||
var DefaultResponseMimeType string
|
|
||||||
|
|
||||||
//PrettyPrintResponses controls the indentation feature of XML and JSON serialization
|
|
||||||
var PrettyPrintResponses = true
|
|
||||||
|
|
||||||
// Response is a wrapper on the actual http ResponseWriter
|
|
||||||
// It provides several convenience methods to prepare and write response content.
|
|
||||||
type Response struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
requestAccept string // mime-type what the Http Request says it wants to receive
|
|
||||||
routeProduces []string // mime-types what the Route says it can produce
|
|
||||||
statusCode int // HTTP status code that has been written explicity (if zero then net/http has written 200)
|
|
||||||
contentLength int // number of bytes written for the response body
|
|
||||||
prettyPrint bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
|
|
||||||
err error // err property is kept when WriteError is called
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewResponse creates a new response based on a http ResponseWriter.
|
|
||||||
func NewResponse(httpWriter http.ResponseWriter) *Response {
|
|
||||||
return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses, nil} // empty content-types
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultResponseContentType set a default.
|
|
||||||
// If Accept header matching fails, fall back to this type.
|
|
||||||
// Valid values are restful.MIME_JSON and restful.MIME_XML
|
|
||||||
// Example:
|
|
||||||
// restful.DefaultResponseContentType(restful.MIME_JSON)
|
|
||||||
func DefaultResponseContentType(mime string) {
|
|
||||||
DefaultResponseMimeType = mime
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalServerError writes the StatusInternalServerError header.
|
|
||||||
// DEPRECATED, use WriteErrorString(http.StatusInternalServerError,reason)
|
|
||||||
func (r Response) InternalServerError() Response {
|
|
||||||
r.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettyPrint changes whether this response must produce pretty (line-by-line, indented) JSON or XML output.
|
|
||||||
func (r *Response) PrettyPrint(bePretty bool) {
|
|
||||||
r.prettyPrint = bePretty
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHeader is a shortcut for .Header().Add(header,value)
|
|
||||||
func (r Response) AddHeader(header string, value string) Response {
|
|
||||||
r.Header().Add(header, value)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRequestAccepts tells the response what Mime-type(s) the HTTP request said it wants to accept. Exposed for testing.
|
|
||||||
func (r *Response) SetRequestAccepts(mime string) {
|
|
||||||
r.requestAccept = mime
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntityWriter returns the registered EntityWriter that the entity (requested resource)
|
|
||||||
// can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say.
|
|
||||||
// If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable.
|
|
||||||
func (r *Response) EntityWriter() (EntityReaderWriter, bool) {
|
|
||||||
sorted := sortedMimes(r.requestAccept)
|
|
||||||
for _, eachAccept := range sorted {
|
|
||||||
for _, eachProduce := range r.routeProduces {
|
|
||||||
if eachProduce == eachAccept.media {
|
|
||||||
if w, ok := entityAccessRegistry.accessorAt(eachAccept.media); ok {
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if eachAccept.media == "*/*" {
|
|
||||||
for _, each := range r.routeProduces {
|
|
||||||
if w, ok := entityAccessRegistry.accessorAt(each); ok {
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if requestAccept is empty
|
|
||||||
writer, ok := entityAccessRegistry.accessorAt(r.requestAccept)
|
|
||||||
if !ok {
|
|
||||||
// if not registered then fallback to the defaults (if set)
|
|
||||||
if DefaultResponseMimeType == MIME_JSON {
|
|
||||||
return entityAccessRegistry.accessorAt(MIME_JSON)
|
|
||||||
}
|
|
||||||
if DefaultResponseMimeType == MIME_XML {
|
|
||||||
return entityAccessRegistry.accessorAt(MIME_XML)
|
|
||||||
}
|
|
||||||
// Fallback to whatever the route says it can produce.
|
|
||||||
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
||||||
for _, each := range r.routeProduces {
|
|
||||||
if w, ok := entityAccessRegistry.accessorAt(each); ok {
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return writer, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteEntity calls WriteHeaderAndEntity with Http Status OK (200)
|
|
||||||
func (r *Response) WriteEntity(value interface{}) error {
|
|
||||||
return r.WriteHeaderAndEntity(http.StatusOK, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeaderAndEntity marshals the value using the representation denoted by the Accept Header and the registered EntityWriters.
|
|
||||||
// If no Accept header is specified (or */*) then respond with the Content-Type as specified by the first in the Route.Produces.
|
|
||||||
// If an Accept header is specified then respond with the Content-Type as specified by the first in the Route.Produces that is matched with the Accept header.
|
|
||||||
// If the value is nil then no response is send except for the Http status. You may want to call WriteHeader(http.StatusNotFound) instead.
|
|
||||||
// If there is no writer available that can represent the value in the requested MIME type then Http Status NotAcceptable is written.
|
|
||||||
// Current implementation ignores any q-parameters in the Accept Header.
|
|
||||||
// Returns an error if the value could not be written on the response.
|
|
||||||
func (r *Response) WriteHeaderAndEntity(status int, value interface{}) error {
|
|
||||||
writer, ok := r.EntityWriter()
|
|
||||||
if !ok {
|
|
||||||
r.WriteHeader(http.StatusNotAcceptable)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return writer.Write(r, status, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the value)
|
|
||||||
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteAsXml(value interface{}) error {
|
|
||||||
return writeXML(r, http.StatusOK, MIME_XML, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeaderAndXml is a convenience method for writing a status and value in xml (requires Xml tags on the value)
|
|
||||||
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteHeaderAndXml(status int, value interface{}) error {
|
|
||||||
return writeXML(r, status, MIME_XML, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAsJson is a convenience method for writing a value in json.
|
|
||||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteAsJson(value interface{}) error {
|
|
||||||
return writeJSON(r, http.StatusOK, MIME_JSON, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteJson is a convenience method for writing a value in Json with a given Content-Type.
|
|
||||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteJson(value interface{}, contentType string) error {
|
|
||||||
return writeJSON(r, http.StatusOK, contentType, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeaderAndJson is a convenience method for writing the status and a value in Json with a given Content-Type.
|
|
||||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteHeaderAndJson(status int, value interface{}, contentType string) error {
|
|
||||||
return writeJSON(r, status, contentType, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteError write the http status and the error string on the response.
|
|
||||||
func (r *Response) WriteError(httpStatus int, err error) error {
|
|
||||||
r.err = err
|
|
||||||
return r.WriteErrorString(httpStatus, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteServiceError is a convenience method for a responding with a status and a ServiceError
|
|
||||||
func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error {
|
|
||||||
r.err = err
|
|
||||||
return r.WriteHeaderAndEntity(httpStatus, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteErrorString is a convenience method for an error status with the actual error
|
|
||||||
func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
|
|
||||||
if r.err == nil {
|
|
||||||
// if not called from WriteError
|
|
||||||
r.err = errors.New(errorReason)
|
|
||||||
}
|
|
||||||
r.WriteHeader(httpStatus)
|
|
||||||
if _, err := r.Write([]byte(errorReason)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush implements http.Flusher interface, which sends any buffered data to the client.
|
|
||||||
func (r *Response) Flush() {
|
|
||||||
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
|
||||||
f.Flush()
|
|
||||||
} else if trace {
|
|
||||||
traceLogger.Printf("ResponseWriter %v doesn't support Flush", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader is overridden to remember the Status Code that has been written.
|
|
||||||
// Changes to the Header of the response have no effect after this.
|
|
||||||
func (r *Response) WriteHeader(httpStatus int) {
|
|
||||||
r.statusCode = httpStatus
|
|
||||||
r.ResponseWriter.WriteHeader(httpStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatusCode returns the code that has been written using WriteHeader.
|
|
||||||
func (r Response) StatusCode() int {
|
|
||||||
if 0 == r.statusCode {
|
|
||||||
// no status code has been written yet; assume OK
|
|
||||||
return http.StatusOK
|
|
||||||
}
|
|
||||||
return r.statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes the data to the connection as part of an HTTP reply.
|
|
||||||
// Write is part of http.ResponseWriter interface.
|
|
||||||
func (r *Response) Write(bytes []byte) (int, error) {
|
|
||||||
written, err := r.ResponseWriter.Write(bytes)
|
|
||||||
r.contentLength += written
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContentLength returns the number of bytes written for the response content.
|
|
||||||
// Note that this value is only correct if all data is written through the Response using its Write* methods.
|
|
||||||
// Data written directly using the underlying http.ResponseWriter is not accounted for.
|
|
||||||
func (r Response) ContentLength() int {
|
|
||||||
return r.contentLength
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseNotify is part of http.CloseNotifier interface
|
|
||||||
func (r Response) CloseNotify() <-chan bool {
|
|
||||||
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the err created by WriteError
|
|
||||||
func (r Response) Error() error {
|
|
||||||
return r.err
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouteFunction declares the signature of a function that can be bound to a Route.
|
|
||||||
type RouteFunction func(*Request, *Response)
|
|
||||||
|
|
||||||
// Route binds a HTTP Method,Path,Consumes combination to a RouteFunction.
|
|
||||||
type Route struct {
|
|
||||||
Method string
|
|
||||||
Produces []string
|
|
||||||
Consumes []string
|
|
||||||
Path string // webservice root path + described path
|
|
||||||
Function RouteFunction
|
|
||||||
Filters []FilterFunction
|
|
||||||
|
|
||||||
// cached values for dispatching
|
|
||||||
relativePath string
|
|
||||||
pathParts []string
|
|
||||||
pathExpr *pathExpression // cached compilation of relativePath as RegExp
|
|
||||||
|
|
||||||
// documentation
|
|
||||||
Doc string
|
|
||||||
Notes string
|
|
||||||
Operation string
|
|
||||||
ParameterDocs []*Parameter
|
|
||||||
ResponseErrors map[int]ResponseError
|
|
||||||
ReadSample, WriteSample interface{} // structs that model an example request or response payload
|
|
||||||
|
|
||||||
// Extra information used to store custom information about the route.
|
|
||||||
Metadata map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize for Route
|
|
||||||
func (r *Route) postBuild() {
|
|
||||||
r.pathParts = tokenizePath(r.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Request and Response from their http versions
|
|
||||||
func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
|
|
||||||
params := r.extractParameters(httpRequest.URL.Path)
|
|
||||||
wrappedRequest := NewRequest(httpRequest)
|
|
||||||
wrappedRequest.pathParameters = params
|
|
||||||
wrappedRequest.selectedRoutePath = r.Path
|
|
||||||
wrappedResponse := NewResponse(httpWriter)
|
|
||||||
wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept)
|
|
||||||
wrappedResponse.routeProduces = r.Produces
|
|
||||||
return wrappedRequest, wrappedResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispatchWithFilters call the function after passing through its own filters
|
|
||||||
func (r *Route) dispatchWithFilters(wrappedRequest *Request, wrappedResponse *Response) {
|
|
||||||
if len(r.Filters) > 0 {
|
|
||||||
chain := FilterChain{Filters: r.Filters, Target: r.Function}
|
|
||||||
chain.ProcessFilter(wrappedRequest, wrappedResponse)
|
|
||||||
} else {
|
|
||||||
// unfiltered
|
|
||||||
r.Function(wrappedRequest, wrappedResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return whether the mimeType matches to what this Route can produce.
|
|
||||||
func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
|
|
||||||
parts := strings.Split(mimeTypesWithQuality, ",")
|
|
||||||
for _, each := range parts {
|
|
||||||
var withoutQuality string
|
|
||||||
if strings.Contains(each, ";") {
|
|
||||||
withoutQuality = strings.Split(each, ";")[0]
|
|
||||||
} else {
|
|
||||||
withoutQuality = each
|
|
||||||
}
|
|
||||||
// trim before compare
|
|
||||||
withoutQuality = strings.Trim(withoutQuality, " ")
|
|
||||||
if withoutQuality == "*/*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, producibleType := range r.Produces {
|
|
||||||
if producibleType == "*/*" || producibleType == withoutQuality {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
|
|
||||||
func (r Route) matchesContentType(mimeTypes string) bool {
|
|
||||||
|
|
||||||
if len(r.Consumes) == 0 {
|
|
||||||
// did not specify what it can consume ; any media type (“*/*”) is assumed
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mimeTypes) == 0 {
|
|
||||||
// idempotent methods with (most-likely or garanteed) empty content match missing Content-Type
|
|
||||||
m := r.Method
|
|
||||||
if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// proceed with default
|
|
||||||
mimeTypes = MIME_OCTET
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(mimeTypes, ",")
|
|
||||||
for _, each := range parts {
|
|
||||||
var contentType string
|
|
||||||
if strings.Contains(each, ";") {
|
|
||||||
contentType = strings.Split(each, ";")[0]
|
|
||||||
} else {
|
|
||||||
contentType = each
|
|
||||||
}
|
|
||||||
// trim before compare
|
|
||||||
contentType = strings.Trim(contentType, " ")
|
|
||||||
for _, consumeableType := range r.Consumes {
|
|
||||||
if consumeableType == "*/*" || consumeableType == contentType {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the parameters from the request url path
|
|
||||||
func (r Route) extractParameters(urlPath string) map[string]string {
|
|
||||||
urlParts := tokenizePath(urlPath)
|
|
||||||
pathParameters := map[string]string{}
|
|
||||||
for i, key := range r.pathParts {
|
|
||||||
var value string
|
|
||||||
if i >= len(urlParts) {
|
|
||||||
value = ""
|
|
||||||
} else {
|
|
||||||
value = urlParts[i]
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(key, "{") { // path-parameter
|
|
||||||
if colon := strings.Index(key, ":"); colon != -1 {
|
|
||||||
// extract by regex
|
|
||||||
regPart := key[colon+1 : len(key)-1]
|
|
||||||
keyPart := key[1:colon]
|
|
||||||
if regPart == "*" {
|
|
||||||
pathParameters[keyPart] = untokenizePath(i, urlParts)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
pathParameters[keyPart] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// without enclosing {}
|
|
||||||
pathParameters[key[1:len(key)-1]] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pathParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// Untokenize back into an URL path using the slash separator
|
|
||||||
func untokenizePath(offset int, parts []string) string {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
for p := offset; p < len(parts); p++ {
|
|
||||||
buffer.WriteString(parts[p])
|
|
||||||
// do not end
|
|
||||||
if p < len(parts)-1 {
|
|
||||||
buffer.WriteString("/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tokenize an URL path using the slash separator ; the result does not have empty tokens
|
|
||||||
func tokenizePath(path string) []string {
|
|
||||||
if "/" == path {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
return strings.Split(strings.Trim(path, "/"), "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debugging
|
|
||||||
func (r Route) String() string {
|
|
||||||
return r.Method + " " + r.Path
|
|
||||||
}
|
|
|
@ -1,293 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouteBuilder is a helper to construct Routes.
|
|
||||||
type RouteBuilder struct {
|
|
||||||
rootPath string
|
|
||||||
currentPath string
|
|
||||||
produces []string
|
|
||||||
consumes []string
|
|
||||||
httpMethod string // required
|
|
||||||
function RouteFunction // required
|
|
||||||
filters []FilterFunction
|
|
||||||
|
|
||||||
typeNameHandleFunc TypeNameHandleFunction // required
|
|
||||||
|
|
||||||
// documentation
|
|
||||||
doc string
|
|
||||||
notes string
|
|
||||||
operation string
|
|
||||||
readSample, writeSample interface{}
|
|
||||||
parameters []*Parameter
|
|
||||||
errorMap map[int]ResponseError
|
|
||||||
metadata map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do evaluates each argument with the RouteBuilder itself.
|
|
||||||
// This allows you to follow DRY principles without breaking the fluent programming style.
|
|
||||||
// Example:
|
|
||||||
// ws.Route(ws.DELETE("/{name}").To(t.deletePerson).Do(Returns200, Returns500))
|
|
||||||
//
|
|
||||||
// func Returns500(b *RouteBuilder) {
|
|
||||||
// b.Returns(500, "Internal Server Error", restful.ServiceError{})
|
|
||||||
// }
|
|
||||||
func (b *RouteBuilder) Do(oneArgBlocks ...func(*RouteBuilder)) *RouteBuilder {
|
|
||||||
for _, each := range oneArgBlocks {
|
|
||||||
each(b)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// To bind the route to a function.
|
|
||||||
// If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
|
|
||||||
func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
|
|
||||||
b.function = function
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method specifies what HTTP method to match. Required.
|
|
||||||
func (b *RouteBuilder) Method(method string) *RouteBuilder {
|
|
||||||
b.httpMethod = method
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Produces specifies what MIME types can be produced ; the matched one will appear in the Content-Type Http header.
|
|
||||||
func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
|
|
||||||
b.produces = mimeTypes
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumes specifies what MIME types can be consumes ; the Accept Http header must matched any of these
|
|
||||||
func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
|
|
||||||
b.consumes = mimeTypes
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
|
|
||||||
func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
|
|
||||||
b.currentPath = subPath
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doc tells what this route is all about. Optional.
|
|
||||||
func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
|
|
||||||
b.doc = documentation
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// A verbose explanation of the operation behavior. Optional.
|
|
||||||
func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
|
|
||||||
b.notes = notes
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads tells what resource type will be read from the request payload. Optional.
|
|
||||||
// A parameter of type "body" is added ,required is set to true and the dataType is set to the qualified name of the sample's type.
|
|
||||||
func (b *RouteBuilder) Reads(sample interface{}) *RouteBuilder {
|
|
||||||
fn := b.typeNameHandleFunc
|
|
||||||
if fn == nil {
|
|
||||||
fn = reflectTypeName
|
|
||||||
}
|
|
||||||
typeAsName := fn(sample)
|
|
||||||
|
|
||||||
b.readSample = sample
|
|
||||||
bodyParameter := &Parameter{&ParameterData{Name: "body"}}
|
|
||||||
bodyParameter.beBody()
|
|
||||||
bodyParameter.Required(true)
|
|
||||||
bodyParameter.DataType(typeAsName)
|
|
||||||
b.Param(bodyParameter)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not.
|
|
||||||
// Use this to modify or extend information for the Parameter (through its Data()).
|
|
||||||
func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
|
|
||||||
for _, each := range b.parameters {
|
|
||||||
if each.Data().Name == name {
|
|
||||||
return each
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes tells what resource type will be written as the response payload. Optional.
|
|
||||||
func (b *RouteBuilder) Writes(sample interface{}) *RouteBuilder {
|
|
||||||
b.writeSample = sample
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
|
|
||||||
func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
|
|
||||||
if b.parameters == nil {
|
|
||||||
b.parameters = []*Parameter{}
|
|
||||||
}
|
|
||||||
b.parameters = append(b.parameters, parameter)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operation allows you to document what the actual method/function call is of the Route.
|
|
||||||
// Unless called, the operation name is derived from the RouteFunction set using To(..).
|
|
||||||
func (b *RouteBuilder) Operation(name string) *RouteBuilder {
|
|
||||||
b.operation = name
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReturnsError is deprecated, use Returns instead.
|
|
||||||
func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
|
|
||||||
log.Print("ReturnsError is deprecated, use Returns instead.")
|
|
||||||
return b.Returns(code, message, model)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns allows you to document what responses (errors or regular) can be expected.
|
|
||||||
// The model parameter is optional ; either pass a struct instance or use nil if not applicable.
|
|
||||||
func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
|
|
||||||
err := ResponseError{
|
|
||||||
Code: code,
|
|
||||||
Message: message,
|
|
||||||
Model: model,
|
|
||||||
IsDefault: false,
|
|
||||||
}
|
|
||||||
// lazy init because there is no NewRouteBuilder (yet)
|
|
||||||
if b.errorMap == nil {
|
|
||||||
b.errorMap = map[int]ResponseError{}
|
|
||||||
}
|
|
||||||
b.errorMap[code] = err
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultReturns is a special Returns call that sets the default of the response ; the code is zero.
|
|
||||||
func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder {
|
|
||||||
b.Returns(0, message, model)
|
|
||||||
// Modify the ResponseError just added/updated
|
|
||||||
re := b.errorMap[0]
|
|
||||||
// errorMap is initialized
|
|
||||||
b.errorMap[0] = ResponseError{
|
|
||||||
Code: re.Code,
|
|
||||||
Message: re.Message,
|
|
||||||
Model: re.Model,
|
|
||||||
IsDefault: true,
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata adds or updates a key=value pair to the metadata map.
|
|
||||||
func (b *RouteBuilder) Metadata(key string, value interface{}) *RouteBuilder {
|
|
||||||
if b.metadata == nil {
|
|
||||||
b.metadata = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
b.metadata[key] = value
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseError represents a response; not necessarily an error.
|
|
||||||
type ResponseError struct {
|
|
||||||
Code int
|
|
||||||
Message string
|
|
||||||
Model interface{}
|
|
||||||
IsDefault bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
|
|
||||||
b.rootPath = path
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter appends a FilterFunction to the end of filters for this Route to build.
|
|
||||||
func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
|
|
||||||
b.filters = append(b.filters, filter)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no specific Route path then set to rootPath
|
|
||||||
// If no specific Produces then set to rootProduces
|
|
||||||
// If no specific Consumes then set to rootConsumes
|
|
||||||
func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
|
|
||||||
if len(b.produces) == 0 {
|
|
||||||
b.produces = rootProduces
|
|
||||||
}
|
|
||||||
if len(b.consumes) == 0 {
|
|
||||||
b.consumes = rootConsumes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeNameHandler sets the function that will convert types to strings in the parameter
|
|
||||||
// and model definitions.
|
|
||||||
func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
|
|
||||||
b.typeNameHandleFunc = handler
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build creates a new Route using the specification details collected by the RouteBuilder
|
|
||||||
func (b *RouteBuilder) Build() Route {
|
|
||||||
pathExpr, err := newPathExpression(b.currentPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[restful] Invalid path:%s because:%v", b.currentPath, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if b.function == nil {
|
|
||||||
log.Printf("[restful] No function specified for route:" + b.currentPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
operationName := b.operation
|
|
||||||
if len(operationName) == 0 && b.function != nil {
|
|
||||||
// extract from definition
|
|
||||||
operationName = nameOfFunction(b.function)
|
|
||||||
}
|
|
||||||
route := Route{
|
|
||||||
Method: b.httpMethod,
|
|
||||||
Path: concatPath(b.rootPath, b.currentPath),
|
|
||||||
Produces: b.produces,
|
|
||||||
Consumes: b.consumes,
|
|
||||||
Function: b.function,
|
|
||||||
Filters: b.filters,
|
|
||||||
relativePath: b.currentPath,
|
|
||||||
pathExpr: pathExpr,
|
|
||||||
Doc: b.doc,
|
|
||||||
Notes: b.notes,
|
|
||||||
Operation: operationName,
|
|
||||||
ParameterDocs: b.parameters,
|
|
||||||
ResponseErrors: b.errorMap,
|
|
||||||
ReadSample: b.readSample,
|
|
||||||
WriteSample: b.writeSample,
|
|
||||||
Metadata: b.metadata}
|
|
||||||
route.postBuild()
|
|
||||||
return route
|
|
||||||
}
|
|
||||||
|
|
||||||
func concatPath(path1, path2 string) string {
|
|
||||||
return strings.TrimRight(path1, "/") + "/" + strings.TrimLeft(path2, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
var anonymousFuncCount int32
|
|
||||||
|
|
||||||
// nameOfFunction returns the short name of the function f for documentation.
|
|
||||||
// It uses a runtime feature for debugging ; its value may change for later Go versions.
|
|
||||||
func nameOfFunction(f interface{}) string {
|
|
||||||
fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
|
|
||||||
tokenized := strings.Split(fun.Name(), ".")
|
|
||||||
last := tokenized[len(tokenized)-1]
|
|
||||||
last = strings.TrimSuffix(last, ")·fm") // < Go 1.5
|
|
||||||
last = strings.TrimSuffix(last, ")-fm") // Go 1.5
|
|
||||||
last = strings.TrimSuffix(last, "·fm") // < Go 1.5
|
|
||||||
last = strings.TrimSuffix(last, "-fm") // Go 1.5
|
|
||||||
if last == "func1" { // this could mean conflicts in API docs
|
|
||||||
val := atomic.AddInt32(&anonymousFuncCount, 1)
|
|
||||||
last = "func" + fmt.Sprintf("%d", val)
|
|
||||||
atomic.StoreInt32(&anonymousFuncCount, val)
|
|
||||||
}
|
|
||||||
return last
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// A RouteSelector finds the best matching Route given the input HTTP Request
|
|
||||||
type RouteSelector interface {
|
|
||||||
|
|
||||||
// SelectRoute finds a Route given the input HTTP Request and a list of WebServices.
|
|
||||||
// It returns a selected Route and its containing WebService or an error indicating
|
|
||||||
// a problem.
|
|
||||||
SelectRoute(
|
|
||||||
webServices []*WebService,
|
|
||||||
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error)
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// ServiceError is a transport object to pass information about a non-Http error occurred in a WebService while processing a request.
|
|
||||||
type ServiceError struct {
|
|
||||||
Code int
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewError returns a ServiceError using the code and reason
|
|
||||||
func NewError(code int, message string) ServiceError {
|
|
||||||
return ServiceError{Code: code, Message: message}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns a text representation of the service error
|
|
||||||
func (s ServiceError) Error() string {
|
|
||||||
return fmt.Sprintf("[ServiceError:%v] %v", s.Code, s.Message)
|
|
||||||
}
|
|
|
@ -1,290 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// WebService holds a collection of Route values that bind a Http Method + URL Path to a function.
|
|
||||||
type WebService struct {
|
|
||||||
rootPath string
|
|
||||||
pathExpr *pathExpression // cached compilation of rootPath as RegExp
|
|
||||||
routes []Route
|
|
||||||
produces []string
|
|
||||||
consumes []string
|
|
||||||
pathParameters []*Parameter
|
|
||||||
filters []FilterFunction
|
|
||||||
documentation string
|
|
||||||
apiVersion string
|
|
||||||
|
|
||||||
typeNameHandleFunc TypeNameHandleFunction
|
|
||||||
|
|
||||||
dynamicRoutes bool
|
|
||||||
|
|
||||||
// protects 'routes' if dynamic routes are enabled
|
|
||||||
routesLock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WebService) SetDynamicRoutes(enable bool) {
|
|
||||||
w.dynamicRoutes = enable
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeNameHandleFunction declares functions that can handle translating the name of a sample object
|
|
||||||
// into the restful documentation for the service.
|
|
||||||
type TypeNameHandleFunction func(sample interface{}) string
|
|
||||||
|
|
||||||
// TypeNameHandler sets the function that will convert types to strings in the parameter
|
|
||||||
// and model definitions. If not set, the web service will invoke
|
|
||||||
// reflect.TypeOf(object).String().
|
|
||||||
func (w *WebService) TypeNameHandler(handler TypeNameHandleFunction) *WebService {
|
|
||||||
w.typeNameHandleFunc = handler
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// reflectTypeName is the default TypeNameHandleFunction and for a given object
|
|
||||||
// returns the name that Go identifies it with (e.g. "string" or "v1.Object") via
|
|
||||||
// the reflection API.
|
|
||||||
func reflectTypeName(sample interface{}) string {
|
|
||||||
return reflect.TypeOf(sample).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// compilePathExpression ensures that the path is compiled into a RegEx for those routers that need it.
|
|
||||||
func (w *WebService) compilePathExpression() {
|
|
||||||
compiled, err := newPathExpression(w.rootPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[restful] invalid path:%s because:%v", w.rootPath, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
w.pathExpr = compiled
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApiVersion sets the API version for documentation purposes.
|
|
||||||
func (w *WebService) ApiVersion(apiVersion string) *WebService {
|
|
||||||
w.apiVersion = apiVersion
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns the API version for documentation purposes.
|
|
||||||
func (w *WebService) Version() string { return w.apiVersion }
|
|
||||||
|
|
||||||
// Path specifies the root URL template path of the WebService.
|
|
||||||
// All Routes will be relative to this path.
|
|
||||||
func (w *WebService) Path(root string) *WebService {
|
|
||||||
w.rootPath = root
|
|
||||||
if len(w.rootPath) == 0 {
|
|
||||||
w.rootPath = "/"
|
|
||||||
}
|
|
||||||
w.compilePathExpression()
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Param adds a PathParameter to document parameters used in the root path.
|
|
||||||
func (w *WebService) Param(parameter *Parameter) *WebService {
|
|
||||||
if w.pathParameters == nil {
|
|
||||||
w.pathParameters = []*Parameter{}
|
|
||||||
}
|
|
||||||
w.pathParameters = append(w.pathParameters, parameter)
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameter creates a new Parameter of kind Path for documentation purposes.
|
|
||||||
// It is initialized as required with string as its DataType.
|
|
||||||
func (w *WebService) PathParameter(name, description string) *Parameter {
|
|
||||||
return PathParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameter creates a new Parameter of kind Path for documentation purposes.
|
|
||||||
// It is initialized as required with string as its DataType.
|
|
||||||
func PathParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: true, DataType: "string"}}
|
|
||||||
p.bePath()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryParameter creates a new Parameter of kind Query for documentation purposes.
|
|
||||||
// It is initialized as not required with string as its DataType.
|
|
||||||
func (w *WebService) QueryParameter(name, description string) *Parameter {
|
|
||||||
return QueryParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryParameter creates a new Parameter of kind Query for documentation purposes.
|
|
||||||
// It is initialized as not required with string as its DataType.
|
|
||||||
func QueryParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}}
|
|
||||||
p.beQuery()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// BodyParameter creates a new Parameter of kind Body for documentation purposes.
|
|
||||||
// It is initialized as required without a DataType.
|
|
||||||
func (w *WebService) BodyParameter(name, description string) *Parameter {
|
|
||||||
return BodyParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BodyParameter creates a new Parameter of kind Body for documentation purposes.
|
|
||||||
// It is initialized as required without a DataType.
|
|
||||||
func BodyParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: true}}
|
|
||||||
p.beBody()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes.
|
|
||||||
// It is initialized as not required with string as its DataType.
|
|
||||||
func (w *WebService) HeaderParameter(name, description string) *Parameter {
|
|
||||||
return HeaderParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes.
|
|
||||||
// It is initialized as not required with string as its DataType.
|
|
||||||
func HeaderParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}}
|
|
||||||
p.beHeader()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes.
|
|
||||||
// It is initialized as required with string as its DataType.
|
|
||||||
func (w *WebService) FormParameter(name, description string) *Parameter {
|
|
||||||
return FormParameter(name, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes.
|
|
||||||
// It is initialized as required with string as its DataType.
|
|
||||||
func FormParameter(name, description string) *Parameter {
|
|
||||||
p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}}
|
|
||||||
p.beForm()
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route creates a new Route using the RouteBuilder and add to the ordered list of Routes.
|
|
||||||
func (w *WebService) Route(builder *RouteBuilder) *WebService {
|
|
||||||
w.routesLock.Lock()
|
|
||||||
defer w.routesLock.Unlock()
|
|
||||||
builder.copyDefaults(w.produces, w.consumes)
|
|
||||||
w.routes = append(w.routes, builder.Build())
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveRoute removes the specified route, looks for something that matches 'path' and 'method'
|
|
||||||
func (w *WebService) RemoveRoute(path, method string) error {
|
|
||||||
if !w.dynamicRoutes {
|
|
||||||
return errors.New("dynamic routes are not enabled.")
|
|
||||||
}
|
|
||||||
w.routesLock.Lock()
|
|
||||||
defer w.routesLock.Unlock()
|
|
||||||
newRoutes := make([]Route, (len(w.routes) - 1))
|
|
||||||
current := 0
|
|
||||||
for ix := range w.routes {
|
|
||||||
if w.routes[ix].Method == method && w.routes[ix].Path == path {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newRoutes[current] = w.routes[ix]
|
|
||||||
current = current + 1
|
|
||||||
}
|
|
||||||
w.routes = newRoutes
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method creates a new RouteBuilder and initialize its http method
|
|
||||||
func (w *WebService) Method(httpMethod string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method(httpMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Produces specifies that this WebService can produce one or more MIME types.
|
|
||||||
// Http requests must have one of these values set for the Accept header.
|
|
||||||
func (w *WebService) Produces(contentTypes ...string) *WebService {
|
|
||||||
w.produces = contentTypes
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumes specifies that this WebService can consume one or more MIME types.
|
|
||||||
// Http requests must have one of these values set for the Content-Type header.
|
|
||||||
func (w *WebService) Consumes(accepts ...string) *WebService {
|
|
||||||
w.consumes = accepts
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routes returns the Routes associated with this WebService
|
|
||||||
func (w *WebService) Routes() []Route {
|
|
||||||
if !w.dynamicRoutes {
|
|
||||||
return w.routes
|
|
||||||
}
|
|
||||||
// Make a copy of the array to prevent concurrency problems
|
|
||||||
w.routesLock.RLock()
|
|
||||||
defer w.routesLock.RUnlock()
|
|
||||||
result := make([]Route, len(w.routes))
|
|
||||||
for ix := range w.routes {
|
|
||||||
result[ix] = w.routes[ix]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// RootPath returns the RootPath associated with this WebService. Default "/"
|
|
||||||
func (w *WebService) RootPath() string {
|
|
||||||
return w.rootPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameters return the path parameter names for (shared amoung its Routes)
|
|
||||||
func (w *WebService) PathParameters() []*Parameter {
|
|
||||||
return w.pathParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter adds a filter function to the chain of filters applicable to all its Routes
|
|
||||||
func (w *WebService) Filter(filter FilterFunction) *WebService {
|
|
||||||
w.filters = append(w.filters, filter)
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doc is used to set the documentation of this service.
|
|
||||||
func (w *WebService) Doc(plainText string) *WebService {
|
|
||||||
w.documentation = plainText
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Documentation returns it.
|
|
||||||
func (w *WebService) Documentation() string {
|
|
||||||
return w.documentation
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convenience methods
|
|
||||||
*/
|
|
||||||
|
|
||||||
// HEAD is a shortcut for .Method("HEAD").Path(subPath)
|
|
||||||
func (w *WebService) HEAD(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("HEAD").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET is a shortcut for .Method("GET").Path(subPath)
|
|
||||||
func (w *WebService) GET(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST is a shortcut for .Method("POST").Path(subPath)
|
|
||||||
func (w *WebService) POST(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("POST").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT is a shortcut for .Method("PUT").Path(subPath)
|
|
||||||
func (w *WebService) PUT(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PUT").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH is a shortcut for .Method("PATCH").Path(subPath)
|
|
||||||
func (w *WebService) PATCH(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PATCH").Path(subPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE is a shortcut for .Method("DELETE").Path(subPath)
|
|
||||||
func (w *WebService) DELETE(subPath string) *RouteBuilder {
|
|
||||||
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("DELETE").Path(subPath)
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultContainer is a restful.Container that uses http.DefaultServeMux
|
|
||||||
var DefaultContainer *Container
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
DefaultContainer = NewContainer()
|
|
||||||
DefaultContainer.ServeMux = http.DefaultServeMux
|
|
||||||
}
|
|
||||||
|
|
||||||
// If set the true then panics will not be caught to return HTTP 500.
|
|
||||||
// In that case, Route functions are responsible for handling any error situation.
|
|
||||||
// Default value is false = recover from panics. This has performance implications.
|
|
||||||
// OBSOLETE ; use restful.DefaultContainer.DoNotRecover(true)
|
|
||||||
var DoNotRecover = false
|
|
||||||
|
|
||||||
// Add registers a new WebService add it to the DefaultContainer.
|
|
||||||
func Add(service *WebService) {
|
|
||||||
DefaultContainer.Add(service)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter appends a container FilterFunction from the DefaultContainer.
|
|
||||||
// These are called before dispatching a http.Request to a WebService.
|
|
||||||
func Filter(filter FilterFunction) {
|
|
||||||
DefaultContainer.Filter(filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisteredWebServices returns the collections of WebServices from the DefaultContainer
|
|
||||||
func RegisteredWebServices() []*WebService {
|
|
||||||
return DefaultContainer.RegisteredWebServices()
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
# gojsonpointer [![Build Status](https://ci.vmware.run/api/badges/go-openapi/jsonpointer/status.svg)](https://ci.vmware.run/go-openapi/jsonpointer) [![Coverage](https://coverage.vmware.run/badges/go-openapi/jsonpointer/coverage.svg)](https://coverage.vmware.run/go-openapi/jsonpointer) [![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/jsonpointer/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/jsonpointer?status.svg)](http://godoc.org/github.com/go-openapi/jsonpointer)
|
|
||||||
An implementation of JSON Pointer - Go language
|
|
||||||
|
|
||||||
## Status
|
|
||||||
Completed YES
|
|
||||||
|
|
||||||
Tested YES
|
|
||||||
|
|
||||||
## References
|
|
||||||
http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
|
|
||||||
|
|
||||||
### Note
|
|
||||||
The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, the reference token MUST contain either...' is not implemented.
|
|
|
@ -1,238 +0,0 @@
|
||||||
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// author sigu-399
|
|
||||||
// author-github https://github.com/sigu-399
|
|
||||||
// author-mail sigu.399@gmail.com
|
|
||||||
//
|
|
||||||
// repository-name jsonpointer
|
|
||||||
// repository-desc An implementation of JSON Pointer - Go language
|
|
||||||
//
|
|
||||||
// description Main and unique file.
|
|
||||||
//
|
|
||||||
// created 25-02-2013
|
|
||||||
|
|
||||||
package jsonpointer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
emptyPointer = ``
|
|
||||||
pointerSeparator = `/`
|
|
||||||
|
|
||||||
invalidStart = `JSON pointer must be empty or start with a "` + pointerSeparator
|
|
||||||
)
|
|
||||||
|
|
||||||
var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem()
|
|
||||||
|
|
||||||
// JSONPointable is an interface for structs to implement when they need to customize the
|
|
||||||
// json pointer process
|
|
||||||
type JSONPointable interface {
|
|
||||||
JSONLookup(string) (interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type implStruct struct {
|
|
||||||
mode string // "SET" or "GET"
|
|
||||||
|
|
||||||
inDocument interface{}
|
|
||||||
|
|
||||||
setInValue interface{}
|
|
||||||
|
|
||||||
getOutNode interface{}
|
|
||||||
getOutKind reflect.Kind
|
|
||||||
outError error
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new json pointer for the given string
|
|
||||||
func New(jsonPointerString string) (Pointer, error) {
|
|
||||||
|
|
||||||
var p Pointer
|
|
||||||
err := p.parse(jsonPointerString)
|
|
||||||
return p, err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pointer the json pointer reprsentation
|
|
||||||
type Pointer struct {
|
|
||||||
referenceTokens []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Constructor", parses the given string JSON pointer
|
|
||||||
func (p *Pointer) parse(jsonPointerString string) error {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if jsonPointerString != emptyPointer {
|
|
||||||
if !strings.HasPrefix(jsonPointerString, pointerSeparator) {
|
|
||||||
err = errors.New(invalidStart)
|
|
||||||
} else {
|
|
||||||
referenceTokens := strings.Split(jsonPointerString, pointerSeparator)
|
|
||||||
for _, referenceToken := range referenceTokens[1:] {
|
|
||||||
p.referenceTokens = append(p.referenceTokens, referenceToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get uses the pointer to retrieve a value from a JSON document
|
|
||||||
func (p *Pointer) Get(document interface{}) (interface{}, reflect.Kind, error) {
|
|
||||||
return p.get(document, swag.DefaultJSONNameProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetForToken gets a value for a json pointer token 1 level deep
|
|
||||||
func GetForToken(document interface{}, decodedToken string) (interface{}, reflect.Kind, error) {
|
|
||||||
return getSingleImpl(document, decodedToken, swag.DefaultJSONNameProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSingleImpl(node interface{}, decodedToken string, nameProvider *swag.NameProvider) (interface{}, reflect.Kind, error) {
|
|
||||||
kind := reflect.Invalid
|
|
||||||
rValue := reflect.Indirect(reflect.ValueOf(node))
|
|
||||||
kind = rValue.Kind()
|
|
||||||
switch kind {
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
if rValue.Type().Implements(jsonPointableType) {
|
|
||||||
r, err := node.(JSONPointable).JSONLookup(decodedToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, kind, err
|
|
||||||
}
|
|
||||||
return r, kind, nil
|
|
||||||
}
|
|
||||||
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
|
|
||||||
if !ok {
|
|
||||||
return nil, kind, fmt.Errorf("object has no field %q", decodedToken)
|
|
||||||
}
|
|
||||||
fld := rValue.FieldByName(nm)
|
|
||||||
return fld.Interface(), kind, nil
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
kv := reflect.ValueOf(decodedToken)
|
|
||||||
mv := rValue.MapIndex(kv)
|
|
||||||
if mv.IsValid() && !swag.IsZero(mv) {
|
|
||||||
return mv.Interface(), kind, nil
|
|
||||||
}
|
|
||||||
return nil, kind, fmt.Errorf("object has no key %q", decodedToken)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
tokenIndex, err := strconv.Atoi(decodedToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, kind, err
|
|
||||||
}
|
|
||||||
sLength := rValue.Len()
|
|
||||||
if tokenIndex < 0 || tokenIndex >= sLength {
|
|
||||||
return nil, kind, fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
elem := rValue.Index(tokenIndex)
|
|
||||||
return elem.Interface(), kind, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, kind, fmt.Errorf("invalid token reference %q", decodedToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pointer) get(node interface{}, nameProvider *swag.NameProvider) (interface{}, reflect.Kind, error) {
|
|
||||||
|
|
||||||
if nameProvider == nil {
|
|
||||||
nameProvider = swag.DefaultJSONNameProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
kind := reflect.Invalid
|
|
||||||
|
|
||||||
// Full document when empty
|
|
||||||
if len(p.referenceTokens) == 0 {
|
|
||||||
return node, kind, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, token := range p.referenceTokens {
|
|
||||||
|
|
||||||
decodedToken := Unescape(token)
|
|
||||||
|
|
||||||
r, knd, err := getSingleImpl(node, decodedToken, nameProvider)
|
|
||||||
if err != nil {
|
|
||||||
return nil, knd, err
|
|
||||||
}
|
|
||||||
node, kind = r, knd
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
rValue := reflect.ValueOf(node)
|
|
||||||
kind = rValue.Kind()
|
|
||||||
|
|
||||||
return node, kind, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodedTokens returns the decoded tokens
|
|
||||||
func (p *Pointer) DecodedTokens() []string {
|
|
||||||
result := make([]string, 0, len(p.referenceTokens))
|
|
||||||
for _, t := range p.referenceTokens {
|
|
||||||
result = append(result, Unescape(t))
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if this is an empty json pointer
|
|
||||||
// this indicates that it points to the root document
|
|
||||||
func (p *Pointer) IsEmpty() bool {
|
|
||||||
return len(p.referenceTokens) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pointer to string representation function
|
|
||||||
func (p *Pointer) String() string {
|
|
||||||
|
|
||||||
if len(p.referenceTokens) == 0 {
|
|
||||||
return emptyPointer
|
|
||||||
}
|
|
||||||
|
|
||||||
pointerString := pointerSeparator + strings.Join(p.referenceTokens, pointerSeparator)
|
|
||||||
|
|
||||||
return pointerString
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specific JSON pointer encoding here
|
|
||||||
// ~0 => ~
|
|
||||||
// ~1 => /
|
|
||||||
// ... and vice versa
|
|
||||||
|
|
||||||
const (
|
|
||||||
encRefTok0 = `~0`
|
|
||||||
encRefTok1 = `~1`
|
|
||||||
decRefTok0 = `~`
|
|
||||||
decRefTok1 = `/`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Unescape unescapes a json pointer reference token string to the original representation
|
|
||||||
func Unescape(token string) string {
|
|
||||||
step1 := strings.Replace(token, encRefTok1, decRefTok1, -1)
|
|
||||||
step2 := strings.Replace(step1, encRefTok0, decRefTok0, -1)
|
|
||||||
return step2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escape escapes a pointer reference token string
|
|
||||||
func Escape(token string) string {
|
|
||||||
step1 := strings.Replace(token, decRefTok0, encRefTok0, -1)
|
|
||||||
step2 := strings.Replace(step1, decRefTok1, encRefTok1, -1)
|
|
||||||
return step2
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
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 "[]"
|
|
||||||
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
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
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.
|
|
||||||
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.
|
|
|
@ -1,15 +0,0 @@
|
||||||
# gojsonreference [![Build Status](https://ci.vmware.run/api/badges/go-openapi/jsonreference/status.svg)](https://ci.vmware.run/go-openapi/jsonreference) [![Coverage](https://coverage.vmware.run/badges/go-openapi/jsonreference/coverage.svg)](https://coverage.vmware.run/go-openapi/jsonreference) [![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/jsonreference/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/jsonreference?status.svg)](http://godoc.org/github.com/go-openapi/jsonreference)
|
|
||||||
An implementation of JSON Reference - Go language
|
|
||||||
|
|
||||||
## Status
|
|
||||||
Work in progress ( 90% done )
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
https://github.com/xeipuuv/gojsonpointer
|
|
||||||
|
|
||||||
## References
|
|
||||||
http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
|
|
||||||
|
|
||||||
http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03
|
|
|
@ -1,156 +0,0 @@
|
||||||
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// author sigu-399
|
|
||||||
// author-github https://github.com/sigu-399
|
|
||||||
// author-mail sigu.399@gmail.com
|
|
||||||
//
|
|
||||||
// repository-name jsonreference
|
|
||||||
// repository-desc An implementation of JSON Reference - Go language
|
|
||||||
//
|
|
||||||
// description Main and unique file.
|
|
||||||
//
|
|
||||||
// created 26-02-2013
|
|
||||||
|
|
||||||
package jsonreference
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/purell"
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
fragmentRune = `#`
|
|
||||||
)
|
|
||||||
|
|
||||||
// New creates a new reference for the given string
|
|
||||||
func New(jsonReferenceString string) (Ref, error) {
|
|
||||||
|
|
||||||
var r Ref
|
|
||||||
err := r.parse(jsonReferenceString)
|
|
||||||
return r, err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustCreateRef parses the ref string and panics when it's invalid.
|
|
||||||
// Use the New method for a version that returns an error
|
|
||||||
func MustCreateRef(ref string) Ref {
|
|
||||||
r, err := New(ref)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ref represents a json reference object
|
|
||||||
type Ref struct {
|
|
||||||
referenceURL *url.URL
|
|
||||||
referencePointer jsonpointer.Pointer
|
|
||||||
|
|
||||||
HasFullURL bool
|
|
||||||
HasURLPathOnly bool
|
|
||||||
HasFragmentOnly bool
|
|
||||||
HasFileScheme bool
|
|
||||||
HasFullFilePath bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetURL gets the URL for this reference
|
|
||||||
func (r *Ref) GetURL() *url.URL {
|
|
||||||
return r.referenceURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPointer gets the json pointer for this reference
|
|
||||||
func (r *Ref) GetPointer() *jsonpointer.Pointer {
|
|
||||||
return &r.referencePointer
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the best version of the url for this reference
|
|
||||||
func (r *Ref) String() string {
|
|
||||||
|
|
||||||
if r.referenceURL != nil {
|
|
||||||
return r.referenceURL.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.HasFragmentOnly {
|
|
||||||
return fragmentRune + r.referencePointer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.referencePointer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRoot returns true if this reference is a root document
|
|
||||||
func (r *Ref) IsRoot() bool {
|
|
||||||
return r.referenceURL != nil &&
|
|
||||||
!r.IsCanonical() &&
|
|
||||||
!r.HasURLPathOnly &&
|
|
||||||
r.referenceURL.Fragment == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCanonical returns true when this pointer starts with http(s):// or file://
|
|
||||||
func (r *Ref) IsCanonical() bool {
|
|
||||||
return (r.HasFileScheme && r.HasFullFilePath) || (!r.HasFileScheme && r.HasFullURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Constructor", parses the given string JSON reference
|
|
||||||
func (r *Ref) parse(jsonReferenceString string) error {
|
|
||||||
|
|
||||||
parsed, err := url.Parse(jsonReferenceString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.referenceURL, _ = url.Parse(purell.NormalizeURL(parsed, purell.FlagsSafe|purell.FlagRemoveDuplicateSlashes))
|
|
||||||
refURL := r.referenceURL
|
|
||||||
|
|
||||||
if refURL.Scheme != "" && refURL.Host != "" {
|
|
||||||
r.HasFullURL = true
|
|
||||||
} else {
|
|
||||||
if refURL.Path != "" {
|
|
||||||
r.HasURLPathOnly = true
|
|
||||||
} else if refURL.RawQuery == "" && refURL.Fragment != "" {
|
|
||||||
r.HasFragmentOnly = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.HasFileScheme = refURL.Scheme == "file"
|
|
||||||
r.HasFullFilePath = strings.HasPrefix(refURL.Path, "/")
|
|
||||||
|
|
||||||
// invalid json-pointer error means url has no json-pointer fragment. simply ignore error
|
|
||||||
r.referencePointer, _ = jsonpointer.New(refURL.Fragment)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inherits creates a new reference from a parent and a child
|
|
||||||
// If the child cannot inherit from the parent, an error is returned
|
|
||||||
func (r *Ref) Inherits(child Ref) (*Ref, error) {
|
|
||||||
childURL := child.GetURL()
|
|
||||||
parentURL := r.GetURL()
|
|
||||||
if childURL == nil {
|
|
||||||
return nil, errors.New("child url is nil")
|
|
||||||
}
|
|
||||||
if parentURL == nil {
|
|
||||||
return &child, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ref, err := New(parentURL.ResolveReference(childURL).String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ref, nil
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
# OAI object model [![Build Status](https://ci.vmware.run/api/badges/go-openapi/spec/status.svg)](https://ci.vmware.run/go-openapi/spec) [![Coverage](https://coverage.vmware.run/badges/go-openapi/spec/coverage.svg)](https://coverage.vmware.run/go-openapi/spec) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
|
|
||||||
|
|
||||||
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/spec/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/spec?status.svg)](http://godoc.org/github.com/go-openapi/spec)
|
|
||||||
|
|
||||||
The object model for OpenAPI specification documents
|
|
File diff suppressed because one or more lines are too long
|
@ -1,24 +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 spec
|
|
||||||
|
|
||||||
// ContactInfo contact information for the exposed API.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#contactObject
|
|
||||||
type ContactInfo struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
Email string `json:"email,omitempty"`
|
|
||||||
}
|
|
|
@ -1,626 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResolutionCache a cache for resolving urls
|
|
||||||
type ResolutionCache interface {
|
|
||||||
Get(string) (interface{}, bool)
|
|
||||||
Set(string, interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type simpleCache struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
store map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var resCache = initResolutionCache()
|
|
||||||
|
|
||||||
func initResolutionCache() ResolutionCache {
|
|
||||||
return &simpleCache{store: map[string]interface{}{
|
|
||||||
"http://swagger.io/v2/schema.json": MustLoadSwagger20Schema(),
|
|
||||||
"http://json-schema.org/draft-04/schema": MustLoadJSONSchemaDraft04(),
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *simpleCache) Get(uri string) (interface{}, bool) {
|
|
||||||
s.lock.Lock()
|
|
||||||
v, ok := s.store[uri]
|
|
||||||
s.lock.Unlock()
|
|
||||||
return v, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *simpleCache) Set(uri string, data interface{}) {
|
|
||||||
s.lock.Lock()
|
|
||||||
s.store[uri] = data
|
|
||||||
s.lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveRef resolves a reference against a context root
|
|
||||||
func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
|
|
||||||
resolver, err := defaultSchemaLoader(root, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := new(Schema)
|
|
||||||
if err := resolver.Resolve(ref, result); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveParameter resolves a paramter reference against a context root
|
|
||||||
func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) {
|
|
||||||
resolver, err := defaultSchemaLoader(root, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := new(Parameter)
|
|
||||||
if err := resolver.Resolve(&ref, result); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveResponse resolves response a reference against a context root
|
|
||||||
func ResolveResponse(root interface{}, ref Ref) (*Response, error) {
|
|
||||||
resolver, err := defaultSchemaLoader(root, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := new(Response)
|
|
||||||
if err := resolver.Resolve(&ref, result); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type schemaLoader struct {
|
|
||||||
loadingRef *Ref
|
|
||||||
startingRef *Ref
|
|
||||||
currentRef *Ref
|
|
||||||
root interface{}
|
|
||||||
cache ResolutionCache
|
|
||||||
loadDoc func(string) (json.RawMessage, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var idPtr, _ = jsonpointer.New("/id")
|
|
||||||
var schemaPtr, _ = jsonpointer.New("/$schema")
|
|
||||||
var refPtr, _ = jsonpointer.New("/$ref")
|
|
||||||
|
|
||||||
func defaultSchemaLoader(root interface{}, ref *Ref, cache ResolutionCache) (*schemaLoader, error) {
|
|
||||||
if cache == nil {
|
|
||||||
cache = resCache
|
|
||||||
}
|
|
||||||
|
|
||||||
var ptr *jsonpointer.Pointer
|
|
||||||
if ref != nil {
|
|
||||||
ptr = ref.GetPointer()
|
|
||||||
}
|
|
||||||
|
|
||||||
currentRef := nextRef(root, ref, ptr)
|
|
||||||
|
|
||||||
return &schemaLoader{
|
|
||||||
root: root,
|
|
||||||
loadingRef: ref,
|
|
||||||
startingRef: ref,
|
|
||||||
cache: cache,
|
|
||||||
loadDoc: func(path string) (json.RawMessage, error) {
|
|
||||||
data, err := swag.LoadFromFileOrHTTP(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return json.RawMessage(data), nil
|
|
||||||
},
|
|
||||||
currentRef: currentRef,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func idFromNode(node interface{}) (*Ref, error) {
|
|
||||||
if idValue, _, err := idPtr.Get(node); err == nil {
|
|
||||||
if refStr, ok := idValue.(string); ok && refStr != "" {
|
|
||||||
idRef, err := NewRef(refStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &idRef, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextRef(startingNode interface{}, startingRef *Ref, ptr *jsonpointer.Pointer) *Ref {
|
|
||||||
if startingRef == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if ptr == nil {
|
|
||||||
return startingRef
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := startingRef
|
|
||||||
var idRef *Ref
|
|
||||||
node := startingNode
|
|
||||||
|
|
||||||
for _, tok := range ptr.DecodedTokens() {
|
|
||||||
node, _, _ = jsonpointer.GetForToken(node, tok)
|
|
||||||
if node == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
idRef, _ = idFromNode(node)
|
|
||||||
if idRef != nil {
|
|
||||||
nw, err := ret.Inherits(*idRef)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ret = nw
|
|
||||||
}
|
|
||||||
|
|
||||||
refRef, _, _ := refPtr.Get(node)
|
|
||||||
if refRef != nil {
|
|
||||||
rf, _ := NewRef(refRef.(string))
|
|
||||||
nw, err := ret.Inherits(rf)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ret = nw
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *schemaLoader) resolveRef(currentRef, ref *Ref, node, target interface{}) error {
|
|
||||||
tgt := reflect.ValueOf(target)
|
|
||||||
if tgt.Kind() != reflect.Ptr {
|
|
||||||
return fmt.Errorf("resolve ref: target needs to be a pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
oldRef := currentRef
|
|
||||||
if currentRef != nil {
|
|
||||||
var err error
|
|
||||||
currentRef, err = currentRef.Inherits(*nextRef(node, ref, currentRef.GetPointer()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if currentRef == nil {
|
|
||||||
currentRef = ref
|
|
||||||
}
|
|
||||||
|
|
||||||
refURL := currentRef.GetURL()
|
|
||||||
if refURL == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if currentRef.IsRoot() {
|
|
||||||
nv := reflect.ValueOf(node)
|
|
||||||
reflect.Indirect(tgt).Set(reflect.Indirect(nv))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(refURL.String(), "#") {
|
|
||||||
res, _, err := ref.GetPointer().Get(node)
|
|
||||||
if err != nil {
|
|
||||||
res, _, err = ref.GetPointer().Get(r.root)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rv := reflect.Indirect(reflect.ValueOf(res))
|
|
||||||
tgtType := reflect.Indirect(tgt).Type()
|
|
||||||
if rv.Type().AssignableTo(tgtType) {
|
|
||||||
reflect.Indirect(tgt).Set(reflect.Indirect(reflect.ValueOf(res)))
|
|
||||||
} else {
|
|
||||||
if err := swag.DynamicJSONToStruct(rv.Interface(), target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if refURL.Scheme != "" && refURL.Host != "" {
|
|
||||||
// most definitely take the red pill
|
|
||||||
data, _, _, err := r.load(refURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((oldRef == nil && currentRef != nil) ||
|
|
||||||
(oldRef != nil && currentRef == nil) ||
|
|
||||||
oldRef.String() != currentRef.String()) &&
|
|
||||||
((oldRef == nil && ref != nil) ||
|
|
||||||
(oldRef != nil && ref == nil) ||
|
|
||||||
(oldRef.String() != ref.String())) {
|
|
||||||
|
|
||||||
return r.resolveRef(currentRef, ref, data, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
var res interface{}
|
|
||||||
if currentRef.String() != "" {
|
|
||||||
res, _, err = currentRef.GetPointer().Get(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res = data
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := swag.DynamicJSONToStruct(res, target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) {
|
|
||||||
toFetch := *refURL
|
|
||||||
toFetch.Fragment = ""
|
|
||||||
|
|
||||||
data, fromCache := r.cache.Get(toFetch.String())
|
|
||||||
if !fromCache {
|
|
||||||
b, err := r.loadDoc(toFetch.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, url.URL{}, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(b, &data); err != nil {
|
|
||||||
return nil, url.URL{}, false, err
|
|
||||||
}
|
|
||||||
r.cache.Set(toFetch.String(), data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, toFetch, fromCache, nil
|
|
||||||
}
|
|
||||||
func (r *schemaLoader) Resolve(ref *Ref, target interface{}) error {
|
|
||||||
if err := r.resolveRef(r.currentRef, ref, r.root, target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type specExpander struct {
|
|
||||||
spec *Swagger
|
|
||||||
resolver *schemaLoader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandSpec expands the references in a swagger spec
|
|
||||||
func ExpandSpec(spec *Swagger) error {
|
|
||||||
resolver, err := defaultSchemaLoader(spec, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, defintition := range spec.Definitions {
|
|
||||||
var def *Schema
|
|
||||||
var err error
|
|
||||||
if def, err = expandSchema(defintition, []string{"#/definitions/" + key}, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
spec.Definitions[key] = *def
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, parameter := range spec.Parameters {
|
|
||||||
if err := expandParameter(¶meter, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
spec.Parameters[key] = parameter
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, response := range spec.Responses {
|
|
||||||
if err := expandResponse(&response, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
spec.Responses[key] = response
|
|
||||||
}
|
|
||||||
|
|
||||||
if spec.Paths != nil {
|
|
||||||
for key, path := range spec.Paths.Paths {
|
|
||||||
if err := expandPathItem(&path, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
spec.Paths.Paths[key] = path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandSchema expands the refs in the schema object
|
|
||||||
func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error {
|
|
||||||
|
|
||||||
if schema == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if root == nil {
|
|
||||||
root = schema
|
|
||||||
}
|
|
||||||
|
|
||||||
nrr, _ := NewRef(schema.ID)
|
|
||||||
var rrr *Ref
|
|
||||||
if nrr.String() != "" {
|
|
||||||
switch root.(type) {
|
|
||||||
case *Schema:
|
|
||||||
rid, _ := NewRef(root.(*Schema).ID)
|
|
||||||
rrr, _ = rid.Inherits(nrr)
|
|
||||||
case *Swagger:
|
|
||||||
rid, _ := NewRef(root.(*Swagger).ID)
|
|
||||||
rrr, _ = rid.Inherits(nrr)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
resolver, err := defaultSchemaLoader(root, rrr, cache)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
refs := []string{""}
|
|
||||||
if rrr != nil {
|
|
||||||
refs[0] = rrr.String()
|
|
||||||
}
|
|
||||||
var s *Schema
|
|
||||||
if s, err = expandSchema(*schema, refs, resolver); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
*schema = *s
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandItems(target Schema, parentRefs []string, resolver *schemaLoader) (*Schema, error) {
|
|
||||||
if target.Items != nil {
|
|
||||||
if target.Items.Schema != nil {
|
|
||||||
t, err := expandSchema(*target.Items.Schema, parentRefs, resolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
*target.Items.Schema = *t
|
|
||||||
}
|
|
||||||
for i := range target.Items.Schemas {
|
|
||||||
t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
target.Items.Schemas[i] = *t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &target, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader) (schema *Schema, err error) {
|
|
||||||
defer func() {
|
|
||||||
schema = &target
|
|
||||||
}()
|
|
||||||
if target.Ref.String() == "" && target.Ref.IsRoot() {
|
|
||||||
target = *resolver.root.(*Schema)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// t is the new expanded schema
|
|
||||||
var t *Schema
|
|
||||||
for target.Ref.String() != "" {
|
|
||||||
// var newTarget Schema
|
|
||||||
pRefs := strings.Join(parentRefs, ",")
|
|
||||||
pRefs += ","
|
|
||||||
if strings.Contains(pRefs, target.Ref.String()+",") {
|
|
||||||
err = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = resolver.Resolve(&target.Ref, &t); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parentRefs = append(parentRefs, target.Ref.String())
|
|
||||||
target = *t
|
|
||||||
}
|
|
||||||
|
|
||||||
if t, err = expandItems(target, parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target = *t
|
|
||||||
|
|
||||||
for i := range target.AllOf {
|
|
||||||
if t, err = expandSchema(target.AllOf[i], parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target.AllOf[i] = *t
|
|
||||||
}
|
|
||||||
for i := range target.AnyOf {
|
|
||||||
if t, err = expandSchema(target.AnyOf[i], parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target.AnyOf[i] = *t
|
|
||||||
}
|
|
||||||
for i := range target.OneOf {
|
|
||||||
if t, err = expandSchema(target.OneOf[i], parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target.OneOf[i] = *t
|
|
||||||
}
|
|
||||||
if target.Not != nil {
|
|
||||||
if t, err = expandSchema(*target.Not, parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*target.Not = *t
|
|
||||||
}
|
|
||||||
for k, _ := range target.Properties {
|
|
||||||
if t, err = expandSchema(target.Properties[k], parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target.Properties[k] = *t
|
|
||||||
}
|
|
||||||
if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
|
|
||||||
if t, err = expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*target.AdditionalProperties.Schema = *t
|
|
||||||
}
|
|
||||||
for k, _ := range target.PatternProperties {
|
|
||||||
if t, err = expandSchema(target.PatternProperties[k], parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target.PatternProperties[k] = *t
|
|
||||||
}
|
|
||||||
for k, _ := range target.Dependencies {
|
|
||||||
if target.Dependencies[k].Schema != nil {
|
|
||||||
if t, err = expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*target.Dependencies[k].Schema = *t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
|
|
||||||
if t, err = expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*target.AdditionalItems.Schema = *t
|
|
||||||
}
|
|
||||||
for k, _ := range target.Definitions {
|
|
||||||
if t, err = expandSchema(target.Definitions[k], parentRefs, resolver); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target.Definitions[k] = *t
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandPathItem(pathItem *PathItem, resolver *schemaLoader) error {
|
|
||||||
if pathItem == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if pathItem.Ref.String() != "" {
|
|
||||||
if err := resolver.Resolve(&pathItem.Ref, &pathItem); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx := range pathItem.Parameters {
|
|
||||||
if err := expandParameter(&(pathItem.Parameters[idx]), resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := expandOperation(pathItem.Get, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := expandOperation(pathItem.Head, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := expandOperation(pathItem.Options, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := expandOperation(pathItem.Put, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := expandOperation(pathItem.Post, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := expandOperation(pathItem.Patch, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := expandOperation(pathItem.Delete, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandOperation(op *Operation, resolver *schemaLoader) error {
|
|
||||||
if op == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i, param := range op.Parameters {
|
|
||||||
if err := expandParameter(¶m, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
op.Parameters[i] = param
|
|
||||||
}
|
|
||||||
|
|
||||||
if op.Responses != nil {
|
|
||||||
responses := op.Responses
|
|
||||||
if err := expandResponse(responses.Default, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for code, response := range responses.StatusCodeResponses {
|
|
||||||
if err := expandResponse(&response, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
responses.StatusCodeResponses[code] = response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandResponse(response *Response, resolver *schemaLoader) error {
|
|
||||||
if response == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.Ref.String() != "" {
|
|
||||||
if err := resolver.Resolve(&response.Ref, response); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.Schema != nil {
|
|
||||||
parentRefs := []string{response.Schema.Ref.String()}
|
|
||||||
if err := resolver.Resolve(&response.Schema.Ref, &response.Schema); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s, err := expandSchema(*response.Schema, parentRefs, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
*response.Schema = *s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandParameter(parameter *Parameter, resolver *schemaLoader) error {
|
|
||||||
if parameter == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if parameter.Ref.String() != "" {
|
|
||||||
if err := resolver.Resolve(¶meter.Ref, parameter); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if parameter.Schema != nil {
|
|
||||||
parentRefs := []string{parameter.Schema.Ref.String()}
|
|
||||||
if err := resolver.Resolve(¶meter.Schema.Ref, ¶meter.Schema); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s, err := expandSchema(*parameter.Schema, parentRefs, resolver); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
*parameter.Schema = *s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,24 +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 spec
|
|
||||||
|
|
||||||
// ExternalDocumentation allows referencing an external resource for
|
|
||||||
// extended documentation.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#externalDocumentationObject
|
|
||||||
type ExternalDocumentation struct {
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
}
|
|
|
@ -1,165 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HeaderProps struct {
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header describes a header for a response of the API
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#headerObject
|
|
||||||
type Header struct {
|
|
||||||
CommonValidations
|
|
||||||
SimpleSchema
|
|
||||||
HeaderProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseHeader creates a new header instance for use in a response
|
|
||||||
func ResponseHeader() *Header {
|
|
||||||
return new(Header)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDescription sets the description on this response, allows for chaining
|
|
||||||
func (h *Header) WithDescription(description string) *Header {
|
|
||||||
h.Description = description
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typed a fluent builder method for the type of parameter
|
|
||||||
func (h *Header) Typed(tpe, format string) *Header {
|
|
||||||
h.Type = tpe
|
|
||||||
h.Format = format
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// CollectionOf a fluent builder method for an array item
|
|
||||||
func (h *Header) CollectionOf(items *Items, format string) *Header {
|
|
||||||
h.Type = "array"
|
|
||||||
h.Items = items
|
|
||||||
h.CollectionFormat = format
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefault sets the default value on this item
|
|
||||||
func (h *Header) WithDefault(defaultValue interface{}) *Header {
|
|
||||||
h.Default = defaultValue
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxLength sets a max length value
|
|
||||||
func (h *Header) WithMaxLength(max int64) *Header {
|
|
||||||
h.MaxLength = &max
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinLength sets a min length value
|
|
||||||
func (h *Header) WithMinLength(min int64) *Header {
|
|
||||||
h.MinLength = &min
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPattern sets a pattern value
|
|
||||||
func (h *Header) WithPattern(pattern string) *Header {
|
|
||||||
h.Pattern = pattern
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMultipleOf sets a multiple of value
|
|
||||||
func (h *Header) WithMultipleOf(number float64) *Header {
|
|
||||||
h.MultipleOf = &number
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaximum sets a maximum number value
|
|
||||||
func (h *Header) WithMaximum(max float64, exclusive bool) *Header {
|
|
||||||
h.Maximum = &max
|
|
||||||
h.ExclusiveMaximum = exclusive
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinimum sets a minimum number value
|
|
||||||
func (h *Header) WithMinimum(min float64, exclusive bool) *Header {
|
|
||||||
h.Minimum = &min
|
|
||||||
h.ExclusiveMinimum = exclusive
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithEnum sets a the enum values (replace)
|
|
||||||
func (h *Header) WithEnum(values ...interface{}) *Header {
|
|
||||||
h.Enum = append([]interface{}{}, values...)
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxItems sets the max items
|
|
||||||
func (h *Header) WithMaxItems(size int64) *Header {
|
|
||||||
h.MaxItems = &size
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinItems sets the min items
|
|
||||||
func (h *Header) WithMinItems(size int64) *Header {
|
|
||||||
h.MinItems = &size
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniqueValues dictates that this array can only have unique items
|
|
||||||
func (h *Header) UniqueValues() *Header {
|
|
||||||
h.UniqueItems = true
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowDuplicates this array can have duplicates
|
|
||||||
func (h *Header) AllowDuplicates() *Header {
|
|
||||||
h.UniqueItems = false
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshal this to JSON
|
|
||||||
func (h Header) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(h.CommonValidations)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(h.SimpleSchema)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b3, err := json.Marshal(h.HeaderProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return swag.ConcatJSON(b1, b2, b3), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON marshal this from JSON
|
|
||||||
func (h *Header) UnmarshalJSON(data []byte) error {
|
|
||||||
if err := json.Unmarshal(data, &h.CommonValidations); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &h.SimpleSchema); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &h.HeaderProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,168 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Extensions vendor specific extensions
|
|
||||||
type Extensions map[string]interface{}
|
|
||||||
|
|
||||||
// Add adds a value to these extensions
|
|
||||||
func (e Extensions) Add(key string, value interface{}) {
|
|
||||||
realKey := strings.ToLower(key)
|
|
||||||
e[realKey] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetString gets a string value from the extensions
|
|
||||||
func (e Extensions) GetString(key string) (string, bool) {
|
|
||||||
if v, ok := e[strings.ToLower(key)]; ok {
|
|
||||||
str, ok := v.(string)
|
|
||||||
return str, ok
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBool gets a string value from the extensions
|
|
||||||
func (e Extensions) GetBool(key string) (bool, bool) {
|
|
||||||
if v, ok := e[strings.ToLower(key)]; ok {
|
|
||||||
str, ok := v.(bool)
|
|
||||||
return str, ok
|
|
||||||
}
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStringSlice gets a string value from the extensions
|
|
||||||
func (e Extensions) GetStringSlice(key string) ([]string, bool) {
|
|
||||||
if v, ok := e[strings.ToLower(key)]; ok {
|
|
||||||
arr, ok := v.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
var strs []string
|
|
||||||
for _, iface := range arr {
|
|
||||||
str, ok := iface.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
strs = append(strs, str)
|
|
||||||
}
|
|
||||||
return strs, ok
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// VendorExtensible composition block.
|
|
||||||
type VendorExtensible struct {
|
|
||||||
Extensions Extensions
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddExtension adds an extension to this extensible object
|
|
||||||
func (v *VendorExtensible) AddExtension(key string, value interface{}) {
|
|
||||||
if value == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if v.Extensions == nil {
|
|
||||||
v.Extensions = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
v.Extensions.Add(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshals the extensions to json
|
|
||||||
func (v VendorExtensible) MarshalJSON() ([]byte, error) {
|
|
||||||
toser := make(map[string]interface{})
|
|
||||||
for k, v := range v.Extensions {
|
|
||||||
lk := strings.ToLower(k)
|
|
||||||
if strings.HasPrefix(lk, "x-") {
|
|
||||||
toser[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return json.Marshal(toser)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON for this extensible object
|
|
||||||
func (v *VendorExtensible) UnmarshalJSON(data []byte) error {
|
|
||||||
var d map[string]interface{}
|
|
||||||
if err := json.Unmarshal(data, &d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for k, vv := range d {
|
|
||||||
lk := strings.ToLower(k)
|
|
||||||
if strings.HasPrefix(lk, "x-") {
|
|
||||||
if v.Extensions == nil {
|
|
||||||
v.Extensions = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
v.Extensions[k] = vv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoProps the properties for an info definition
|
|
||||||
type InfoProps struct {
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
TermsOfService string `json:"termsOfService,omitempty"`
|
|
||||||
Contact *ContactInfo `json:"contact,omitempty"`
|
|
||||||
License *License `json:"license,omitempty"`
|
|
||||||
Version string `json:"version,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info object provides metadata about the API.
|
|
||||||
// The metadata can be used by the clients if needed, and can be presented in the Swagger-UI for convenience.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#infoObject
|
|
||||||
type Info struct {
|
|
||||||
VendorExtensible
|
|
||||||
InfoProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup look up a value by the json property name
|
|
||||||
func (i Info) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if ex, ok := i.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
r, _, err := jsonpointer.GetForToken(i.InfoProps, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshal this to JSON
|
|
||||||
func (i Info) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(i.InfoProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(i.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return swag.ConcatJSON(b1, b2), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON marshal this from JSON
|
|
||||||
func (i *Info) UnmarshalJSON(data []byte) error {
|
|
||||||
if err := json.Unmarshal(data, &i.InfoProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &i.VendorExtensible); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,199 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SimpleSchema struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
Format string `json:"format,omitempty"`
|
|
||||||
Items *Items `json:"items,omitempty"`
|
|
||||||
CollectionFormat string `json:"collectionFormat,omitempty"`
|
|
||||||
Default interface{} `json:"default,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SimpleSchema) TypeName() string {
|
|
||||||
if s.Format != "" {
|
|
||||||
return s.Format
|
|
||||||
}
|
|
||||||
return s.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SimpleSchema) ItemsTypeName() string {
|
|
||||||
if s.Items == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return s.Items.TypeName()
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommonValidations struct {
|
|
||||||
Maximum *float64 `json:"maximum,omitempty"`
|
|
||||||
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"`
|
|
||||||
Minimum *float64 `json:"minimum,omitempty"`
|
|
||||||
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"`
|
|
||||||
MaxLength *int64 `json:"maxLength,omitempty"`
|
|
||||||
MinLength *int64 `json:"minLength,omitempty"`
|
|
||||||
Pattern string `json:"pattern,omitempty"`
|
|
||||||
MaxItems *int64 `json:"maxItems,omitempty"`
|
|
||||||
MinItems *int64 `json:"minItems,omitempty"`
|
|
||||||
UniqueItems bool `json:"uniqueItems,omitempty"`
|
|
||||||
MultipleOf *float64 `json:"multipleOf,omitempty"`
|
|
||||||
Enum []interface{} `json:"enum,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Items a limited subset of JSON-Schema's items object.
|
|
||||||
// It is used by parameter definitions that are not located in "body".
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#items-object-
|
|
||||||
type Items struct {
|
|
||||||
Refable
|
|
||||||
CommonValidations
|
|
||||||
SimpleSchema
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewItems creates a new instance of items
|
|
||||||
func NewItems() *Items {
|
|
||||||
return &Items{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typed a fluent builder method for the type of item
|
|
||||||
func (i *Items) Typed(tpe, format string) *Items {
|
|
||||||
i.Type = tpe
|
|
||||||
i.Format = format
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// CollectionOf a fluent builder method for an array item
|
|
||||||
func (i *Items) CollectionOf(items *Items, format string) *Items {
|
|
||||||
i.Type = "array"
|
|
||||||
i.Items = items
|
|
||||||
i.CollectionFormat = format
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefault sets the default value on this item
|
|
||||||
func (i *Items) WithDefault(defaultValue interface{}) *Items {
|
|
||||||
i.Default = defaultValue
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxLength sets a max length value
|
|
||||||
func (i *Items) WithMaxLength(max int64) *Items {
|
|
||||||
i.MaxLength = &max
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinLength sets a min length value
|
|
||||||
func (i *Items) WithMinLength(min int64) *Items {
|
|
||||||
i.MinLength = &min
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPattern sets a pattern value
|
|
||||||
func (i *Items) WithPattern(pattern string) *Items {
|
|
||||||
i.Pattern = pattern
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMultipleOf sets a multiple of value
|
|
||||||
func (i *Items) WithMultipleOf(number float64) *Items {
|
|
||||||
i.MultipleOf = &number
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaximum sets a maximum number value
|
|
||||||
func (i *Items) WithMaximum(max float64, exclusive bool) *Items {
|
|
||||||
i.Maximum = &max
|
|
||||||
i.ExclusiveMaximum = exclusive
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinimum sets a minimum number value
|
|
||||||
func (i *Items) WithMinimum(min float64, exclusive bool) *Items {
|
|
||||||
i.Minimum = &min
|
|
||||||
i.ExclusiveMinimum = exclusive
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithEnum sets a the enum values (replace)
|
|
||||||
func (i *Items) WithEnum(values ...interface{}) *Items {
|
|
||||||
i.Enum = append([]interface{}{}, values...)
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxItems sets the max items
|
|
||||||
func (i *Items) WithMaxItems(size int64) *Items {
|
|
||||||
i.MaxItems = &size
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinItems sets the min items
|
|
||||||
func (i *Items) WithMinItems(size int64) *Items {
|
|
||||||
i.MinItems = &size
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniqueValues dictates that this array can only have unique items
|
|
||||||
func (i *Items) UniqueValues() *Items {
|
|
||||||
i.UniqueItems = true
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowDuplicates this array can have duplicates
|
|
||||||
func (i *Items) AllowDuplicates() *Items {
|
|
||||||
i.UniqueItems = false
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
|
||||||
func (i *Items) UnmarshalJSON(data []byte) error {
|
|
||||||
var validations CommonValidations
|
|
||||||
if err := json.Unmarshal(data, &validations); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var ref Refable
|
|
||||||
if err := json.Unmarshal(data, &ref); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var simpleSchema SimpleSchema
|
|
||||||
if err := json.Unmarshal(data, &simpleSchema); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.Refable = ref
|
|
||||||
i.CommonValidations = validations
|
|
||||||
i.SimpleSchema = simpleSchema
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this items object to JSON
|
|
||||||
func (i Items) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(i.CommonValidations)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(i.SimpleSchema)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b3, err := json.Marshal(i.Refable)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return swag.ConcatJSON(b3, b1, b2), nil
|
|
||||||
}
|
|
|
@ -1,23 +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 spec
|
|
||||||
|
|
||||||
// License information for the exposed API.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#licenseObject
|
|
||||||
type License struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
}
|
|
|
@ -1,233 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OperationProps struct {
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Consumes []string `json:"consumes,omitempty"`
|
|
||||||
Produces []string `json:"produces,omitempty"`
|
|
||||||
Schemes []string `json:"schemes,omitempty"` // the scheme, when present must be from [http, https, ws, wss]
|
|
||||||
Tags []string `json:"tags,omitempty"`
|
|
||||||
Summary string `json:"summary,omitempty"`
|
|
||||||
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
|
|
||||||
ID string `json:"operationId,omitempty"`
|
|
||||||
Deprecated bool `json:"deprecated,omitempty"`
|
|
||||||
Security []map[string][]string `json:"security,omitempty"`
|
|
||||||
Parameters []Parameter `json:"parameters,omitempty"`
|
|
||||||
Responses *Responses `json:"responses,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operation describes a single API operation on a path.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#operationObject
|
|
||||||
type Operation struct {
|
|
||||||
VendorExtensible
|
|
||||||
OperationProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuccessResponse gets a success response model
|
|
||||||
func (o *Operation) SuccessResponse() (*Response, int, bool) {
|
|
||||||
if o.Responses == nil {
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range o.Responses.StatusCodeResponses {
|
|
||||||
if k/100 == 2 {
|
|
||||||
return &v, k, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.Responses.Default, 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup look up a value by the json property name
|
|
||||||
func (o Operation) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if ex, ok := o.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
r, _, err := jsonpointer.GetForToken(o.OperationProps, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
|
||||||
func (o *Operation) UnmarshalJSON(data []byte) error {
|
|
||||||
if err := json.Unmarshal(data, &o.OperationProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &o.VendorExtensible); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this items object to JSON
|
|
||||||
func (o Operation) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(o.OperationProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(o.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
concated := swag.ConcatJSON(b1, b2)
|
|
||||||
return concated, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOperation creates a new operation instance.
|
|
||||||
// It expects an ID as parameter but not passing an ID is also valid.
|
|
||||||
func NewOperation(id string) *Operation {
|
|
||||||
op := new(Operation)
|
|
||||||
op.ID = id
|
|
||||||
return op
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithID sets the ID property on this operation, allows for chaining.
|
|
||||||
func (o *Operation) WithID(id string) *Operation {
|
|
||||||
o.ID = id
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDescription sets the description on this operation, allows for chaining
|
|
||||||
func (o *Operation) WithDescription(description string) *Operation {
|
|
||||||
o.Description = description
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSummary sets the summary on this operation, allows for chaining
|
|
||||||
func (o *Operation) WithSummary(summary string) *Operation {
|
|
||||||
o.Summary = summary
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithExternalDocs sets/removes the external docs for/from this operation.
|
|
||||||
// When you pass empty strings as params the external documents will be removed.
|
|
||||||
// When you pass non-empty string as one value then those values will be used on the external docs object.
|
|
||||||
// So when you pass a non-empty description, you should also pass the url and vice versa.
|
|
||||||
func (o *Operation) WithExternalDocs(description, url string) *Operation {
|
|
||||||
if description == "" && url == "" {
|
|
||||||
o.ExternalDocs = nil
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.ExternalDocs == nil {
|
|
||||||
o.ExternalDocs = &ExternalDocumentation{}
|
|
||||||
}
|
|
||||||
o.ExternalDocs.Description = description
|
|
||||||
o.ExternalDocs.URL = url
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecate marks the operation as deprecated
|
|
||||||
func (o *Operation) Deprecate() *Operation {
|
|
||||||
o.Deprecated = true
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// Undeprecate marks the operation as not deprected
|
|
||||||
func (o *Operation) Undeprecate() *Operation {
|
|
||||||
o.Deprecated = false
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithConsumes adds media types for incoming body values
|
|
||||||
func (o *Operation) WithConsumes(mediaTypes ...string) *Operation {
|
|
||||||
o.Consumes = append(o.Consumes, mediaTypes...)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithProduces adds media types for outgoing body values
|
|
||||||
func (o *Operation) WithProduces(mediaTypes ...string) *Operation {
|
|
||||||
o.Produces = append(o.Produces, mediaTypes...)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTags adds tags for this operation
|
|
||||||
func (o *Operation) WithTags(tags ...string) *Operation {
|
|
||||||
o.Tags = append(o.Tags, tags...)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddParam adds a parameter to this operation, when a parameter for that location
|
|
||||||
// and with that name already exists it will be replaced
|
|
||||||
func (o *Operation) AddParam(param *Parameter) *Operation {
|
|
||||||
if param == nil {
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, p := range o.Parameters {
|
|
||||||
if p.Name == param.Name && p.In == param.In {
|
|
||||||
params := append(o.Parameters[:i], *param)
|
|
||||||
params = append(params, o.Parameters[i+1:]...)
|
|
||||||
o.Parameters = params
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
o.Parameters = append(o.Parameters, *param)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveParam removes a parameter from the operation
|
|
||||||
func (o *Operation) RemoveParam(name, in string) *Operation {
|
|
||||||
for i, p := range o.Parameters {
|
|
||||||
if p.Name == name && p.In == name {
|
|
||||||
o.Parameters = append(o.Parameters[:i], o.Parameters[i+1:]...)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecuredWith adds a security scope to this operation.
|
|
||||||
func (o *Operation) SecuredWith(name string, scopes ...string) *Operation {
|
|
||||||
o.Security = append(o.Security, map[string][]string{name: scopes})
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefaultResponse adds a default response to the operation.
|
|
||||||
// Passing a nil value will remove the response
|
|
||||||
func (o *Operation) WithDefaultResponse(response *Response) *Operation {
|
|
||||||
return o.RespondsWith(0, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RespondsWith adds a status code response to the operation.
|
|
||||||
// When the code is 0 the value of the response will be used as default response value.
|
|
||||||
// When the value of the response is nil it will be removed from the operation
|
|
||||||
func (o *Operation) RespondsWith(code int, response *Response) *Operation {
|
|
||||||
if o.Responses == nil {
|
|
||||||
o.Responses = new(Responses)
|
|
||||||
}
|
|
||||||
if code == 0 {
|
|
||||||
o.Responses.Default = response
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
if response == nil {
|
|
||||||
delete(o.Responses.StatusCodeResponses, code)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
if o.Responses.StatusCodeResponses == nil {
|
|
||||||
o.Responses.StatusCodeResponses = make(map[int]Response)
|
|
||||||
}
|
|
||||||
o.Responses.StatusCodeResponses[code] = *response
|
|
||||||
return o
|
|
||||||
}
|
|
|
@ -1,299 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryParam creates a query parameter
|
|
||||||
func QueryParam(name string) *Parameter {
|
|
||||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "query"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderParam creates a header parameter, this is always required by default
|
|
||||||
func HeaderParam(name string) *Parameter {
|
|
||||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "header", Required: true}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParam creates a path parameter, this is always required
|
|
||||||
func PathParam(name string) *Parameter {
|
|
||||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "path", Required: true}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BodyParam creates a body parameter
|
|
||||||
func BodyParam(name string, schema *Schema) *Parameter {
|
|
||||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "body", Schema: schema}, SimpleSchema: SimpleSchema{Type: "object"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormDataParam creates a body parameter
|
|
||||||
func FormDataParam(name string) *Parameter {
|
|
||||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "formData"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileParam creates a body parameter
|
|
||||||
func FileParam(name string) *Parameter {
|
|
||||||
return &Parameter{ParamProps: ParamProps{Name: name, In: "formData"}, SimpleSchema: SimpleSchema{Type: "file"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleArrayParam creates a param for a simple array (string, int, date etc)
|
|
||||||
func SimpleArrayParam(name, tpe, fmt string) *Parameter {
|
|
||||||
return &Parameter{ParamProps: ParamProps{Name: name}, SimpleSchema: SimpleSchema{Type: "array", CollectionFormat: "csv", Items: &Items{SimpleSchema: SimpleSchema{Type: "string", Format: fmt}}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParamRef creates a parameter that's a json reference
|
|
||||||
func ParamRef(uri string) *Parameter {
|
|
||||||
p := new(Parameter)
|
|
||||||
p.Ref = MustCreateRef(uri)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParamProps struct {
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
In string `json:"in,omitempty"`
|
|
||||||
Required bool `json:"required,omitempty"`
|
|
||||||
Schema *Schema `json:"schema,omitempty"` // when in == "body"
|
|
||||||
AllowEmptyValue bool `json:"allowEmptyValue,omitempty"` // when in == "query" || "formData"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameter a unique parameter is defined by a combination of a [name](#parameterName) and [location](#parameterIn).
|
|
||||||
//
|
|
||||||
// There are five possible parameter types.
|
|
||||||
// * Path - Used together with [Path Templating](#pathTemplating), where the parameter value is actually part of the operation's URL. This does not include the host or base path of the API. For example, in `/items/{itemId}`, the path parameter is `itemId`.
|
|
||||||
// * Query - Parameters that are appended to the URL. For example, in `/items?id=###`, the query parameter is `id`.
|
|
||||||
// * Header - Custom headers that are expected as part of the request.
|
|
||||||
// * Body - The payload that's appended to the HTTP request. Since there can only be one payload, there can only be *one* body parameter. The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only. Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation.
|
|
||||||
// * Form - Used to describe the payload of an HTTP request when either `application/x-www-form-urlencoded` or `multipart/form-data` are used as the content type of the request (in Swagger's definition, the [`consumes`](#operationConsumes) property of an operation). This is the only parameter type that can be used to send files, thus supporting the `file` type. Since form parameters are sent in the payload, they cannot be declared together with a body parameter for the same operation. Form parameters have a different format based on the content-type used (for further details, consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4):
|
|
||||||
// * `application/x-www-form-urlencoded` - Similar to the format of Query parameters but as a payload. For example, `foo=1&bar=swagger` - both `foo` and `bar` are form parameters. This is normally used for simple parameters that are being transferred.
|
|
||||||
// * `multipart/form-data` - each parameter takes a section in the payload with an internal header. For example, for the header `Content-Disposition: form-data; name="submit-name"` the name of the parameter is `submit-name`. This type of form parameters is more commonly used for file transfers.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#parameterObject
|
|
||||||
type Parameter struct {
|
|
||||||
Refable
|
|
||||||
CommonValidations
|
|
||||||
SimpleSchema
|
|
||||||
VendorExtensible
|
|
||||||
ParamProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup look up a value by the json property name
|
|
||||||
func (p Parameter) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if ex, ok := p.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
if token == "$ref" {
|
|
||||||
return &p.Ref, nil
|
|
||||||
}
|
|
||||||
r, _, err := jsonpointer.GetForToken(p.CommonValidations, token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if r != nil {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
r, _, err = jsonpointer.GetForToken(p.SimpleSchema, token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if r != nil {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
r, _, err = jsonpointer.GetForToken(p.ParamProps, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDescription a fluent builder method for the description of the parameter
|
|
||||||
func (p *Parameter) WithDescription(description string) *Parameter {
|
|
||||||
p.Description = description
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Named a fluent builder method to override the name of the parameter
|
|
||||||
func (p *Parameter) Named(name string) *Parameter {
|
|
||||||
p.Name = name
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLocation a fluent builder method to override the location of the parameter
|
|
||||||
func (p *Parameter) WithLocation(in string) *Parameter {
|
|
||||||
p.In = in
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typed a fluent builder method for the type of the parameter value
|
|
||||||
func (p *Parameter) Typed(tpe, format string) *Parameter {
|
|
||||||
p.Type = tpe
|
|
||||||
p.Format = format
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// CollectionOf a fluent builder method for an array parameter
|
|
||||||
func (p *Parameter) CollectionOf(items *Items, format string) *Parameter {
|
|
||||||
p.Type = "array"
|
|
||||||
p.Items = items
|
|
||||||
p.CollectionFormat = format
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefault sets the default value on this parameter
|
|
||||||
func (p *Parameter) WithDefault(defaultValue interface{}) *Parameter {
|
|
||||||
p.AsOptional() // with default implies optional
|
|
||||||
p.Default = defaultValue
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowsEmptyValues flags this parameter as being ok with empty values
|
|
||||||
func (p *Parameter) AllowsEmptyValues() *Parameter {
|
|
||||||
p.AllowEmptyValue = true
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// NoEmptyValues flags this parameter as not liking empty values
|
|
||||||
func (p *Parameter) NoEmptyValues() *Parameter {
|
|
||||||
p.AllowEmptyValue = false
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsOptional flags this parameter as optional
|
|
||||||
func (p *Parameter) AsOptional() *Parameter {
|
|
||||||
p.Required = false
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsRequired flags this parameter as required
|
|
||||||
func (p *Parameter) AsRequired() *Parameter {
|
|
||||||
if p.Default != nil { // with a default required makes no sense
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
p.Required = true
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxLength sets a max length value
|
|
||||||
func (p *Parameter) WithMaxLength(max int64) *Parameter {
|
|
||||||
p.MaxLength = &max
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinLength sets a min length value
|
|
||||||
func (p *Parameter) WithMinLength(min int64) *Parameter {
|
|
||||||
p.MinLength = &min
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPattern sets a pattern value
|
|
||||||
func (p *Parameter) WithPattern(pattern string) *Parameter {
|
|
||||||
p.Pattern = pattern
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMultipleOf sets a multiple of value
|
|
||||||
func (p *Parameter) WithMultipleOf(number float64) *Parameter {
|
|
||||||
p.MultipleOf = &number
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaximum sets a maximum number value
|
|
||||||
func (p *Parameter) WithMaximum(max float64, exclusive bool) *Parameter {
|
|
||||||
p.Maximum = &max
|
|
||||||
p.ExclusiveMaximum = exclusive
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinimum sets a minimum number value
|
|
||||||
func (p *Parameter) WithMinimum(min float64, exclusive bool) *Parameter {
|
|
||||||
p.Minimum = &min
|
|
||||||
p.ExclusiveMinimum = exclusive
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithEnum sets a the enum values (replace)
|
|
||||||
func (p *Parameter) WithEnum(values ...interface{}) *Parameter {
|
|
||||||
p.Enum = append([]interface{}{}, values...)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxItems sets the max items
|
|
||||||
func (p *Parameter) WithMaxItems(size int64) *Parameter {
|
|
||||||
p.MaxItems = &size
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinItems sets the min items
|
|
||||||
func (p *Parameter) WithMinItems(size int64) *Parameter {
|
|
||||||
p.MinItems = &size
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniqueValues dictates that this array can only have unique items
|
|
||||||
func (p *Parameter) UniqueValues() *Parameter {
|
|
||||||
p.UniqueItems = true
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowDuplicates this array can have duplicates
|
|
||||||
func (p *Parameter) AllowDuplicates() *Parameter {
|
|
||||||
p.UniqueItems = false
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
|
||||||
func (p *Parameter) UnmarshalJSON(data []byte) error {
|
|
||||||
if err := json.Unmarshal(data, &p.CommonValidations); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &p.Refable); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &p.SimpleSchema); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &p.VendorExtensible); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &p.ParamProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this items object to JSON
|
|
||||||
func (p Parameter) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(p.CommonValidations)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(p.SimpleSchema)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b3, err := json.Marshal(p.Refable)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b4, err := json.Marshal(p.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b5, err := json.Marshal(p.ParamProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return swag.ConcatJSON(b3, b1, b2, b4, b5), nil
|
|
||||||
}
|
|
|
@ -1,90 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// pathItemProps the path item specific properties
|
|
||||||
type PathItemProps struct {
|
|
||||||
Get *Operation `json:"get,omitempty"`
|
|
||||||
Put *Operation `json:"put,omitempty"`
|
|
||||||
Post *Operation `json:"post,omitempty"`
|
|
||||||
Delete *Operation `json:"delete,omitempty"`
|
|
||||||
Options *Operation `json:"options,omitempty"`
|
|
||||||
Head *Operation `json:"head,omitempty"`
|
|
||||||
Patch *Operation `json:"patch,omitempty"`
|
|
||||||
Parameters []Parameter `json:"parameters,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathItem describes the operations available on a single path.
|
|
||||||
// A Path Item may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
|
|
||||||
// The path itself is still exposed to the documentation viewer but they will
|
|
||||||
// not know which operations and parameters are available.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#pathItemObject
|
|
||||||
type PathItem struct {
|
|
||||||
Refable
|
|
||||||
VendorExtensible
|
|
||||||
PathItemProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup look up a value by the json property name
|
|
||||||
func (p PathItem) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if ex, ok := p.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
if token == "$ref" {
|
|
||||||
return &p.Ref, nil
|
|
||||||
}
|
|
||||||
r, _, err := jsonpointer.GetForToken(p.PathItemProps, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
|
||||||
func (p *PathItem) UnmarshalJSON(data []byte) error {
|
|
||||||
if err := json.Unmarshal(data, &p.Refable); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &p.VendorExtensible); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &p.PathItemProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this items object to JSON
|
|
||||||
func (p PathItem) MarshalJSON() ([]byte, error) {
|
|
||||||
b3, err := json.Marshal(p.Refable)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b4, err := json.Marshal(p.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b5, err := json.Marshal(p.PathItemProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
concated := swag.ConcatJSON(b3, b4, b5)
|
|
||||||
return concated, nil
|
|
||||||
}
|
|
|
@ -1,97 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Paths holds the relative paths to the individual endpoints.
|
|
||||||
// The path is appended to the [`basePath`](http://goo.gl/8us55a#swaggerBasePath) in order
|
|
||||||
// to construct the full URL.
|
|
||||||
// The Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#pathsObject
|
|
||||||
type Paths struct {
|
|
||||||
VendorExtensible
|
|
||||||
Paths map[string]PathItem `json:"-"` // custom serializer to flatten this, each entry must start with "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup look up a value by the json property name
|
|
||||||
func (p Paths) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if pi, ok := p.Paths[token]; ok {
|
|
||||||
return &pi, nil
|
|
||||||
}
|
|
||||||
if ex, ok := p.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("object has no field %q", token)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
|
||||||
func (p *Paths) UnmarshalJSON(data []byte) error {
|
|
||||||
var res map[string]json.RawMessage
|
|
||||||
if err := json.Unmarshal(data, &res); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for k, v := range res {
|
|
||||||
if strings.HasPrefix(strings.ToLower(k), "x-") {
|
|
||||||
if p.Extensions == nil {
|
|
||||||
p.Extensions = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
var d interface{}
|
|
||||||
if err := json.Unmarshal(v, &d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Extensions[k] = d
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(k, "/") {
|
|
||||||
if p.Paths == nil {
|
|
||||||
p.Paths = make(map[string]PathItem)
|
|
||||||
}
|
|
||||||
var pi PathItem
|
|
||||||
if err := json.Unmarshal(v, &pi); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Paths[k] = pi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this items object to JSON
|
|
||||||
func (p Paths) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(p.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pths := make(map[string]PathItem)
|
|
||||||
for k, v := range p.Paths {
|
|
||||||
if strings.HasPrefix(k, "/") {
|
|
||||||
pths[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(pths)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
concated := swag.ConcatJSON(b1, b2)
|
|
||||||
return concated, nil
|
|
||||||
}
|
|
|
@ -1,167 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonreference"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Refable is a struct for things that accept a $ref property
|
|
||||||
type Refable struct {
|
|
||||||
Ref Ref
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshals the ref to json
|
|
||||||
func (r Refable) MarshalJSON() ([]byte, error) {
|
|
||||||
return r.Ref.MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unmarshalss the ref from json
|
|
||||||
func (r *Refable) UnmarshalJSON(d []byte) error {
|
|
||||||
return json.Unmarshal(d, &r.Ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ref represents a json reference that is potentially resolved
|
|
||||||
type Ref struct {
|
|
||||||
jsonreference.Ref
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteURI gets the remote uri part of the ref
|
|
||||||
func (r *Ref) RemoteURI() string {
|
|
||||||
if r.String() == "" {
|
|
||||||
return r.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
u := *r.GetURL()
|
|
||||||
u.Fragment = ""
|
|
||||||
return u.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValidURI returns true when the url the ref points to can be found
|
|
||||||
func (r *Ref) IsValidURI() bool {
|
|
||||||
if r.String() == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
v := r.RemoteURI()
|
|
||||||
if v == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.HasFullURL {
|
|
||||||
rr, err := http.Get(v)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return rr.StatusCode/100 == 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(r.HasFileScheme || r.HasFullFilePath || r.HasURLPathOnly) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for local file
|
|
||||||
pth := v
|
|
||||||
if r.HasURLPathOnly {
|
|
||||||
p, e := filepath.Abs(pth)
|
|
||||||
if e != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
pth = p
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, err := os.Stat(pth)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return !fi.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inherits creates a new reference from a parent and a child
|
|
||||||
// If the child cannot inherit from the parent, an error is returned
|
|
||||||
func (r *Ref) Inherits(child Ref) (*Ref, error) {
|
|
||||||
ref, err := r.Ref.Inherits(child.Ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Ref{Ref: *ref}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRef creates a new instance of a ref object
|
|
||||||
// returns an error when the reference uri is an invalid uri
|
|
||||||
func NewRef(refURI string) (Ref, error) {
|
|
||||||
ref, err := jsonreference.New(refURI)
|
|
||||||
if err != nil {
|
|
||||||
return Ref{}, err
|
|
||||||
}
|
|
||||||
return Ref{Ref: ref}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustCreateRef creates a ref object but
|
|
||||||
func MustCreateRef(refURI string) Ref {
|
|
||||||
return Ref{Ref: jsonreference.MustCreateRef(refURI)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // NewResolvedRef creates a resolved ref
|
|
||||||
// func NewResolvedRef(refURI string, data interface{}) Ref {
|
|
||||||
// return Ref{
|
|
||||||
// Ref: jsonreference.MustCreateRef(refURI),
|
|
||||||
// Resolved: data,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// MarshalJSON marshals this ref into a JSON object
|
|
||||||
func (r Ref) MarshalJSON() ([]byte, error) {
|
|
||||||
str := r.String()
|
|
||||||
if str == "" {
|
|
||||||
if r.IsRoot() {
|
|
||||||
return []byte(`{"$ref":"#"}`), nil
|
|
||||||
}
|
|
||||||
return []byte("{}"), nil
|
|
||||||
}
|
|
||||||
v := map[string]interface{}{"$ref": str}
|
|
||||||
return json.Marshal(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals this ref from a JSON object
|
|
||||||
func (r *Ref) UnmarshalJSON(d []byte) error {
|
|
||||||
var v map[string]interface{}
|
|
||||||
if err := json.Unmarshal(d, &v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if vv, ok := v["$ref"]; ok {
|
|
||||||
if str, ok := vv.(string); ok {
|
|
||||||
ref, err := jsonreference.New(str)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*r = Ref{Ref: ref}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,113 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResponseProps properties specific to a response
|
|
||||||
type ResponseProps struct {
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Schema *Schema `json:"schema,omitempty"`
|
|
||||||
Headers map[string]Header `json:"headers,omitempty"`
|
|
||||||
Examples map[string]interface{} `json:"examples,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response describes a single response from an API Operation.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#responseObject
|
|
||||||
type Response struct {
|
|
||||||
Refable
|
|
||||||
ResponseProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
|
||||||
func (r *Response) UnmarshalJSON(data []byte) error {
|
|
||||||
if err := json.Unmarshal(data, &r.ResponseProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &r.Refable); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this items object to JSON
|
|
||||||
func (r Response) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(r.ResponseProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(r.Refable)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return swag.ConcatJSON(b1, b2), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewResponse creates a new response instance
|
|
||||||
func NewResponse() *Response {
|
|
||||||
return new(Response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseRef creates a response as a json reference
|
|
||||||
func ResponseRef(url string) *Response {
|
|
||||||
resp := NewResponse()
|
|
||||||
resp.Ref = MustCreateRef(url)
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDescription sets the description on this response, allows for chaining
|
|
||||||
func (r *Response) WithDescription(description string) *Response {
|
|
||||||
r.Description = description
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSchema sets the schema on this response, allows for chaining.
|
|
||||||
// Passing a nil argument removes the schema from this response
|
|
||||||
func (r *Response) WithSchema(schema *Schema) *Response {
|
|
||||||
r.Schema = schema
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHeader adds a header to this response
|
|
||||||
func (r *Response) AddHeader(name string, header *Header) *Response {
|
|
||||||
if header == nil {
|
|
||||||
return r.RemoveHeader(name)
|
|
||||||
}
|
|
||||||
if r.Headers == nil {
|
|
||||||
r.Headers = make(map[string]Header)
|
|
||||||
}
|
|
||||||
r.Headers[name] = *header
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveHeader removes a header from this response
|
|
||||||
func (r *Response) RemoveHeader(name string) *Response {
|
|
||||||
delete(r.Headers, name)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddExample adds an example to this response
|
|
||||||
func (r *Response) AddExample(mediaType string, example interface{}) *Response {
|
|
||||||
if r.Examples == nil {
|
|
||||||
r.Examples = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
r.Examples[mediaType] = example
|
|
||||||
return r
|
|
||||||
}
|
|
|
@ -1,122 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Responses is a container for the expected responses of an operation.
|
|
||||||
// The container maps a HTTP response code to the expected response.
|
|
||||||
// It is not expected from the documentation to necessarily cover all possible HTTP response codes,
|
|
||||||
// since they may not be known in advance. However, it is expected from the documentation to cover
|
|
||||||
// a successful operation response and any known errors.
|
|
||||||
//
|
|
||||||
// The `default` can be used a default response object for all HTTP codes that are not covered
|
|
||||||
// individually by the specification.
|
|
||||||
//
|
|
||||||
// The `Responses Object` MUST contain at least one response code, and it SHOULD be the response
|
|
||||||
// for a successful operation call.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#responsesObject
|
|
||||||
type Responses struct {
|
|
||||||
VendorExtensible
|
|
||||||
ResponsesProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup implements an interface to customize json pointer lookup
|
|
||||||
func (r Responses) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if token == "default" {
|
|
||||||
return r.Default, nil
|
|
||||||
}
|
|
||||||
if ex, ok := r.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
if i, err := strconv.Atoi(token); err == nil {
|
|
||||||
if scr, ok := r.StatusCodeResponses[i]; ok {
|
|
||||||
return &scr, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("object has no field %q", token)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON hydrates this items instance with the data from JSON
|
|
||||||
func (r *Responses) UnmarshalJSON(data []byte) error {
|
|
||||||
if err := json.Unmarshal(data, &r.ResponsesProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &r.VendorExtensible); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if reflect.DeepEqual(ResponsesProps{}, r.ResponsesProps) {
|
|
||||||
r.ResponsesProps = ResponsesProps{}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this items object to JSON
|
|
||||||
func (r Responses) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(r.ResponsesProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(r.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
concated := swag.ConcatJSON(b1, b2)
|
|
||||||
return concated, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponsesProps struct {
|
|
||||||
Default *Response
|
|
||||||
StatusCodeResponses map[int]Response
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r ResponsesProps) MarshalJSON() ([]byte, error) {
|
|
||||||
toser := map[string]Response{}
|
|
||||||
if r.Default != nil {
|
|
||||||
toser["default"] = *r.Default
|
|
||||||
}
|
|
||||||
for k, v := range r.StatusCodeResponses {
|
|
||||||
toser[strconv.Itoa(k)] = v
|
|
||||||
}
|
|
||||||
return json.Marshal(toser)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResponsesProps) UnmarshalJSON(data []byte) error {
|
|
||||||
var res map[string]Response
|
|
||||||
if err := json.Unmarshal(data, &res); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if v, ok := res["default"]; ok {
|
|
||||||
r.Default = &v
|
|
||||||
delete(res, "default")
|
|
||||||
}
|
|
||||||
for k, v := range res {
|
|
||||||
if nk, err := strconv.Atoi(k); err == nil {
|
|
||||||
if r.StatusCodeResponses == nil {
|
|
||||||
r.StatusCodeResponses = map[int]Response{}
|
|
||||||
}
|
|
||||||
r.StatusCodeResponses[nk] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,628 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BooleanProperty creates a boolean property
|
|
||||||
func BooleanProperty() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"boolean"}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolProperty creates a boolean property
|
|
||||||
func BoolProperty() *Schema { return BooleanProperty() }
|
|
||||||
|
|
||||||
// StringProperty creates a string property
|
|
||||||
func StringProperty() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CharProperty creates a string property
|
|
||||||
func CharProperty() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64Property creates a float64/double property
|
|
||||||
func Float64Property() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "double"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float32Property creates a float32/float property
|
|
||||||
func Float32Property() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "float"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int8Property creates an int8 property
|
|
||||||
func Int8Property() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int8"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int16Property creates an int16 property
|
|
||||||
func Int16Property() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int16"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int32Property creates an int32 property
|
|
||||||
func Int32Property() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int32"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Property creates an int64 property
|
|
||||||
func Int64Property() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int64"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrFmtProperty creates a property for the named string format
|
|
||||||
func StrFmtProperty(format string) *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: format}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DateProperty creates a date property
|
|
||||||
func DateProperty() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DateTimeProperty creates a date time property
|
|
||||||
func DateTimeProperty() *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date-time"}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapProperty creates a map property
|
|
||||||
func MapProperty(property *Schema) *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"object"}, AdditionalProperties: &SchemaOrBool{Allows: true, Schema: property}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefProperty creates a ref property
|
|
||||||
func RefProperty(name string) *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefSchema creates a ref property
|
|
||||||
func RefSchema(name string) *Schema {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArrayProperty creates an array property
|
|
||||||
func ArrayProperty(items *Schema) *Schema {
|
|
||||||
if items == nil {
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Type: []string{"array"}}}
|
|
||||||
}
|
|
||||||
return &Schema{SchemaProps: SchemaProps{Items: &SchemaOrArray{Schema: items}, Type: []string{"array"}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComposedSchema creates a schema with allOf
|
|
||||||
func ComposedSchema(schemas ...Schema) *Schema {
|
|
||||||
s := new(Schema)
|
|
||||||
s.AllOf = schemas
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// SchemaURL represents a schema url
|
|
||||||
type SchemaURL string
|
|
||||||
|
|
||||||
// MarshalJSON marshal this to JSON
|
|
||||||
func (r SchemaURL) MarshalJSON() ([]byte, error) {
|
|
||||||
if r == "" {
|
|
||||||
return []byte("{}"), nil
|
|
||||||
}
|
|
||||||
v := map[string]interface{}{"$schema": string(r)}
|
|
||||||
return json.Marshal(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unmarshal this from JSON
|
|
||||||
func (r *SchemaURL) UnmarshalJSON(data []byte) error {
|
|
||||||
var v map[string]interface{}
|
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if vv, ok := v["$schema"]; ok {
|
|
||||||
if str, ok := vv.(string); ok {
|
|
||||||
u, err := url.Parse(str)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*r = SchemaURL(u.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// type ExtraSchemaProps map[string]interface{}
|
|
||||||
|
|
||||||
// // JSONSchema represents a structure that is a json schema draft 04
|
|
||||||
// type JSONSchema struct {
|
|
||||||
// SchemaProps
|
|
||||||
// ExtraSchemaProps
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // MarshalJSON marshal this to JSON
|
|
||||||
// func (s JSONSchema) MarshalJSON() ([]byte, error) {
|
|
||||||
// b1, err := json.Marshal(s.SchemaProps)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// b2, err := s.Ref.MarshalJSON()
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// b3, err := s.Schema.MarshalJSON()
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// b4, err := json.Marshal(s.ExtraSchemaProps)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// return swag.ConcatJSON(b1, b2, b3, b4), nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // UnmarshalJSON marshal this from JSON
|
|
||||||
// func (s *JSONSchema) UnmarshalJSON(data []byte) error {
|
|
||||||
// var sch JSONSchema
|
|
||||||
// if err := json.Unmarshal(data, &sch.SchemaProps); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// if err := json.Unmarshal(data, &sch.Ref); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// if err := json.Unmarshal(data, &sch.Schema); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// if err := json.Unmarshal(data, &sch.ExtraSchemaProps); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// *s = sch
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
type SchemaProps struct {
|
|
||||||
ID string `json:"id,omitempty"`
|
|
||||||
Ref Ref `json:"-,omitempty"`
|
|
||||||
Schema SchemaURL `json:"-,omitempty"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Type StringOrArray `json:"type,omitempty"`
|
|
||||||
Format string `json:"format,omitempty"`
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
Default interface{} `json:"default,omitempty"`
|
|
||||||
Maximum *float64 `json:"maximum,omitempty"`
|
|
||||||
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"`
|
|
||||||
Minimum *float64 `json:"minimum,omitempty"`
|
|
||||||
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"`
|
|
||||||
MaxLength *int64 `json:"maxLength,omitempty"`
|
|
||||||
MinLength *int64 `json:"minLength,omitempty"`
|
|
||||||
Pattern string `json:"pattern,omitempty"`
|
|
||||||
MaxItems *int64 `json:"maxItems,omitempty"`
|
|
||||||
MinItems *int64 `json:"minItems,omitempty"`
|
|
||||||
UniqueItems bool `json:"uniqueItems,omitempty"`
|
|
||||||
MultipleOf *float64 `json:"multipleOf,omitempty"`
|
|
||||||
Enum []interface{} `json:"enum,omitempty"`
|
|
||||||
MaxProperties *int64 `json:"maxProperties,omitempty"`
|
|
||||||
MinProperties *int64 `json:"minProperties,omitempty"`
|
|
||||||
Required []string `json:"required,omitempty"`
|
|
||||||
Items *SchemaOrArray `json:"items,omitempty"`
|
|
||||||
AllOf []Schema `json:"allOf,omitempty"`
|
|
||||||
OneOf []Schema `json:"oneOf,omitempty"`
|
|
||||||
AnyOf []Schema `json:"anyOf,omitempty"`
|
|
||||||
Not *Schema `json:"not,omitempty"`
|
|
||||||
Properties map[string]Schema `json:"properties,omitempty"`
|
|
||||||
AdditionalProperties *SchemaOrBool `json:"additionalProperties,omitempty"`
|
|
||||||
PatternProperties map[string]Schema `json:"patternProperties,omitempty"`
|
|
||||||
Dependencies Dependencies `json:"dependencies,omitempty"`
|
|
||||||
AdditionalItems *SchemaOrBool `json:"additionalItems,omitempty"`
|
|
||||||
Definitions Definitions `json:"definitions,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SwaggerSchemaProps struct {
|
|
||||||
Discriminator string `json:"discriminator,omitempty"`
|
|
||||||
ReadOnly bool `json:"readOnly,omitempty"`
|
|
||||||
XML *XMLObject `json:"xml,omitempty"`
|
|
||||||
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
|
|
||||||
Example interface{} `json:"example,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schema the schema object allows the definition of input and output data types.
|
|
||||||
// These types can be objects, but also primitives and arrays.
|
|
||||||
// This object is based on the [JSON Schema Specification Draft 4](http://json-schema.org/)
|
|
||||||
// and uses a predefined subset of it.
|
|
||||||
// On top of this subset, there are extensions provided by this specification to allow for more complete documentation.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#schemaObject
|
|
||||||
type Schema struct {
|
|
||||||
VendorExtensible
|
|
||||||
SchemaProps
|
|
||||||
SwaggerSchemaProps
|
|
||||||
ExtraProps map[string]interface{} `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup implements an interface to customize json pointer lookup
|
|
||||||
func (s Schema) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if ex, ok := s.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ex, ok := s.ExtraProps[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, err := jsonpointer.GetForToken(s.SchemaProps, token)
|
|
||||||
if r != nil || err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
r, _, err = jsonpointer.GetForToken(s.SwaggerSchemaProps, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithID sets the id for this schema, allows for chaining
|
|
||||||
func (s *Schema) WithID(id string) *Schema {
|
|
||||||
s.ID = id
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTitle sets the title for this schema, allows for chaining
|
|
||||||
func (s *Schema) WithTitle(title string) *Schema {
|
|
||||||
s.Title = title
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDescription sets the description for this schema, allows for chaining
|
|
||||||
func (s *Schema) WithDescription(description string) *Schema {
|
|
||||||
s.Description = description
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithProperties sets the properties for this schema
|
|
||||||
func (s *Schema) WithProperties(schemas map[string]Schema) *Schema {
|
|
||||||
s.Properties = schemas
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetProperty sets a property on this schema
|
|
||||||
func (s *Schema) SetProperty(name string, schema Schema) *Schema {
|
|
||||||
if s.Properties == nil {
|
|
||||||
s.Properties = make(map[string]Schema)
|
|
||||||
}
|
|
||||||
s.Properties[name] = schema
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAllOf sets the all of property
|
|
||||||
func (s *Schema) WithAllOf(schemas ...Schema) *Schema {
|
|
||||||
s.AllOf = schemas
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxProperties sets the max number of properties an object can have
|
|
||||||
func (s *Schema) WithMaxProperties(max int64) *Schema {
|
|
||||||
s.MaxProperties = &max
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinProperties sets the min number of properties an object must have
|
|
||||||
func (s *Schema) WithMinProperties(min int64) *Schema {
|
|
||||||
s.MinProperties = &min
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typed sets the type of this schema for a single value item
|
|
||||||
func (s *Schema) Typed(tpe, format string) *Schema {
|
|
||||||
s.Type = []string{tpe}
|
|
||||||
s.Format = format
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddType adds a type with potential format to the types for this schema
|
|
||||||
func (s *Schema) AddType(tpe, format string) *Schema {
|
|
||||||
s.Type = append(s.Type, tpe)
|
|
||||||
if format != "" {
|
|
||||||
s.Format = format
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// CollectionOf a fluent builder method for an array parameter
|
|
||||||
func (s *Schema) CollectionOf(items Schema) *Schema {
|
|
||||||
s.Type = []string{"array"}
|
|
||||||
s.Items = &SchemaOrArray{Schema: &items}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefault sets the default value on this parameter
|
|
||||||
func (s *Schema) WithDefault(defaultValue interface{}) *Schema {
|
|
||||||
s.Default = defaultValue
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRequired flags this parameter as required
|
|
||||||
func (s *Schema) WithRequired(items ...string) *Schema {
|
|
||||||
s.Required = items
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRequired adds field names to the required properties array
|
|
||||||
func (s *Schema) AddRequired(items ...string) *Schema {
|
|
||||||
s.Required = append(s.Required, items...)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxLength sets a max length value
|
|
||||||
func (s *Schema) WithMaxLength(max int64) *Schema {
|
|
||||||
s.MaxLength = &max
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinLength sets a min length value
|
|
||||||
func (s *Schema) WithMinLength(min int64) *Schema {
|
|
||||||
s.MinLength = &min
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPattern sets a pattern value
|
|
||||||
func (s *Schema) WithPattern(pattern string) *Schema {
|
|
||||||
s.Pattern = pattern
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMultipleOf sets a multiple of value
|
|
||||||
func (s *Schema) WithMultipleOf(number float64) *Schema {
|
|
||||||
s.MultipleOf = &number
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaximum sets a maximum number value
|
|
||||||
func (s *Schema) WithMaximum(max float64, exclusive bool) *Schema {
|
|
||||||
s.Maximum = &max
|
|
||||||
s.ExclusiveMaximum = exclusive
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinimum sets a minimum number value
|
|
||||||
func (s *Schema) WithMinimum(min float64, exclusive bool) *Schema {
|
|
||||||
s.Minimum = &min
|
|
||||||
s.ExclusiveMinimum = exclusive
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithEnum sets a the enum values (replace)
|
|
||||||
func (s *Schema) WithEnum(values ...interface{}) *Schema {
|
|
||||||
s.Enum = append([]interface{}{}, values...)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxItems sets the max items
|
|
||||||
func (s *Schema) WithMaxItems(size int64) *Schema {
|
|
||||||
s.MaxItems = &size
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinItems sets the min items
|
|
||||||
func (s *Schema) WithMinItems(size int64) *Schema {
|
|
||||||
s.MinItems = &size
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniqueValues dictates that this array can only have unique items
|
|
||||||
func (s *Schema) UniqueValues() *Schema {
|
|
||||||
s.UniqueItems = true
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowDuplicates this array can have duplicates
|
|
||||||
func (s *Schema) AllowDuplicates() *Schema {
|
|
||||||
s.UniqueItems = false
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddToAllOf adds a schema to the allOf property
|
|
||||||
func (s *Schema) AddToAllOf(schemas ...Schema) *Schema {
|
|
||||||
s.AllOf = append(s.AllOf, schemas...)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDiscriminator sets the name of the discriminator field
|
|
||||||
func (s *Schema) WithDiscriminator(discriminator string) *Schema {
|
|
||||||
s.Discriminator = discriminator
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsReadOnly flags this schema as readonly
|
|
||||||
func (s *Schema) AsReadOnly() *Schema {
|
|
||||||
s.ReadOnly = true
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsWritable flags this schema as writeable (not read-only)
|
|
||||||
func (s *Schema) AsWritable() *Schema {
|
|
||||||
s.ReadOnly = false
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithExample sets the example for this schema
|
|
||||||
func (s *Schema) WithExample(example interface{}) *Schema {
|
|
||||||
s.Example = example
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithExternalDocs sets/removes the external docs for/from this schema.
|
|
||||||
// When you pass empty strings as params the external documents will be removed.
|
|
||||||
// When you pass non-empty string as one value then those values will be used on the external docs object.
|
|
||||||
// So when you pass a non-empty description, you should also pass the url and vice versa.
|
|
||||||
func (s *Schema) WithExternalDocs(description, url string) *Schema {
|
|
||||||
if description == "" && url == "" {
|
|
||||||
s.ExternalDocs = nil
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.ExternalDocs == nil {
|
|
||||||
s.ExternalDocs = &ExternalDocumentation{}
|
|
||||||
}
|
|
||||||
s.ExternalDocs.Description = description
|
|
||||||
s.ExternalDocs.URL = url
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithXMLName sets the xml name for the object
|
|
||||||
func (s *Schema) WithXMLName(name string) *Schema {
|
|
||||||
if s.XML == nil {
|
|
||||||
s.XML = new(XMLObject)
|
|
||||||
}
|
|
||||||
s.XML.Name = name
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithXMLNamespace sets the xml namespace for the object
|
|
||||||
func (s *Schema) WithXMLNamespace(namespace string) *Schema {
|
|
||||||
if s.XML == nil {
|
|
||||||
s.XML = new(XMLObject)
|
|
||||||
}
|
|
||||||
s.XML.Namespace = namespace
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithXMLPrefix sets the xml prefix for the object
|
|
||||||
func (s *Schema) WithXMLPrefix(prefix string) *Schema {
|
|
||||||
if s.XML == nil {
|
|
||||||
s.XML = new(XMLObject)
|
|
||||||
}
|
|
||||||
s.XML.Prefix = prefix
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsXMLAttribute flags this object as xml attribute
|
|
||||||
func (s *Schema) AsXMLAttribute() *Schema {
|
|
||||||
if s.XML == nil {
|
|
||||||
s.XML = new(XMLObject)
|
|
||||||
}
|
|
||||||
s.XML.Attribute = true
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsXMLElement flags this object as an xml node
|
|
||||||
func (s *Schema) AsXMLElement() *Schema {
|
|
||||||
if s.XML == nil {
|
|
||||||
s.XML = new(XMLObject)
|
|
||||||
}
|
|
||||||
s.XML.Attribute = false
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsWrappedXML flags this object as wrapped, this is mostly useful for array types
|
|
||||||
func (s *Schema) AsWrappedXML() *Schema {
|
|
||||||
if s.XML == nil {
|
|
||||||
s.XML = new(XMLObject)
|
|
||||||
}
|
|
||||||
s.XML.Wrapped = true
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsUnwrappedXML flags this object as an xml node
|
|
||||||
func (s *Schema) AsUnwrappedXML() *Schema {
|
|
||||||
if s.XML == nil {
|
|
||||||
s.XML = new(XMLObject)
|
|
||||||
}
|
|
||||||
s.XML.Wrapped = false
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshal this to JSON
|
|
||||||
func (s Schema) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(s.SchemaProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("schema props %v", err)
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(s.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("vendor props %v", err)
|
|
||||||
}
|
|
||||||
b3, err := s.Ref.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("ref prop %v", err)
|
|
||||||
}
|
|
||||||
b4, err := s.Schema.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("schema prop %v", err)
|
|
||||||
}
|
|
||||||
b5, err := json.Marshal(s.SwaggerSchemaProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("common validations %v", err)
|
|
||||||
}
|
|
||||||
var b6 []byte
|
|
||||||
if s.ExtraProps != nil {
|
|
||||||
jj, err := json.Marshal(s.ExtraProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("extra props %v", err)
|
|
||||||
}
|
|
||||||
b6 = jj
|
|
||||||
}
|
|
||||||
return swag.ConcatJSON(b1, b2, b3, b4, b5, b6), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON marshal this from JSON
|
|
||||||
func (s *Schema) UnmarshalJSON(data []byte) error {
|
|
||||||
var sch Schema
|
|
||||||
if err := json.Unmarshal(data, &sch.SchemaProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &sch.Ref); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &sch.Schema); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &sch.SwaggerSchemaProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var d map[string]interface{}
|
|
||||||
if err := json.Unmarshal(data, &d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(d, "$ref")
|
|
||||||
delete(d, "$schema")
|
|
||||||
for _, pn := range swag.DefaultJSONNameProvider.GetJSONNames(s) {
|
|
||||||
delete(d, pn)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, vv := range d {
|
|
||||||
lk := strings.ToLower(k)
|
|
||||||
if strings.HasPrefix(lk, "x-") {
|
|
||||||
if sch.Extensions == nil {
|
|
||||||
sch.Extensions = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
sch.Extensions[k] = vv
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if sch.ExtraProps == nil {
|
|
||||||
sch.ExtraProps = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
sch.ExtraProps[k] = vv
|
|
||||||
}
|
|
||||||
|
|
||||||
*s = sch
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,142 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
basic = "basic"
|
|
||||||
apiKey = "apiKey"
|
|
||||||
oauth2 = "oauth2"
|
|
||||||
implicit = "implicit"
|
|
||||||
password = "password"
|
|
||||||
application = "application"
|
|
||||||
accessCode = "accessCode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BasicAuth creates a basic auth security scheme
|
|
||||||
func BasicAuth() *SecurityScheme {
|
|
||||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{Type: basic}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIKeyAuth creates an api key auth security scheme
|
|
||||||
func APIKeyAuth(fieldName, valueSource string) *SecurityScheme {
|
|
||||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{Type: apiKey, Name: fieldName, In: valueSource}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OAuth2Implicit creates an implicit flow oauth2 security scheme
|
|
||||||
func OAuth2Implicit(authorizationURL string) *SecurityScheme {
|
|
||||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{
|
|
||||||
Type: oauth2,
|
|
||||||
Flow: implicit,
|
|
||||||
AuthorizationURL: authorizationURL,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OAuth2Password creates a password flow oauth2 security scheme
|
|
||||||
func OAuth2Password(tokenURL string) *SecurityScheme {
|
|
||||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{
|
|
||||||
Type: oauth2,
|
|
||||||
Flow: password,
|
|
||||||
TokenURL: tokenURL,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OAuth2Application creates an application flow oauth2 security scheme
|
|
||||||
func OAuth2Application(tokenURL string) *SecurityScheme {
|
|
||||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{
|
|
||||||
Type: oauth2,
|
|
||||||
Flow: application,
|
|
||||||
TokenURL: tokenURL,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OAuth2AccessToken creates an access token flow oauth2 security scheme
|
|
||||||
func OAuth2AccessToken(authorizationURL, tokenURL string) *SecurityScheme {
|
|
||||||
return &SecurityScheme{SecuritySchemeProps: SecuritySchemeProps{
|
|
||||||
Type: oauth2,
|
|
||||||
Flow: accessCode,
|
|
||||||
AuthorizationURL: authorizationURL,
|
|
||||||
TokenURL: tokenURL,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SecuritySchemeProps struct {
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Name string `json:"name,omitempty"` // api key
|
|
||||||
In string `json:"in,omitempty"` // api key
|
|
||||||
Flow string `json:"flow,omitempty"` // oauth2
|
|
||||||
AuthorizationURL string `json:"authorizationUrl,omitempty"` // oauth2
|
|
||||||
TokenURL string `json:"tokenUrl,omitempty"` // oauth2
|
|
||||||
Scopes map[string]string `json:"scopes,omitempty"` // oauth2
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddScope adds a scope to this security scheme
|
|
||||||
func (s *SecuritySchemeProps) AddScope(scope, description string) {
|
|
||||||
if s.Scopes == nil {
|
|
||||||
s.Scopes = make(map[string]string)
|
|
||||||
}
|
|
||||||
s.Scopes[scope] = description
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecurityScheme allows the definition of a security scheme that can be used by the operations.
|
|
||||||
// Supported schemes are basic authentication, an API key (either as a header or as a query parameter)
|
|
||||||
// and OAuth2's common flows (implicit, password, application and access code).
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#securitySchemeObject
|
|
||||||
type SecurityScheme struct {
|
|
||||||
VendorExtensible
|
|
||||||
SecuritySchemeProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup implements an interface to customize json pointer lookup
|
|
||||||
func (s SecurityScheme) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if ex, ok := s.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, err := jsonpointer.GetForToken(s.SecuritySchemeProps, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshal this to JSON
|
|
||||||
func (s SecurityScheme) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(s.SecuritySchemeProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(s.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return swag.ConcatJSON(b1, b2), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON marshal this from JSON
|
|
||||||
func (s *SecurityScheme) UnmarshalJSON(data []byte) error {
|
|
||||||
if err := json.Unmarshal(data, &s.SecuritySchemeProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &s.VendorExtensible); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,79 +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 spec
|
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
//go:generate go-bindata -pkg=spec -prefix=./schemas -ignore=.*\.md ./schemas/...
|
|
||||||
//go:generate perl -pi -e s,Json,JSON,g bindata.go
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SwaggerSchemaURL the url for the swagger 2.0 schema to validate specs
|
|
||||||
SwaggerSchemaURL = "http://swagger.io/v2/schema.json#"
|
|
||||||
// JSONSchemaURL the url for the json schema schema
|
|
||||||
JSONSchemaURL = "http://json-schema.org/draft-04/schema#"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
jsonSchema = MustLoadJSONSchemaDraft04()
|
|
||||||
swaggerSchema = MustLoadSwagger20Schema()
|
|
||||||
)
|
|
||||||
|
|
||||||
// MustLoadJSONSchemaDraft04 panics when Swagger20Schema returns an error
|
|
||||||
func MustLoadJSONSchemaDraft04() *Schema {
|
|
||||||
d, e := JSONSchemaDraft04()
|
|
||||||
if e != nil {
|
|
||||||
panic(e)
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONSchemaDraft04 loads the json schema document for json shema draft04
|
|
||||||
func JSONSchemaDraft04() (*Schema, error) {
|
|
||||||
b, err := Asset("jsonschema-draft-04.json")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
schema := new(Schema)
|
|
||||||
if err := json.Unmarshal(b, schema); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return schema, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustLoadSwagger20Schema panics when Swagger20Schema returns an error
|
|
||||||
func MustLoadSwagger20Schema() *Schema {
|
|
||||||
d, e := Swagger20Schema()
|
|
||||||
if e != nil {
|
|
||||||
panic(e)
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swagger20Schema loads the swagger 2.0 schema from the embedded assets
|
|
||||||
func Swagger20Schema() (*Schema, error) {
|
|
||||||
|
|
||||||
b, err := Asset("v2/schema.json")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
schema := new(Schema)
|
|
||||||
if err := json.Unmarshal(b, schema); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return schema, nil
|
|
||||||
}
|
|
|
@ -1,317 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Swagger this is the root document object for the API specification.
|
|
||||||
// It combines what previously was the Resource Listing and API Declaration (version 1.2 and earlier) together into one document.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#swagger-object-
|
|
||||||
type Swagger struct {
|
|
||||||
VendorExtensible
|
|
||||||
SwaggerProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup look up a value by the json property name
|
|
||||||
func (s Swagger) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if ex, ok := s.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
r, _, err := jsonpointer.GetForToken(s.SwaggerProps, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshals this swagger structure to json
|
|
||||||
func (s Swagger) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(s.SwaggerProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(s.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return swag.ConcatJSON(b1, b2), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals a swagger spec from json
|
|
||||||
func (s *Swagger) UnmarshalJSON(data []byte) error {
|
|
||||||
var sw Swagger
|
|
||||||
if err := json.Unmarshal(data, &sw.SwaggerProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &sw.VendorExtensible); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*s = sw
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type SwaggerProps struct {
|
|
||||||
ID string `json:"id,omitempty"`
|
|
||||||
Consumes []string `json:"consumes,omitempty"`
|
|
||||||
Produces []string `json:"produces,omitempty"`
|
|
||||||
Schemes []string `json:"schemes,omitempty"` // the scheme, when present must be from [http, https, ws, wss]
|
|
||||||
Swagger string `json:"swagger,omitempty"`
|
|
||||||
Info *Info `json:"info,omitempty"`
|
|
||||||
Host string `json:"host,omitempty"`
|
|
||||||
BasePath string `json:"basePath,omitempty"` // must start with a leading "/"
|
|
||||||
Paths *Paths `json:"paths"` // required
|
|
||||||
Definitions Definitions `json:"definitions"`
|
|
||||||
Parameters map[string]Parameter `json:"parameters,omitempty"`
|
|
||||||
Responses map[string]Response `json:"responses,omitempty"`
|
|
||||||
SecurityDefinitions SecurityDefinitions `json:"securityDefinitions,omitempty"`
|
|
||||||
Security []map[string][]string `json:"security,omitempty"`
|
|
||||||
Tags []Tag `json:"tags,omitempty"`
|
|
||||||
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dependencies represent a dependencies property
|
|
||||||
type Dependencies map[string]SchemaOrStringArray
|
|
||||||
|
|
||||||
// SchemaOrBool represents a schema or boolean value, is biased towards true for the boolean property
|
|
||||||
type SchemaOrBool struct {
|
|
||||||
Allows bool
|
|
||||||
Schema *Schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup implements an interface to customize json pointer lookup
|
|
||||||
func (s SchemaOrBool) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if token == "allows" {
|
|
||||||
return s.Allows, nil
|
|
||||||
}
|
|
||||||
r, _, err := jsonpointer.GetForToken(s.Schema, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var jsTrue = []byte("true")
|
|
||||||
var jsFalse = []byte("false")
|
|
||||||
|
|
||||||
// MarshalJSON convert this object to JSON
|
|
||||||
func (s SchemaOrBool) MarshalJSON() ([]byte, error) {
|
|
||||||
if s.Schema != nil {
|
|
||||||
return json.Marshal(s.Schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Schema == nil && !s.Allows {
|
|
||||||
return jsFalse, nil
|
|
||||||
}
|
|
||||||
return jsTrue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts this bool or schema object from a JSON structure
|
|
||||||
func (s *SchemaOrBool) UnmarshalJSON(data []byte) error {
|
|
||||||
var nw SchemaOrBool
|
|
||||||
if len(data) >= 4 {
|
|
||||||
if data[0] == '{' {
|
|
||||||
var sch Schema
|
|
||||||
if err := json.Unmarshal(data, &sch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nw.Schema = &sch
|
|
||||||
}
|
|
||||||
nw.Allows = !(data[0] == 'f' && data[1] == 'a' && data[2] == 'l' && data[3] == 's' && data[4] == 'e')
|
|
||||||
}
|
|
||||||
*s = nw
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SchemaOrStringArray represents a schema or a string array
|
|
||||||
type SchemaOrStringArray struct {
|
|
||||||
Schema *Schema
|
|
||||||
Property []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup implements an interface to customize json pointer lookup
|
|
||||||
func (s SchemaOrStringArray) JSONLookup(token string) (interface{}, error) {
|
|
||||||
r, _, err := jsonpointer.GetForToken(s.Schema, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this schema object or array into JSON structure
|
|
||||||
func (s SchemaOrStringArray) MarshalJSON() ([]byte, error) {
|
|
||||||
if len(s.Property) > 0 {
|
|
||||||
return json.Marshal(s.Property)
|
|
||||||
}
|
|
||||||
if s.Schema != nil {
|
|
||||||
return json.Marshal(s.Schema)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts this schema object or array from a JSON structure
|
|
||||||
func (s *SchemaOrStringArray) UnmarshalJSON(data []byte) error {
|
|
||||||
var first byte
|
|
||||||
if len(data) > 1 {
|
|
||||||
first = data[0]
|
|
||||||
}
|
|
||||||
var nw SchemaOrStringArray
|
|
||||||
if first == '{' {
|
|
||||||
var sch Schema
|
|
||||||
if err := json.Unmarshal(data, &sch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nw.Schema = &sch
|
|
||||||
}
|
|
||||||
if first == '[' {
|
|
||||||
if err := json.Unmarshal(data, &nw.Property); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*s = nw
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Definitions contains the models explicitly defined in this spec
|
|
||||||
// An object to hold data types that can be consumed and produced by operations.
|
|
||||||
// These data types can be primitives, arrays or models.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#definitionsObject
|
|
||||||
type Definitions map[string]Schema
|
|
||||||
|
|
||||||
// SecurityDefinitions a declaration of the security schemes available to be used in the specification.
|
|
||||||
// This does not enforce the security schemes on the operations and only serves to provide
|
|
||||||
// the relevant details for each scheme.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#securityDefinitionsObject
|
|
||||||
type SecurityDefinitions map[string]*SecurityScheme
|
|
||||||
|
|
||||||
// StringOrArray represents a value that can either be a string
|
|
||||||
// or an array of strings. Mainly here for serialization purposes
|
|
||||||
type StringOrArray []string
|
|
||||||
|
|
||||||
// Contains returns true when the value is contained in the slice
|
|
||||||
func (s StringOrArray) Contains(value string) bool {
|
|
||||||
for _, str := range s {
|
|
||||||
if str == value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup implements an interface to customize json pointer lookup
|
|
||||||
func (s SchemaOrArray) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if _, err := strconv.Atoi(token); err == nil {
|
|
||||||
r, _, err := jsonpointer.GetForToken(s.Schemas, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
r, _, err := jsonpointer.GetForToken(s.Schema, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals this string or array object from a JSON array or JSON string
|
|
||||||
func (s *StringOrArray) UnmarshalJSON(data []byte) error {
|
|
||||||
var first byte
|
|
||||||
if len(data) > 1 {
|
|
||||||
first = data[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if first == '[' {
|
|
||||||
var parsed []string
|
|
||||||
if err := json.Unmarshal(data, &parsed); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*s = StringOrArray(parsed)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var single interface{}
|
|
||||||
if err := json.Unmarshal(data, &single); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if single == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch single.(type) {
|
|
||||||
case string:
|
|
||||||
*s = StringOrArray([]string{single.(string)})
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("only string or array is allowed, not %T", single)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this string or array to a JSON array or JSON string
|
|
||||||
func (s StringOrArray) MarshalJSON() ([]byte, error) {
|
|
||||||
if len(s) == 1 {
|
|
||||||
return json.Marshal([]string(s)[0])
|
|
||||||
}
|
|
||||||
return json.Marshal([]string(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SchemaOrArray represents a value that can either be a Schema
|
|
||||||
// or an array of Schema. Mainly here for serialization purposes
|
|
||||||
type SchemaOrArray struct {
|
|
||||||
Schema *Schema
|
|
||||||
Schemas []Schema
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of schemas in this property
|
|
||||||
func (s SchemaOrArray) Len() int {
|
|
||||||
if s.Schema != nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return len(s.Schemas)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainsType returns true when one of the schemas is of the specified type
|
|
||||||
func (s *SchemaOrArray) ContainsType(name string) bool {
|
|
||||||
if s.Schema != nil {
|
|
||||||
return s.Schema.Type != nil && s.Schema.Type.Contains(name)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts this schema object or array into JSON structure
|
|
||||||
func (s SchemaOrArray) MarshalJSON() ([]byte, error) {
|
|
||||||
if len(s.Schemas) > 0 {
|
|
||||||
return json.Marshal(s.Schemas)
|
|
||||||
}
|
|
||||||
return json.Marshal(s.Schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON converts this schema object or array from a JSON structure
|
|
||||||
func (s *SchemaOrArray) UnmarshalJSON(data []byte) error {
|
|
||||||
var nw SchemaOrArray
|
|
||||||
var first byte
|
|
||||||
if len(data) > 1 {
|
|
||||||
first = data[0]
|
|
||||||
}
|
|
||||||
if first == '{' {
|
|
||||||
var sch Schema
|
|
||||||
if err := json.Unmarshal(data, &sch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nw.Schema = &sch
|
|
||||||
}
|
|
||||||
if first == '[' {
|
|
||||||
if err := json.Unmarshal(data, &nw.Schemas); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*s = nw
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// vim:set ft=go noet sts=2 sw=2 ts=2:
|
|
|
@ -1,73 +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 spec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TagProps struct {
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTag creates a new tag
|
|
||||||
func NewTag(name, description string, externalDocs *ExternalDocumentation) Tag {
|
|
||||||
return Tag{TagProps: TagProps{description, name, externalDocs}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tag allows adding meta data to a single tag that is used by the [Operation Object](http://goo.gl/8us55a#operationObject).
|
|
||||||
// It is not mandatory to have a Tag Object per tag used there.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#tagObject
|
|
||||||
type Tag struct {
|
|
||||||
VendorExtensible
|
|
||||||
TagProps
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONLookup implements an interface to customize json pointer lookup
|
|
||||||
func (t Tag) JSONLookup(token string) (interface{}, error) {
|
|
||||||
if ex, ok := t.Extensions[token]; ok {
|
|
||||||
return &ex, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, err := jsonpointer.GetForToken(t.TagProps, token)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshal this to JSON
|
|
||||||
func (t Tag) MarshalJSON() ([]byte, error) {
|
|
||||||
b1, err := json.Marshal(t.TagProps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b2, err := json.Marshal(t.VendorExtensible)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return swag.ConcatJSON(b1, b2), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON marshal this from JSON
|
|
||||||
func (t *Tag) UnmarshalJSON(data []byte) error {
|
|
||||||
if err := json.Unmarshal(data, &t.TagProps); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return json.Unmarshal(data, &t.VendorExtensible)
|
|
||||||
}
|
|
|
@ -1,68 +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 spec
|
|
||||||
|
|
||||||
// XMLObject a metadata object that allows for more fine-tuned XML model definitions.
|
|
||||||
//
|
|
||||||
// For more information: http://goo.gl/8us55a#xmlObject
|
|
||||||
type XMLObject struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Namespace string `json:"namespace,omitempty"`
|
|
||||||
Prefix string `json:"prefix,omitempty"`
|
|
||||||
Attribute bool `json:"attribute,omitempty"`
|
|
||||||
Wrapped bool `json:"wrapped,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithName sets the xml name for the object
|
|
||||||
func (x *XMLObject) WithName(name string) *XMLObject {
|
|
||||||
x.Name = name
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNamespace sets the xml namespace for the object
|
|
||||||
func (x *XMLObject) WithNamespace(namespace string) *XMLObject {
|
|
||||||
x.Namespace = namespace
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPrefix sets the xml prefix for the object
|
|
||||||
func (x *XMLObject) WithPrefix(prefix string) *XMLObject {
|
|
||||||
x.Prefix = prefix
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsAttribute flags this object as xml attribute
|
|
||||||
func (x *XMLObject) AsAttribute() *XMLObject {
|
|
||||||
x.Attribute = true
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsElement flags this object as an xml node
|
|
||||||
func (x *XMLObject) AsElement() *XMLObject {
|
|
||||||
x.Attribute = false
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsWrapped flags this object as wrapped, this is mostly useful for array types
|
|
||||||
func (x *XMLObject) AsWrapped() *XMLObject {
|
|
||||||
x.Wrapped = true
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsUnwrapped flags this object as an xml node
|
|
||||||
func (x *XMLObject) AsUnwrapped() *XMLObject {
|
|
||||||
x.Wrapped = false
|
|
||||||
return x
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
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 "[]"
|
|
||||||
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
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
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.
|
|
||||||
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.
|
|
|
@ -1,12 +0,0 @@
|
||||||
# Swag [![Build Status](https://ci.vmware.run/api/badges/go-openapi/swag/status.svg)](https://ci.vmware.run/go-openapi/swag) [![Coverage](https://coverage.vmware.run/badges/go-openapi/swag/coverage.svg)](https://coverage.vmware.run/go-openapi/swag) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
|
|
||||||
|
|
||||||
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/swag/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/swag?status.svg)](http://godoc.org/github.com/go-openapi/swag)
|
|
||||||
|
|
||||||
Contains a bunch of helper functions:
|
|
||||||
|
|
||||||
* convert between value and pointers for builtins
|
|
||||||
* convert from string to builtin
|
|
||||||
* fast json concatenation
|
|
||||||
* search in path
|
|
||||||
* load from file or http
|
|
||||||
* name manglin
|
|
|
@ -1,188 +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 swag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER
|
|
||||||
const (
|
|
||||||
maxJSONFloat = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1
|
|
||||||
minJSONFloat = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsFloat64AJSONInteger allow for integers [-2^53, 2^53-1] inclusive
|
|
||||||
func IsFloat64AJSONInteger(f float64) bool {
|
|
||||||
if math.IsNaN(f) || math.IsInf(f, 0) || f < minJSONFloat || f > maxJSONFloat {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return f == float64(int64(f)) || f == float64(uint64(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
var evaluatesAsTrue = map[string]struct{}{
|
|
||||||
"true": struct{}{},
|
|
||||||
"1": struct{}{},
|
|
||||||
"yes": struct{}{},
|
|
||||||
"ok": struct{}{},
|
|
||||||
"y": struct{}{},
|
|
||||||
"on": struct{}{},
|
|
||||||
"selected": struct{}{},
|
|
||||||
"checked": struct{}{},
|
|
||||||
"t": struct{}{},
|
|
||||||
"enabled": struct{}{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertBool turn a string into a boolean
|
|
||||||
func ConvertBool(str string) (bool, error) {
|
|
||||||
_, ok := evaluatesAsTrue[strings.ToLower(str)]
|
|
||||||
return ok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertFloat32 turn a string into a float32
|
|
||||||
func ConvertFloat32(str string) (float32, error) {
|
|
||||||
f, err := strconv.ParseFloat(str, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return float32(f), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertFloat64 turn a string into a float64
|
|
||||||
func ConvertFloat64(str string) (float64, error) {
|
|
||||||
return strconv.ParseFloat(str, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertInt8 turn a string into int8 boolean
|
|
||||||
func ConvertInt8(str string) (int8, error) {
|
|
||||||
i, err := strconv.ParseInt(str, 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return int8(i), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertInt16 turn a string into a int16
|
|
||||||
func ConvertInt16(str string) (int16, error) {
|
|
||||||
i, err := strconv.ParseInt(str, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return int16(i), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertInt32 turn a string into a int32
|
|
||||||
func ConvertInt32(str string) (int32, error) {
|
|
||||||
i, err := strconv.ParseInt(str, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return int32(i), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertInt64 turn a string into a int64
|
|
||||||
func ConvertInt64(str string) (int64, error) {
|
|
||||||
return strconv.ParseInt(str, 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertUint8 turn a string into a uint8
|
|
||||||
func ConvertUint8(str string) (uint8, error) {
|
|
||||||
i, err := strconv.ParseUint(str, 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return uint8(i), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertUint16 turn a string into a uint16
|
|
||||||
func ConvertUint16(str string) (uint16, error) {
|
|
||||||
i, err := strconv.ParseUint(str, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return uint16(i), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertUint32 turn a string into a uint32
|
|
||||||
func ConvertUint32(str string) (uint32, error) {
|
|
||||||
i, err := strconv.ParseUint(str, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return uint32(i), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertUint64 turn a string into a uint64
|
|
||||||
func ConvertUint64(str string) (uint64, error) {
|
|
||||||
return strconv.ParseUint(str, 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatBool turns a boolean into a string
|
|
||||||
func FormatBool(value bool) string {
|
|
||||||
return strconv.FormatBool(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatFloat32 turns a float32 into a string
|
|
||||||
func FormatFloat32(value float32) string {
|
|
||||||
return strconv.FormatFloat(float64(value), 'f', -1, 32)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatFloat64 turns a float64 into a string
|
|
||||||
func FormatFloat64(value float64) string {
|
|
||||||
return strconv.FormatFloat(value, 'f', -1, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatInt8 turns an int8 into a string
|
|
||||||
func FormatInt8(value int8) string {
|
|
||||||
return strconv.FormatInt(int64(value), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatInt16 turns an int16 into a string
|
|
||||||
func FormatInt16(value int16) string {
|
|
||||||
return strconv.FormatInt(int64(value), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatInt32 turns an int32 into a string
|
|
||||||
func FormatInt32(value int32) string {
|
|
||||||
return strconv.FormatInt(int64(value), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatInt64 turns an int64 into a string
|
|
||||||
func FormatInt64(value int64) string {
|
|
||||||
return strconv.FormatInt(value, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatUint8 turns an uint8 into a string
|
|
||||||
func FormatUint8(value uint8) string {
|
|
||||||
return strconv.FormatUint(uint64(value), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatUint16 turns an uint16 into a string
|
|
||||||
func FormatUint16(value uint16) string {
|
|
||||||
return strconv.FormatUint(uint64(value), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatUint32 turns an uint32 into a string
|
|
||||||
func FormatUint32(value uint32) string {
|
|
||||||
return strconv.FormatUint(uint64(value), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatUint64 turns an uint64 into a string
|
|
||||||
func FormatUint64(value uint64) string {
|
|
||||||
return strconv.FormatUint(value, 10)
|
|
||||||
}
|
|
|
@ -1,595 +0,0 @@
|
||||||
package swag
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// This file was taken from the aws go sdk
|
|
||||||
|
|
||||||
// String returns a pointer to of the string value passed in.
|
|
||||||
func String(v string) *string {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringValue returns the value of the string pointer passed in or
|
|
||||||
// "" if the pointer is nil.
|
|
||||||
func StringValue(v *string) string {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSlice converts a slice of string values into a slice of
|
|
||||||
// string pointers
|
|
||||||
func StringSlice(src []string) []*string {
|
|
||||||
dst := make([]*string, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringValueSlice converts a slice of string pointers into a slice of
|
|
||||||
// string values
|
|
||||||
func StringValueSlice(src []*string) []string {
|
|
||||||
dst := make([]string, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringMap converts a string map of string values into a string
|
|
||||||
// map of string pointers
|
|
||||||
func StringMap(src map[string]string) map[string]*string {
|
|
||||||
dst := make(map[string]*string)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringValueMap converts a string map of string pointers into a string
|
|
||||||
// map of string values
|
|
||||||
func StringValueMap(src map[string]*string) map[string]string {
|
|
||||||
dst := make(map[string]string)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool returns a pointer to of the bool value passed in.
|
|
||||||
func Bool(v bool) *bool {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolValue returns the value of the bool pointer passed in or
|
|
||||||
// false if the pointer is nil.
|
|
||||||
func BoolValue(v *bool) bool {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolSlice converts a slice of bool values into a slice of
|
|
||||||
// bool pointers
|
|
||||||
func BoolSlice(src []bool) []*bool {
|
|
||||||
dst := make([]*bool, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolValueSlice converts a slice of bool pointers into a slice of
|
|
||||||
// bool values
|
|
||||||
func BoolValueSlice(src []*bool) []bool {
|
|
||||||
dst := make([]bool, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolMap converts a string map of bool values into a string
|
|
||||||
// map of bool pointers
|
|
||||||
func BoolMap(src map[string]bool) map[string]*bool {
|
|
||||||
dst := make(map[string]*bool)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolValueMap converts a string map of bool pointers into a string
|
|
||||||
// map of bool values
|
|
||||||
func BoolValueMap(src map[string]*bool) map[string]bool {
|
|
||||||
dst := make(map[string]bool)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int returns a pointer to of the int value passed in.
|
|
||||||
func Int(v int) *int {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntValue returns the value of the int pointer passed in or
|
|
||||||
// 0 if the pointer is nil.
|
|
||||||
func IntValue(v *int) int {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSlice converts a slice of int values into a slice of
|
|
||||||
// int pointers
|
|
||||||
func IntSlice(src []int) []*int {
|
|
||||||
dst := make([]*int, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntValueSlice converts a slice of int pointers into a slice of
|
|
||||||
// int values
|
|
||||||
func IntValueSlice(src []*int) []int {
|
|
||||||
dst := make([]int, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntMap converts a string map of int values into a string
|
|
||||||
// map of int pointers
|
|
||||||
func IntMap(src map[string]int) map[string]*int {
|
|
||||||
dst := make(map[string]*int)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntValueMap converts a string map of int pointers into a string
|
|
||||||
// map of int values
|
|
||||||
func IntValueMap(src map[string]*int) map[string]int {
|
|
||||||
dst := make(map[string]int)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int32 returns a pointer to of the int64 value passed in.
|
|
||||||
func Int32(v int32) *int32 {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int32Value returns the value of the int64 pointer passed in or
|
|
||||||
// 0 if the pointer is nil.
|
|
||||||
func Int32Value(v *int32) int32 {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int32Slice converts a slice of int64 values into a slice of
|
|
||||||
// int32 pointers
|
|
||||||
func Int32Slice(src []int32) []*int32 {
|
|
||||||
dst := make([]*int32, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int32ValueSlice converts a slice of int32 pointers into a slice of
|
|
||||||
// int32 values
|
|
||||||
func Int32ValueSlice(src []*int32) []int32 {
|
|
||||||
dst := make([]int32, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int32Map converts a string map of int32 values into a string
|
|
||||||
// map of int32 pointers
|
|
||||||
func Int32Map(src map[string]int32) map[string]*int32 {
|
|
||||||
dst := make(map[string]*int32)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int32ValueMap converts a string map of int32 pointers into a string
|
|
||||||
// map of int32 values
|
|
||||||
func Int32ValueMap(src map[string]*int32) map[string]int32 {
|
|
||||||
dst := make(map[string]int32)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64 returns a pointer to of the int64 value passed in.
|
|
||||||
func Int64(v int64) *int64 {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Value returns the value of the int64 pointer passed in or
|
|
||||||
// 0 if the pointer is nil.
|
|
||||||
func Int64Value(v *int64) int64 {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Slice converts a slice of int64 values into a slice of
|
|
||||||
// int64 pointers
|
|
||||||
func Int64Slice(src []int64) []*int64 {
|
|
||||||
dst := make([]*int64, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64ValueSlice converts a slice of int64 pointers into a slice of
|
|
||||||
// int64 values
|
|
||||||
func Int64ValueSlice(src []*int64) []int64 {
|
|
||||||
dst := make([]int64, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Map converts a string map of int64 values into a string
|
|
||||||
// map of int64 pointers
|
|
||||||
func Int64Map(src map[string]int64) map[string]*int64 {
|
|
||||||
dst := make(map[string]*int64)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64ValueMap converts a string map of int64 pointers into a string
|
|
||||||
// map of int64 values
|
|
||||||
func Int64ValueMap(src map[string]*int64) map[string]int64 {
|
|
||||||
dst := make(map[string]int64)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint returns a pouinter to of the uint value passed in.
|
|
||||||
func Uint(v uint) *uint {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// UintValue returns the value of the uint pouinter passed in or
|
|
||||||
// 0 if the pouinter is nil.
|
|
||||||
func UintValue(v *uint) uint {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UintSlice converts a slice of uint values uinto a slice of
|
|
||||||
// uint pouinters
|
|
||||||
func UintSlice(src []uint) []*uint {
|
|
||||||
dst := make([]*uint, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// UintValueSlice converts a slice of uint pouinters uinto a slice of
|
|
||||||
// uint values
|
|
||||||
func UintValueSlice(src []*uint) []uint {
|
|
||||||
dst := make([]uint, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// UintMap converts a string map of uint values uinto a string
|
|
||||||
// map of uint pouinters
|
|
||||||
func UintMap(src map[string]uint) map[string]*uint {
|
|
||||||
dst := make(map[string]*uint)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// UintValueMap converts a string map of uint pouinters uinto a string
|
|
||||||
// map of uint values
|
|
||||||
func UintValueMap(src map[string]*uint) map[string]uint {
|
|
||||||
dst := make(map[string]uint)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint32 returns a pouinter to of the uint64 value passed in.
|
|
||||||
func Uint32(v uint32) *uint32 {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint32Value returns the value of the uint64 pouinter passed in or
|
|
||||||
// 0 if the pouinter is nil.
|
|
||||||
func Uint32Value(v *uint32) uint32 {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint32Slice converts a slice of uint64 values uinto a slice of
|
|
||||||
// uint32 pouinters
|
|
||||||
func Uint32Slice(src []uint32) []*uint32 {
|
|
||||||
dst := make([]*uint32, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint32ValueSlice converts a slice of uint32 pouinters uinto a slice of
|
|
||||||
// uint32 values
|
|
||||||
func Uint32ValueSlice(src []*uint32) []uint32 {
|
|
||||||
dst := make([]uint32, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint32Map converts a string map of uint32 values uinto a string
|
|
||||||
// map of uint32 pouinters
|
|
||||||
func Uint32Map(src map[string]uint32) map[string]*uint32 {
|
|
||||||
dst := make(map[string]*uint32)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint32ValueMap converts a string map of uint32 pouinters uinto a string
|
|
||||||
// map of uint32 values
|
|
||||||
func Uint32ValueMap(src map[string]*uint32) map[string]uint32 {
|
|
||||||
dst := make(map[string]uint32)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64 returns a pouinter to of the uint64 value passed in.
|
|
||||||
func Uint64(v uint64) *uint64 {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64Value returns the value of the uint64 pouinter passed in or
|
|
||||||
// 0 if the pouinter is nil.
|
|
||||||
func Uint64Value(v *uint64) uint64 {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64Slice converts a slice of uint64 values uinto a slice of
|
|
||||||
// uint64 pouinters
|
|
||||||
func Uint64Slice(src []uint64) []*uint64 {
|
|
||||||
dst := make([]*uint64, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64ValueSlice converts a slice of uint64 pouinters uinto a slice of
|
|
||||||
// uint64 values
|
|
||||||
func Uint64ValueSlice(src []*uint64) []uint64 {
|
|
||||||
dst := make([]uint64, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64Map converts a string map of uint64 values uinto a string
|
|
||||||
// map of uint64 pouinters
|
|
||||||
func Uint64Map(src map[string]uint64) map[string]*uint64 {
|
|
||||||
dst := make(map[string]*uint64)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64ValueMap converts a string map of uint64 pouinters uinto a string
|
|
||||||
// map of uint64 values
|
|
||||||
func Uint64ValueMap(src map[string]*uint64) map[string]uint64 {
|
|
||||||
dst := make(map[string]uint64)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64 returns a pointer to of the float64 value passed in.
|
|
||||||
func Float64(v float64) *float64 {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64Value returns the value of the float64 pointer passed in or
|
|
||||||
// 0 if the pointer is nil.
|
|
||||||
func Float64Value(v *float64) float64 {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64Slice converts a slice of float64 values into a slice of
|
|
||||||
// float64 pointers
|
|
||||||
func Float64Slice(src []float64) []*float64 {
|
|
||||||
dst := make([]*float64, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64ValueSlice converts a slice of float64 pointers into a slice of
|
|
||||||
// float64 values
|
|
||||||
func Float64ValueSlice(src []*float64) []float64 {
|
|
||||||
dst := make([]float64, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64Map converts a string map of float64 values into a string
|
|
||||||
// map of float64 pointers
|
|
||||||
func Float64Map(src map[string]float64) map[string]*float64 {
|
|
||||||
dst := make(map[string]*float64)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64ValueMap converts a string map of float64 pointers into a string
|
|
||||||
// map of float64 values
|
|
||||||
func Float64ValueMap(src map[string]*float64) map[string]float64 {
|
|
||||||
dst := make(map[string]float64)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time returns a pointer to of the time.Time value passed in.
|
|
||||||
func Time(v time.Time) *time.Time {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeValue returns the value of the time.Time pointer passed in or
|
|
||||||
// time.Time{} if the pointer is nil.
|
|
||||||
func TimeValue(v *time.Time) time.Time {
|
|
||||||
if v != nil {
|
|
||||||
return *v
|
|
||||||
}
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeSlice converts a slice of time.Time values into a slice of
|
|
||||||
// time.Time pointers
|
|
||||||
func TimeSlice(src []time.Time) []*time.Time {
|
|
||||||
dst := make([]*time.Time, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
dst[i] = &(src[i])
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeValueSlice converts a slice of time.Time pointers into a slice of
|
|
||||||
// time.Time values
|
|
||||||
func TimeValueSlice(src []*time.Time) []time.Time {
|
|
||||||
dst := make([]time.Time, len(src))
|
|
||||||
for i := 0; i < len(src); i++ {
|
|
||||||
if src[i] != nil {
|
|
||||||
dst[i] = *(src[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeMap converts a string map of time.Time values into a string
|
|
||||||
// map of time.Time pointers
|
|
||||||
func TimeMap(src map[string]time.Time) map[string]*time.Time {
|
|
||||||
dst := make(map[string]*time.Time)
|
|
||||||
for k, val := range src {
|
|
||||||
v := val
|
|
||||||
dst[k] = &v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeValueMap converts a string map of time.Time pointers into a string
|
|
||||||
// map of time.Time values
|
|
||||||
func TimeValueMap(src map[string]*time.Time) map[string]time.Time {
|
|
||||||
dst := make(map[string]time.Time)
|
|
||||||
for k, val := range src {
|
|
||||||
if val != nil {
|
|
||||||
dst[k] = *val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
|
@ -1,270 +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 swag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/mailru/easyjson/jlexer"
|
|
||||||
"github.com/mailru/easyjson/jwriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultJSONNameProvider the default cache for types
|
|
||||||
var DefaultJSONNameProvider = NewNameProvider()
|
|
||||||
|
|
||||||
const comma = byte(',')
|
|
||||||
|
|
||||||
var closers = map[byte]byte{
|
|
||||||
'{': '}',
|
|
||||||
'[': ']',
|
|
||||||
}
|
|
||||||
|
|
||||||
type ejMarshaler interface {
|
|
||||||
MarshalEasyJSON(w *jwriter.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ejUnmarshaler interface {
|
|
||||||
UnmarshalEasyJSON(w *jlexer.Lexer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteJSON writes json data, prefers finding an appropriate interface to short-circuit the marshaller
|
|
||||||
// so it takes the fastest option available.
|
|
||||||
func WriteJSON(data interface{}) ([]byte, error) {
|
|
||||||
if d, ok := data.(ejMarshaler); ok {
|
|
||||||
jw := new(jwriter.Writer)
|
|
||||||
d.MarshalEasyJSON(jw)
|
|
||||||
return jw.BuildBytes()
|
|
||||||
}
|
|
||||||
if d, ok := data.(json.Marshaler); ok {
|
|
||||||
return d.MarshalJSON()
|
|
||||||
}
|
|
||||||
return json.Marshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadJSON reads json data, prefers finding an appropriate interface to short-circuit the unmarshaller
|
|
||||||
// so it takes the fastes option available
|
|
||||||
func ReadJSON(data []byte, value interface{}) error {
|
|
||||||
if d, ok := value.(ejUnmarshaler); ok {
|
|
||||||
jl := &jlexer.Lexer{Data: data}
|
|
||||||
d.UnmarshalEasyJSON(jl)
|
|
||||||
return jl.Error()
|
|
||||||
}
|
|
||||||
if d, ok := value.(json.Unmarshaler); ok {
|
|
||||||
return d.UnmarshalJSON(data)
|
|
||||||
}
|
|
||||||
return json.Unmarshal(data, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DynamicJSONToStruct converts an untyped json structure into a struct
|
|
||||||
func DynamicJSONToStruct(data interface{}, target interface{}) error {
|
|
||||||
// TODO: convert straight to a json typed map (mergo + iterate?)
|
|
||||||
b, err := WriteJSON(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ReadJSON(b, target); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConcatJSON concatenates multiple json objects efficiently
|
|
||||||
func ConcatJSON(blobs ...[]byte) []byte {
|
|
||||||
if len(blobs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(blobs) == 1 {
|
|
||||||
return blobs[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
last := len(blobs) - 1
|
|
||||||
var opening, closing byte
|
|
||||||
a := 0
|
|
||||||
idx := 0
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
|
|
||||||
for i, b := range blobs {
|
|
||||||
if len(b) > 0 && opening == 0 { // is this an array or an object?
|
|
||||||
opening, closing = b[0], closers[b[0]]
|
|
||||||
}
|
|
||||||
|
|
||||||
if opening != '{' && opening != '[' {
|
|
||||||
continue // don't know how to concatenate non container objects
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) < 3 { // yep empty but also the last one, so closing this thing
|
|
||||||
if i == last && a > 0 {
|
|
||||||
buf.WriteByte(closing)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
idx = 0
|
|
||||||
if a > 0 { // we need to join with a comma for everything beyond the first non-empty item
|
|
||||||
buf.WriteByte(comma)
|
|
||||||
idx = 1 // this is not the first or the last so we want to drop the leading bracket
|
|
||||||
}
|
|
||||||
|
|
||||||
if i != last { // not the last one, strip brackets
|
|
||||||
buf.Write(b[idx : len(b)-1])
|
|
||||||
} else { // last one, strip only the leading bracket
|
|
||||||
buf.Write(b[idx:])
|
|
||||||
}
|
|
||||||
a++
|
|
||||||
}
|
|
||||||
// somehow it ended up being empty, so provide a default value
|
|
||||||
if buf.Len() == 0 {
|
|
||||||
buf.WriteByte(opening)
|
|
||||||
buf.WriteByte(closing)
|
|
||||||
}
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToDynamicJSON turns an object into a properly JSON typed structure
|
|
||||||
func ToDynamicJSON(data interface{}) interface{} {
|
|
||||||
// TODO: convert straight to a json typed map (mergo + iterate?)
|
|
||||||
b, _ := json.Marshal(data)
|
|
||||||
var res interface{}
|
|
||||||
json.Unmarshal(b, &res)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromDynamicJSON turns an object into a properly JSON typed structure
|
|
||||||
func FromDynamicJSON(data, target interface{}) error {
|
|
||||||
b, _ := json.Marshal(data)
|
|
||||||
return json.Unmarshal(b, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NameProvider represents an object capabale of translating from go property names
|
|
||||||
// to json property names
|
|
||||||
// This type is thread-safe.
|
|
||||||
type NameProvider struct {
|
|
||||||
lock *sync.Mutex
|
|
||||||
index map[reflect.Type]nameIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
type nameIndex struct {
|
|
||||||
jsonNames map[string]string
|
|
||||||
goNames map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNameProvider creates a new name provider
|
|
||||||
func NewNameProvider() *NameProvider {
|
|
||||||
return &NameProvider{
|
|
||||||
lock: &sync.Mutex{},
|
|
||||||
index: make(map[reflect.Type]nameIndex),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildnameIndex(tpe reflect.Type, idx, reverseIdx map[string]string) {
|
|
||||||
for i := 0; i < tpe.NumField(); i++ {
|
|
||||||
targetDes := tpe.Field(i)
|
|
||||||
|
|
||||||
if targetDes.PkgPath != "" { // unexported
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if targetDes.Anonymous { // walk embedded structures tree down first
|
|
||||||
buildnameIndex(targetDes.Type, idx, reverseIdx)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag := targetDes.Tag.Get("json"); tag != "" {
|
|
||||||
|
|
||||||
parts := strings.Split(tag, ",")
|
|
||||||
if len(parts) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
nm := parts[0]
|
|
||||||
if nm == "-" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if nm == "" { // empty string means we want to use the Go name
|
|
||||||
nm = targetDes.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
idx[nm] = targetDes.Name
|
|
||||||
reverseIdx[targetDes.Name] = nm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNameIndex(tpe reflect.Type) nameIndex {
|
|
||||||
var idx = make(map[string]string, tpe.NumField())
|
|
||||||
var reverseIdx = make(map[string]string, tpe.NumField())
|
|
||||||
|
|
||||||
buildnameIndex(tpe, idx, reverseIdx)
|
|
||||||
return nameIndex{jsonNames: idx, goNames: reverseIdx}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetJSONNames gets all the json property names for a type
|
|
||||||
func (n *NameProvider) GetJSONNames(subject interface{}) []string {
|
|
||||||
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
|
|
||||||
names, ok := n.index[tpe]
|
|
||||||
if !ok {
|
|
||||||
names = n.makeNameIndex(tpe)
|
|
||||||
}
|
|
||||||
|
|
||||||
var res []string
|
|
||||||
for k := range names.jsonNames {
|
|
||||||
res = append(res, k)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetJSONName gets the json name for a go property name
|
|
||||||
func (n *NameProvider) GetJSONName(subject interface{}, name string) (string, bool) {
|
|
||||||
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
|
|
||||||
return n.GetJSONNameForType(tpe, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetJSONNameForType gets the json name for a go property name on a given type
|
|
||||||
func (n *NameProvider) GetJSONNameForType(tpe reflect.Type, name string) (string, bool) {
|
|
||||||
names, ok := n.index[tpe]
|
|
||||||
if !ok {
|
|
||||||
names = n.makeNameIndex(tpe)
|
|
||||||
}
|
|
||||||
nme, ok := names.goNames[name]
|
|
||||||
return nme, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NameProvider) makeNameIndex(tpe reflect.Type) nameIndex {
|
|
||||||
n.lock.Lock()
|
|
||||||
defer n.lock.Unlock()
|
|
||||||
names := newNameIndex(tpe)
|
|
||||||
n.index[tpe] = names
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGoName gets the go name for a json property name
|
|
||||||
func (n *NameProvider) GetGoName(subject interface{}, name string) (string, bool) {
|
|
||||||
tpe := reflect.Indirect(reflect.ValueOf(subject)).Type()
|
|
||||||
return n.GetGoNameForType(tpe, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGoNameForType gets the go name for a given type for a json property name
|
|
||||||
func (n *NameProvider) GetGoNameForType(tpe reflect.Type, name string) (string, bool) {
|
|
||||||
names, ok := n.index[tpe]
|
|
||||||
if !ok {
|
|
||||||
names = n.makeNameIndex(tpe)
|
|
||||||
}
|
|
||||||
nme, ok := names.jsonNames[name]
|
|
||||||
return nme, ok
|
|
||||||
}
|
|
|
@ -1,49 +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 swag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in
|
|
||||||
func LoadFromFileOrHTTP(path string) ([]byte, error) {
|
|
||||||
return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes)(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadStrategy returns a loader function for a given path or uri
|
|
||||||
func LoadStrategy(path string, local, remote func(string) ([]byte, error)) func(string) ([]byte, error) {
|
|
||||||
if strings.HasPrefix(path, "http") {
|
|
||||||
return remote
|
|
||||||
}
|
|
||||||
return local
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadHTTPBytes(path string) ([]byte, error) {
|
|
||||||
resp, err := http.Get(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("could not access document at %q [%s] ", path, resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.ReadAll(resp.Body)
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package swag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SplitHostPort splits a network address into a host and a port.
|
|
||||||
// The port is -1 when there is no port to be found
|
|
||||||
func SplitHostPort(addr string) (host string, port int, err error) {
|
|
||||||
h, p, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return "", -1, err
|
|
||||||
}
|
|
||||||
if p == "" {
|
|
||||||
return "", -1, &net.AddrError{Err: "missing port in address", Addr: addr}
|
|
||||||
}
|
|
||||||
|
|
||||||
pi, err := strconv.Atoi(p)
|
|
||||||
if err != nil {
|
|
||||||
return "", -1, err
|
|
||||||
}
|
|
||||||
return h, pi, nil
|
|
||||||
}
|
|
|
@ -1,56 +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 swag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// GOPATHKey represents the env key for gopath
|
|
||||||
GOPATHKey = "GOPATH"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FindInSearchPath finds a package in a provided lists of paths
|
|
||||||
func FindInSearchPath(searchPath, pkg string) string {
|
|
||||||
pathsList := filepath.SplitList(searchPath)
|
|
||||||
for _, path := range pathsList {
|
|
||||||
if evaluatedPath, err := filepath.EvalSymlinks(filepath.Join(path, "src", pkg)); err == nil {
|
|
||||||
if _, err := os.Stat(evaluatedPath); err == nil {
|
|
||||||
return evaluatedPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindInGoSearchPath finds a package in the $GOPATH:$GOROOT
|
|
||||||
func FindInGoSearchPath(pkg string) string {
|
|
||||||
return FindInSearchPath(FullGoSearchPath(), pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullGoSearchPath gets the search paths for finding packages
|
|
||||||
func FullGoSearchPath() string {
|
|
||||||
allPaths := os.Getenv(GOPATHKey)
|
|
||||||
if allPaths != "" {
|
|
||||||
allPaths = strings.Join([]string{allPaths, runtime.GOROOT()}, ":")
|
|
||||||
} else {
|
|
||||||
allPaths = runtime.GOROOT()
|
|
||||||
}
|
|
||||||
return allPaths
|
|
||||||
}
|
|
|
@ -1,318 +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 swag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Taken from https://github.com/golang/lint/blob/1fab560e16097e5b69afb66eb93aab843ef77845/lint.go#L663-L698
|
|
||||||
var commonInitialisms = map[string]bool{
|
|
||||||
"API": true,
|
|
||||||
"ASCII": true,
|
|
||||||
"CPU": true,
|
|
||||||
"CSS": true,
|
|
||||||
"DNS": true,
|
|
||||||
"EOF": true,
|
|
||||||
"GUID": true,
|
|
||||||
"HTML": true,
|
|
||||||
"HTTPS": true,
|
|
||||||
"HTTP": true,
|
|
||||||
"ID": true,
|
|
||||||
"IP": true,
|
|
||||||
"JSON": true,
|
|
||||||
"LHS": true,
|
|
||||||
"QPS": true,
|
|
||||||
"RAM": true,
|
|
||||||
"RHS": true,
|
|
||||||
"RPC": true,
|
|
||||||
"SLA": true,
|
|
||||||
"SMTP": true,
|
|
||||||
"SSH": true,
|
|
||||||
"TCP": true,
|
|
||||||
"TLS": true,
|
|
||||||
"TTL": true,
|
|
||||||
"UDP": true,
|
|
||||||
"UUID": true,
|
|
||||||
"UID": true,
|
|
||||||
"UI": true,
|
|
||||||
"URI": true,
|
|
||||||
"URL": true,
|
|
||||||
"UTF8": true,
|
|
||||||
"VM": true,
|
|
||||||
"XML": true,
|
|
||||||
"XSRF": true,
|
|
||||||
"XSS": true,
|
|
||||||
}
|
|
||||||
var initialisms []string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
for k := range commonInitialisms {
|
|
||||||
initialisms = append(initialisms, k)
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(byLength(initialisms)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinByFormat joins a string array by a known format:
|
|
||||||
// ssv: space separated value
|
|
||||||
// tsv: tab separated value
|
|
||||||
// pipes: pipe (|) separated value
|
|
||||||
// csv: comma separated value (default)
|
|
||||||
func JoinByFormat(data []string, format string) []string {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
var sep string
|
|
||||||
switch format {
|
|
||||||
case "ssv":
|
|
||||||
sep = " "
|
|
||||||
case "tsv":
|
|
||||||
sep = "\t"
|
|
||||||
case "pipes":
|
|
||||||
sep = "|"
|
|
||||||
case "multi":
|
|
||||||
return data
|
|
||||||
default:
|
|
||||||
sep = ","
|
|
||||||
}
|
|
||||||
return []string{strings.Join(data, sep)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitByFormat splits a string by a known format:
|
|
||||||
// ssv: space separated value
|
|
||||||
// tsv: tab separated value
|
|
||||||
// pipes: pipe (|) separated value
|
|
||||||
// csv: comma separated value (default)
|
|
||||||
func SplitByFormat(data, format string) []string {
|
|
||||||
if data == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var sep string
|
|
||||||
switch format {
|
|
||||||
case "ssv":
|
|
||||||
sep = " "
|
|
||||||
case "tsv":
|
|
||||||
sep = "\t"
|
|
||||||
case "pipes":
|
|
||||||
sep = "|"
|
|
||||||
case "multi":
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
sep = ","
|
|
||||||
}
|
|
||||||
var result []string
|
|
||||||
for _, s := range strings.Split(data, sep) {
|
|
||||||
if ts := strings.TrimSpace(s); ts != "" {
|
|
||||||
result = append(result, ts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type byLength []string
|
|
||||||
|
|
||||||
func (s byLength) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
func (s byLength) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
func (s byLength) Less(i, j int) bool {
|
|
||||||
return len(s[i]) < len(s[j])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepares strings by splitting by caps, spaces, dashes, and underscore
|
|
||||||
func split(str string) (words []string) {
|
|
||||||
repl := strings.NewReplacer(
|
|
||||||
"@", "At ",
|
|
||||||
"&", "And ",
|
|
||||||
"|", "Pipe ",
|
|
||||||
"$", "Dollar ",
|
|
||||||
"!", "Bang ",
|
|
||||||
"-", " ",
|
|
||||||
"_", " ",
|
|
||||||
)
|
|
||||||
|
|
||||||
rex1 := regexp.MustCompile(`(\p{Lu})`)
|
|
||||||
rex2 := regexp.MustCompile(`(\pL|\pM|\pN|\p{Pc})+`)
|
|
||||||
|
|
||||||
str = trim(str)
|
|
||||||
|
|
||||||
// Convert dash and underscore to spaces
|
|
||||||
str = repl.Replace(str)
|
|
||||||
|
|
||||||
// Split when uppercase is found (needed for Snake)
|
|
||||||
str = rex1.ReplaceAllString(str, " $1")
|
|
||||||
// check if consecutive single char things make up an initialism
|
|
||||||
|
|
||||||
for _, k := range initialisms {
|
|
||||||
str = strings.Replace(str, rex1.ReplaceAllString(k, " $1"), " "+k, -1)
|
|
||||||
}
|
|
||||||
// Get the final list of words
|
|
||||||
words = rex2.FindAllString(str, -1)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removes leading whitespaces
|
|
||||||
func trim(str string) string {
|
|
||||||
return strings.Trim(str, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shortcut to strings.ToUpper()
|
|
||||||
func upper(str string) string {
|
|
||||||
return strings.ToUpper(trim(str))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shortcut to strings.ToLower()
|
|
||||||
func lower(str string) string {
|
|
||||||
return strings.ToLower(trim(str))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToFileName lowercases and underscores a go type name
|
|
||||||
func ToFileName(name string) string {
|
|
||||||
var out []string
|
|
||||||
for _, w := range split(name) {
|
|
||||||
out = append(out, lower(w))
|
|
||||||
}
|
|
||||||
return strings.Join(out, "_")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToCommandName lowercases and underscores a go type name
|
|
||||||
func ToCommandName(name string) string {
|
|
||||||
var out []string
|
|
||||||
for _, w := range split(name) {
|
|
||||||
out = append(out, lower(w))
|
|
||||||
}
|
|
||||||
return strings.Join(out, "-")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToHumanNameLower represents a code name as a human series of words
|
|
||||||
func ToHumanNameLower(name string) string {
|
|
||||||
var out []string
|
|
||||||
for _, w := range split(name) {
|
|
||||||
if !commonInitialisms[upper(w)] {
|
|
||||||
out = append(out, lower(w))
|
|
||||||
} else {
|
|
||||||
out = append(out, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(out, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToHumanNameTitle represents a code name as a human series of words with the first letters titleized
|
|
||||||
func ToHumanNameTitle(name string) string {
|
|
||||||
var out []string
|
|
||||||
for _, w := range split(name) {
|
|
||||||
uw := upper(w)
|
|
||||||
if !commonInitialisms[uw] {
|
|
||||||
out = append(out, upper(w[:1])+lower(w[1:]))
|
|
||||||
} else {
|
|
||||||
out = append(out, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(out, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToJSONName camelcases a name which can be underscored or pascal cased
|
|
||||||
func ToJSONName(name string) string {
|
|
||||||
var out []string
|
|
||||||
for i, w := range split(name) {
|
|
||||||
if i == 0 {
|
|
||||||
out = append(out, lower(w))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, upper(w[:1])+lower(w[1:]))
|
|
||||||
}
|
|
||||||
return strings.Join(out, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToVarName camelcases a name which can be underscored or pascal cased
|
|
||||||
func ToVarName(name string) string {
|
|
||||||
res := ToGoName(name)
|
|
||||||
if len(res) <= 1 {
|
|
||||||
return lower(res)
|
|
||||||
}
|
|
||||||
return lower(res[:1]) + res[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToGoName translates a swagger name which can be underscored or camel cased to a name that golint likes
|
|
||||||
func ToGoName(name string) string {
|
|
||||||
var out []string
|
|
||||||
for _, w := range split(name) {
|
|
||||||
uw := upper(w)
|
|
||||||
mod := int(math.Min(float64(len(uw)), 2))
|
|
||||||
if !commonInitialisms[uw] && !commonInitialisms[uw[:len(uw)-mod]] {
|
|
||||||
uw = upper(w[:1]) + lower(w[1:])
|
|
||||||
}
|
|
||||||
out = append(out, uw)
|
|
||||||
}
|
|
||||||
return strings.Join(out, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainsStringsCI searches a slice of strings for a case-insensitive match
|
|
||||||
func ContainsStringsCI(coll []string, item string) bool {
|
|
||||||
for _, a := range coll {
|
|
||||||
if strings.EqualFold(a, item) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type zeroable interface {
|
|
||||||
IsZero() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsZero returns true when the value passed into the function is a zero value.
|
|
||||||
// This allows for safer checking of interface values.
|
|
||||||
func IsZero(data interface{}) bool {
|
|
||||||
// check for things that have an IsZero method instead
|
|
||||||
if vv, ok := data.(zeroable); ok {
|
|
||||||
return vv.IsZero()
|
|
||||||
}
|
|
||||||
// continue with slightly more complex reflection
|
|
||||||
v := reflect.ValueOf(data)
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
return v.Len() == 0
|
|
||||||
case reflect.Bool:
|
|
||||||
return !v.Bool()
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return v.Int() == 0
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return v.Uint() == 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return v.Float() == 0
|
|
||||||
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
||||||
return v.IsNil()
|
|
||||||
case reflect.Struct, reflect.Array:
|
|
||||||
return reflect.DeepEqual(data, reflect.Zero(v.Type()).Interface())
|
|
||||||
case reflect.Invalid:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandLineOptionsGroup represents a group of user-defined command line options
|
|
||||||
type CommandLineOptionsGroup struct {
|
|
||||||
ShortDescription string
|
|
||||||
LongDescription string
|
|
||||||
Options interface{}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2012 Chris Howey
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
@ -1,384 +0,0 @@
|
||||||
Unless otherwise noted, all files in this distribution are released
|
|
||||||
under the Common Development and Distribution License (CDDL).
|
|
||||||
Exceptions are noted within the associated source files.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
1.1. "Contributor" means each individual or entity that creates
|
|
||||||
or contributes to the creation of Modifications.
|
|
||||||
|
|
||||||
1.2. "Contributor Version" means the combination of the Original
|
|
||||||
Software, prior Modifications used by a Contributor (if any),
|
|
||||||
and the Modifications made by that particular Contributor.
|
|
||||||
|
|
||||||
1.3. "Covered Software" means (a) the Original Software, or (b)
|
|
||||||
Modifications, or (c) the combination of files containing
|
|
||||||
Original Software with files containing Modifications, in
|
|
||||||
each case including portions thereof.
|
|
||||||
|
|
||||||
1.4. "Executable" means the Covered Software in any form other
|
|
||||||
than Source Code.
|
|
||||||
|
|
||||||
1.5. "Initial Developer" means the individual or entity that first
|
|
||||||
makes Original Software available under this License.
|
|
||||||
|
|
||||||
1.6. "Larger Work" means a work which combines Covered Software or
|
|
||||||
portions thereof with code not governed by the terms of this
|
|
||||||
License.
|
|
||||||
|
|
||||||
1.7. "License" means this document.
|
|
||||||
|
|
||||||
1.8. "Licensable" means having the right to grant, to the maximum
|
|
||||||
extent possible, whether at the time of the initial grant or
|
|
||||||
subsequently acquired, any and all of the rights conveyed
|
|
||||||
herein.
|
|
||||||
|
|
||||||
1.9. "Modifications" means the Source Code and Executable form of
|
|
||||||
any of the following:
|
|
||||||
|
|
||||||
A. Any file that results from an addition to, deletion from or
|
|
||||||
modification of the contents of a file containing Original
|
|
||||||
Software or previous Modifications;
|
|
||||||
|
|
||||||
B. Any new file that contains any part of the Original
|
|
||||||
Software or previous Modifications; or
|
|
||||||
|
|
||||||
C. Any new file that is contributed or otherwise made
|
|
||||||
available under the terms of this License.
|
|
||||||
|
|
||||||
1.10. "Original Software" means the Source Code and Executable
|
|
||||||
form of computer software code that is originally released
|
|
||||||
under this License.
|
|
||||||
|
|
||||||
1.11. "Patent Claims" means any patent claim(s), now owned or
|
|
||||||
hereafter acquired, including without limitation, method,
|
|
||||||
process, and apparatus claims, in any patent Licensable by
|
|
||||||
grantor.
|
|
||||||
|
|
||||||
1.12. "Source Code" means (a) the common form of computer software
|
|
||||||
code in which modifications are made and (b) associated
|
|
||||||
documentation included in or with such code.
|
|
||||||
|
|
||||||
1.13. "You" (or "Your") means an individual or a legal entity
|
|
||||||
exercising rights under, and complying with all of the terms
|
|
||||||
of, this License. For legal entities, "You" includes any
|
|
||||||
entity which controls, is controlled by, or is under common
|
|
||||||
control with You. For purposes of this definition,
|
|
||||||
"control" means (a) the power, direct or indirect, to cause
|
|
||||||
the direction or management of such entity, whether by
|
|
||||||
contract or otherwise, or (b) ownership of more than fifty
|
|
||||||
percent (50%) of the outstanding shares or beneficial
|
|
||||||
ownership of such entity.
|
|
||||||
|
|
||||||
2. License Grants.
|
|
||||||
|
|
||||||
2.1. The Initial Developer Grant.
|
|
||||||
|
|
||||||
Conditioned upon Your compliance with Section 3.1 below and
|
|
||||||
subject to third party intellectual property claims, the Initial
|
|
||||||
Developer hereby grants You a world-wide, royalty-free,
|
|
||||||
non-exclusive license:
|
|
||||||
|
|
||||||
(a) under intellectual property rights (other than patent or
|
|
||||||
trademark) Licensable by Initial Developer, to use,
|
|
||||||
reproduce, modify, display, perform, sublicense and
|
|
||||||
distribute the Original Software (or portions thereof),
|
|
||||||
with or without Modifications, and/or as part of a Larger
|
|
||||||
Work; and
|
|
||||||
|
|
||||||
(b) under Patent Claims infringed by the making, using or
|
|
||||||
selling of Original Software, to make, have made, use,
|
|
||||||
practice, sell, and offer for sale, and/or otherwise
|
|
||||||
dispose of the Original Software (or portions thereof).
|
|
||||||
|
|
||||||
(c) The licenses granted in Sections 2.1(a) and (b) are
|
|
||||||
effective on the date Initial Developer first distributes
|
|
||||||
or otherwise makes the Original Software available to a
|
|
||||||
third party under the terms of this License.
|
|
||||||
|
|
||||||
(d) Notwithstanding Section 2.1(b) above, no patent license is
|
|
||||||
granted: (1) for code that You delete from the Original
|
|
||||||
Software, or (2) for infringements caused by: (i) the
|
|
||||||
modification of the Original Software, or (ii) the
|
|
||||||
combination of the Original Software with other software
|
|
||||||
or devices.
|
|
||||||
|
|
||||||
2.2. Contributor Grant.
|
|
||||||
|
|
||||||
Conditioned upon Your compliance with Section 3.1 below and
|
|
||||||
subject to third party intellectual property claims, each
|
|
||||||
Contributor hereby grants You a world-wide, royalty-free,
|
|
||||||
non-exclusive license:
|
|
||||||
|
|
||||||
(a) under intellectual property rights (other than patent or
|
|
||||||
trademark) Licensable by Contributor to use, reproduce,
|
|
||||||
modify, display, perform, sublicense and distribute the
|
|
||||||
Modifications created by such Contributor (or portions
|
|
||||||
thereof), either on an unmodified basis, with other
|
|
||||||
Modifications, as Covered Software and/or as part of a
|
|
||||||
Larger Work; and
|
|
||||||
|
|
||||||
(b) under Patent Claims infringed by the making, using, or
|
|
||||||
selling of Modifications made by that Contributor either
|
|
||||||
alone and/or in combination with its Contributor Version
|
|
||||||
(or portions of such combination), to make, use, sell,
|
|
||||||
offer for sale, have made, and/or otherwise dispose of:
|
|
||||||
(1) Modifications made by that Contributor (or portions
|
|
||||||
thereof); and (2) the combination of Modifications made by
|
|
||||||
that Contributor with its Contributor Version (or portions
|
|
||||||
of such combination).
|
|
||||||
|
|
||||||
(c) The licenses granted in Sections 2.2(a) and 2.2(b) are
|
|
||||||
effective on the date Contributor first distributes or
|
|
||||||
otherwise makes the Modifications available to a third
|
|
||||||
party.
|
|
||||||
|
|
||||||
(d) Notwithstanding Section 2.2(b) above, no patent license is
|
|
||||||
granted: (1) for any code that Contributor has deleted
|
|
||||||
from the Contributor Version; (2) for infringements caused
|
|
||||||
by: (i) third party modifications of Contributor Version,
|
|
||||||
or (ii) the combination of Modifications made by that
|
|
||||||
Contributor with other software (except as part of the
|
|
||||||
Contributor Version) or other devices; or (3) under Patent
|
|
||||||
Claims infringed by Covered Software in the absence of
|
|
||||||
Modifications made by that Contributor.
|
|
||||||
|
|
||||||
3. Distribution Obligations.
|
|
||||||
|
|
||||||
3.1. Availability of Source Code.
|
|
||||||
|
|
||||||
Any Covered Software that You distribute or otherwise make
|
|
||||||
available in Executable form must also be made available in Source
|
|
||||||
Code form and that Source Code form must be distributed only under
|
|
||||||
the terms of this License. You must include a copy of this
|
|
||||||
License with every copy of the Source Code form of the Covered
|
|
||||||
Software You distribute or otherwise make available. You must
|
|
||||||
inform recipients of any such Covered Software in Executable form
|
|
||||||
as to how they can obtain such Covered Software in Source Code
|
|
||||||
form in a reasonable manner on or through a medium customarily
|
|
||||||
used for software exchange.
|
|
||||||
|
|
||||||
3.2. Modifications.
|
|
||||||
|
|
||||||
The Modifications that You create or to which You contribute are
|
|
||||||
governed by the terms of this License. You represent that You
|
|
||||||
believe Your Modifications are Your original creation(s) and/or
|
|
||||||
You have sufficient rights to grant the rights conveyed by this
|
|
||||||
License.
|
|
||||||
|
|
||||||
3.3. Required Notices.
|
|
||||||
|
|
||||||
You must include a notice in each of Your Modifications that
|
|
||||||
identifies You as the Contributor of the Modification. You may
|
|
||||||
not remove or alter any copyright, patent or trademark notices
|
|
||||||
contained within the Covered Software, or any notices of licensing
|
|
||||||
or any descriptive text giving attribution to any Contributor or
|
|
||||||
the Initial Developer.
|
|
||||||
|
|
||||||
3.4. Application of Additional Terms.
|
|
||||||
|
|
||||||
You may not offer or impose any terms on any Covered Software in
|
|
||||||
Source Code form that alters or restricts the applicable version
|
|
||||||
of this License or the recipients' rights hereunder. You may
|
|
||||||
choose to offer, and to charge a fee for, warranty, support,
|
|
||||||
indemnity or liability obligations to one or more recipients of
|
|
||||||
Covered Software. However, you may do so only on Your own behalf,
|
|
||||||
and not on behalf of the Initial Developer or any Contributor.
|
|
||||||
You must make it absolutely clear that any such warranty, support,
|
|
||||||
indemnity or liability obligation is offered by You alone, and You
|
|
||||||
hereby agree to indemnify the Initial Developer and every
|
|
||||||
Contributor for any liability incurred by the Initial Developer or
|
|
||||||
such Contributor as a result of warranty, support, indemnity or
|
|
||||||
liability terms You offer.
|
|
||||||
|
|
||||||
3.5. Distribution of Executable Versions.
|
|
||||||
|
|
||||||
You may distribute the Executable form of the Covered Software
|
|
||||||
under the terms of this License or under the terms of a license of
|
|
||||||
Your choice, which may contain terms different from this License,
|
|
||||||
provided that You are in compliance with the terms of this License
|
|
||||||
and that the license for the Executable form does not attempt to
|
|
||||||
limit or alter the recipient's rights in the Source Code form from
|
|
||||||
the rights set forth in this License. If You distribute the
|
|
||||||
Covered Software in Executable form under a different license, You
|
|
||||||
must make it absolutely clear that any terms which differ from
|
|
||||||
this License are offered by You alone, not by the Initial
|
|
||||||
Developer or Contributor. You hereby agree to indemnify the
|
|
||||||
Initial Developer and every Contributor for any liability incurred
|
|
||||||
by the Initial Developer or such Contributor as a result of any
|
|
||||||
such terms You offer.
|
|
||||||
|
|
||||||
3.6. Larger Works.
|
|
||||||
|
|
||||||
You may create a Larger Work by combining Covered Software with
|
|
||||||
other code not governed by the terms of this License and
|
|
||||||
distribute the Larger Work as a single product. In such a case,
|
|
||||||
You must make sure the requirements of this License are fulfilled
|
|
||||||
for the Covered Software.
|
|
||||||
|
|
||||||
4. Versions of the License.
|
|
||||||
|
|
||||||
4.1. New Versions.
|
|
||||||
|
|
||||||
Sun Microsystems, Inc. is the initial license steward and may
|
|
||||||
publish revised and/or new versions of this License from time to
|
|
||||||
time. Each version will be given a distinguishing version number.
|
|
||||||
Except as provided in Section 4.3, no one other than the license
|
|
||||||
steward has the right to modify this License.
|
|
||||||
|
|
||||||
4.2. Effect of New Versions.
|
|
||||||
|
|
||||||
You may always continue to use, distribute or otherwise make the
|
|
||||||
Covered Software available under the terms of the version of the
|
|
||||||
License under which You originally received the Covered Software.
|
|
||||||
If the Initial Developer includes a notice in the Original
|
|
||||||
Software prohibiting it from being distributed or otherwise made
|
|
||||||
available under any subsequent version of the License, You must
|
|
||||||
distribute and make the Covered Software available under the terms
|
|
||||||
of the version of the License under which You originally received
|
|
||||||
the Covered Software. Otherwise, You may also choose to use,
|
|
||||||
distribute or otherwise make the Covered Software available under
|
|
||||||
the terms of any subsequent version of the License published by
|
|
||||||
the license steward.
|
|
||||||
|
|
||||||
4.3. Modified Versions.
|
|
||||||
|
|
||||||
When You are an Initial Developer and You want to create a new
|
|
||||||
license for Your Original Software, You may create and use a
|
|
||||||
modified version of this License if You: (a) rename the license
|
|
||||||
and remove any references to the name of the license steward
|
|
||||||
(except to note that the license differs from this License); and
|
|
||||||
(b) otherwise make it clear that the license contains terms which
|
|
||||||
differ from this License.
|
|
||||||
|
|
||||||
5. DISCLAIMER OF WARRANTY.
|
|
||||||
|
|
||||||
COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS"
|
|
||||||
BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
|
|
||||||
INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED
|
|
||||||
SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR
|
|
||||||
PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
|
|
||||||
PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY
|
|
||||||
COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE
|
|
||||||
INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY
|
|
||||||
NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
|
|
||||||
WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
|
|
||||||
ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
|
|
||||||
DISCLAIMER.
|
|
||||||
|
|
||||||
6. TERMINATION.
|
|
||||||
|
|
||||||
6.1. This License and the rights granted hereunder will terminate
|
|
||||||
automatically if You fail to comply with terms herein and fail to
|
|
||||||
cure such breach within 30 days of becoming aware of the breach.
|
|
||||||
Provisions which, by their nature, must remain in effect beyond
|
|
||||||
the termination of this License shall survive.
|
|
||||||
|
|
||||||
6.2. If You assert a patent infringement claim (excluding
|
|
||||||
declaratory judgment actions) against Initial Developer or a
|
|
||||||
Contributor (the Initial Developer or Contributor against whom You
|
|
||||||
assert such claim is referred to as "Participant") alleging that
|
|
||||||
the Participant Software (meaning the Contributor Version where
|
|
||||||
the Participant is a Contributor or the Original Software where
|
|
||||||
the Participant is the Initial Developer) directly or indirectly
|
|
||||||
infringes any patent, then any and all rights granted directly or
|
|
||||||
indirectly to You by such Participant, the Initial Developer (if
|
|
||||||
the Initial Developer is not the Participant) and all Contributors
|
|
||||||
under Sections 2.1 and/or 2.2 of this License shall, upon 60 days
|
|
||||||
notice from Participant terminate prospectively and automatically
|
|
||||||
at the expiration of such 60 day notice period, unless if within
|
|
||||||
such 60 day period You withdraw Your claim with respect to the
|
|
||||||
Participant Software against such Participant either unilaterally
|
|
||||||
or pursuant to a written agreement with Participant.
|
|
||||||
|
|
||||||
6.3. In the event of termination under Sections 6.1 or 6.2 above,
|
|
||||||
all end user licenses that have been validly granted by You or any
|
|
||||||
distributor hereunder prior to termination (excluding licenses
|
|
||||||
granted to You by any distributor) shall survive termination.
|
|
||||||
|
|
||||||
7. LIMITATION OF LIABILITY.
|
|
||||||
|
|
||||||
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
|
|
||||||
(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
|
|
||||||
INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF
|
|
||||||
COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE
|
|
||||||
LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
|
|
||||||
CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
|
|
||||||
LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK
|
|
||||||
STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
|
|
||||||
COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
|
|
||||||
INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
|
|
||||||
LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
|
|
||||||
INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
|
|
||||||
APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO
|
|
||||||
NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
|
|
||||||
CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT
|
|
||||||
APPLY TO YOU.
|
|
||||||
|
|
||||||
8. U.S. GOVERNMENT END USERS.
|
|
||||||
|
|
||||||
The Covered Software is a "commercial item," as that term is
|
|
||||||
defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial
|
|
||||||
computer software" (as that term is defined at 48
|
|
||||||
C.F.R. 252.227-7014(a)(1)) and "commercial computer software
|
|
||||||
documentation" as such terms are used in 48 C.F.R. 12.212
|
|
||||||
(Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48
|
|
||||||
C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all
|
|
||||||
U.S. Government End Users acquire Covered Software with only those
|
|
||||||
rights set forth herein. This U.S. Government Rights clause is in
|
|
||||||
lieu of, and supersedes, any other FAR, DFAR, or other clause or
|
|
||||||
provision that addresses Government rights in computer software
|
|
||||||
under this License.
|
|
||||||
|
|
||||||
9. MISCELLANEOUS.
|
|
||||||
|
|
||||||
This License represents the complete agreement concerning subject
|
|
||||||
matter hereof. If any provision of this License is held to be
|
|
||||||
unenforceable, such provision shall be reformed only to the extent
|
|
||||||
necessary to make it enforceable. This License shall be governed
|
|
||||||
by the law of the jurisdiction specified in a notice contained
|
|
||||||
within the Original Software (except to the extent applicable law,
|
|
||||||
if any, provides otherwise), excluding such jurisdiction's
|
|
||||||
conflict-of-law provisions. Any litigation relating to this
|
|
||||||
License shall be subject to the jurisdiction of the courts located
|
|
||||||
in the jurisdiction and venue specified in a notice contained
|
|
||||||
within the Original Software, with the losing party responsible
|
|
||||||
for costs, including, without limitation, court costs and
|
|
||||||
reasonable attorneys' fees and expenses. The application of the
|
|
||||||
United Nations Convention on Contracts for the International Sale
|
|
||||||
of Goods is expressly excluded. Any law or regulation which
|
|
||||||
provides that the language of a contract shall be construed
|
|
||||||
against the drafter shall not apply to this License. You agree
|
|
||||||
that You alone are responsible for compliance with the United
|
|
||||||
States export administration regulations (and the export control
|
|
||||||
laws and regulation of any other countries) when You use,
|
|
||||||
distribute or otherwise make available any Covered Software.
|
|
||||||
|
|
||||||
10. RESPONSIBILITY FOR CLAIMS.
|
|
||||||
|
|
||||||
As between Initial Developer and the Contributors, each party is
|
|
||||||
responsible for claims and damages arising, directly or
|
|
||||||
indirectly, out of its utilization of rights under this License
|
|
||||||
and You agree to work with Initial Developer and Contributors to
|
|
||||||
distribute such responsibility on an equitable basis. Nothing
|
|
||||||
herein is intended or shall be deemed to constitute any admission
|
|
||||||
of liability.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND
|
|
||||||
DISTRIBUTION LICENSE (CDDL)
|
|
||||||
|
|
||||||
For Covered Software in this distribution, this License shall
|
|
||||||
be governed by the laws of the State of California (excluding
|
|
||||||
conflict-of-law provisions).
|
|
||||||
|
|
||||||
Any litigation relating to this License shall be subject to the
|
|
||||||
jurisdiction of the Federal Courts of the Northern District of
|
|
||||||
California and the state courts of the State of California, with
|
|
||||||
venue lying in Santa Clara County, California.
|
|
|
@ -1,27 +0,0 @@
|
||||||
# getpasswd in Go [![GoDoc](https://godoc.org/github.com/howeyc/gopass?status.svg)](https://godoc.org/github.com/howeyc/gopass) [![Build Status](https://secure.travis-ci.org/howeyc/gopass.png?branch=master)](http://travis-ci.org/howeyc/gopass)
|
|
||||||
|
|
||||||
Retrieve password from user terminal or piped input without echo.
|
|
||||||
|
|
||||||
Verified on BSD, Linux, and Windows.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
import "github.com/howeyc/gopass"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Printf("Password: ")
|
|
||||||
|
|
||||||
// Silent. For printing *'s use gopass.GetPasswdMasked()
|
|
||||||
pass, err := gopass.GetPasswd()
|
|
||||||
if err != nil {
|
|
||||||
// Handle gopass.ErrInterrupted or getch() read error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do something with pass
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Caution: Multi-byte characters not supported!
|
|
|
@ -1,91 +0,0 @@
|
||||||
package gopass
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultGetCh = func() (byte, error) {
|
|
||||||
buf := make([]byte, 1)
|
|
||||||
if n, err := os.Stdin.Read(buf); n == 0 || err != nil {
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
return buf[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
maxLength = 512
|
|
||||||
ErrInterrupted = errors.New("interrupted")
|
|
||||||
ErrMaxLengthExceeded = fmt.Errorf("maximum byte limit (%v) exceeded", maxLength)
|
|
||||||
|
|
||||||
// Provide variable so that tests can provide a mock implementation.
|
|
||||||
getch = defaultGetCh
|
|
||||||
)
|
|
||||||
|
|
||||||
// getPasswd returns the input read from terminal.
|
|
||||||
// If masked is true, typing will be matched by asterisks on the screen.
|
|
||||||
// Otherwise, typing will echo nothing.
|
|
||||||
func getPasswd(masked bool) ([]byte, error) {
|
|
||||||
var err error
|
|
||||||
var pass, bs, mask []byte
|
|
||||||
if masked {
|
|
||||||
bs = []byte("\b \b")
|
|
||||||
mask = []byte("*")
|
|
||||||
}
|
|
||||||
|
|
||||||
if isTerminal(os.Stdin.Fd()) {
|
|
||||||
if oldState, err := makeRaw(os.Stdin.Fd()); err != nil {
|
|
||||||
return pass, err
|
|
||||||
} else {
|
|
||||||
defer restore(os.Stdin.Fd(), oldState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track total bytes read, not just bytes in the password. This ensures any
|
|
||||||
// errors that might flood the console with nil or -1 bytes infinitely are
|
|
||||||
// capped.
|
|
||||||
var counter int
|
|
||||||
for counter = 0; counter <= maxLength; counter++ {
|
|
||||||
if v, e := getch(); e != nil {
|
|
||||||
err = e
|
|
||||||
break
|
|
||||||
} else if v == 127 || v == 8 {
|
|
||||||
if l := len(pass); l > 0 {
|
|
||||||
pass = pass[:l-1]
|
|
||||||
fmt.Print(string(bs))
|
|
||||||
}
|
|
||||||
} else if v == 13 || v == 10 {
|
|
||||||
break
|
|
||||||
} else if v == 3 {
|
|
||||||
err = ErrInterrupted
|
|
||||||
break
|
|
||||||
} else if v != 0 {
|
|
||||||
pass = append(pass, v)
|
|
||||||
fmt.Print(string(mask))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if counter > maxLength {
|
|
||||||
err = ErrMaxLengthExceeded
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
return pass, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPasswd returns the password read from the terminal without echoing input.
|
|
||||||
// The returned byte array does not include end-of-line characters.
|
|
||||||
func GetPasswd() ([]byte, error) {
|
|
||||||
return getPasswd(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPasswdMasked returns the password read from the terminal, echoing asterisks.
|
|
||||||
// The returned byte array does not include end-of-line characters.
|
|
||||||
func GetPasswdMasked() ([]byte, error) {
|
|
||||||
return getPasswd(true)
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// +build !solaris
|
|
||||||
|
|
||||||
package gopass
|
|
||||||
|
|
||||||
import "golang.org/x/crypto/ssh/terminal"
|
|
||||||
|
|
||||||
type terminalState struct {
|
|
||||||
state *terminal.State
|
|
||||||
}
|
|
||||||
|
|
||||||
func isTerminal(fd uintptr) bool {
|
|
||||||
return terminal.IsTerminal(int(fd))
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeRaw(fd uintptr) (*terminalState, error) {
|
|
||||||
state, err := terminal.MakeRaw(int(fd))
|
|
||||||
|
|
||||||
return &terminalState{
|
|
||||||
state: state,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func restore(fd uintptr, oldState *terminalState) error {
|
|
||||||
return terminal.Restore(int(fd), oldState.state)
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* CDDL HEADER START
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the terms of the
|
|
||||||
* Common Development and Distribution License, Version 1.0 only
|
|
||||||
* (the "License"). You may not use this file except in compliance
|
|
||||||
* with the License.
|
|
||||||
*
|
|
||||||
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
|
||||||
* or http://www.opensolaris.org/os/licensing.
|
|
||||||
* See the License for the specific language governing permissions
|
|
||||||
* and limitations under the License.
|
|
||||||
*
|
|
||||||
* When distributing Covered Code, include this CDDL HEADER in each
|
|
||||||
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
|
||||||
* If applicable, add the following below this CDDL HEADER, with the
|
|
||||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
|
||||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
|
||||||
*
|
|
||||||
* CDDL HEADER END
|
|
||||||
*/
|
|
||||||
// Below is derived from Solaris source, so CDDL license is included.
|
|
||||||
|
|
||||||
package gopass
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
type terminalState struct {
|
|
||||||
state *unix.Termios
|
|
||||||
}
|
|
||||||
|
|
||||||
// isTerminal returns true if there is a terminal attached to the given
|
|
||||||
// file descriptor.
|
|
||||||
// Source: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
|
|
||||||
func isTerminal(fd uintptr) bool {
|
|
||||||
var termio unix.Termio
|
|
||||||
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeRaw puts the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
// Source: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
|
||||||
func makeRaw(fd uintptr) (*terminalState, error) {
|
|
||||||
oldTermiosPtr, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
oldTermios := *oldTermiosPtr
|
|
||||||
|
|
||||||
newTermios := oldTermios
|
|
||||||
newTermios.Lflag &^= syscall.ECHO | syscall.ECHOE | syscall.ECHOK | syscall.ECHONL
|
|
||||||
if err := unix.IoctlSetTermios(int(fd), unix.TCSETS, &newTermios); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &terminalState{
|
|
||||||
state: oldTermiosPtr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func restore(fd uintptr, oldState *terminalState) error {
|
|
||||||
return unix.IoctlSetTermios(int(fd), unix.TCSETS, oldState.state)
|
|
||||||
}
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
A high-performance 100% compatible drop-in replacement of "encoding/json"
|
A high-performance 100% compatible drop-in replacement of "encoding/json"
|
||||||
|
|
||||||
|
You can also use thrift like JSON using [thrift-iterator](https://github.com/thrift-iterator/go)
|
||||||
|
|
||||||
```
|
```
|
||||||
Go开发者们请加入我们,滴滴出行平台技术部 taowen@didichuxing.com
|
Go开发者们请加入我们,滴滴出行平台技术部 taowen@didichuxing.com
|
||||||
```
|
```
|
||||||
|
@ -29,6 +31,9 @@ Raw Result (easyjson requires static code generation)
|
||||||
| easyjson encode | 883 ns/op | 576 B/op | 3 allocs/op |
|
| easyjson encode | 883 ns/op | 576 B/op | 3 allocs/op |
|
||||||
| jsoniter encode | 837 ns/op | 384 B/op | 4 allocs/op |
|
| jsoniter encode | 837 ns/op | 384 B/op | 4 allocs/op |
|
||||||
|
|
||||||
|
Always benchmark with your own workload.
|
||||||
|
The result depends heavily on the data input.
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
100% compatibility with standard lib
|
100% compatibility with standard lib
|
||||||
|
|
|
@ -16,15 +16,6 @@ func Unmarshal(data []byte, v interface{}) error {
|
||||||
return ConfigDefault.Unmarshal(data, v)
|
return ConfigDefault.Unmarshal(data, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lastNotSpacePos(data []byte) int {
|
|
||||||
for i := len(data) - 1; i >= 0; i-- {
|
|
||||||
if data[i] != ' ' && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
|
|
||||||
return i + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalFromString convenient method to read from string instead of []byte
|
// UnmarshalFromString convenient method to read from string instead of []byte
|
||||||
func UnmarshalFromString(str string, v interface{}) error {
|
func UnmarshalFromString(str string, v interface{}) error {
|
||||||
return ConfigDefault.UnmarshalFromString(str, v)
|
return ConfigDefault.UnmarshalFromString(str, v)
|
||||||
|
@ -71,6 +62,11 @@ type Decoder struct {
|
||||||
|
|
||||||
// Decode decode JSON into interface{}
|
// Decode decode JSON into interface{}
|
||||||
func (adapter *Decoder) Decode(obj interface{}) error {
|
func (adapter *Decoder) Decode(obj interface{}) error {
|
||||||
|
if adapter.iter.head == adapter.iter.tail && adapter.iter.reader != nil {
|
||||||
|
if !adapter.iter.loadMore() {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
adapter.iter.ReadVal(obj)
|
adapter.iter.ReadVal(obj)
|
||||||
err := adapter.iter.Error
|
err := adapter.iter.Error
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
@ -81,7 +77,14 @@ func (adapter *Decoder) Decode(obj interface{}) error {
|
||||||
|
|
||||||
// More is there more?
|
// More is there more?
|
||||||
func (adapter *Decoder) More() bool {
|
func (adapter *Decoder) More() bool {
|
||||||
return adapter.iter.head != adapter.iter.tail
|
iter := adapter.iter
|
||||||
|
if iter.Error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if iter.head != iter.tail {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return iter.loadMore()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffered remaining buffer
|
// Buffered remaining buffer
|
||||||
|
@ -90,11 +93,21 @@ func (adapter *Decoder) Buffered() io.Reader {
|
||||||
return bytes.NewReader(remaining)
|
return bytes.NewReader(remaining)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseNumber for number JSON element, use float64 or json.NumberValue (alias of string)
|
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
||||||
|
// Number instead of as a float64.
|
||||||
func (adapter *Decoder) UseNumber() {
|
func (adapter *Decoder) UseNumber() {
|
||||||
origCfg := adapter.iter.cfg.configBeforeFrozen
|
cfg := adapter.iter.cfg.configBeforeFrozen
|
||||||
origCfg.UseNumber = true
|
cfg.UseNumber = true
|
||||||
adapter.iter.cfg = origCfg.Froze().(*frozenConfig)
|
adapter.iter.cfg = cfg.frozeWithCacheReuse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisallowUnknownFields causes the Decoder to return an error when the destination
|
||||||
|
// is a struct and the input contains object keys which do not match any
|
||||||
|
// non-ignored, exported fields in the destination.
|
||||||
|
func (adapter *Decoder) DisallowUnknownFields() {
|
||||||
|
cfg := adapter.iter.cfg.configBeforeFrozen
|
||||||
|
cfg.DisallowUnknownFields = true
|
||||||
|
adapter.iter.cfg = cfg.frozeWithCacheReuse()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEncoder same as json.NewEncoder
|
// NewEncoder same as json.NewEncoder
|
||||||
|
@ -110,20 +123,23 @@ type Encoder struct {
|
||||||
// Encode encode interface{} as JSON to io.Writer
|
// Encode encode interface{} as JSON to io.Writer
|
||||||
func (adapter *Encoder) Encode(val interface{}) error {
|
func (adapter *Encoder) Encode(val interface{}) error {
|
||||||
adapter.stream.WriteVal(val)
|
adapter.stream.WriteVal(val)
|
||||||
|
adapter.stream.WriteRaw("\n")
|
||||||
adapter.stream.Flush()
|
adapter.stream.Flush()
|
||||||
return adapter.stream.Error
|
return adapter.stream.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetIndent set the indention. Prefix is not supported
|
// SetIndent set the indention. Prefix is not supported
|
||||||
func (adapter *Encoder) SetIndent(prefix, indent string) {
|
func (adapter *Encoder) SetIndent(prefix, indent string) {
|
||||||
adapter.stream.cfg.indentionStep = len(indent)
|
config := adapter.stream.cfg.configBeforeFrozen
|
||||||
|
config.IndentionStep = len(indent)
|
||||||
|
adapter.stream.cfg = config.frozeWithCacheReuse()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEscapeHTML escape html by default, set to false to disable
|
// SetEscapeHTML escape html by default, set to false to disable
|
||||||
func (adapter *Encoder) SetEscapeHTML(escapeHTML bool) {
|
func (adapter *Encoder) SetEscapeHTML(escapeHTML bool) {
|
||||||
config := adapter.stream.cfg.configBeforeFrozen
|
config := adapter.stream.cfg.configBeforeFrozen
|
||||||
config.EscapeHTML = escapeHTML
|
config.EscapeHTML = escapeHTML
|
||||||
adapter.stream.cfg = config.Froze().(*frozenConfig)
|
adapter.stream.cfg = config.frozeWithCacheReuse()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid reports whether data is a valid JSON encoding.
|
// Valid reports whether data is a valid JSON encoding.
|
|
@ -1,9 +1,13 @@
|
||||||
package jsoniter
|
package jsoniter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Any generic object representation.
|
// Any generic object representation.
|
||||||
|
@ -24,7 +28,6 @@ type Any interface {
|
||||||
ToString() string
|
ToString() string
|
||||||
ToVal(val interface{})
|
ToVal(val interface{})
|
||||||
Get(path ...interface{}) Any
|
Get(path ...interface{}) Any
|
||||||
// TODO: add Set
|
|
||||||
Size() int
|
Size() int
|
||||||
Keys() []string
|
Keys() []string
|
||||||
GetInterface() interface{}
|
GetInterface() interface{}
|
||||||
|
@ -34,7 +37,7 @@ type Any interface {
|
||||||
type baseAny struct{}
|
type baseAny struct{}
|
||||||
|
|
||||||
func (any *baseAny) Get(path ...interface{}) Any {
|
func (any *baseAny) Get(path ...interface{}) Any {
|
||||||
return &invalidAny{baseAny{}, fmt.Errorf("Get %v from simple value", path)}
|
return &invalidAny{baseAny{}, fmt.Errorf("GetIndex %v from simple value", path)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (any *baseAny) Size() int {
|
func (any *baseAny) Size() int {
|
||||||
|
@ -88,7 +91,7 @@ func Wrap(val interface{}) Any {
|
||||||
if isAny {
|
if isAny {
|
||||||
return asAny
|
return asAny
|
||||||
}
|
}
|
||||||
typ := reflect.TypeOf(val)
|
typ := reflect2.TypeOf(val)
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
return wrapArray(val)
|
return wrapArray(val)
|
||||||
|
@ -99,6 +102,9 @@ func Wrap(val interface{}) Any {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return WrapString(val.(string))
|
return WrapString(val.(string))
|
||||||
case reflect.Int:
|
case reflect.Int:
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return WrapInt32(int32(val.(int)))
|
||||||
|
}
|
||||||
return WrapInt64(int64(val.(int)))
|
return WrapInt64(int64(val.(int)))
|
||||||
case reflect.Int8:
|
case reflect.Int8:
|
||||||
return WrapInt32(int32(val.(int8)))
|
return WrapInt32(int32(val.(int8)))
|
||||||
|
@ -109,7 +115,15 @@ func Wrap(val interface{}) Any {
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
return WrapInt64(val.(int64))
|
return WrapInt64(val.(int64))
|
||||||
case reflect.Uint:
|
case reflect.Uint:
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return WrapUint32(uint32(val.(uint)))
|
||||||
|
}
|
||||||
return WrapUint64(uint64(val.(uint)))
|
return WrapUint64(uint64(val.(uint)))
|
||||||
|
case reflect.Uintptr:
|
||||||
|
if ptrSize == 32 {
|
||||||
|
return WrapUint32(uint32(val.(uintptr)))
|
||||||
|
}
|
||||||
|
return WrapUint64(uint64(val.(uintptr)))
|
||||||
case reflect.Uint8:
|
case reflect.Uint8:
|
||||||
return WrapUint32(uint32(val.(uint8)))
|
return WrapUint32(uint32(val.(uint8)))
|
||||||
case reflect.Uint16:
|
case reflect.Uint16:
|
||||||
|
@ -157,6 +171,8 @@ func (iter *Iterator) readAny() Any {
|
||||||
return iter.readArrayAny()
|
return iter.readArrayAny()
|
||||||
case '-':
|
case '-':
|
||||||
return iter.readNumberAny(false)
|
return iter.readNumberAny(false)
|
||||||
|
case 0:
|
||||||
|
return &invalidAny{baseAny{}, errors.New("input is empty")}
|
||||||
default:
|
default:
|
||||||
return iter.readNumberAny(true)
|
return iter.readNumberAny(true)
|
||||||
}
|
}
|
||||||
|
@ -240,3 +256,66 @@ func locatePath(iter *Iterator, path []interface{}) Any {
|
||||||
}
|
}
|
||||||
return iter.readAny()
|
return iter.readAny()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var anyType = reflect2.TypeOfPtr((*Any)(nil)).Elem()
|
||||||
|
|
||||||
|
func createDecoderOfAny(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
if typ == anyType {
|
||||||
|
return &directAnyCodec{}
|
||||||
|
}
|
||||||
|
if typ.Implements(anyType) {
|
||||||
|
return &anyCodec{
|
||||||
|
valType: typ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEncoderOfAny(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
if typ == anyType {
|
||||||
|
return &directAnyCodec{}
|
||||||
|
}
|
||||||
|
if typ.Implements(anyType) {
|
||||||
|
return &anyCodec{
|
||||||
|
valType: typ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type anyCodec struct {
|
||||||
|
valType reflect2.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *anyCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *anyCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
obj := codec.valType.UnsafeIndirect(ptr)
|
||||||
|
any := obj.(Any)
|
||||||
|
any.WriteTo(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *anyCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
obj := codec.valType.UnsafeIndirect(ptr)
|
||||||
|
any := obj.(Any)
|
||||||
|
return any.Size() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type directAnyCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *directAnyCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
*(*Any)(ptr) = iter.readAny()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *directAnyCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
any := *(*Any)(ptr)
|
||||||
|
any.WriteTo(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *directAnyCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
any := *(*Any)(ptr)
|
||||||
|
return any.Size() == 0
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue