VMware vSphere Integrated Containers provider (#206)

* Add Virtual Kubelet provider for VIC

Initial virtual kubelet provider for VMware VIC.  This provider currently
handles creating and starting of a pod VM via the VIC portlayer and persona
server.  Image store handling via the VIC persona server.  This provider
currently requires the feature/wolfpack branch of VIC.

* Added pod stop and delete.  Also added node capacity.

Added the ability to stop and delete pod VMs via VIC.  Also retrieve
node capacity information from the VCH.

* Cleanup and readme file

Some file clean up and added a Readme.md markdown file for the VIC
provider.

* Cleaned up errors, added function comments, moved operation code

1. Cleaned up error handling.  Set standard for creating errors.
2. Added method prototype comments for all interface functions.
3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder.

* Add mocking code and unit tests for podcache, podcreator, and podstarter

Used the unit test framework used in VIC to handle assertions in the provider's
unit test.  Mocking code generated using OSS project mockery, which is compatible
with the testify assertion framework.

* Vendored packages for the VIC provider

Requires feature/wolfpack branch of VIC and a few specific commit sha of
projects used within VIC.

* Implementation of POD Stopper and Deleter unit tests (#4)

* Updated files for initial PR
This commit is contained in:
Loc Nguyen
2018-06-04 15:41:32 -07:00
committed by Ria Bhatia
parent 98a111e8b7
commit 513cebe7b7
6296 changed files with 1123685 additions and 8 deletions

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

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

View File

@@ -0,0 +1,117 @@
## Contribution Guidelines
### Pull requests are always welcome
We are always thrilled to receive pull requests, and do our best to
process them as fast as possible. Not sure if that typo is worth a pull
request? Do it! We will appreciate it.
If your pull request is not accepted on the first try, don't be
discouraged! If there's a problem with the implementation, hopefully you
received feedback on what to improve.
We're trying very hard to keep go-swagger lean and focused. We don't want it
to do everything for everybody. This means that we might decide against
incorporating a new feature. However, there might be a way to implement
that feature *on top of* go-swagger.
### Conventions
Fork the repo and make changes on your fork in a feature branch:
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
issue
- If it's a feature branch, create an enhancement issue to announce your
intentions, and name it XXX-something where XXX is the number of the issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. Run the full test suite on
your branch before submitting a pull request.
Update the documentation when creating or modifying features. Test
your documentation changes for clarity, concision, and correctness, as
well as a clean documentation build. See ``docs/README.md`` for more
information on building the docs and how docs get released.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plugins that do this automatically.
Pull requests descriptions should be as clear as possible and include a
reference to all the issues that they address.
Pull requests must not contain commits from other users or branches.
Commit messages must start with a capitalized and short summary (max. 50
chars) written in the imperative, followed by an optional, more detailed
explanatory text which is separated from the summary by an empty line.
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Be
sure to post a comment after pushing. The new commits will show up in the pull
request automatically, but the reviewers will not be notified unless you
comment.
Before the pull request is merged, make sure that you squash your commits into
logical units of work using `git rebase -i` and `git push -f`. After every
commit the test suite should be passing. Include documentation changes in the
same commit so that a revert would remove all traces of the feature or fix.
Commits that fix or close an issue should include a reference like `Closes #XXX`
or `Fixes #XXX`, which will automatically close the issue when merged.
### Sign your work
The sign-off is a simple line at the end of the explanation for the
patch, which certifies that you wrote it or otherwise have the right to
pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from
[developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe@gmail.com>
using your real name (sorry, no pseudonyms or anonymous contributions.)
You can add the sign off when creating the git commit via `git commit -s`.

5
vendor/github.com/go-openapi/runtime/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
secrets.yml
coverage.out
*.cov
*.out
playground

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

@@ -0,0 +1,24 @@
language: go
go:
- 1.7.1
install:
- go get -u github.com/axw/gocov/gocov
- go get -u gopkg.in/matm/v1/gocov-html
- go get -u github.com/cee-dub/go-junit-report
- go get -u github.com/stretchr/testify/assert
- go get -u golang.org/x/net/context
- go get -u gopkg.in/yaml.v2
- go get -u github.com/gorilla/context
- go get -u github.com/go-openapi/analysis
- go get -u github.com/go-openapi/errors
- go get -u github.com/go-openapi/loads
- go get -u github.com/go-openapi/strfmt
- go get -u github.com/go-openapi/validate
- go get -u github.com/docker/go-units
script:
- ./hack/coverage
after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
slack:
secure: EmObnQuM9Mw8J9vpFaKKHqSMN4Wsr/A9+v7ewAD5cEhA0T1P4m7MbJMiJOhxUhj/X+BFh2DamW+P2lT8mybj5wg8wnkQ2BteKA8Tawi6f9PRw2NRheO8tAi8o/npLnlmet0kc93mn+oLuqHw36w4+j5mkOl2FghkfGiUVhwrhkCP7KXQN+3TU87e+/HzQumlJ3nsE+6terVxkH3PmaUTsS5ONaODZfuxFpfb7RsoEl3skHf6d+tr+1nViLxxly7558Nc33C+W1mr0qiEvMLZ+kJ/CpGWBJ6CUJM3jm6hNe2eMuIPwEK2hxZob8c7n22VPap4K6a0bBRoydoDXaba+2sD7Ym6ivDO/DVyL44VeBBLyIiIBylDGQdZH+6SoWm90Qe/i7tnY/T5Ao5igT8f3cfQY1c3EsTfqmlDfrhmACBmwSlgkdVBLTprHL63JMY24LWmh4jhxsmMRZhCL4dze8su1w6pLN/pD1pGHtKYCEVbdTmaM3PblNRFf12XB7qosmQsgUndH4Vq3bTbU0s1pKjeDhRyLvFzvR0TBbo0pDLEoF1A/i5GVFWa7yLZNUDudQERRh7qv/xBl2excIaQ1sV4DSVm7bAE9l6Kp+yeHQJW2uN6Y3X8wu9gB9nv9l5HBze7wh8KE6PyWAOLYYqZg9/sAtsv/2GcQqXcKFF1zcA=

View File

@@ -0,0 +1,74 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

202
vendor/github.com/go-openapi/runtime/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,202 @@
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.

5
vendor/github.com/go-openapi/runtime/README.md generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# runtime [![Build Status](https://travis-ci.org/go-openapi/runtime.svg?branch=client-context)](https://travis-ci.org/go-openapi/runtime) [![codecov](https://codecov.io/gh/go-openapi/runtime/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/runtime) [![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/runtime/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/runtime?status.svg)](http://godoc.org/github.com/go-openapi/runtime)
The runtime component for use in codegeneration or as untyped usage.

33
vendor/github.com/go-openapi/runtime/authinfo_test.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
// 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 runtime
import (
"testing"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
)
func TestAuthInfoWriter(t *testing.T) {
hand := ClientAuthInfoWriterFunc(func(r ClientRequest, _ strfmt.Registry) error {
r.SetHeaderParam("authorization", "Bearer the-token-goes-here")
return nil
})
tr := new(trw)
hand.AuthenticateRequest(tr, nil)
assert.Equal(t, "Bearer the-token-goes-here", tr.Headers.Get("Authorization"))
}

99
vendor/github.com/go-openapi/runtime/bytestream.go generated vendored Normal file
View File

@@ -0,0 +1,99 @@
// 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 runtime
import (
"bytes"
"encoding"
"errors"
"fmt"
"io"
"reflect"
)
// ByteStreamConsumer creates a consmer for byte streams,
// takes a Writer/BinaryUnmarshaler interface or binary slice by reference,
// and reads from the provided reader
func ByteStreamConsumer() Consumer {
return ConsumerFunc(func(reader io.Reader, data interface{}) error {
if reader == nil {
return errors.New("ByteStreamConsumer requires a reader") // early exit
}
if wrtr, ok := data.(io.Writer); ok {
_, err := io.Copy(wrtr, reader)
return err
}
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(reader)
if err != nil {
return err
}
b := buf.Bytes()
if bu, ok := data.(encoding.BinaryUnmarshaler); ok {
return bu.UnmarshalBinary(b)
}
if t := reflect.TypeOf(data); data != nil && t.Kind() == reflect.Ptr {
v := reflect.Indirect(reflect.ValueOf(data))
if t = v.Type(); t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
v.SetBytes(b)
return nil
}
}
return fmt.Errorf("%v (%T) is not supported by the ByteStreamConsumer, %s",
data, data, "can be resolved by supporting Writer/BinaryUnmarshaler interface")
})
}
// ByteStreamProducer creates a producer for byte streams,
// takes a Reader/BinaryMarshaler interface or binary slice,
// and writes to a writer (essentially a pipe)
func ByteStreamProducer() Producer {
return ProducerFunc(func(writer io.Writer, data interface{}) error {
if writer == nil {
return errors.New("ByteStreamProducer requires a writer") // early exit
}
if rdr, ok := data.(io.Reader); ok {
_, err := io.Copy(writer, rdr)
return err
}
if bm, ok := data.(encoding.BinaryMarshaler); ok {
bytes, err := bm.MarshalBinary()
if err != nil {
return err
}
_, err = writer.Write(bytes)
return err
}
if data != nil {
v := reflect.Indirect(reflect.ValueOf(data))
if t := v.Type(); t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
_, err := writer.Write(v.Bytes())
return err
}
}
return fmt.Errorf("%v (%T) is not supported by the ByteStreamProducer, %s",
data, data, "can be resolved by supporting Reader/BinaryMarshaler interface")
})
}

114
vendor/github.com/go-openapi/runtime/bytestream_test.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
package runtime
import (
"bytes"
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestByteStreamConsumer(t *testing.T) {
cons := ByteStreamConsumer()
expected := "the data for the stream to be sent over the wire"
// can consume as a Writer
var b bytes.Buffer
if assert.NoError(t, cons.Consume(bytes.NewBufferString(expected), &b)) {
assert.Equal(t, expected, b.String())
}
// can consume as an UnmarshalBinary
var bu binaryUnmarshalDummy
if assert.NoError(t, cons.Consume(bytes.NewBufferString(expected), &bu)) {
assert.Equal(t, expected, bu.str)
}
// can consume as a binary slice
var bs []byte
if assert.NoError(t, cons.Consume(bytes.NewBufferString(expected), &bs)) {
assert.Equal(t, expected, string(bs))
}
type binarySlice []byte
var bs2 binarySlice
if assert.NoError(t, cons.Consume(bytes.NewBufferString(expected), &bs2)) {
assert.Equal(t, expected, string(bs2))
}
// passing in a nilslice wil result in an error
var ns *[]byte
assert.Error(t, cons.Consume(bytes.NewBufferString(expected), &ns))
// passing in nil wil result in an error as well
assert.Error(t, cons.Consume(bytes.NewBufferString(expected), nil))
// a reader who results in an error, will make it fail
assert.Error(t, cons.Consume(new(nopReader), &bu))
assert.Error(t, cons.Consume(new(nopReader), &bs))
// the readers can also not be nil
assert.Error(t, cons.Consume(nil, &bs))
}
type binaryUnmarshalDummy struct {
str string
}
func (b *binaryUnmarshalDummy) UnmarshalBinary(bytes []byte) error {
if len(bytes) == 0 {
return errors.New("no text given")
}
b.str = string(bytes)
return nil
}
func TestByteStreamProducer(t *testing.T) {
cons := ByteStreamProducer()
expected := "the data for the stream to be sent over the wire"
var rdr bytes.Buffer
// can produce using a reader
if assert.NoError(t, cons.Produce(&rdr, bytes.NewBufferString(expected))) {
assert.Equal(t, expected, rdr.String())
rdr.Reset()
}
// can produce using a binary marshaller
if assert.NoError(t, cons.Produce(&rdr, &binaryMarshalDummy{expected})) {
assert.Equal(t, expected, rdr.String())
rdr.Reset()
}
// binary slices can also be used to produce
if assert.NoError(t, cons.Produce(&rdr, []byte(expected))) {
assert.Equal(t, expected, rdr.String())
rdr.Reset()
}
type binarySlice []byte
if assert.NoError(t, cons.Produce(&rdr, binarySlice(expected))) {
assert.Equal(t, expected, rdr.String())
rdr.Reset()
}
// when binaryMarshal data is used, its potential error gets propagated
assert.Error(t, cons.Produce(&rdr, new(binaryMarshalDummy)))
// nil data should never be accepted either
assert.Error(t, cons.Produce(&rdr, nil))
// nil readers should also never be acccepted
assert.Error(t, cons.Produce(nil, bytes.NewBufferString(expected)))
}
type binaryMarshalDummy struct {
str string
}
func (b *binaryMarshalDummy) MarshalBinary() ([]byte, error) {
if len(b.str) == 0 {
return nil, errors.New("no text set")
}
return []byte(b.str), nil
}

View File

@@ -0,0 +1,64 @@
// 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 client
import (
"encoding/base64"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
)
// PassThroughAuth never manipulates the request
var PassThroughAuth runtime.ClientAuthInfoWriter
func init() {
PassThroughAuth = runtime.ClientAuthInfoWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error { return nil })
}
// BasicAuth provides a basic auth info writer
func BasicAuth(username, password string) runtime.ClientAuthInfoWriter {
return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error {
encoded := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
r.SetHeaderParam("Authorization", "Basic "+encoded)
return nil
})
}
// APIKeyAuth provides an API key auth info writer
func APIKeyAuth(name, in, value string) runtime.ClientAuthInfoWriter {
if in == "query" {
return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error {
r.SetQueryParam(name, value)
return nil
})
}
if in == "header" {
return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error {
r.SetHeaderParam(name, value)
return nil
})
}
return nil
}
// BearerToken provides a header based oauth2 bearer access token auth info writer
func BearerToken(token string) runtime.ClientAuthInfoWriter {
return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error {
r.SetHeaderParam("Authorization", "Bearer "+token)
return nil
})
}

View File

@@ -0,0 +1,65 @@
// 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 client
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBasicAuth(t *testing.T) {
r, _ := newRequest("GET", "/", nil)
writer := BasicAuth("someone", "with a password")
writer.AuthenticateRequest(r, nil)
req := new(http.Request)
req.Header = make(http.Header)
req.Header.Set("Authorization", r.header.Get("Authorization"))
usr, pw, ok := req.BasicAuth()
if assert.True(t, ok) {
assert.Equal(t, "someone", usr)
assert.Equal(t, "with a password", pw)
}
}
func TestAPIKeyAuth_Query(t *testing.T) {
r, _ := newRequest("GET", "/", nil)
writer := APIKeyAuth("api_key", "query", "the-shared-key")
writer.AuthenticateRequest(r, nil)
assert.Equal(t, "the-shared-key", r.query.Get("api_key"))
}
func TestAPIKeyAuth_Header(t *testing.T) {
r, _ := newRequest("GET", "/", nil)
writer := APIKeyAuth("x-api-token", "header", "the-shared-key")
writer.AuthenticateRequest(r, nil)
assert.Equal(t, "the-shared-key", r.header.Get("x-api-token"))
}
func TestBearerTokenAuth(t *testing.T) {
r, _ := newRequest("GET", "/", nil)
writer := BearerToken("the-shared-token")
writer.AuthenticateRequest(r, nil)
assert.Equal(t, "Bearer the-shared-token", r.header.Get("Authorization"))
}

272
vendor/github.com/go-openapi/runtime/client/request.go generated vendored Normal file
View File

@@ -0,0 +1,272 @@
// 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 client
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
)
// NewRequest creates a new swagger http client request
func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter) (*request, error) {
return &request{
pathPattern: pathPattern,
method: method,
writer: writer,
header: make(http.Header),
query: make(url.Values),
timeout: DefaultTimeout,
}, nil
}
// Request represents a swagger client request.
//
// This Request struct converts to a HTTP request.
// There might be others that convert to other transports.
// There is no error checking here, it is assumed to be used after a spec has been validated.
// so impossible combinations should not arise (hopefully).
//
// The main purpose of this struct is to hide the machinery of adding params to a transport request.
// The generated code only implements what is necessary to turn a param into a valid value for these methods.
type request struct {
pathPattern string
method string
writer runtime.ClientRequestWriter
pathParams map[string]string
header http.Header
query url.Values
formFields url.Values
fileFields map[string]runtime.NamedReadCloser
payload interface{}
timeout time.Duration
}
var (
// ensure interface compliance
_ runtime.ClientRequest = new(request)
)
// BuildHTTP creates a new http request based on the data from the params
func (r *request) BuildHTTP(mediaType string, producers map[string]runtime.Producer, registry strfmt.Registry) (*http.Request, error) {
// build the data
if err := r.writer.WriteToRequest(r, registry); err != nil {
return nil, err
}
// create http request
path := r.pathPattern
for k, v := range r.pathParams {
path = strings.Replace(path, "{"+k+"}", v, -1)
}
var body io.ReadCloser
var pr *io.PipeReader
var pw *io.PipeWriter
buf := bytes.NewBuffer(nil)
body = ioutil.NopCloser(buf)
if r.fileFields != nil {
pr, pw = io.Pipe()
body = pr
}
req, err := http.NewRequest(r.method, path, body)
if err != nil {
return nil, err
}
req.URL.RawQuery = r.query.Encode()
req.Header = r.header
// check if this is a form type request
if len(r.formFields) > 0 || len(r.fileFields) > 0 {
// check if this is multipart
if len(r.fileFields) > 0 {
mp := multipart.NewWriter(pw)
req.Header.Set(runtime.HeaderContentType, mp.FormDataContentType())
go func() {
defer func() {
mp.Close()
pw.Close()
}()
for fn, v := range r.formFields {
if len(v) > 0 {
if err := mp.WriteField(fn, v[0]); err != nil {
pw.CloseWithError(err)
log.Println(err)
}
}
}
for fn, f := range r.fileFields {
wrtr, err := mp.CreateFormFile(fn, filepath.Base(f.Name()))
if err != nil {
pw.CloseWithError(err)
log.Println(err)
}
defer func() {
for _, ff := range r.fileFields {
ff.Close()
}
}()
if _, err := io.Copy(wrtr, f); err != nil {
pw.CloseWithError(err)
log.Println(err)
}
}
}()
return req, nil
}
req.Header.Set(runtime.HeaderContentType, mediaType)
// write the form values as the body
buf.WriteString(r.formFields.Encode())
return req, nil
}
// if there is payload, use the producer to write the payload, and then
// set the header to the content-type appropriate for the payload produced
if r.payload != nil {
// TODO: infer most appropriate content type based on the producer used,
// and the `consumers` section of the spec/operation
req.Header.Set(runtime.HeaderContentType, mediaType)
if rdr, ok := r.payload.(io.ReadCloser); ok {
req.Body = rdr
return req, nil
}
if rdr, ok := r.payload.(io.Reader); ok {
req.Body = ioutil.NopCloser(rdr)
return req, nil
}
// set the content length of the request or else a chunked transfer is
// declared, and this corrupts outgoing JSON payloads. the content's
// length must be set prior to the body being written per the spec at
// https://golang.org/pkg/net/http
//
// If Body is present, Content-Length is <= 0 and TransferEncoding
// hasn't been set to "identity", Write adds
// "Transfer-Encoding: chunked" to the header. Body is closed
// after it is sent.
//
// to that end a temporary buffer, b, is created to produce the payload
// body, and then its size is used to set the request's content length
var b bytes.Buffer
producer := producers[mediaType]
if err := producer.Produce(&b, r.payload); err != nil {
return nil, err
}
req.ContentLength = int64(b.Len())
if _, err := buf.Write(b.Bytes()); err != nil {
return nil, err
}
}
return req, nil
}
// SetHeaderParam adds a header param to the request
// when there is only 1 value provided for the varargs, it will set it.
// when there are several values provided for the varargs it will add it (no overriding)
func (r *request) SetHeaderParam(name string, values ...string) error {
if r.header == nil {
r.header = make(http.Header)
}
r.header[http.CanonicalHeaderKey(name)] = values
return nil
}
// SetQueryParam adds a query param to the request
// when there is only 1 value provided for the varargs, it will set it.
// when there are several values provided for the varargs it will add it (no overriding)
func (r *request) SetQueryParam(name string, values ...string) error {
if r.query == nil {
r.query = make(url.Values)
}
r.query[name] = values
return nil
}
// SetFormParam adds a forn param to the request
// when there is only 1 value provided for the varargs, it will set it.
// when there are several values provided for the varargs it will add it (no overriding)
func (r *request) SetFormParam(name string, values ...string) error {
if r.formFields == nil {
r.formFields = make(url.Values)
}
r.formFields[name] = values
return nil
}
// SetPathParam adds a path param to the request
func (r *request) SetPathParam(name string, value string) error {
if r.pathParams == nil {
r.pathParams = make(map[string]string)
}
r.pathParams[name] = value
return nil
}
// SetFileParam adds a file param to the request
func (r *request) SetFileParam(name string, file runtime.NamedReadCloser) error {
if actualFile, ok := file.(*os.File); ok {
fi, err := os.Stat(actualFile.Name())
if err != nil {
return err
}
if fi.IsDir() {
return fmt.Errorf("%q is a directory, only files are supported", file.Name())
}
}
if r.fileFields == nil {
r.fileFields = make(map[string]runtime.NamedReadCloser)
}
if r.formFields == nil {
r.formFields = make(url.Values)
}
r.fileFields[name] = file
return nil
}
// SetBodyParam sets a body parameter on the request.
// This does not yet serialze the object, this happens as late as possible.
func (r *request) SetBodyParam(payload interface{}) error {
r.payload = payload
return nil
}
// SetTimeout sets the timeout for a request
func (r *request) SetTimeout(timeout time.Duration) error {
r.timeout = timeout
return nil
}

View File

@@ -0,0 +1,244 @@
// 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 client
import (
"encoding/json"
"encoding/xml"
"io/ioutil"
"mime"
"mime/multipart"
"os"
"path/filepath"
"testing"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
)
var testProducers = map[string]runtime.Producer{
runtime.JSONMime: runtime.JSONProducer(),
runtime.XMLMime: runtime.XMLProducer(),
runtime.TextMime: runtime.TextProducer(),
}
func TestBuildRequest_SetHeaders(t *testing.T) {
r, _ := newRequest("GET", "/flats/{id}/", nil)
// single value
r.SetHeaderParam("X-Rate-Limit", "500")
assert.Equal(t, "500", r.header.Get("X-Rate-Limit"))
r.SetHeaderParam("X-Rate-Limit", "400")
assert.Equal(t, "400", r.header.Get("X-Rate-Limit"))
// multi value
r.SetHeaderParam("X-Accepts", "json", "xml", "yaml")
assert.EqualValues(t, []string{"json", "xml", "yaml"}, r.header["X-Accepts"])
}
func TestBuildRequest_SetPath(t *testing.T) {
r, _ := newRequest("GET", "/flats/{id}/?hello=world", nil)
r.SetPathParam("id", "1345")
assert.Equal(t, "1345", r.pathParams["id"])
}
func TestBuildRequest_SetQuery(t *testing.T) {
r, _ := newRequest("GET", "/flats/{id}/", nil)
// single value
r.SetQueryParam("hello", "there")
assert.Equal(t, "there", r.query.Get("hello"))
// multi value
r.SetQueryParam("goodbye", "cruel", "world")
assert.Equal(t, []string{"cruel", "world"}, r.query["goodbye"])
}
func TestBuildRequest_SetForm(t *testing.T) {
// non-multipart
r, _ := newRequest("POST", "/flats", nil)
r.SetFormParam("hello", "world")
assert.Equal(t, "world", r.formFields.Get("hello"))
r.SetFormParam("goodbye", "cruel", "world")
assert.Equal(t, []string{"cruel", "world"}, r.formFields["goodbye"])
}
func TestBuildRequest_SetFile(t *testing.T) {
// needs to convert form to multipart
r, _ := newRequest("POST", "/flats/{id}/image", nil)
// error if it isn't there
err := r.SetFileParam("not there", os.NewFile(0, "./i-dont-exist"))
assert.Error(t, err)
// error if it isn't a file
err = r.SetFileParam("directory", os.NewFile(0, "../client"))
assert.Error(t, err)
// success adds it to the map
err = r.SetFileParam("file", mustGetFile("./runtime.go"))
if assert.NoError(t, err) {
fl, ok := r.fileFields["file"]
if assert.True(t, ok) {
assert.Equal(t, "runtime.go", filepath.Base(fl.Name()))
}
}
}
func mustGetFile(path string) *os.File {
f, err := os.Open(path)
if err != nil {
panic(err)
}
return f
}
func TestBuildRequest_SetBody(t *testing.T) {
r, _ := newRequest("GET", "/flats/{id}/?hello=world", nil)
bd := []struct{ Name, Hobby string }{{"Tom", "Organ trail"}, {"John", "Bird watching"}}
r.SetBodyParam(bd)
assert.Equal(t, bd, r.payload)
}
func TestBuildRequest_BuildHTTP_Payload(t *testing.T) {
bd := []struct{ Name, Hobby string }{{"Tom", "Organ trail"}, {"John", "Bird watching"}}
reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
req.SetBodyParam(bd)
req.SetQueryParam("hello", "world")
req.SetPathParam("id", "1234")
req.SetHeaderParam("X-Rate-Limit", "200")
return nil
})
r, _ := newRequest("GET", "/flats/{id}/", reqWrtr)
r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime)
req, err := r.BuildHTTP(runtime.JSONMime, testProducers, nil)
if assert.NoError(t, err) && assert.NotNil(t, req) {
assert.Equal(t, "200", req.Header.Get("x-rate-limit"))
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expectedBody, _ := json.Marshal(bd)
actualBody, _ := ioutil.ReadAll(req.Body)
assert.Equal(t, append(expectedBody, '\n'), actualBody)
}
}
func TestBuildRequest_BuildHTTP_XMLPayload(t *testing.T) {
bd := []struct {
XMLName xml.Name `xml:"person"`
Name string `xml:"name"`
Hobby string `xml:"hobby"`
}{{xml.Name{}, "Tom", "Organ trail"}, {xml.Name{}, "John", "Bird watching"}}
reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
req.SetBodyParam(bd)
req.SetQueryParam("hello", "world")
req.SetPathParam("id", "1234")
req.SetHeaderParam("X-Rate-Limit", "200")
return nil
})
r, _ := newRequest("GET", "/flats/{id}/", reqWrtr)
r.SetHeaderParam(runtime.HeaderContentType, runtime.XMLMime)
req, err := r.BuildHTTP(runtime.XMLMime, testProducers, nil)
if assert.NoError(t, err) && assert.NotNil(t, req) {
assert.Equal(t, "200", req.Header.Get("x-rate-limit"))
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expectedBody, _ := xml.Marshal(bd)
actualBody, _ := ioutil.ReadAll(req.Body)
assert.Equal(t, expectedBody, actualBody)
}
}
func TestBuildRequest_BuildHTTP_TextPayload(t *testing.T) {
bd := "Tom: Organ trail; John: Bird watching"
reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
req.SetBodyParam(bd)
req.SetQueryParam("hello", "world")
req.SetPathParam("id", "1234")
req.SetHeaderParam("X-Rate-Limit", "200")
return nil
})
r, _ := newRequest("GET", "/flats/{id}/", reqWrtr)
r.SetHeaderParam(runtime.HeaderContentType, runtime.TextMime)
req, err := r.BuildHTTP(runtime.TextMime, testProducers, nil)
if assert.NoError(t, err) && assert.NotNil(t, req) {
assert.Equal(t, "200", req.Header.Get("x-rate-limit"))
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expectedBody := []byte(bd)
actualBody, _ := ioutil.ReadAll(req.Body)
assert.Equal(t, expectedBody, actualBody)
}
}
func TestBuildRequest_BuildHTTP_Form(t *testing.T) {
reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
req.SetFormParam("something", "some value")
req.SetQueryParam("hello", "world")
req.SetPathParam("id", "1234")
req.SetHeaderParam("X-Rate-Limit", "200")
return nil
})
r, _ := newRequest("GET", "/flats/{id}/", reqWrtr)
r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime)
req, err := r.BuildHTTP(runtime.JSONMime, testProducers, nil)
if assert.NoError(t, err) && assert.NotNil(t, req) {
assert.Equal(t, "200", req.Header.Get("x-rate-limit"))
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expected := []byte("something=some+value")
actual, _ := ioutil.ReadAll(req.Body)
assert.Equal(t, expected, actual)
}
}
func TestBuildRequest_BuildHTTP_Files(t *testing.T) {
cont, _ := ioutil.ReadFile("./runtime.go")
reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
req.SetFormParam("something", "some value")
req.SetFileParam("file", mustGetFile("./runtime.go"))
req.SetQueryParam("hello", "world")
req.SetPathParam("id", "1234")
req.SetHeaderParam("X-Rate-Limit", "200")
return nil
})
r, _ := newRequest("GET", "/flats/{id}/", reqWrtr)
r.SetHeaderParam(runtime.HeaderContentType, runtime.JSONMime)
req, err := r.BuildHTTP(runtime.JSONMime, testProducers, nil)
if assert.NoError(t, err) && assert.NotNil(t, req) {
assert.Equal(t, "200", req.Header.Get("x-rate-limit"))
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
mediaType, params, err := mime.ParseMediaType(req.Header.Get(runtime.HeaderContentType))
if assert.NoError(t, err) {
assert.Equal(t, runtime.MultipartFormMime, mediaType)
boundary := params["boundary"]
mr := multipart.NewReader(req.Body, boundary)
defer req.Body.Close()
frm, err := mr.ReadForm(1 << 20)
if assert.NoError(t, err) {
assert.Equal(t, "some value", frm.Value["something"][0])
mpff := frm.File["file"][0]
mpf, _ := mpff.Open()
defer mpf.Close()
assert.Equal(t, "runtime.go", mpff.Filename)
actual, _ := ioutil.ReadAll(mpf)
assert.Equal(t, cont, actual)
}
}
}
}

View File

@@ -0,0 +1,44 @@
// 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 client
import (
"io"
"net/http"
"github.com/go-openapi/runtime"
)
var _ runtime.ClientResponse = response{}
type response struct {
resp *http.Response
}
func (r response) Code() int {
return r.resp.StatusCode
}
func (r response) Message() string {
return r.resp.Status
}
func (r response) GetHeader(name string) string {
return r.resp.Header.Get(name)
}
func (r response) Body() io.ReadCloser {
return r.resp.Body
}

View File

@@ -0,0 +1,40 @@
// 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 client
import (
"bytes"
"io/ioutil"
"net/http"
"testing"
"github.com/go-openapi/runtime"
"github.com/stretchr/testify/assert"
)
func TestResponse(t *testing.T) {
under := new(http.Response)
under.Status = "the status message"
under.StatusCode = 392
under.Header = make(http.Header)
under.Header.Set("Blah", "blah blah")
under.Body = ioutil.NopCloser(bytes.NewBufferString("some content"))
var resp runtime.ClientResponse = response{under}
assert.EqualValues(t, under.StatusCode, resp.Code())
assert.Equal(t, under.Status, resp.Message())
assert.Equal(t, "blah blah", resp.GetHeader("blah"))
assert.Equal(t, under.Body, resp.Body())
}

323
vendor/github.com/go-openapi/runtime/client/runtime.go generated vendored Normal file
View File

@@ -0,0 +1,323 @@
// 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 client
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"mime"
"net/http"
"net/http/httputil"
"os"
"path"
"strings"
"sync"
"time"
"golang.org/x/net/context"
"golang.org/x/net/context/ctxhttp"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
)
// TLSClientOptions to configure client authentication with mutual TLS
type TLSClientOptions struct {
Certificate string
Key string
CA string
ServerName string
InsecureSkipVerify bool
_ struct{}
}
// TLSClientAuth creates a tls.Config for mutual auth
func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
// load client cert
cert, err := tls.LoadX509KeyPair(opts.Certificate, opts.Key)
if err != nil {
return nil, fmt.Errorf("tls client cert: %v", err)
}
// create client tls config
cfg := &tls.Config{}
cfg.Certificates = []tls.Certificate{cert}
cfg.InsecureSkipVerify = opts.InsecureSkipVerify
// When no CA certificate is provided, default to the system cert pool
// that way when a request is made to a server known by the system trust store,
// the name is still verified
if opts.CA != "" {
// load ca cert
caCert, err := ioutil.ReadFile(opts.CA)
if err != nil {
return nil, fmt.Errorf("tls client ca: %v", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
cfg.RootCAs = caCertPool
}
// apply servername overrride
if opts.ServerName != "" {
cfg.InsecureSkipVerify = false
cfg.ServerName = opts.ServerName
}
cfg.BuildNameToCertificate()
return cfg, nil
}
// TLSTransport creates a http client transport suitable for mutual tls auth
func TLSTransport(opts TLSClientOptions) (http.RoundTripper, error) {
cfg, err := TLSClientAuth(opts)
if err != nil {
return nil, err
}
return &http.Transport{TLSClientConfig: cfg}, nil
}
// TLSClient creates a http.Client for mutual auth
func TLSClient(opts TLSClientOptions) (*http.Client, error) {
transport, err := TLSTransport(opts)
if err != nil {
return nil, err
}
return &http.Client{Transport: transport}, nil
}
// DefaultTimeout the default request timeout
var DefaultTimeout = 30 * time.Second
// Runtime represents an API client that uses the transport
// to make http requests based on a swagger specification.
type Runtime struct {
DefaultMediaType string
DefaultAuthentication runtime.ClientAuthInfoWriter
Consumers map[string]runtime.Consumer
Producers map[string]runtime.Producer
Transport http.RoundTripper
Jar http.CookieJar
//Spec *spec.Document
Host string
BasePath string
Formats strfmt.Registry
Debug bool
Context context.Context
clientOnce *sync.Once
client *http.Client
schemes []string
do func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error)
}
// New creates a new default runtime for a swagger api runtime.Client
func New(host, basePath string, schemes []string) *Runtime {
var rt Runtime
rt.DefaultMediaType = runtime.JSONMime
// TODO: actually infer this stuff from the spec
rt.Consumers = map[string]runtime.Consumer{
runtime.JSONMime: runtime.JSONConsumer(),
runtime.XMLMime: runtime.XMLConsumer(),
runtime.TextMime: runtime.TextConsumer(),
runtime.DefaultMime: runtime.ByteStreamConsumer(),
}
rt.Producers = map[string]runtime.Producer{
runtime.JSONMime: runtime.JSONProducer(),
runtime.XMLMime: runtime.XMLProducer(),
runtime.TextMime: runtime.TextProducer(),
runtime.DefaultMime: runtime.ByteStreamProducer(),
}
rt.Transport = http.DefaultTransport
rt.Jar = nil
rt.Host = host
rt.BasePath = basePath
rt.Context = context.Background()
rt.clientOnce = new(sync.Once)
if !strings.HasPrefix(rt.BasePath, "/") {
rt.BasePath = "/" + rt.BasePath
}
rt.Debug = len(os.Getenv("DEBUG")) > 0
if len(schemes) > 0 {
rt.schemes = schemes
}
rt.do = ctxhttp.Do
return &rt
}
// NewWithClient allows you to create a new transport with a configured http.Client
func NewWithClient(host, basePath string, schemes []string, client *http.Client) *Runtime {
rt := New(host, basePath, schemes)
if client != nil {
rt.clientOnce.Do(func() {
rt.client = client
})
}
return rt
}
func (r *Runtime) pickScheme(schemes []string) string {
if v := r.selectScheme(r.schemes); v != "" {
return v
}
if v := r.selectScheme(schemes); v != "" {
return v
}
return "http"
}
func (r *Runtime) selectScheme(schemes []string) string {
schLen := len(schemes)
if schLen == 0 {
return ""
}
scheme := schemes[0]
// prefer https, but skip when not possible
if scheme != "https" && schLen > 1 {
for _, sch := range schemes {
if sch == "https" {
scheme = sch
break
}
}
}
return scheme
}
// Submit a request and when there is a body on success it will turn that into the result
// all other things are turned into an api error for swagger which retains the status code
func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error) {
params, readResponse, auth := operation.Params, operation.Reader, operation.AuthInfo
request, err := newRequest(operation.Method, operation.PathPattern, params)
if err != nil {
return nil, err
}
var accept []string
for _, mimeType := range operation.ProducesMediaTypes {
accept = append(accept, mimeType)
}
request.SetHeaderParam(runtime.HeaderAccept, accept...)
if auth == nil && r.DefaultAuthentication != nil {
auth = r.DefaultAuthentication
}
if auth != nil {
if err := auth.AuthenticateRequest(request, r.Formats); err != nil {
return nil, err
}
}
// TODO: pick appropriate media type
cmt := r.DefaultMediaType
if len(operation.ConsumesMediaTypes) > 0 {
cmt = operation.ConsumesMediaTypes[0]
}
req, err := request.BuildHTTP(cmt, r.Producers, r.Formats)
if err != nil {
return nil, err
}
req.URL.Scheme = r.pickScheme(operation.Schemes)
req.URL.Host = r.Host
var reinstateSlash bool
if req.URL.Path != "" && req.URL.Path != "/" && req.URL.Path[len(req.URL.Path)-1] == '/' {
reinstateSlash = true
}
req.URL.Path = path.Join(r.BasePath, req.URL.Path)
if reinstateSlash {
req.URL.Path = req.URL.Path + "/"
}
r.clientOnce.Do(func() {
r.client = &http.Client{
Transport: r.Transport,
Jar: r.Jar,
}
})
if r.Debug {
b, err2 := httputil.DumpRequestOut(req, true)
if err2 != nil {
return nil, err2
}
fmt.Fprintln(os.Stderr, string(b))
}
var hasTimeout bool
pctx := operation.Context
if pctx == nil {
pctx = r.Context
} else {
hasTimeout = true
}
if pctx == nil {
pctx = context.Background()
}
var ctx context.Context
var cancel context.CancelFunc
if hasTimeout {
ctx, cancel = context.WithCancel(pctx)
} else {
ctx, cancel = context.WithTimeout(pctx, request.timeout)
}
defer cancel()
client := operation.Client
if client == nil {
client = r.client
}
if r.do == nil {
r.do = ctxhttp.Do
}
res, err := r.do(ctx, client, req) // make requests, by default follows 10 redirects before failing
if err != nil {
return nil, err
}
defer res.Body.Close()
if r.Debug {
b, err2 := httputil.DumpResponse(res, true)
if err2 != nil {
return nil, err2
}
fmt.Fprintln(os.Stderr, string(b))
}
ct := res.Header.Get(runtime.HeaderContentType)
if ct == "" { // this should really really never occur
ct = r.DefaultMediaType
}
mt, _, err := mime.ParseMediaType(ct)
if err != nil {
return nil, fmt.Errorf("parse content type: %s", err)
}
cons, ok := r.Consumers[mt]
if !ok {
// scream about not knowing what to do
return nil, fmt.Errorf("no consumer: %q", ct)
}
return readResponse.ReadResponse(response{res}, cons)
}

View File

@@ -0,0 +1,722 @@
// 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 client
import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
"golang.org/x/net/context"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
)
// task This describes a task. Tasks require a content property to be set.
type task struct {
// Completed
Completed bool `json:"completed" xml:"completed"`
// Content Task content can contain [GFM](https://help.github.com/articles/github-flavored-markdown/).
Content string `json:"content" xml:"content"`
// ID This id property is autogenerated when a task is created.
ID int64 `json:"id" xml:"id"`
}
func TestRuntime_TLSAuthConfig(t *testing.T) {
var opts TLSClientOptions
opts.CA = "../fixtures/certs/myCA.crt"
opts.Key = "../fixtures/certs/myclient.key"
opts.Certificate = "../fixtures/certs/myclient.crt"
opts.ServerName = "somewhere"
cfg, err := TLSClientAuth(opts)
if assert.NoError(t, err) {
if assert.NotNil(t, cfg) {
assert.Len(t, cfg.Certificates, 1)
assert.NotNil(t, cfg.RootCAs)
assert.Equal(t, "somewhere", cfg.ServerName)
}
}
}
func TestRuntime_Concurrent(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
resCC := make(chan interface{})
errCC := make(chan error)
var res interface{}
var err error
for j := 0; j < 6; j++ {
go func() {
resC := make(chan interface{})
errC := make(chan error)
go func() {
var resp interface{}
var errp error
for i := 0; i < 3; i++ {
resp, errp = rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "GET",
PathPattern: "/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
var result []task
if err := consumer.Consume(response.Body(), &result); err != nil {
return nil, err
}
return result, nil
}
return nil, errors.New("Generic error")
}),
})
<-time.After(100 * time.Millisecond)
}
resC <- resp
errC <- errp
}()
resCC <- <-resC
errCC <- <-errC
}()
}
c := 6
for c > 0 {
res = <-resCC
err = <-errCC
c--
}
if assert.NoError(t, err) {
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
}
func TestRuntime_Canary(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "GET",
PathPattern: "/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
var result []task
if err := consumer.Consume(response.Body(), &result); err != nil {
return nil, err
}
return result, nil
}
return nil, errors.New("Generic error")
}),
})
if assert.NoError(t, err) {
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
}
type tasks struct {
Tasks []task `xml:"task"`
}
func TestRuntime_XMLCanary(t *testing.T) {
// test that it can make a simple XML request
// and get the response for it.
result := tasks{
Tasks: []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.XMLMime)
rw.WriteHeader(http.StatusOK)
xmlgen := xml.NewEncoder(rw)
xmlgen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "GET",
PathPattern: "/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
var result tasks
if err := consumer.Consume(response.Body(), &result); err != nil {
return nil, err
}
return result, nil
}
return nil, errors.New("Generic error")
}),
})
if assert.NoError(t, err) {
assert.IsType(t, tasks{}, res)
actual := res.(tasks)
assert.EqualValues(t, result, actual)
}
}
func TestRuntime_TextCanary(t *testing.T) {
// test that it can make a simple text request
// and get the response for it.
result := "1: task 1 content; 2: task 2 content"
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.TextMime)
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(result))
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "GET",
PathPattern: "/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
var result string
if err := consumer.Consume(response.Body(), &result); err != nil {
return nil, err
}
return result, nil
}
return nil, errors.New("Generic error")
}),
})
if assert.NoError(t, err) {
assert.IsType(t, "", res)
actual := res.(string)
assert.EqualValues(t, result, actual)
}
}
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return fn(req)
}
func TestRuntime_CustomTransport(t *testing.T) {
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
rt := New("localhost:3245", "/", []string{"ws", "wss", "https"})
rt.Transport = roundTripperFunc(func(req *http.Request) (*http.Response, error) {
if req.URL.Scheme != "https" {
return nil, errors.New("this was not a https request")
}
var resp http.Response
resp.StatusCode = 200
resp.Header = make(http.Header)
resp.Header.Set("content-type", "application/json")
buf := bytes.NewBuffer(nil)
enc := json.NewEncoder(buf)
enc.Encode(result)
resp.Body = ioutil.NopCloser(buf)
return &resp, nil
})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "GET",
PathPattern: "/",
Schemes: []string{"ws", "wss", "https"},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
var result []task
if err := consumer.Consume(response.Body(), &result); err != nil {
return nil, err
}
return result, nil
}
return nil, errors.New("Generic error")
}),
})
if assert.NoError(t, err) {
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
}
func TestRuntime_CustomCookieJar(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
authenticated := false
for _, cookie := range req.Cookies() {
if cookie.Name == "sessionid" && cookie.Value == "abc" {
authenticated = true
}
}
if !authenticated {
username, password, ok := req.BasicAuth()
if ok && username == "username" && password == "password" {
authenticated = true
http.SetCookie(rw, &http.Cookie{Name: "sessionid", Value: "abc"})
}
}
if authenticated {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
jsongen.Encode([]task{})
} else {
rw.WriteHeader(http.StatusUnauthorized)
}
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
rt.Jar, _ = cookiejar.New(nil)
submit := func(authInfo runtime.ClientAuthInfoWriter) {
_, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "GET",
PathPattern: "/",
Params: rwrtr,
AuthInfo: authInfo,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
return nil, nil
}
return nil, errors.New("Generic error")
}),
})
assert.NoError(t, err)
}
submit(BasicAuth("username", "password"))
submit(nil)
}
func TestRuntime_AuthCanary(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("Authorization") != "Bearer the-super-secret-token" {
rw.WriteHeader(400)
return
}
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
var result []task
if err := consumer.Consume(response.Body(), &result); err != nil {
return nil, err
}
return result, nil
}
return nil, errors.New("Generic error")
}),
AuthInfo: BearerToken("the-super-secret-token"),
})
if assert.NoError(t, err) {
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
}
func TestRuntime_PickConsumer(t *testing.T) {
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("Content-Type") != "application/octet-stream" {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+";charset=utf-8")
rw.WriteHeader(400)
return
}
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+";charset=utf-8")
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
req.SetBodyParam(bytes.NewBufferString("hello"))
return nil
})
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "POST",
PathPattern: "/",
Schemes: []string{"http"},
ConsumesMediaTypes: []string{"application/octet-stream"},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
var result []task
if err := consumer.Consume(response.Body(), &result); err != nil {
return nil, err
}
return result, nil
}
return nil, errors.New("Generic error")
}),
AuthInfo: BearerToken("the-super-secret-token"),
})
if assert.NoError(t, err) {
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
}
func TestRuntime_ContentTypeCanary(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("Authorization") != "Bearer the-super-secret-token" {
rw.WriteHeader(400)
return
}
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+";charset=utf-8")
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
rt.do = nil
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "GET",
PathPattern: "/",
Schemes: []string{"http"},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
var result []task
if err := consumer.Consume(response.Body(), &result); err != nil {
return nil, err
}
return result, nil
}
return nil, errors.New("Generic error")
}),
AuthInfo: BearerToken("the-super-secret-token"),
})
if assert.NoError(t, err) {
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
}
func TestRuntime_ChunkedResponse(t *testing.T) {
// test that it can make a simple request
// and get the response for it.
// defaults all the way down
result := []task{
{false, "task 1 content", 1},
{false, "task 2 content", 2},
}
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("Authorization") != "Bearer the-super-secret-token" {
rw.WriteHeader(400)
return
}
rw.Header().Add(runtime.HeaderTransferEncoding, "chunked")
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+";charset=utf-8")
rw.WriteHeader(http.StatusOK)
jsongen := json.NewEncoder(rw)
jsongen.Encode(result)
}))
defer server.Close()
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
//specDoc, err := spec.Load("../../fixtures/codegen/todolist.simple.yml")
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
res, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "GET",
PathPattern: "/",
Schemes: []string{"http"},
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if response.Code() == 200 {
var result []task
if err := consumer.Consume(response.Body(), &result); err != nil {
return nil, err
}
return result, nil
}
return nil, errors.New("Generic error")
}),
AuthInfo: BearerToken("the-super-secret-token"),
})
if assert.NoError(t, err) {
assert.IsType(t, []task{}, res)
actual := res.([]task)
assert.EqualValues(t, result, actual)
}
}
func TestRuntime_DebugValue(t *testing.T) {
original := os.Getenv("DEBUG")
// Emtpy DEBUG means Debug is False
os.Setenv("DEBUG", "")
runtime := New("", "/", []string{"https"})
assert.False(t, runtime.Debug)
// Non-Empty Debug means Debug is True
os.Setenv("DEBUG", "1")
runtime = New("", "/", []string{"https"})
assert.True(t, runtime.Debug)
os.Setenv("DEBUG", "true")
runtime = New("", "/", []string{"https"})
assert.True(t, runtime.Debug)
os.Setenv("DEBUG", "foo")
runtime = New("", "/", []string{"https"})
assert.True(t, runtime.Debug)
// Make sure DEBUG is initial value once again
os.Setenv("DEBUG", original)
}
func TestRuntime_OverrideScheme(t *testing.T) {
runtime := New("", "/", []string{"https"})
sch := runtime.pickScheme([]string{"http"})
assert.Equal(t, "https", sch)
}
func TestRuntime_OverrideClient(t *testing.T) {
client := &http.Client{}
runtime := NewWithClient("", "/", []string{"https"}, client)
var i int
runtime.clientOnce.Do(func() { i++ })
assert.Equal(t, client, runtime.client)
assert.Equal(t, 0, i)
}
func TestRuntime_OverrideClientOperation(t *testing.T) {
client := &http.Client{}
rt := NewWithClient("", "/", []string{"https"}, client)
var i int
rt.clientOnce.Do(func() { i++ })
assert.Equal(t, client, rt.client)
assert.Equal(t, 0, i)
var seen *http.Client
rt.do = func(_ context.Context, cl *http.Client, _ *http.Request) (*http.Response, error) {
seen = cl
res := new(http.Response)
res.StatusCode = 200
res.Body = ioutil.NopCloser(bytes.NewBufferString("OK"))
return res, nil
}
client2 := new(http.Client)
client2.Timeout = 3 * time.Second
if assert.NotEqual(t, client, client2) {
_, err := rt.Submit(&runtime.ClientOperation{
Client: client2,
Params: runtime.ClientRequestWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error {
return nil
}),
Reader: runtime.ClientResponseReaderFunc(func(_ runtime.ClientResponse, _ runtime.Consumer) (interface{}, error) {
return nil, nil
}),
})
if assert.NoError(t, err) {
assert.Equal(t, client2, seen)
}
}
}
func TestRuntime_PreserveTrailingSlash(t *testing.T) {
var redirected bool
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime+";charset=utf-8")
if req.URL.Path == "/api/tasks" {
redirected = true
return
}
if req.URL.Path == "/api/tasks/" {
rw.WriteHeader(http.StatusOK)
}
}))
defer server.Close()
hu, _ := url.Parse(server.URL)
rt := New(hu.Host, "/", []string{"http"})
rwrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, _ strfmt.Registry) error {
return nil
})
_, err := rt.Submit(&runtime.ClientOperation{
ID: "getTasks",
Method: "GET",
PathPattern: "/api/tasks/",
Params: rwrtr,
Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
if redirected {
return nil, errors.New("expected Submit to preserve trailing slashes - this caused a redirect")
}
if response.Code() == http.StatusOK {
return nil, nil
}
return nil, errors.New("Generic error")
}),
})
assert.NoError(t, err)
}

View File

@@ -0,0 +1,30 @@
// 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 runtime
import "github.com/go-openapi/strfmt"
// A ClientAuthInfoWriterFunc converts a function to a request writer interface
type ClientAuthInfoWriterFunc func(ClientRequest, strfmt.Registry) error
// AuthenticateRequest adds authentication data to the request
func (fn ClientAuthInfoWriterFunc) AuthenticateRequest(req ClientRequest, reg strfmt.Registry) error {
return fn(req, reg)
}
// A ClientAuthInfoWriter implementor knows how to write authentication info to a request
type ClientAuthInfoWriter interface {
AuthenticateRequest(ClientRequest, strfmt.Registry) error
}

View File

@@ -0,0 +1,42 @@
// 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 runtime
import (
"net/http"
"golang.org/x/net/context"
)
// ClientOperation represents the context for a swagger operation to be submitted to the transport
type ClientOperation struct {
ID string
Method string
PathPattern string
ProducesMediaTypes []string
ConsumesMediaTypes []string
Schemes []string
AuthInfo ClientAuthInfoWriter
Params ClientRequestWriter
Reader ClientResponseReader
Context context.Context
Client *http.Client
}
// A ClientTransport implementor knows how to submit Request objects to some destination
type ClientTransport interface {
//Submit(string, RequestWriter, ResponseReader, AuthInfoWriter) (interface{}, error)
Submit(*ClientOperation) (interface{}, error)
}

59
vendor/github.com/go-openapi/runtime/client_request.go generated vendored Normal file
View File

@@ -0,0 +1,59 @@
// 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 runtime
import (
"io"
"time"
"github.com/go-openapi/strfmt"
)
// ClientRequestWriterFunc converts a function to a request writer interface
type ClientRequestWriterFunc func(ClientRequest, strfmt.Registry) error
// WriteToRequest adds data to the request
func (fn ClientRequestWriterFunc) WriteToRequest(req ClientRequest, reg strfmt.Registry) error {
return fn(req, reg)
}
// ClientRequestWriter is an interface for things that know how to write to a request
type ClientRequestWriter interface {
WriteToRequest(ClientRequest, strfmt.Registry) error
}
// ClientRequest is an interface for things that know how to
// add information to a swagger client request
type ClientRequest interface {
SetHeaderParam(string, ...string) error
SetQueryParam(string, ...string) error
SetFormParam(string, ...string) error
SetPathParam(string, string) error
SetFileParam(string, NamedReadCloser) error
SetBodyParam(interface{}) error
SetTimeout(time.Duration) error
}
// NamedReadCloser represents a named ReadCloser interface
type NamedReadCloser interface {
io.ReadCloser
Name() string
}

View File

@@ -0,0 +1,68 @@
// 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 runtime
import (
"net/http"
"testing"
"time"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
)
type trw struct {
Headers http.Header
Body interface{}
}
func (t *trw) SetHeaderParam(name string, values ...string) error {
if t.Headers == nil {
t.Headers = make(http.Header)
}
t.Headers.Set(name, values[0])
return nil
}
func (t *trw) SetQueryParam(_ string, _ ...string) error { return nil }
func (t *trw) SetFormParam(_ string, _ ...string) error { return nil }
func (t *trw) SetPathParam(_ string, _ string) error { return nil }
func (t *trw) SetFileParam(_ string, _ NamedReadCloser) error { return nil }
func (t *trw) SetBodyParam(body interface{}) error {
t.Body = body
return nil
}
func (t *trw) SetTimeout(timeout time.Duration) error {
return nil
}
func TestRequestWriterFunc(t *testing.T) {
hand := ClientRequestWriterFunc(func(r ClientRequest, reg strfmt.Registry) error {
r.SetHeaderParam("blah", "blah blah")
r.SetBodyParam(struct{ Name string }{"Adriana"})
return nil
})
tr := new(trw)
hand.WriteToRequest(tr, nil)
assert.Equal(t, "blah blah", tr.Headers.Get("blah"))
assert.Equal(t, "Adriana", tr.Body.(struct{ Name string }).Name)
}

View File

@@ -0,0 +1,63 @@
// 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 runtime
import (
"fmt"
"io"
)
// A ClientResponse represents a client response
// This bridges between responses obtained from different transports
type ClientResponse interface {
Code() int
Message() string
GetHeader(string) string
Body() io.ReadCloser
}
// A ClientResponseReaderFunc turns a function into a ClientResponseReader interface implementation
type ClientResponseReaderFunc func(ClientResponse, Consumer) (interface{}, error)
// ReadResponse reads the response
func (read ClientResponseReaderFunc) ReadResponse(resp ClientResponse, consumer Consumer) (interface{}, error) {
return read(resp, consumer)
}
// A ClientResponseReader is an interface for things want to read a response.
// An application of this is to create structs from response values
type ClientResponseReader interface {
ReadResponse(ClientResponse, Consumer) (interface{}, error)
}
// NewAPIError creates a new API error
func NewAPIError(opName string, payload interface{}, code int) *APIError {
return &APIError{
OperationName: opName,
Response: payload,
Code: code,
}
}
// APIError wraps an error model and captures the status code
type APIError struct {
OperationName string
Response interface{}
Code int
}
func (a *APIError) Error() string {
return fmt.Sprintf("%s (status %d): %+v ", a.OperationName, a.Code, a.Response)
}

View File

@@ -0,0 +1,60 @@
// 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 runtime
import (
"bytes"
"io"
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
)
type response struct {
}
func (r response) Code() int {
return 490
}
func (r response) Message() string {
return "the message"
}
func (r response) GetHeader(_ string) string {
return "the header"
}
func (r response) Body() io.ReadCloser {
return ioutil.NopCloser(bytes.NewBufferString("the content"))
}
func TestResponseReaderFunc(t *testing.T) {
var actual struct {
Header, Message, Body string
Code int
}
reader := ClientResponseReaderFunc(func(r ClientResponse, _ Consumer) (interface{}, error) {
b, _ := ioutil.ReadAll(r.Body())
actual.Body = string(b)
actual.Code = r.Code()
actual.Message = r.Message()
actual.Header = r.GetHeader("blah")
return actual, nil
})
reader.ReadResponse(response{}, nil)
assert.Equal(t, "the content", actual.Body)
assert.Equal(t, "the message", actual.Message)
assert.Equal(t, "the header", actual.Header)
assert.Equal(t, 490, actual.Code)
}

41
vendor/github.com/go-openapi/runtime/constants.go generated vendored Normal file
View File

@@ -0,0 +1,41 @@
// 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 runtime
const (
// HeaderContentType represents a http content-type header, it's value is supposed to be a mime type
HeaderContentType = "Content-Type"
// HeaderTransferEncoding represents a http transfer-encoding header.
HeaderTransferEncoding = "Transfer-Encoding"
// HeaderAccept the Accept header
HeaderAccept = "Accept"
charsetKey = "charset"
// DefaultMime the default fallback mime type
DefaultMime = "application/octet-stream"
// JSONMime the json mime type
JSONMime = "application/json"
// YAMLMime the yaml mime type
YAMLMime = "application/x-yaml"
// XMLMime the xml mime type
XMLMime = "application/xml"
// TextMime the text mime type
TextMime = "text/plain"
// MultipartFormMime the multipart form mime type
MultipartFormMime = "multipart/form-data"
)

9
vendor/github.com/go-openapi/runtime/discard.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
package runtime
import "io"
// DiscardConsumer does absolutely nothing, it's a black hole.
var DiscardConsumer = ConsumerFunc(func(_ io.Reader, _ interface{}) error { return nil })
// DiscardProducer does absolutely nothing, it's a black hole.
var DiscardProducer = ProducerFunc(func(_ io.Writer, _ interface{}) error { return nil })

View File

@@ -0,0 +1,21 @@
swagger: '2.0'
info:
version: 1.0.0
title: 'Test'
schemes:
- http
produces:
- application/json
consumes:
- application/json
paths:
/key/{id}:
delete:
parameters:
- name: id
in: path
type: integer
required: true
responses:
'200':
description: OK

View File

@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE/TCCAuWgAwIBAgIJAJ0kpLFo4pEzMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV
BAMMCkdvIFN3YWdnZXIwHhcNMTYxMjIxMDgzMzM4WhcNMTgxMjIxMDgzMzM4WjAV
MRMwEQYDVQQDDApHbyBTd2FnZ2VyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAzQ5NC1JBNNP79HPiUBAO59LoUMGbmSU9K9v+cQMuyyOuv0nwuiXc5anU
J1BINqgLR1VJjwTnQsXSlsr2SPs/144KgTsgk/QpMXdlFQwfqLJBIFlsQQBbMx6L
/2Ho6KE7z/qz6cqgKvYrGDu6ELUu016MbUsPWfhPBJE7Ftoajk5AIomDPmiTi0cZ
wdhC8SB0aVVQ2IWrsusfgPeOQ+ZLa/WHmpJ2Syfq41i/VKllEeCrMwtMP2By2kA/
ufBLCnhr7yZ0u22O1Bl1+0XedWli2GiXyt1h9nQ5blTTKZi5grOzAgCcshb/bw1H
1hdJKMqkzbqt2Mxc/78PJbDgicJU1ap+fhfBmUviWIMML6eum2ObuKd4ihhXKfqp
T/nSUA0P9565W71SLAHFLdZX/VSMZnoehkwIicVGgEzjlYj2j9qBc0CjYzbEtQXH
TRGhbjMX5LSByeE6hwLM6hIbQL4nriRobar63rbOc74Tm1ed02R6BvQjgXgOGqAN
BgCKKjfUIm0Qm2qV4WkwGIAOi+hdUpbNJ0X2dU/B00qLhar+h4NT9TW4PmKf4agk
NZ6O3C1saGxjtuPnIdDxWTdRhPSUyjsllmWhrmkY2bsRB8Z47zqrdfyajXlPOmBM
1f0am4Zeo3ditBTfFqtA2LLQbn1yZwYJQ8+sESu6bsm3S89DFT0CAwEAAaNQME4w
HQYDVR0OBBYEFN4BShcjqDbbgaGvPiGMNrUEi/RZMB8GA1UdIwQYMBaAFN4BShcj
qDbbgaGvPiGMNrUEi/RZMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB
AIqZYn+PFMcOwraYtXtAzJwU4gElWDjIA+xVMMzulHde8t7EWo+I5iDdtn+sHzhG
B1oYFhKv/HIX9GR3XDYWluoUVA4Ed9eBamL0Qqzq4Era6ZN1VQd0egvyOT40UQ7m
2aNw1nLgWadCmsxeVMKQRdzct9G3dOfJp7K5WybINWTibNMTuoSuU5RwtzriK000
C9pnxCD8boSNY/flOX0M5Mt3kv2JaIH2UsMKNGBf5+rXcKfhTE6JgiXorUEEztHP
PFpZ6VFKDlr8QC/4aLYhOJ9LIloaxZyk/rccCuHbdPPX5XGA3Z9i/lxSoqtShYlS
mt5vmdRwQob/ul6hPch3YRqD4VgeM1O80FEsWBK2WmGGH3wKNKea7u6dZyfQv3t3
fUVmByAVMllVRA1YiKmBZ/kOeAMku5hpR9kzErCXZd/xrKWVym000RsvRb6apltM
sYnlCyKfIdKxUXavO0Bf4+YoaN4/p3mZchxpLBwrzhPyUpGQ9b3TuGjoEmtG57yn
6I3U40/TouJR0aF7i1bAF5QJWYOS7OycJbHAIZiQx9ENDP3ZMfYNWQO6STFJAjvC
C0u23DyiJWZqE4Uw51O7jWKh7bSEKWutwa0XKWrpxhUjHFX4qGigIvXpO9LMjR60
YDhdCEmUiu/Hc0tt0QzyTA6w47TP0gXREeBLabzuEDPi
-----END CERTIFICATE-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKgIBAAKCAgEAzQ5NC1JBNNP79HPiUBAO59LoUMGbmSU9K9v+cQMuyyOuv0nw
uiXc5anUJ1BINqgLR1VJjwTnQsXSlsr2SPs/144KgTsgk/QpMXdlFQwfqLJBIFls
QQBbMx6L/2Ho6KE7z/qz6cqgKvYrGDu6ELUu016MbUsPWfhPBJE7Ftoajk5AIomD
PmiTi0cZwdhC8SB0aVVQ2IWrsusfgPeOQ+ZLa/WHmpJ2Syfq41i/VKllEeCrMwtM
P2By2kA/ufBLCnhr7yZ0u22O1Bl1+0XedWli2GiXyt1h9nQ5blTTKZi5grOzAgCc
shb/bw1H1hdJKMqkzbqt2Mxc/78PJbDgicJU1ap+fhfBmUviWIMML6eum2ObuKd4
ihhXKfqpT/nSUA0P9565W71SLAHFLdZX/VSMZnoehkwIicVGgEzjlYj2j9qBc0Cj
YzbEtQXHTRGhbjMX5LSByeE6hwLM6hIbQL4nriRobar63rbOc74Tm1ed02R6BvQj
gXgOGqANBgCKKjfUIm0Qm2qV4WkwGIAOi+hdUpbNJ0X2dU/B00qLhar+h4NT9TW4
PmKf4agkNZ6O3C1saGxjtuPnIdDxWTdRhPSUyjsllmWhrmkY2bsRB8Z47zqrdfya
jXlPOmBM1f0am4Zeo3ditBTfFqtA2LLQbn1yZwYJQ8+sESu6bsm3S89DFT0CAwEA
AQKCAgAjBkBOoLwWg+bTOD/9oOCK5FFeCdPD8sJiDW+Gah7B/9RHRB/kC7eRWtKr
7GCJRWa3xm+MCDBgDV4M95ndmVysOsy8ihbkbp3inxwa3jlCHUBWgC+nYqIxNxR+
iIC5y2BmA9JbKor1C5sMxpbfZ7MZ01p1CI8UtP76LrxDCPnkOKVnwMk0DbS1420Y
2RGGEh8QJsxqT1qmctastpwMKPfU9tk0o7Ok3qqWLoBvu4dR6GgVjeZ2JMk5UiQQ
ZGTM4wi8jnr90JbGz5qBUsvOjjOd9y+GLQ4ghHWSzNZMkpONKZh3zRb2rErw8vnE
LbIHT6Wapjovf6ia3k1+CJoxrYnDrsOHcWopm2kle7FXjgfHRXubcNU2aLdIAcRg
ZGGyalex3/NXKjhGf8jhaXKkOYDL37ZFtEmaUJVjjhiIE5jGByBHU0pqKk9Tdtv0
s5r5m0T8Gk8h70+fZ/C+wkYE4h8uzqAlq/yrxBSlGMHEVG9PI9tr9bM1FLM/H92q
CqoVR6YWTC7o5Kasr33RKYJg5vPHfFoIGHX9etbfHPGQsbCLaWhTLIYus+0b4ZS1
D1jHCoxHCjKzf2PFwogtRsmhyQSS3A3GyEWy7BZgFvgKFpq9hRC66k8Z7pnnkKrW
i4YihK17ivI5uG67Aqlc+kdahRNVWOOaPbwjGosmlULyfCOdGQKCAQEA79dD3caa
zXqFQkYDZwZcthXw9Hn+ChTCcfucJz9I0DUeL8FpUQYCkFZRUoEgF/eNrKSG+ctn
VDgrj0IZAcumpfjc4ZMug8svFYkE+vsUVgSI03KPRJ5NZ+Egn+HExURzCSQY6fK8
mCp05+gXndiUhoco2H851esmMtCSd/5IyR3d3C64ZfFGSk/Nx66A4Z643ffB6tOH
KYWFgVoQtSb92pgyxuBzZ1JhxuBVihRzAQtuE+uZ14xPoVv52fUlYXUhGmdqtZ3l
Cio3YGZTaUqtF0BP8HshzAWQ2k2vCJUxY99dbFfsE+v8vCojgMz8KmzO7C+j3Pa1
hq77rT29WFvaHwKCAQEA2t8R3QCkcaCRDMAPomqPJjUHrX2gdPM2zFFCvSqiVkh6
8ft9NF8sO1aXq600IxTiTf/L8ZvM0HlPlYQSjFzGWsOgNww9MKF7L4NnJ7e/8mwP
jqfajNcqecHIXvNi0AqXOpN/hEhm5MWKce/BPV6GpnRnb5doy8wOG0ESsmUA/5TJ
y/65LVxDKT9SdymDVayRwq2vNn9qW2BBcM9yan5GstkE3zzkrzKcCgz5X09/vO3R
K3fYk0FReE9CY9XAQGtz36Ra19efETzvWPi18zsP96QMUYIS2+Y45sVPhGZbY2aG
HQXTg8xIJN51E+jmWpJ1vv27izFh5TXeloRD4qldIwKCAQEAqkG6+KVy4OjXzlsb
MTiP+eaLfVFYaFmiSv3dNPM0wjDi8+2t0Imeqk3MPvBRExJ17ReChbLB8ERLj8/R
Jrgl3e5TBoLP41kKXJQ/B9fS8NkZNFk/oOtrcZGb8kN3xr23l8abNQBOpwqEoNfe
Y/wKO5GZCk8OhHAAVtQ/FZVaoAJmq1YzKpLjXf9WyihzbzaYb2Hgs81jRrN1OYTx
FVfPnyyp5woQgkk2BdLchj/L//LYOqXmOOBu6tH7BKGE3rEiRbciRjkHDXc4hmM9
VSJgy3+o/8K5FDbjREUfOs2GGSrIDBBCE0ZTzFNxjo51d7x0C7Ap98Ley/RNzwZj
8mSJ6wKCAQEA0NXvcXPfdBvEyumnAU2zcL1AqiUoKO635pPSnjRD2RgnVySi/omg
5q1k4oXNLXwLwmjD67DA6FoXuY3fNNaA3LGz+VJQQEqUA23Zy2fkWicJYRB/08qp
2KsxyIdqTR8N1PJPxaRfqQFja/tb4naC++gtmahacbot64tXj6gYH8WUFnThs4pI
+t5UjSarDeAu5BZdDB7fGHjrd/w4K6x5QMUZhPfRK+maQWzHtE1ikJ5J6rPbjgXQ
+n6F1kRpwA3G7ikgFLrEJ+qAZeBJm99LCPsaVdtKq08sE+VITghsQpfcd2zLuQH+
BE/OXkTnJpyAhNANVm6z/cQ8sllZfLglCQKCAQEAkZTQ0xnUeJTV0CqljMNCIN4M
i6Xyqf5uUDPfooY+fILbemT/acYUAkbjamgJlQMQ7yui9WV7Q/9HiCHaVh3o5zrV
zaq3vocA9x9nK998/He7772/uJarINvLFj/p/vTQM4Lni+bJ93bk6XE+FQKPgY9B
GfeFFaVtH7gimB4CjrxYprhAfqyxyE/m6JVMRg1olIFuav37GYP+TJ2K85klQRNa
TEXbm6ZJpSHfNjKZzUczziaIbwnMN9OxJY6M3a1JuEy2h+og5oRdMOoB6RETzhle
mxT5uEtA6mR6KyBZBjWhcl/V/Rw1DVMmtVbHCdc0+Xn/CMemRLCw1bxRUu/iww==
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEqzCCApMCCQChJZEdSdrQkjANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApH
byBTd2FnZ2VyMB4XDTE2MTIyMTA4MzMzOFoXDTE3MTIyMTA4MzMzOFowGjEYMBYG
A1UEAwwPZ29zd2FnZ2VyLmxvY2FsMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAs1MHhleossLBkYKYwOT+82RT651CfCOilpEUhm92KGRSMQXZEk+2TUgc
dGPeQNDNmbpXGzdk1HZkqWR5XKfSjWWxfmBlbBoYnkL3neoiXBdBVsgHkEPdP5ly
uJRkohy6az1vnq2vLaI+YujStutf8hSdcPu9VeALbrR027dMbY2XMC97FteeVaw1
mXmW9UHDVSV9UPBPswUOQWhjIADBk5IYaYASCY3M4X5BPCWFu1oQhgVMEhodBoBj
pHhHrfoDm1TwtT+dp53TmR10zpUiN+FcaVMsjqN4DWX4ma0uhu+zJew2XjCJkNfX
wVqGFpe2Hx+lupOGs/kwBvQ4PYn5ydgcm5DTggBC45JxCAVi3tQCYGsg2xkX9yPj
aXYc8E4/aeQI9UZxUeR2siBn8ECX4gJTmPJbQ4Xykqn6YOHyxIVoqd+9wo9Z1weH
xCWtPGESg7l7Jn/6WQ5V8z6RzrquGi67asrpYpv2lxNXMQA0f3S8sWYe4f8QVazy
ALtu8+0XE17UPjlbNBqEfCIrMsYmL5VyMVbL0dlXXBxHjzfpXraNGoSD4v6LxRxP
dWQgrhEZ6DmfiWfX8uhLdMwlvUxNXj33UDtM8dtN6mHERA9wF2RQQzPddZ0MYmUF
DI92i9mRC7Yzx6mcv/yUnFw213Jnzg297lW0Xp0ifawyPi2V8f8CAwEAATANBgkq
hkiG9w0BAQsFAAOCAgEAme1gyNQry3E5bj4XfdL4aNvZamzLaQVRlNZSHUzDhhpH
6N/DK/CAw4g4Msty7g3KBZPmldJhxH0bnSoRGMjFdKn9tVQeJOjaHQ2Z3cQWwdte
iXtu2F38SVfP5HCh9ASQ9vQXahGOruUPUUNUnDLfOBea7vrT3DmVugXlMSmaYuSJ
JdrbPzD48yy60AEDlCVpY2m1cEc5SmTkXbrAg2jhQd6ytaPQ28vGQnpZHSS/xWjC
Hh68o5SUoGoFErZxPd0o2brHavi4YybYt7CXlWG2TJ89s3BCSPIHclNF2HjxRq/r
2Q/Ttzo3cRBxi3RBnrLdn4qNgJjZnWaLobjaWcs1fbI32allogLsiurCwZb0ToC0
fNMzyHVNWY8BqsuyWyF2H0F9rklmqGFJSmrqt8kDLx0xpkZchGPIDSRh+f+PPDmE
jGPPH2qxz4un0foJx99dtw18TPaplFo2LxRK89koTiQNyzAHwSn6PHGlyXhNPsUt
K5GzjAu6B4uyldcg2m+4O/dbNdeqSczYAFenfEO7PRAy3AP7Lxs2xqQaNiA10965
vYmCNIOuV24CuFEIrjOQkZeFCw+odsgFs5Nv8JfDdA+BRr+Haq8FVX8afEc0BEnr
xY6f2fvgYTMvx0Z3UVT/XJ3POWHRL0HFLj5avHE0eOOkrcPbX6UsANd1v0F2BH8=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAs1MHhleossLBkYKYwOT+82RT651CfCOilpEUhm92KGRSMQXZ
Ek+2TUgcdGPeQNDNmbpXGzdk1HZkqWR5XKfSjWWxfmBlbBoYnkL3neoiXBdBVsgH
kEPdP5lyuJRkohy6az1vnq2vLaI+YujStutf8hSdcPu9VeALbrR027dMbY2XMC97
FteeVaw1mXmW9UHDVSV9UPBPswUOQWhjIADBk5IYaYASCY3M4X5BPCWFu1oQhgVM
EhodBoBjpHhHrfoDm1TwtT+dp53TmR10zpUiN+FcaVMsjqN4DWX4ma0uhu+zJew2
XjCJkNfXwVqGFpe2Hx+lupOGs/kwBvQ4PYn5ydgcm5DTggBC45JxCAVi3tQCYGsg
2xkX9yPjaXYc8E4/aeQI9UZxUeR2siBn8ECX4gJTmPJbQ4Xykqn6YOHyxIVoqd+9
wo9Z1weHxCWtPGESg7l7Jn/6WQ5V8z6RzrquGi67asrpYpv2lxNXMQA0f3S8sWYe
4f8QVazyALtu8+0XE17UPjlbNBqEfCIrMsYmL5VyMVbL0dlXXBxHjzfpXraNGoSD
4v6LxRxPdWQgrhEZ6DmfiWfX8uhLdMwlvUxNXj33UDtM8dtN6mHERA9wF2RQQzPd
dZ0MYmUFDI92i9mRC7Yzx6mcv/yUnFw213Jnzg297lW0Xp0ifawyPi2V8f8CAwEA
AQKCAgBZtF8/RPqO8f4C3GGtnOAswTN52eE4WFstFsY9ueRRsF2tSE+eaWG4pyoU
zyCPK+St0hlg9ATsg403b5uGTi11rjlsDqyttyA5iyZzSHyHtNpqnwWplUlIV2qc
Cx+MOPLIUqNTrW7EVTUAJZfDCVulrcpUipncK4eMiZkrkDYbV4kaAaaBdrsuAEeP
ztNFPPCJ14coxg4Yb58B+UYc7EPpnlu36uka/mRPKOlZPSv43MUHRf8XzxhV+EPg
Moso7LiBK6x9/qTPBJSlM6cK8G99pK6lwYW4lO2pRilmNsvflGj5v4Ay/fTTECZO
AwqwopPoXdx5yPLJdQ4hbGn13t+k0pB4LYXl1xqLg2Z9QN+pgC2h41OrSx8Ozw9U
KTocbsMV6pafnMRoQ5Fjb+eTy4VE8rZl/OlMDX2cR2XL+a3ypIAA5E4KrYDiIBiU
MSA3EA3GsOOnyrV+fII+f2tVo/qDnvxQO/ZPUr/XG2xtJ+gqThWlrBft/O4/lCju
+kfNg8cMHtahGOmLz1ALsl32ANj5jTZmVOEs9xTG7+TeQ2RzWeBYTB7oNTMNIbaL
pTZTzxoeRyxx8sUvtaTb23IWSpRUiS4+F7Tn97g6ks8fYQPsVkl3WzXeECaL9uNN
hFkAwd0omD4TwQlmOUVm3IH7A0InTAaooC9jJfNqmhhHcLUAgQKCAQEA3N+pR1ed
aCXQit6bgw0rIF6RzjeGp6lLGaPdvCUM7sdAUwSGbFOgkcaw9TELFpCpfZGKFXI9
IxPOwjFrURY4S2iuyAVv+Cw7trXW4BF1z+08M9RWYGLvyUsO7FIsGUmdYRtasb5L
IfHfGoXttadKWcdFMSF+25CUcbleyCNrJzXOzeMn1/UoN6+qfsyfaAD03nw/ZmhA
mK3UKjR7UOUPXt9gIXVivRaEQBakrLkJtK33fl1hztqxComE3/h6Qmj6iRmyxX3y
v3mzXbyC6jobq1tLUWpxvlQgoAyk+zZ0LNEHlkVfertaz0XdN/L2ZgjoGjJxfn/y
OK0a4jJyCpXXEwKCAQEAz9fJcpqaB25joZsqG+islNljyFjJRT9+E8IU97OglhJM
8T9TFs4RNsAxIqHwYm4KuoSlI4GhYeuOxGk6yUGm8cO314J7Wv4CAgZeJBcmxJUs
C8FbcXKbesK3VIuvATgzI9Vq/UF+GxJCkPctZJ9Oa0t578rVS4JH5ZJQdw2A77Lq
kGunMDboVY7EYDOn/fNMKGfcnH8KIQn/2b6CKLarj39b1fG7MeCuxPRejijaKtZI
ra5n0ofLluGo9HBXLgqgsjhjkSWU79lRypuKS8qOcjI08Xaw3Q8+qn0uLCARd8rN
2+mQy5OAZJCWpYIKPL6E41GaxcaTdOYzlOCs9Oz65QKCAQEAgSqzXitYvC1RFcU1
AKDU1as4bXZ/YtFYP/halcq9E26mqWX+Dp+hSV7+4YT6zQlwdSSFsiEKq9bLlTk9
X0A1T7Q6cnLrliCYEzOoI4VSdnRwPocwtFFnlTo10fIEJA2u4bkTgtqcKY+/P02P
RCo/Ct3EEwVZoKGejhsv2K8N3PJUrIbpKBwQlvA+LsUPe80DZpEWqpbRH/iYGM50
R0yNfpf3KdnyEk52rNwRFYloqacLE3Uc29F8s4LUl/5B0VB/I2pJ58DOEzfiszCp
Br1QrRdIpqYvOnUMV0zNtrOToRnk6/ZJ7gZfBtP+mNeXTPhsc9WIFchRKN/i1uFV
W+dgzQKCAQEArcXTDdio85GeB1395PuyX3kqbjWdgiJFvStF8JvkpdSDNCknxSdh
SQ+DhVsz6nfqzGtezsLxNTeHVDxPBDm55OUobi0QCdHZx+ufBjm9FhtKikGNvNp/
mDH4qd1n4nMkfs9O9pOtZeDsetvOvhRbsmWWe6BwmQNCLXUZhZBqvv4uE7WOQUeH
FRGaqnxF9pNWl2nPD6E/zMPZgCpCFNw1sHJhTA0h39/k/5L5A46waaRje6MX9vPG
ik39vvG2Ui5ckOWIibCMR8TBF87X3+ppEp1bmo8L7Kd0U4L5+baOJEQRvc4YW7zl
Wi9xZMvG12bLIGv4JWeTnediNRVsRhNk6QKCAQBXYkpxk6LTgP+b6FJ7fiImzDbH
QJ+RbBYJdgYLEarKWdWhNqj3YiDOUJt+ve13reybL4cLmOYoNzeUO9IHyGeTp+WV
gtPf1g2hm2TZannWsoTvnoXJ8yR52ZQQ5JusNosbmlrqWRAN8GhISdYTJDNcS2hD
PnVX/kaJfRDennokD+FWuyygua66LBdZi3UNgGMay15/2CCoC3PoejfQORxDyPP9
am+e3/U6QG1/VWMHen3Mb0AZKwEBAwX1jL4EpoDZ+Y6jP0tbQ5xL7RivsUNtAVlQ
m7lumflcBy1WqkmviVJ9M2iFuo0HznuH1qlgOJpUiqZZjL/gEvkdDNMcQSmH
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEXzCCAkcCAQAwGjEYMBYGA1UEAwwPZ29zd2FnZ2VyLmxvY2FsMIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAs1MHhleossLBkYKYwOT+82RT651CfCOi
lpEUhm92KGRSMQXZEk+2TUgcdGPeQNDNmbpXGzdk1HZkqWR5XKfSjWWxfmBlbBoY
nkL3neoiXBdBVsgHkEPdP5lyuJRkohy6az1vnq2vLaI+YujStutf8hSdcPu9VeAL
brR027dMbY2XMC97FteeVaw1mXmW9UHDVSV9UPBPswUOQWhjIADBk5IYaYASCY3M
4X5BPCWFu1oQhgVMEhodBoBjpHhHrfoDm1TwtT+dp53TmR10zpUiN+FcaVMsjqN4
DWX4ma0uhu+zJew2XjCJkNfXwVqGFpe2Hx+lupOGs/kwBvQ4PYn5ydgcm5DTggBC
45JxCAVi3tQCYGsg2xkX9yPjaXYc8E4/aeQI9UZxUeR2siBn8ECX4gJTmPJbQ4Xy
kqn6YOHyxIVoqd+9wo9Z1weHxCWtPGESg7l7Jn/6WQ5V8z6RzrquGi67asrpYpv2
lxNXMQA0f3S8sWYe4f8QVazyALtu8+0XE17UPjlbNBqEfCIrMsYmL5VyMVbL0dlX
XBxHjzfpXraNGoSD4v6LxRxPdWQgrhEZ6DmfiWfX8uhLdMwlvUxNXj33UDtM8dtN
6mHERA9wF2RQQzPddZ0MYmUFDI92i9mRC7Yzx6mcv/yUnFw213Jnzg297lW0Xp0i
fawyPi2V8f8CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4ICAQB7U21HoBp5Qfrwd8aA
KzBY2BfEp5ouyn32kpfkB3Ha6+XJ69Lt1WHMSKnmYamlwZCSOS2uQ6DzdTLDfZpC
8PH5Gs32O9zJwUeSuYcUQGfcAenauu9gwC5ZnIbhOs5YTnEFquVsBqrNUKS+hLKJ
sAPtucoqlLX5qSkv/BOK2X4os90LAmx+yB/yarAzZOO0ku8qXt+MHI+rOMPLTmm9
kYhtyXejQaXLOVbvQ9b2gxHvMcyLhklc4KpJPRfPzOdNebHsf5o4Em6lxeglGw/A
z05sBSAla69sEygcItZryQ4WjMRUpsLePXJrlSL5DYWGK6BX1gCkWtpXLqE1HgR3
4L/xvaJQ5ZWpLoyJoJauU37Zhd5dLNGpNiSSEA0BKOjj9Kjm8nvsJE9DgziTaG57
qFLRkMkDdBdb5wOfVYI/MY9zc+igrFPQJkQ0Xkdza8yXegBldv1JRe+49zifysea
Y/B+qWx8IpeHke0iEMqR6iWrw6oGBG/obHJ/V09DwC6iU8vot+pLr/bSyoUCUP30
OEATJf50ic9oZYXgdT9oNBcAlAriuzoQuGi9nAKZJss6YkhooWoqXlXNQgAEc2gl
WF4fNumXwVaPVeW2q36Xk1btHz7k+IeVUg1jaPMPUJ+1dgIOZA7FcoYotvF6StyX
xoHybhvC7lbeif8EK7tJ2p4hug==
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE7jCCAtYCCQChJZEdSdrQkzANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApH
byBTd2FnZ2VyMB4XDTE2MTIyMTA4MzM0N1oXDTE3MTIyMTA4MzM0N1owXTELMAkG
A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExETAPBgNVBAcMCE1pbGxicmFl
MRIwEAYDVQQKDAlMb2NhbCBEZXYxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBALGjhpJfej7btWCO4OCRJBliUAUyPMO8
B649Qjn1Yiw9E1L5viByYJSihsfUQ7u2gHip7QdigKCA/s4+w8V2L0Dv8lCowCLk
exf10b7XGQaOqhk2mlr/jOapAg0pKDoUlVErBBZK2s6UbD/gLXAbxudwxCFKJ1Y7
d/Dw5aTl1vlWZpHzf2o9/ZCeHXf8Xu3aMIEPJ79wG0vzNZK7bL1r1lQVzACdHAr3
4HAQAvgWB4ZjKqN8z0vGC0N0MpaAuHD8fH8wQ5YiWBbDhDPFVzRYU8PcQjeZSMFq
Oulew9KVm+vXtcMvteEoXMXwWlqAGlvnv7sskc/VbrLJJQaoswyKgy1QCKxVO47E
f2iU4kP75iDYx6NpApdnpN3zxHMHyZDxuwmtoKealenxl5cZeHc6uUU1wXk+nmy7
TrgW509mcopHzHj+Q0zyGUg/dRws3qXPAGZehJPoaYF1F54eiindF1yLMMH5osvy
1bNp2EQezOlY3P4gqW9VHq3CQvytmDbXqS0vPzVAsFu8YazM3Bs0mW2bBXrEsajW
DSjrvbhdZjlL9j2jqwZ2nzyan88M5t5T0vZhcu+wKisATI1yLdV3oWvLmdFz/XA9
L6UyosTiwC1MWPmkOY4mcHn/Px70f40+wO815pZ6FbjecxRSyMfAm6vDPWtLAMUr
1UoD4vasyvQNAgMBAAEwDQYJKoZIhvcNAQELBQADggIBACI85R1LfDwKpmWFdCeN
M8O6TwSftxh113QzMvobYDMmBI+yVn0ZvpcfP7E+LWRjzNcDxMFbntbbFWt99Rcd
rJek6CVxLJug53mUEFmvktreHsJ1T7cMbk1ZVroAurE7hZOWYM2HwXlKzVyXX0qh
wR26HSuzQcGBfo5/8e6dzMuy0fUVkgDMu4oKt0+mGgS4kXsOyexfRRBkY9GPusVk
gSzu/WbSGNxNvp/ewWNi8waqrN3noM83iae+BXxI0Sq4eLTQ/vnV1ReM4gRR12Vw
anwZqHZ/WzBV27z9gW36t7wRxJS/uTXQ8J08KtBRBPv+19NXSqqjys5Jg0P1f+l9
k+sWwpVqIF2rAQ3FyMfboaFKPC0jRn7iJMjp9KyvMbSI+25/rP5xvMicoJwRlk9I
GNGasxSfmRpVpV+WG04xMGp3cPrCXHBdAAjI3O68YIPOX3VqZ6MasN1iGuYWOmam
yeKzLUApYdtkR7yJ+X1FOKVfbzX27CLYmzwrHnDLJzu8NVgqLGU+qTSK0zm3sYE3
w3ex6WX86Oz2QBJ5h/s2TLbsWis7ZkKjMyXqVWlbg4P3reyNrfpAoc0y1R9EjZlf
1c9HZBRBuRMgaPWmdSR4lxw1FhQBTstIfzC8lBYNbt8QRRtJIxVF9mxiL7H+6XH5
FZXcQCHun6klGtCkypeAaviE
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEojCCAooCAQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx
ETAPBgNVBAcMCE1pbGxicmFlMRIwEAYDVQQKDAlMb2NhbCBEZXYxEjAQBgNVBAMM
CWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALGjhpJf
ej7btWCO4OCRJBliUAUyPMO8B649Qjn1Yiw9E1L5viByYJSihsfUQ7u2gHip7Qdi
gKCA/s4+w8V2L0Dv8lCowCLkexf10b7XGQaOqhk2mlr/jOapAg0pKDoUlVErBBZK
2s6UbD/gLXAbxudwxCFKJ1Y7d/Dw5aTl1vlWZpHzf2o9/ZCeHXf8Xu3aMIEPJ79w
G0vzNZK7bL1r1lQVzACdHAr34HAQAvgWB4ZjKqN8z0vGC0N0MpaAuHD8fH8wQ5Yi
WBbDhDPFVzRYU8PcQjeZSMFqOulew9KVm+vXtcMvteEoXMXwWlqAGlvnv7sskc/V
brLJJQaoswyKgy1QCKxVO47Ef2iU4kP75iDYx6NpApdnpN3zxHMHyZDxuwmtoKea
lenxl5cZeHc6uUU1wXk+nmy7TrgW509mcopHzHj+Q0zyGUg/dRws3qXPAGZehJPo
aYF1F54eiindF1yLMMH5osvy1bNp2EQezOlY3P4gqW9VHq3CQvytmDbXqS0vPzVA
sFu8YazM3Bs0mW2bBXrEsajWDSjrvbhdZjlL9j2jqwZ2nzyan88M5t5T0vZhcu+w
KisATI1yLdV3oWvLmdFz/XA9L6UyosTiwC1MWPmkOY4mcHn/Px70f40+wO815pZ6
FbjecxRSyMfAm6vDPWtLAMUr1UoD4vasyvQNAgMBAAGgADANBgkqhkiG9w0BAQsF
AAOCAgEAM9VLDurmvoYQyNEpRvFpOLPkgr8rgboo/HN+O/hN9jtmXranLxzTzd+u
OJCujyzS3sbqiZwPeT3APHH4c/mLdrEKZHjfy2sEeXMsVW6dCOcIEYsADSCM6chi
zU86aw4rAkd6YYB+lXFsEmBq78AIpw0vcdpoPoqGRG9ETQsjr4kD3ATGHTnaP551
61JJed7Kn5FTbieTmzmMa46dn7GjTTmPEcoAnHNCx4CbJAHwWEzvQWF4lVlyb2di
jFD0NQ0WeaFHK/f6UQMqMq+7TpurN8sLWDlyPHA2X/FT+OsUMAX2mLcwZEsYhTjP
dC4ZCuZ/itDgEp3hyPeKiLo+mL/bhhy50nzah/qclI9PS8ufUXEjWoObqiJ5eyIZ
jTZ73qpLupS+Yrami98IYfuOotwGzKkVLwUPtCWQrKsun6YNtotuKKmqEEQX3Fm3
ZXIYv0BckkXIGd0aKPeMGgMUO26pyxPBSRWB29F07LXzS6eEmfOHvZcT+QLZmys9
FkH3yePeTilojCnxNINPyKT4Dk0NiZviCdKavUIJ5QtOyDJ1Nc9j5ss+QaAaNtZZ
VTTjupNp+cfCh/kdyGpGP+GgXQQcGgw4OaIbfXqmec7RsqTOppK5gDR4Ne3e5FVm
SpPDyHbv2GJolPG8/HCOsLCJED+wAEfhK/wUg8ZpC+7Ymct2TU8=
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEAsaOGkl96Ptu1YI7g4JEkGWJQBTI8w7wHrj1COfViLD0TUvm+
IHJglKKGx9RDu7aAeKntB2KAoID+zj7DxXYvQO/yUKjAIuR7F/XRvtcZBo6qGTaa
Wv+M5qkCDSkoOhSVUSsEFkrazpRsP+AtcBvG53DEIUonVjt38PDlpOXW+VZmkfN/
aj39kJ4dd/xe7dowgQ8nv3AbS/M1krtsvWvWVBXMAJ0cCvfgcBAC+BYHhmMqo3zP
S8YLQ3QyloC4cPx8fzBDliJYFsOEM8VXNFhTw9xCN5lIwWo66V7D0pWb69e1wy+1
4ShcxfBaWoAaW+e/uyyRz9VussklBqizDIqDLVAIrFU7jsR/aJTiQ/vmINjHo2kC
l2ek3fPEcwfJkPG7Ca2gp5qV6fGXlxl4dzq5RTXBeT6ebLtOuBbnT2ZyikfMeP5D
TPIZSD91HCzepc8AZl6Ek+hpgXUXnh6KKd0XXIswwfmiy/LVs2nYRB7M6Vjc/iCp
b1UercJC/K2YNtepLS8/NUCwW7xhrMzcGzSZbZsFesSxqNYNKOu9uF1mOUv2PaOr
BnafPJqfzwzm3lPS9mFy77AqKwBMjXIt1Xeha8uZ0XP9cD0vpTKixOLALUxY+aQ5
jiZwef8/HvR/jT7A7zXmlnoVuN5zFFLIx8Cbq8M9a0sAxSvVSgPi9qzK9A0CAwEA
AQKCAgAb4VyHsLCRGQ64nvQwitctnL6OcjoTRnm2ISs5yYelBdj4lvX+RbVe3rtk
ta4D0jsLtS/cjts9VcGoQTWc0lXMTVysyC+Pymh/dDd9SmlFHDMaTfWf/qfws+n8
gs8rfnuJB8VWcl0xOx5aUCcRh2qKfKprxyWxZRgIGucQIHrDG4pxsdP3qs8XWZmq
cVO85RfjyaslYsUGAKAR7ZS9jiVPgTRJjF8QYaM6M2kj4uE/eGUCz94BOI4gAibG
dGF+akJn+/0/nRhSSlF/hqOPNaXAAdvqugYvRSsF4be+X3jfZTXD8sMLGbil4Hlt
5tk8P31aNT6Vbhw3t1Y2W1fuyfaYbPZfprpR/6ZPV3Uf1oWoh1ystIxWnfU7Qdxu
vrkHkrtho6Qt/7d8nNg0mQ8y5glNcVh/iNu9gkyHIpQ2dZpM9tpHArBGweHVDMvZ
vrb/oJ5fRxnKkyouMtWvqO1TY4STPBwCDNSwJa0yxTn3fLvkOdHk1nGEKra7E7Nc
hgsIe4q1ZoEikg7cZe8pvcsHIFfju3Kv/zgDTvHjzHPTdNear7mpk/LihlWdbTiI
UKkgv17JHRsIhfE5G4pZXLRv2qjCGh+uS8yn2k5qPJqGoyIQ2A7BYbaQ/y2gVh6u
hnVdKeETT2uUqIS3xHrV0U9grAeldPJu7bHRwSoJ+HUbp+D8QQKCAQEA4/5K0+Qq
p4rpB+4FpPkMcM4yE1j0ASwHsKGMDPU70/t8yfzZBlpf5WNHTOWa8frnxOyazo8E
sjm2Xw1RlMb21bFF0wjb3uhN2ak++0zIKMf5vWnM0bb2z7yzbcOJVxLzO9DmRUh0
OXvHvbeKbW9KXHT3YKA2zjaw0mO1zl7sd7r028wYpD6owGtfzooyXwWCnByIQ3nM
JFB7wFJGIg6Kbu2eJULrN1EaT1Ye0FUVmc4x55FLmZvkYziQ88e4UsjYdZ4R5EFi
2XULVI1RA+NPqDXkXmpIx3JnRRvaPc74QatGvDFwY8YeCAjfGFN5LiwFJ6Cz3/jf
WjDLOhqoSiYQ2QKCAQEAx3W7uPE7WNQRsyu2QnsEDdlikgP0FJf3fFZOYurfsxp7
iqTpNkR9pSWXftP4FBM/KRruoY5TucmPTfGPRS6sQWTfUkVOhrUpOLnuWjf2DJxH
Qqb0wnT76WcAB4L5Gr//91a+w3dwAX5YhdTZLxEKgpm8ky290otCg3+AYOb/P3Ja
V8RR8RQCNV1+y7peBgjj/mbYeVpxjTiZ5cq4cx2OU4rnup/k3HIg1Gw+qr0N9AUN
2WYOL+X0qaKffDa2ekv88H6mVnfRSReFIpteuV0XITwvMc0DbHdj6zEj1TSZMExu
rDVe7eh2BeL1QxbuazRUgwZ+kfy2NUzPkB1SSwi8VQKCAQBs8K4qj0S+Z8avjlFO
Id6K7EvLKN72zGYkRRzZeDiNMwbOsS22NmrJ/eUs3i1qYJxsYS4bcwUocCEvS/rm
XyfEtf8KNppw6YmBbrh0dZzSt7MiibJfptBKNP17fkpau+hTdZ8CDfvTF806XsAb
SGk8wnsNxaBKaqGU9iYCJSNSlpe3is9fc71IrEXMOAaXltdw5sVJkKI12+s121o9
nbsSBCJj5ZTlCrDKpfj1TSKUKo13+9om3PGFY5sHkTAHBoc/tDcSXRfxllbCoP/M
HsqKMq4bWyfJfWXRBN0EWagQINocxHbShfEFn8+SHRizMj+ITuaEJ7P5sYT6D5DI
VWYJAoIBAEqaxdFiIYGTKN+sbOqm2phXhB/7bJM7WC1glsc29N8n+6ebEUPkEF7y
FZ0xqavQmyJD2ZgCBV0LgBd2T9FfqLx4/3LlS37lSfrWyMlj/xsuZRUQH6KQYR0n
EoK8wXH4+MPJ5WZ1SSa13GSKfYW2SQkaecdPJ54VypYm3ZzhKf3QRuxnGQMkKcNO
KjwHhF2be7PPQg75/lkFH8MstRsRpgengA90+QRfh9oMdtAkEJECRvDW1F2kFIRS
uHacfFp4C67koFDdViGRs5GDLcYFhL5ApaJp/WrXqT7yTWXU26uOGyM8fzpbZbHD
91rVu+3LUAUGK9ds/7Yl+cj8vqgkJ1UCggEAc0a5kmBREz/8rAWKnlCZrhBsxUUM
tiIj32h6dVdFo5SsoyVTxdB394npw1DAsC8xdowrcm/zsYstB3IDMYlrBnCdRxTU
Xu6RD3Jci0Qg1cfLQg5snlRnrNz12wygXcvTvW8cHsda8vO+FL1RgFdehDtYoyZr
swcLLRAOOLTRXy1Xdbv+8vE6s5ryl3uAO+2Zwbmur3tRL+rhXE+Tb308jlt8g2NK
WrRbc3c092aImdGcKmkMGqo6H+xnL9Jj7sR161uO5JJQjxcYbZ5PBmm3J5Z71cSY
LR5snbYdxUy7WKE3yxAoWi+FMsoGf+O77+oHAcpXRaTDv0Enr/7rEku5Yw==
-----END RSA PRIVATE KEY-----

Binary file not shown.

View File

@@ -0,0 +1 @@
A125911D49DAD093

View File

@@ -0,0 +1,38 @@
package flagext
import (
"github.com/docker/go-units"
)
// ByteSize used to pass byte sizes to a go-flags CLI
type ByteSize int
// MarshalFlag implements go-flags Marshaller interface
func (b ByteSize) MarshalFlag() (string, error) {
return units.HumanSize(float64(b)), nil
}
// UnmarshalFlag implements go-flags Unmarshaller interface
func (b *ByteSize) UnmarshalFlag(value string) error {
sz, err := units.FromHumanSize(value)
if err != nil {
return err
}
*b = ByteSize(int(sz))
return nil
}
// String method for a bytesize (pflag value and stringer interface)
func (b ByteSize) String() string {
return units.HumanSize(float64(b))
}
// Set the value of this bytesize (pflag value interfaces)
func (b *ByteSize) Set(value string) error {
return b.UnmarshalFlag(value)
}
// Type returns the type of the pflag value (pflag value interface)
func (b *ByteSize) Type() string {
return "byte-size"
}

View File

@@ -0,0 +1,43 @@
package flagext
import "testing"
import "github.com/stretchr/testify/assert"
func TestMarshalBytesize(t *testing.T) {
v, err := ByteSize(1024).MarshalFlag()
if assert.NoError(t, err) {
assert.Equal(t, "1.024 kB", v)
}
}
func TestStringBytesize(t *testing.T) {
v := ByteSize(2048).String()
assert.Equal(t, "2.048 kB", v)
}
func TestUnmarshalBytesize(t *testing.T) {
var b ByteSize
err := b.UnmarshalFlag("notASize")
assert.Error(t, err)
err = b.UnmarshalFlag("1MB")
if assert.NoError(t, err) {
assert.Equal(t, ByteSize(1000000), b)
}
}
func TestSetBytesize(t *testing.T) {
var b ByteSize
err := b.Set("notASize")
assert.Error(t, err)
err = b.Set("2MB")
if assert.NoError(t, err) {
assert.Equal(t, ByteSize(2000000), b)
}
}
func TestTypeBytesize(t *testing.T) {
var b ByteSize
assert.Equal(t, "byte-size", b.Type())
}

20
vendor/github.com/go-openapi/runtime/hack/coverage generated vendored Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -e -o pipefail
# Run test coverage on each subdirectories and merge the coverage profile.
echo "mode: ${GOCOVMODE-atomic}" > coverage.txt
# Standard go tooling behavior is to ignore dirs with leading underscores
# skip generator for race detection and coverage
for dir in $(go list ./...)
do
pth="$GOPATH/src/$dir"
go test -race -timeout 20m -covermode=${GOCOVMODE-atomic} -coverprofile=${pth}/profile.out $dir
if [ -f $pth/profile.out ]
then
cat $pth/profile.out | tail -n +2 >> coverage.txt
rm $pth/profile.out
fi
done
go tool cover -func coverage.txt

View File

@@ -0,0 +1,16 @@
#!/bin/sh
# generate CA
openssl genrsa -out myCA.key 4096
openssl req -x509 -new -key myCA.key -out myCA.crt -days 730 -subj /CN="Go Swagger"
# generate server cert and key
openssl genrsa -out mycert1.key 4096
openssl req -new -out mycert1.req -key mycert1.key -subj /CN="goswagger.local"
openssl x509 -req -in mycert1.req -out mycert1.crt -CAkey myCA.key -CA myCA.crt -days 365 -CAcreateserial -CAserial serial
# generate client cert, key and bundle
openssl genrsa -out myclient.key 4096
openssl req -new -key myclient.key -out myclient.csr
openssl x509 -req -days 730 -in myclient.csr -out myclient.crt -CAkey myCA.key -CA myCA.crt -days 365 -CAcreateserial -CAserial serial
openssl pkcs12 -export -clcerts -in myclient.crt -inkey myclient.key -out myclient.p12

45
vendor/github.com/go-openapi/runtime/headers.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
// 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 runtime
import (
"mime"
"net/http"
"github.com/go-openapi/errors"
)
// ContentType parses a content type header
func ContentType(headers http.Header) (string, string, error) {
ct := headers.Get(HeaderContentType)
orig := ct
if ct == "" {
ct = DefaultMime
}
if ct == "" {
return "", "", nil
}
mt, opts, err := mime.ParseMediaType(ct)
if err != nil {
return "", "", errors.NewParseError(HeaderContentType, "header", orig, err)
}
if cs, ok := opts[charsetKey]; ok {
return mt, cs, nil
}
return mt, "", nil
}

61
vendor/github.com/go-openapi/runtime/headers_test.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
// 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 runtime
import (
"mime"
"net/http"
"testing"
"github.com/go-openapi/errors"
"github.com/stretchr/testify/assert"
)
func TestParseContentType(t *testing.T) {
_, _, reason1 := mime.ParseMediaType("application(")
_, _, reason2 := mime.ParseMediaType("application/json;char*")
data := []struct {
hdr, mt, cs string
err *errors.ParseError
}{
{"application/json", "application/json", "", nil},
{"text/html; charset=utf-8", "text/html", "utf-8", nil},
{"text/html;charset=utf-8", "text/html", "utf-8", nil},
{"", "application/octet-stream", "", nil},
{"text/html; charset=utf-8", "text/html", "utf-8", nil},
{"application(", "", "", errors.NewParseError("Content-Type", "header", "application(", reason1)},
{"application/json;char*", "", "", errors.NewParseError("Content-Type", "header", "application/json;char*", reason2)},
}
headers := http.Header(map[string][]string{})
for _, v := range data {
if v.hdr != "" {
headers.Set("Content-Type", v.hdr)
} else {
headers.Del("Content-Type")
}
ct, cs, err := ContentType(headers)
if v.err == nil {
assert.NoError(t, err, "input: %q, err: %v", v.hdr, err)
} else {
assert.Error(t, err, "input: %q", v.hdr)
assert.IsType(t, &errors.ParseError{}, err, "input: %q", v.hdr)
assert.Equal(t, v.err.Error(), err.Error(), "input: %q", v.hdr)
}
assert.Equal(t, v.mt, ct, "input: %q", v.hdr)
assert.Equal(t, v.cs, cs, "input: %q", v.hdr)
}
}

94
vendor/github.com/go-openapi/runtime/interfaces.go generated vendored Normal file
View File

@@ -0,0 +1,94 @@
// 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 runtime
import (
"io"
"mime/multipart"
"github.com/go-openapi/strfmt"
)
// File represents an uploaded file.
type File struct {
Data multipart.File
Header *multipart.FileHeader
}
// OperationHandlerFunc an adapter for a function to the OperationHandler interface
type OperationHandlerFunc func(interface{}) (interface{}, error)
// Handle implements the operation handler interface
func (s OperationHandlerFunc) Handle(data interface{}) (interface{}, error) {
return s(data)
}
// OperationHandler a handler for a swagger operation
type OperationHandler interface {
Handle(interface{}) (interface{}, error)
}
// ConsumerFunc represents a function that can be used as a consumer
type ConsumerFunc func(io.Reader, interface{}) error
// Consume consumes the reader into the data parameter
func (fn ConsumerFunc) Consume(reader io.Reader, data interface{}) error {
return fn(reader, data)
}
// Consumer implementations know how to bind the values on the provided interface to
// data provided by the request body
type Consumer interface {
// Consume performs the binding of request values
Consume(io.Reader, interface{}) error
}
// ProducerFunc represents a function that can be used as a producer
type ProducerFunc func(io.Writer, interface{}) error
// Produce produces the response for the provided data
func (f ProducerFunc) Produce(writer io.Writer, data interface{}) error {
return f(writer, data)
}
// Producer implementations know how to turn the provided interface into a valid
// HTTP response
type Producer interface {
// Produce writes to the http response
Produce(io.Writer, interface{}) error
}
// AuthenticatorFunc turns a function into an authenticator
type AuthenticatorFunc func(interface{}) (bool, interface{}, error)
// Authenticate authenticates the request with the provided data
func (f AuthenticatorFunc) Authenticate(params interface{}) (bool, interface{}, error) {
return f(params)
}
// Authenticator represents an authentication strategy
// implementations of Authenticator know how to authenticate the
// request data and translate that into a valid principal object or an error
type Authenticator interface {
Authenticate(interface{}) (bool, interface{}, error)
}
// Validatable types implementing this interface allow customizing their validation
// this will be used instead of the reflective valditation based on the spec document.
// the implementations are assumed to have been generated by the swagger tool so they should
// contain all the validations obtained from the spec
type Validatable interface {
Validate(strfmt.Registry) error
}

View File

@@ -0,0 +1,811 @@
// 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 testing
import (
"encoding/json"
)
// PetStore20YAML yaml doc for swagger 2.0 pet store
const PetStore20YAML = `swagger: '2.0'
info:
version: '1.0.0'
title: Swagger Petstore
description: A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification
termsOfService: http://helloreverb.com/terms/
contact:
name: Swagger API team
email: foo@example.com
url: http://swagger.io
license:
name: MIT
url: http://opensource.org/licenses/MIT
host: petstore.swagger.wordnik.com
basePath: /api
schemes:
- http
consumes:
- application/json
produces:
- application/json
paths:
/pets:
get:
description: Returns all pets from the system that the user has access to
operationId: findPets
produces:
- application/json
- application/xml
- text/xml
- text/html
parameters:
- name: tags
in: query
description: tags to filter by
required: false
type: array
items:
type: string
collectionFormat: csv
- name: limit
in: query
description: maximum number of results to return
required: false
type: integer
format: int32
responses:
'200':
description: pet response
schema:
type: array
items:
$ref: '#/definitions/pet'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
post:
description: Creates a new pet in the store. Duplicates are allowed
operationId: addPet
produces:
- application/json
parameters:
- name: pet
in: body
description: Pet to add to the store
required: true
schema:
$ref: '#/definitions/newPet'
responses:
'200':
description: pet response
schema:
$ref: '#/definitions/pet'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
/pets/{id}:
get:
description: Returns a user based on a single ID, if the user does not have access to the pet
operationId: findPetById
produces:
- application/json
- application/xml
- text/xml
- text/html
parameters:
- name: id
in: path
description: ID of pet to fetch
required: true
type: integer
format: int64
responses:
'200':
description: pet response
schema:
$ref: '#/definitions/pet'
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
delete:
description: deletes a single pet based on the ID supplied
operationId: deletePet
parameters:
- name: id
in: path
description: ID of pet to delete
required: true
type: integer
format: int64
responses:
'204':
description: pet deleted
default:
description: unexpected error
schema:
$ref: '#/definitions/errorModel'
definitions:
pet:
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
newPet:
allOf:
- $ref: '#/definitions/pet'
- required:
- name
properties:
id:
type: integer
format: int64
name:
type: string
errorModel:
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
`
// PetStore20 json doc for swagger 2.0 pet store
const PetStore20 = `{
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"contact": {
"name": "Wordnik API Team",
"url": "http://developer.wordnik.com"
},
"license": {
"name": "Creative Commons 4.0 International",
"url": "http://creativecommons.org/licenses/by/4.0/"
}
},
"host": "petstore.swagger.wordnik.com",
"basePath": "/api",
"schemes": [
"http"
],
"paths": {
"/pets": {
"get": {
"security": [
{
"basic": []
}
],
"tags": [ "Pet Operations" ],
"operationId": "getAllPets",
"parameters": [
{
"name": "status",
"in": "query",
"description": "The status to filter by",
"type": "string"
},
{
"name": "limit",
"in": "query",
"description": "The maximum number of results to return",
"type": "integer",
"format": "int64"
}
],
"summary": "Finds all pets in the system",
"responses": {
"200": {
"description": "Pet response",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Pet"
}
}
},
"default": {
"description": "Unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"post": {
"security": [
{
"basic": []
}
],
"tags": [ "Pet Operations" ],
"operationId": "createPet",
"summary": "Creates a new pet",
"consumes": ["application/x-yaml"],
"produces": ["application/x-yaml"],
"parameters": [
{
"name": "pet",
"in": "body",
"description": "The Pet to create",
"required": true,
"schema": {
"$ref": "#/definitions/newPet"
}
}
],
"responses": {
"200": {
"description": "Created Pet response",
"schema": {
"$ref": "#/definitions/Pet"
}
},
"default": {
"description": "Unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/pets/{id}": {
"delete": {
"security": [
{
"apiKey": []
}
],
"description": "Deletes the Pet by id",
"operationId": "deletePet",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of pet to delete",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"204": {
"description": "pet deleted"
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"get": {
"tags": [ "Pet Operations" ],
"operationId": "getPetById",
"summary": "Finds the pet by id",
"responses": {
"200": {
"description": "Pet response",
"schema": {
"$ref": "#/definitions/Pet"
}
},
"default": {
"description": "Unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of pet",
"required": true,
"type": "integer",
"format": "int64"
}
]
}
},
"definitions": {
"Category": {
"id": "Category",
"properties": {
"id": {
"format": "int64",
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"Pet": {
"id": "Pet",
"properties": {
"category": {
"$ref": "#/definitions/Category"
},
"id": {
"description": "unique identifier for the pet",
"format": "int64",
"maximum": 100.0,
"minimum": 0.0,
"type": "integer"
},
"name": {
"type": "string"
},
"photoUrls": {
"items": {
"type": "string"
},
"type": "array"
},
"status": {
"description": "pet status in the store",
"enum": [
"available",
"pending",
"sold"
],
"type": "string"
},
"tags": {
"items": {
"$ref": "#/definitions/Tag"
},
"type": "array"
}
},
"required": [
"id",
"name"
]
},
"newPet": {
"anyOf": [
{
"$ref": "#/definitions/Pet"
},
{
"required": [
"name"
]
}
]
},
"Tag": {
"id": "Tag",
"properties": {
"id": {
"format": "int64",
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"Error": {
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
},
"consumes": [
"application/json",
"application/xml"
],
"produces": [
"application/json",
"application/xml",
"text/plain",
"text/html"
],
"securityDefinitions": {
"basic": {
"type": "basic"
},
"apiKey": {
"type": "apiKey",
"in": "header",
"name": "X-API-KEY"
}
}
}
`
// RootPetStore20 json doc for swagger 2.0 pet store at /
const RootPetStore20 = `{
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"contact": {
"name": "Wordnik API Team",
"url": "http://developer.wordnik.com"
},
"license": {
"name": "Creative Commons 4.0 International",
"url": "http://creativecommons.org/licenses/by/4.0/"
}
},
"host": "petstore.swagger.wordnik.com",
"basePath": "/",
"schemes": [
"http"
],
"paths": {
"/pets": {
"get": {
"security": [
{
"basic": []
}
],
"tags": [ "Pet Operations" ],
"operationId": "getAllPets",
"parameters": [
{
"name": "status",
"in": "query",
"description": "The status to filter by",
"type": "string"
}
],
"summary": "Finds all pets in the system",
"responses": {
"200": {
"description": "Pet response",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Pet"
}
}
},
"default": {
"description": "Unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"post": {
"security": [
{
"basic": []
}
],
"tags": [ "Pet Operations" ],
"operationId": "createPet",
"summary": "Creates a new pet",
"consumes": ["application/x-yaml"],
"produces": ["application/x-yaml"],
"parameters": [
{
"name": "pet",
"in": "body",
"description": "The Pet to create",
"required": true,
"schema": {
"$ref": "#/definitions/newPet"
}
}
],
"responses": {
"200": {
"description": "Created Pet response",
"schema": {
"$ref": "#/definitions/Pet"
}
},
"default": {
"description": "Unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/pets/{id}": {
"delete": {
"security": [
{
"apiKey": []
}
],
"description": "Deletes the Pet by id",
"operationId": "deletePet",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of pet to delete",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"204": {
"description": "pet deleted"
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"get": {
"tags": [ "Pet Operations" ],
"operationId": "getPetById",
"summary": "Finds the pet by id",
"responses": {
"200": {
"description": "Pet response",
"schema": {
"$ref": "#/definitions/Pet"
}
},
"default": {
"description": "Unexpected error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of pet",
"required": true,
"type": "integer",
"format": "int64"
}
]
}
},
"definitions": {
"Category": {
"id": "Category",
"properties": {
"id": {
"format": "int64",
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"Pet": {
"id": "Pet",
"properties": {
"category": {
"$ref": "#/definitions/Category"
},
"id": {
"description": "unique identifier for the pet",
"format": "int64",
"maximum": 100.0,
"minimum": 0.0,
"type": "integer"
},
"name": {
"type": "string"
},
"photoUrls": {
"items": {
"type": "string"
},
"type": "array"
},
"status": {
"description": "pet status in the store",
"enum": [
"available",
"pending",
"sold"
],
"type": "string"
},
"tags": {
"items": {
"$ref": "#/definitions/Tag"
},
"type": "array"
}
},
"required": [
"id",
"name"
]
},
"newPet": {
"anyOf": [
{
"$ref": "#/definitions/Pet"
},
{
"required": [
"name"
]
}
]
},
"Tag": {
"id": "Tag",
"properties": {
"id": {
"format": "int64",
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"Error": {
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
},
"consumes": [
"application/json",
"application/xml"
],
"produces": [
"application/json",
"application/xml",
"text/plain",
"text/html"
],
"securityDefinitions": {
"basic": {
"type": "basic"
},
"apiKey": {
"type": "apiKey",
"in": "header",
"name": "X-API-KEY"
}
}
}
`
// PetStoreJSONMessage json raw message for Petstore20
var PetStoreJSONMessage = json.RawMessage([]byte(PetStore20))
// RootPetStoreJSONMessage json raw message for Petstore20
var RootPetStoreJSONMessage = json.RawMessage([]byte(RootPetStore20))
// InvalidJSON invalid swagger 2.0 spec in JSON
const InvalidJSON = `{
"apiVersion": "1.0.0",
"apis": [
{
"description": "Operations about pets",
"path": "/pet"
},
{
"description": "Operations about user",
"path": "/user"
},
{
"description": "Operations about store",
"path": "/store"
}
],
"authorizations": {
"oauth2": {
"grantTypes": {
"authorization_code": {
"tokenEndpoint": {
"tokenName": "auth_code",
"url": "http://petstore.swagger.wordnik.com/oauth/token"
},
"tokenRequestEndpoint": {
"clientIdName": "client_id",
"clientSecretName": "client_secret",
"url": "http://petstore.swagger.wordnik.com/oauth/requestToken"
}
},
"implicit": {
"loginEndpoint": {
"url": "http://petstore.swagger.wordnik.com/oauth/dialog"
},
"tokenName": "access_token"
}
},
"scopes": [
{
"description": "Modify pets in your account",
"scope": "write:pets"
},
{
"description": "Read your pets",
"scope": "read:pets"
},
{
"description": "Anything (testing)",
"scope": "test:anything"
}
],
"type": "oauth2"
}
},
"info": {
"contact": "apiteam@wordnik.com",
"description": "This is a sample server Petstore server. You can find out more about Swagger \n at <a href=\"http://swagger.wordnik.com\">http://swagger.wordnik.com</a> or on irc.freenode.net, #swagger. For this sample,\n you can use the api key \"special-key\" to test the authorization filters",
"license": "Apache 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.html",
"termsOfServiceUrl": "http://helloreverb.com/terms/",
"title": "Swagger Sample App"
},
"swaggerVersion": "1.2"
}
`
// InvalidJSONMessage json raw message for invalid json
var InvalidJSONMessage = json.RawMessage([]byte(InvalidJSON))

View File

@@ -0,0 +1,147 @@
// 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 petstore
import (
"io"
gotest "testing"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
testingutil "github.com/go-openapi/runtime/internal/testing"
"github.com/go-openapi/runtime/middleware/untyped"
"github.com/go-openapi/runtime/security"
"github.com/go-openapi/runtime/yamlpc"
"github.com/stretchr/testify/assert"
)
// NewAPI registers a stub api for the pet store
func NewAPI(t gotest.TB) (*loads.Document, *untyped.API) {
spec, err := loads.Analyzed(testingutil.PetStoreJSONMessage, "")
assert.NoError(t, err)
api := untyped.NewAPI(spec)
api.RegisterConsumer("application/json", runtime.JSONConsumer())
api.RegisterProducer("application/json", runtime.JSONProducer())
api.RegisterConsumer("application/xml", new(stubConsumer))
api.RegisterProducer("application/xml", new(stubProducer))
api.RegisterProducer("text/plain", new(stubProducer))
api.RegisterProducer("text/html", new(stubProducer))
api.RegisterConsumer("application/x-yaml", yamlpc.YAMLConsumer())
api.RegisterProducer("application/x-yaml", yamlpc.YAMLProducer())
api.RegisterAuth("basic", security.BasicAuth(func(username, password string) (interface{}, error) {
if username == "admin" && password == "admin" {
return "admin", nil
}
return nil, errors.Unauthenticated("basic")
}))
api.RegisterAuth("apiKey", security.APIKeyAuth("X-API-KEY", "header", func(token string) (interface{}, error) {
if token == "token123" {
return "admin", nil
}
return nil, errors.Unauthenticated("token")
}))
api.RegisterOperation("get", "/pets", new(stubOperationHandler))
api.RegisterOperation("post", "/pets", new(stubOperationHandler))
api.RegisterOperation("delete", "/pets/{id}", new(stubOperationHandler))
api.RegisterOperation("get", "/pets/{id}", new(stubOperationHandler))
api.Models["pet"] = func() interface{} { return new(Pet) }
api.Models["newPet"] = func() interface{} { return new(Pet) }
api.Models["tag"] = func() interface{} { return new(Tag) }
return spec, api
}
// NewRootAPI registers a stub api for the pet store
func NewRootAPI(t gotest.TB) (*loads.Document, *untyped.API) {
spec, err := loads.Analyzed(testingutil.RootPetStoreJSONMessage, "")
assert.NoError(t, err)
api := untyped.NewAPI(spec)
api.RegisterConsumer("application/json", runtime.JSONConsumer())
api.RegisterProducer("application/json", runtime.JSONProducer())
api.RegisterConsumer("application/xml", new(stubConsumer))
api.RegisterProducer("application/xml", new(stubProducer))
api.RegisterProducer("text/plain", new(stubProducer))
api.RegisterProducer("text/html", new(stubProducer))
api.RegisterConsumer("application/x-yaml", yamlpc.YAMLConsumer())
api.RegisterProducer("application/x-yaml", yamlpc.YAMLProducer())
api.RegisterAuth("basic", security.BasicAuth(func(username, password string) (interface{}, error) {
if username == "admin" && password == "admin" {
return "admin", nil
}
return nil, errors.Unauthenticated("basic")
}))
api.RegisterAuth("apiKey", security.APIKeyAuth("X-API-KEY", "header", func(token string) (interface{}, error) {
if token == "token123" {
return "admin", nil
}
return nil, errors.Unauthenticated("token")
}))
api.RegisterOperation("get", "/pets", new(stubOperationHandler))
api.RegisterOperation("post", "/pets", new(stubOperationHandler))
api.RegisterOperation("delete", "/pets/{id}", new(stubOperationHandler))
api.RegisterOperation("get", "/pets/{id}", new(stubOperationHandler))
api.Models["pet"] = func() interface{} { return new(Pet) }
api.Models["newPet"] = func() interface{} { return new(Pet) }
api.Models["tag"] = func() interface{} { return new(Tag) }
return spec, api
}
// Tag the tag model
type Tag struct {
ID int64
Name string
}
// Pet the pet model
type Pet struct {
ID int64
Name string
PhotoURLs []string
Status string
Tags []Tag
}
type stubConsumer struct {
}
func (s *stubConsumer) Consume(_ io.Reader, _ interface{}) error {
return nil
}
type stubProducer struct {
}
func (s *stubProducer) Produce(_ io.Writer, _ interface{}) error {
return nil
}
type stubOperationHandler struct {
}
func (s *stubOperationHandler) ParameterModel() interface{} {
return nil
}
func (s *stubOperationHandler) Handle(params interface{}) (interface{}, error) {
return nil, nil
}

View File

@@ -0,0 +1,351 @@
// 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 simplepetstore
import (
"encoding/json"
"net/http"
"sync"
"sync/atomic"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/runtime/middleware/untyped"
)
// NewPetstore creates a new petstore api handler
func NewPetstore() (http.Handler, error) {
spec, err := loads.Analyzed(json.RawMessage([]byte(swaggerJSON)), "")
if err != nil {
return nil, err
}
api := untyped.NewAPI(spec)
api.RegisterOperation("get", "/pets", getAllPets)
api.RegisterOperation("post", "/pets", createPet)
api.RegisterOperation("delete", "/pets/{id}", deletePet)
api.RegisterOperation("get", "/pets/{id}", getPetByID)
return middleware.Serve(spec, api), nil
}
var getAllPets = runtime.OperationHandlerFunc(func(data interface{}) (interface{}, error) {
return pets, nil
})
var createPet = runtime.OperationHandlerFunc(func(data interface{}) (interface{}, error) {
body := data.(map[string]interface{})["pet"].(map[string]interface{})
return addPet(Pet{
Name: body["name"].(string),
Status: body["status"].(string),
}), nil
})
var deletePet = runtime.OperationHandlerFunc(func(data interface{}) (interface{}, error) {
id := data.(map[string]interface{})["id"].(int64)
removePet(id)
return nil, nil
})
var getPetByID = runtime.OperationHandlerFunc(func(data interface{}) (interface{}, error) {
id := data.(map[string]interface{})["id"].(int64)
return petByID(id)
})
// Tag the tag model
type Tag struct {
ID int64
Name string
}
// Pet the pet model
type Pet struct {
ID int64 `json:"id"`
Name string `json:"name"`
PhotoURLs []string `json:"photoUrls,omitempty"`
Status string `json:"status,omitempty"`
Tags []Tag `json:"tags,omitempty"`
}
var pets = []Pet{
{1, "Dog", []string{}, "available", nil},
{2, "Cat", []string{}, "pending", nil},
}
var petsLock = &sync.Mutex{}
var lastPetID int64 = 2
func newPetID() int64 {
return atomic.AddInt64(&lastPetID, 1)
}
func addPet(pet Pet) Pet {
petsLock.Lock()
defer petsLock.Unlock()
pet.ID = newPetID()
pets = append(pets, pet)
return pet
}
func removePet(id int64) {
petsLock.Lock()
defer petsLock.Unlock()
var newPets []Pet
for _, pet := range pets {
if pet.ID != id {
newPets = append(newPets, pet)
}
}
pets = newPets
}
func petByID(id int64) (*Pet, error) {
for _, pet := range pets {
if pet.ID == id {
return &pet, nil
}
}
return nil, errors.NotFound("not found: pet %d", id)
}
var swaggerJSON = `{
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "Swagger Petstore",
"description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
"termsOfService": "http://helloreverb.com/terms/",
"contact": {
"name": "Wordnik API Team"
},
"license": {
"name": "MIT"
}
},
"host": "localhost:8344",
"basePath": "/api",
"schemes": [
"http"
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/pets": {
"get": {
"description": "Returns all pets from the system that the user has access to",
"operationId": "findPets",
"produces": [
"application/json",
"application/xml",
"text/xml",
"text/html"
],
"parameters": [
{
"name": "tags",
"in": "query",
"description": "tags to filter by",
"required": false,
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "csv"
},
{
"name": "limit",
"in": "query",
"description": "maximum number of results to return",
"required": false,
"type": "integer",
"format": "int32"
}
],
"responses": {
"200": {
"description": "pet response",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/pet"
}
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/errorModel"
}
}
}
},
"post": {
"description": "Creates a new pet in the store. Duplicates are allowed",
"operationId": "addPet",
"produces": [
"application/json"
],
"parameters": [
{
"name": "pet",
"in": "body",
"description": "Pet to add to the store",
"required": true,
"schema": {
"$ref": "#/definitions/petInput"
}
}
],
"responses": {
"200": {
"description": "pet response",
"schema": {
"$ref": "#/definitions/pet"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/errorModel"
}
}
}
}
},
"/pets/{id}": {
"get": {
"description": "Returns a user based on a single ID, if the user does not have access to the pet",
"operationId": "findPetById",
"produces": [
"application/json",
"application/xml",
"text/xml",
"text/html"
],
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of pet to fetch",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"200": {
"description": "pet response",
"schema": {
"$ref": "#/definitions/pet"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/errorModel"
}
}
}
},
"delete": {
"description": "deletes a single pet based on the ID supplied",
"operationId": "deletePet",
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of pet to delete",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"204": {
"description": "pet deleted"
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/errorModel"
}
}
}
}
}
},
"definitions": {
"pet": {
"required": [
"name",
"status"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"status": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"petInput": {
"allOf": [
{
"$ref": "#/definitions/pet"
},
{
"properties": {
"id": {
"type": "integer",
"format": "int64"
}
}
}
]
},
"errorModel": {
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
}
}`

View File

@@ -0,0 +1,81 @@
// 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 simplepetstore
import (
"bytes"
"net/http/httptest"
"testing"
"github.com/go-openapi/runtime"
"github.com/stretchr/testify/assert"
)
func TestSimplePetstoreSpec(t *testing.T) {
handler, _ := NewPetstore()
// Serves swagger spec document
r, _ := runtime.JSONRequest("GET", "/swagger.json", nil)
rw := httptest.NewRecorder()
handler.ServeHTTP(rw, r)
assert.Equal(t, 200, rw.Code)
assert.Equal(t, swaggerJSON, rw.Body.String())
}
func TestSimplePetstoreAllPets(t *testing.T) {
handler, _ := NewPetstore()
// Serves swagger spec document
r, _ := runtime.JSONRequest("GET", "/api/pets", nil)
rw := httptest.NewRecorder()
handler.ServeHTTP(rw, r)
assert.Equal(t, 200, rw.Code)
assert.Equal(t, "[{\"id\":1,\"name\":\"Dog\",\"status\":\"available\"},{\"id\":2,\"name\":\"Cat\",\"status\":\"pending\"}]\n", rw.Body.String())
}
func TestSimplePetstorePetByID(t *testing.T) {
handler, _ := NewPetstore()
// Serves swagger spec document
r, _ := runtime.JSONRequest("GET", "/api/pets/1", nil)
rw := httptest.NewRecorder()
handler.ServeHTTP(rw, r)
assert.Equal(t, 200, rw.Code)
assert.Equal(t, "{\"id\":1,\"name\":\"Dog\",\"status\":\"available\"}\n", rw.Body.String())
}
func TestSimplePetstoreAddPet(t *testing.T) {
handler, _ := NewPetstore()
// Serves swagger spec document
r, _ := runtime.JSONRequest("POST", "/api/pets", bytes.NewBuffer([]byte(`{"name": "Fish","status": "available"}`)))
rw := httptest.NewRecorder()
handler.ServeHTTP(rw, r)
assert.Equal(t, 200, rw.Code)
assert.Equal(t, "{\"id\":3,\"name\":\"Fish\",\"status\":\"available\"}\n", rw.Body.String())
}
func TestSimplePetstoreDeletePet(t *testing.T) {
handler, _ := NewPetstore()
// Serves swagger spec document
r, _ := runtime.JSONRequest("DELETE", "/api/pets/1", nil)
rw := httptest.NewRecorder()
handler.ServeHTTP(rw, r)
assert.Equal(t, 204, rw.Code)
assert.Equal(t, "", rw.Body.String())
r, _ = runtime.JSONRequest("GET", "/api/pets/1", nil)
rw = httptest.NewRecorder()
handler.ServeHTTP(rw, r)
assert.Equal(t, 404, rw.Code)
assert.Equal(t, "{\"code\":404,\"message\":\"not found: pet 1\"}", rw.Body.String())
}

37
vendor/github.com/go-openapi/runtime/json.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
// 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 runtime
import (
"encoding/json"
"io"
)
// JSONConsumer creates a new JSON consumer
func JSONConsumer() Consumer {
return ConsumerFunc(func(reader io.Reader, data interface{}) error {
dec := json.NewDecoder(reader)
dec.UseNumber() // preserve number formats
return dec.Decode(data)
})
}
// JSONProducer creates a new JSON producer
func JSONProducer() Producer {
return ProducerFunc(func(writer io.Writer, data interface{}) error {
enc := json.NewEncoder(writer)
return enc.Encode(data)
})
}

62
vendor/github.com/go-openapi/runtime/json_test.go generated vendored Normal file
View File

@@ -0,0 +1,62 @@
// 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 runtime
import (
"bytes"
"io"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
var consProdJSON = `{"name":"Somebody","id":1}`
type eofRdr struct {
}
func (r *eofRdr) Read(d []byte) (int, error) {
return 0, io.EOF
}
func TestJSONConsumer(t *testing.T) {
cons := JSONConsumer()
var data struct {
Name string
ID int
}
err := cons.Consume(bytes.NewBuffer([]byte(consProdJSON)), &data)
if assert.NoError(t, err) {
assert.Equal(t, "Somebody", data.Name)
assert.Equal(t, 1, data.ID)
err = cons.Consume(new(eofRdr), &data)
assert.Error(t, err)
}
}
func TestJSONProducer(t *testing.T) {
prod := JSONProducer()
data := struct {
Name string `json:"name"`
ID int `json:"id"`
}{Name: "Somebody", ID: 1}
rw := httptest.NewRecorder()
err := prod.Produce(rw, data)
assert.NoError(t, err)
assert.Equal(t, consProdJSON+"\n", rw.Body.String())
}

View File

@@ -0,0 +1,101 @@
package middleware
import (
"io"
"net/http"
"path"
"testing"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/internal/testing/petstore"
"github.com/stretchr/testify/assert"
)
type eofReader struct {
}
func (r *eofReader) Read(b []byte) (int, error) {
return 0, io.EOF
}
func (r *eofReader) Close() error {
return nil
}
type rbn func(*http.Request, *MatchedRoute) error
func (b rbn) BindRequest(r *http.Request, rr *MatchedRoute) error {
return b(r, rr)
}
func TestBindRequest_BodyValidation(t *testing.T) {
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
api.DefaultConsumes = runtime.JSONMime
ctx.router = DefaultRouter(spec, ctx.api)
req, err := http.NewRequest("GET", path.Join(spec.BasePath(), "/pets"), new(eofReader))
if assert.NoError(t, err) {
req.Header.Set("Content-Type", runtime.JSONMime)
ri, ok := ctx.RouteInfo(req)
if assert.True(t, ok) {
err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error {
defer r.Body.Close()
var data interface{}
err := runtime.JSONConsumer().Consume(r.Body, &data)
_ = data
return err
}))
assert.Error(t, err)
assert.Equal(t, io.EOF, err)
}
}
}
func TestBindRequest_DeleteNoBody(t *testing.T) {
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
api.DefaultConsumes = runtime.JSONMime
ctx.router = DefaultRouter(spec, ctx.api)
req, err := http.NewRequest("DELETE", path.Join(spec.BasePath(), "/pets/123"), new(eofReader))
if assert.NoError(t, err) {
req.Header.Set("Accept", "*/*")
ri, ok := ctx.RouteInfo(req)
if assert.True(t, ok) {
err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error {
return nil
}))
assert.NoError(t, err)
//assert.Equal(t, io.EOF, err)
}
}
req, err = http.NewRequest("DELETE", path.Join(spec.BasePath(), "/pets/123"), new(eofReader))
if assert.NoError(t, err) {
req.Header.Set("Accept", "*/*")
req.Header.Set("Content-Type", runtime.JSONMime)
req.ContentLength = 1
ri, ok := ctx.RouteInfo(req)
if assert.True(t, ok) {
err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error {
defer r.Body.Close()
var data interface{}
err := runtime.JSONConsumer().Consume(r.Body, &data)
_ = data
return err
}))
assert.Error(t, err)
assert.Equal(t, io.EOF, err)
}
}
}

View File

@@ -0,0 +1,526 @@
// 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 middleware
import (
"log"
"net/http"
"os"
"strings"
"sync"
"github.com/go-openapi/analysis"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware/untyped"
"github.com/go-openapi/runtime/security"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/gorilla/context"
)
// Debug when true turns on verbose logging
var Debug = os.Getenv("SWAGGER_DEBUG") != "" || os.Getenv("DEBUG") != ""
func debugLog(format string, args ...interface{}) {
if Debug {
log.Printf(format, args...)
}
}
// A Builder can create middlewares
type Builder func(http.Handler) http.Handler
// PassthroughBuilder returns the handler, aka the builder identity function
func PassthroughBuilder(handler http.Handler) http.Handler { return handler }
// RequestBinder is an interface for types to implement
// when they want to be able to bind from a request
type RequestBinder interface {
BindRequest(*http.Request, *MatchedRoute) error
}
// Responder is an interface for types to implement
// when they want to be considered for writing HTTP responses
type Responder interface {
WriteResponse(http.ResponseWriter, runtime.Producer)
}
// ResponderFunc wraps a func as a Responder interface
type ResponderFunc func(http.ResponseWriter, runtime.Producer)
// WriteResponse writes to the response
func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr runtime.Producer) {
fn(rw, pr)
}
// Context is a type safe wrapper around an untyped request context
// used throughout to store request context with the gorilla context module
type Context struct {
spec *loads.Document
analyzer *analysis.Spec
api RoutableAPI
router Router
formats strfmt.Registry
}
type routableUntypedAPI struct {
api *untyped.API
hlock *sync.Mutex
handlers map[string]map[string]http.Handler
defaultConsumes string
defaultProduces string
}
func newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *Context) *routableUntypedAPI {
var handlers map[string]map[string]http.Handler
if spec == nil || api == nil {
return nil
}
analyzer := analysis.New(spec.Spec())
for method, hls := range analyzer.Operations() {
um := strings.ToUpper(method)
for path, op := range hls {
schemes := analyzer.SecurityDefinitionsFor(op)
if oh, ok := api.OperationHandlerFor(method, path); ok {
if handlers == nil {
handlers = make(map[string]map[string]http.Handler)
}
if b, ok := handlers[um]; !ok || b == nil {
handlers[um] = make(map[string]http.Handler)
}
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// lookup route info in the context
route, _ := context.RouteInfo(r)
// bind and validate the request using reflection
bound, validation := context.BindAndValidate(r, route)
if validation != nil {
context.Respond(w, r, route.Produces, route, validation)
return
}
// actually handle the request
result, err := oh.Handle(bound)
if err != nil {
// respond with failure
context.Respond(w, r, route.Produces, route, err)
return
}
// respond with success
context.Respond(w, r, route.Produces, route, result)
})
if len(schemes) > 0 {
handler = newSecureAPI(context, handler)
}
handlers[um][path] = handler
}
}
}
return &routableUntypedAPI{
api: api,
hlock: new(sync.Mutex),
handlers: handlers,
defaultProduces: api.DefaultProduces,
defaultConsumes: api.DefaultConsumes,
}
}
func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {
r.hlock.Lock()
paths, ok := r.handlers[strings.ToUpper(method)]
if !ok {
r.hlock.Unlock()
return nil, false
}
handler, ok := paths[path]
r.hlock.Unlock()
return handler, ok
}
func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
return r.api.ServeError
}
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
return r.api.ConsumersFor(mediaTypes)
}
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
return r.api.ProducersFor(mediaTypes)
}
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
return r.api.AuthenticatorsFor(schemes)
}
func (r *routableUntypedAPI) Formats() strfmt.Registry {
return r.api.Formats()
}
func (r *routableUntypedAPI) DefaultProduces() string {
return r.defaultProduces
}
func (r *routableUntypedAPI) DefaultConsumes() string {
return r.defaultConsumes
}
// NewRoutableContext creates a new context for a routable API
func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Router) *Context {
var an *analysis.Spec
if spec != nil {
an = analysis.New(spec.Spec())
}
ctx := &Context{spec: spec, api: routableAPI, analyzer: an}
return ctx
}
// NewContext creates a new context wrapper
func NewContext(spec *loads.Document, api *untyped.API, routes Router) *Context {
var an *analysis.Spec
if spec != nil {
an = analysis.New(spec.Spec())
}
ctx := &Context{spec: spec, analyzer: an}
ctx.api = newRoutableUntypedAPI(spec, api, ctx)
return ctx
}
// Serve serves the specified spec with the specified api registrations as a http.Handler
func Serve(spec *loads.Document, api *untyped.API) http.Handler {
return ServeWithBuilder(spec, api, PassthroughBuilder)
}
// ServeWithBuilder serves the specified spec with the specified api registrations as a http.Handler that is decorated
// by the Builder
func ServeWithBuilder(spec *loads.Document, api *untyped.API, builder Builder) http.Handler {
context := NewContext(spec, api, nil)
return context.APIHandler(builder)
}
type contextKey int8
const (
_ contextKey = iota
ctxContentType
ctxResponseFormat
ctxMatchedRoute
ctxAllowedMethods
ctxBoundParams
ctxSecurityPrincipal
ctxSecurityScopes
ctxConsumer
)
type contentTypeValue struct {
MediaType string
Charset string
}
// BasePath returns the base path for this API
func (c *Context) BasePath() string {
return c.spec.BasePath()
}
// RequiredProduces returns the accepted content types for responses
func (c *Context) RequiredProduces() []string {
return c.analyzer.RequiredProduces()
}
// BindValidRequest binds a params object to a request but only when the request is valid
// if the request is not valid an error will be returned
func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {
var res []error
requestContentType := "*/*"
// check and validate content type, select consumer
if runtime.HasBody(request) {
ct, _, err := runtime.ContentType(request.Header)
if err != nil {
res = append(res, err)
} else {
if err := validateContentType(route.Consumes, ct); err != nil {
res = append(res, err)
}
if len(res) == 0 {
cons, ok := route.Consumers[ct]
if !ok {
res = append(res, errors.New(500, "no consumer registered for %s", ct))
} else {
route.Consumer = cons
requestContentType = ct
}
}
}
}
// check and validate the response format
if len(res) == 0 && runtime.HasBody(request) {
if str := NegotiateContentType(request, route.Produces, requestContentType); str == "" {
res = append(res, errors.InvalidResponseFormat(request.Header.Get(runtime.HeaderAccept), route.Produces))
}
}
// now bind the request with the provided binder
// it's assumed the binder will also validate the request and return an error if the
// request is invalid
if binder != nil && len(res) == 0 {
if err := binder.BindRequest(request, route); err != nil {
return err
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContentType gets the parsed value of a content type
func (c *Context) ContentType(request *http.Request) (string, string, error) {
if v, ok := context.GetOk(request, ctxContentType); ok {
if val, ok := v.(*contentTypeValue); ok {
return val.MediaType, val.Charset, nil
}
}
mt, cs, err := runtime.ContentType(request.Header)
if err != nil {
return "", "", err
}
context.Set(request, ctxContentType, &contentTypeValue{mt, cs})
return mt, cs, nil
}
// LookupRoute looks a route up and returns true when it is found
func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {
if route, ok := c.router.Lookup(request.Method, request.URL.EscapedPath()); ok {
return route, ok
}
return nil, false
}
// RouteInfo tries to match a route for this request
func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, bool) {
if v, ok := context.GetOk(request, ctxMatchedRoute); ok {
if val, ok := v.(*MatchedRoute); ok {
return val, ok
}
}
if route, ok := c.LookupRoute(request); ok {
context.Set(request, ctxMatchedRoute, route)
return route, ok
}
return nil, false
}
// ResponseFormat negotiates the response content type
func (c *Context) ResponseFormat(r *http.Request, offers []string) string {
if v, ok := context.GetOk(r, ctxResponseFormat); ok {
if val, ok := v.(string); ok {
return val
}
}
format := NegotiateContentType(r, offers, "")
if format != "" {
context.Set(r, ctxResponseFormat, format)
}
return format
}
// AllowedMethods gets the allowed methods for the path of this request
func (c *Context) AllowedMethods(request *http.Request) []string {
return c.router.OtherMethods(request.Method, request.URL.EscapedPath())
}
// Authorize authorizes the request
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, error) {
if route == nil || len(route.Authenticators) == 0 {
return nil, nil
}
if v, ok := context.GetOk(request, ctxSecurityPrincipal); ok {
return v, nil
}
var lastError error
for scheme, authenticator := range route.Authenticators {
applies, usr, err := authenticator.Authenticate(&security.ScopedAuthRequest{
Request: request,
RequiredScopes: route.Scopes[scheme],
})
if !applies || err != nil || usr == nil {
if err != nil {
lastError = err
}
continue
}
context.Set(request, ctxSecurityPrincipal, usr)
context.Set(request, ctxSecurityScopes, route.Scopes[scheme])
return usr, nil
}
if lastError != nil {
return nil, lastError
}
return nil, errors.Unauthenticated("invalid credentials")
}
// BindAndValidate binds and validates the request
func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, error) {
if v, ok := context.GetOk(request, ctxBoundParams); ok {
if val, ok := v.(*validation); ok {
debugLog("got cached validation (valid: %t)", len(val.result) == 0)
if len(val.result) > 0 {
return val.bound, errors.CompositeValidationError(val.result...)
}
return val.bound, nil
}
}
result := validateRequest(c, request, matched)
if result != nil {
context.Set(request, ctxBoundParams, result)
}
if len(result.result) > 0 {
return result.bound, errors.CompositeValidationError(result.result...)
}
debugLog("no validation errors found")
return result.bound, nil
}
// NotFound the default not found responder for when no route has been matched yet
func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
c.Respond(rw, r, []string{c.api.DefaultProduces()}, nil, errors.NotFound("not found"))
}
// Respond renders the response after doing some content negotiation
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
offers := []string{}
for _, mt := range produces {
if mt != c.api.DefaultProduces() {
offers = append(offers, mt)
}
}
// the default producer is last so more specific producers take precedence
offers = append(offers, c.api.DefaultProduces())
format := c.ResponseFormat(r, offers)
rw.Header().Set(runtime.HeaderContentType, format)
if resp, ok := data.(Responder); ok {
producers := route.Producers
prod, ok := producers[format]
if !ok {
prods := c.api.ProducersFor([]string{c.api.DefaultProduces()})
pr, ok := prods[c.api.DefaultProduces()]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
}
prod = pr
}
resp.WriteResponse(rw, prod)
return
}
if err, ok := data.(error); ok {
if format == "" {
rw.Header().Set(runtime.HeaderContentType, runtime.JSONMime)
}
if route == nil || route.Operation == nil {
c.api.ServeErrorFor("")(rw, r, err)
return
}
c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
return
}
if route == nil || route.Operation == nil {
rw.WriteHeader(200)
if r.Method == "HEAD" {
return
}
producers := c.api.ProducersFor(offers)
prod, ok := producers[format]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
}
if err := prod.Produce(rw, data); err != nil {
panic(err) // let the recovery middleware deal with this
}
return
}
if _, code, ok := route.Operation.SuccessResponse(); ok {
rw.WriteHeader(code)
if code == 204 || r.Method == "HEAD" {
return
}
producers := route.Producers
prod, ok := producers[format]
if !ok {
if !ok {
prods := c.api.ProducersFor([]string{c.api.DefaultProduces()})
pr, ok := prods[c.api.DefaultProduces()]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
}
prod = pr
}
}
if err := prod.Produce(rw, data); err != nil {
panic(err) // let the recovery middleware deal with this
}
return
}
c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
}
// APIHandler returns a handler to serve the API, this includes a swagger spec, router and the contract defined in the swagger spec
func (c *Context) APIHandler(builder Builder) http.Handler {
b := builder
if b == nil {
b = PassthroughBuilder
}
var title string
sp := c.spec.Spec()
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
title = sp.Info.Title
}
redocOpts := RedocOpts{
BasePath: c.BasePath(),
Title: title,
}
return Spec("", c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(builder)))
}
// RoutesHandler returns a handler to serve the API, just the routes and the contract defined in the swagger spec
func (c *Context) RoutesHandler(builder Builder) http.Handler {
b := builder
if b == nil {
b = PassthroughBuilder
}
return NewRouter(c, b(NewOperationExecutor(c)))
}

View File

@@ -0,0 +1,395 @@
// 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 middleware
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-openapi/loads"
"github.com/go-openapi/loads/fmts"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/internal/testing/petstore"
"github.com/go-openapi/runtime/middleware/untyped"
"github.com/gorilla/context"
"github.com/stretchr/testify/assert"
)
type stubOperationHandler struct {
}
func (s *stubOperationHandler) ParameterModel() interface{} {
return nil
}
func (s *stubOperationHandler) Handle(params interface{}) (interface{}, error) {
return nil, nil
}
type testBinder struct {
}
func (t *testBinder) BindRequest(r *http.Request, m *MatchedRoute) error {
return nil
}
func init() {
loads.AddLoader(fmts.YAMLMatcher, fmts.YAMLDoc)
}
func TestContentType_Issue264(t *testing.T) {
swspec, err := loads.Spec("../fixtures/bugs/264/swagger.yml")
if assert.NoError(t, err) {
api := untyped.NewAPI(swspec)
api.RegisterConsumer("application/json", runtime.JSONConsumer())
api.RegisterProducer("application/json", runtime.JSONProducer())
api.RegisterOperation("delete", "/key/{id}", new(stubOperationHandler))
handler := Serve(swspec, api)
request, _ := http.NewRequest("DELETE", "/key/1", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
}
}
func TestServe(t *testing.T) {
spec, api := petstore.NewAPI(t)
handler := Serve(spec, api)
// serve spec document
request, _ := http.NewRequest("GET", "http://localhost:8080/swagger.json", nil)
request.Header.Add("Content-Type", runtime.JSONMime)
request.Header.Add("Accept", runtime.JSONMime)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
request, _ = http.NewRequest("GET", "http://localhost:8080/swagger-ui", nil)
recorder = httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
assert.Equal(t, 404, recorder.Code)
}
func TestContextAuthorize(t *testing.T) {
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
ctx.router = DefaultRouter(spec, ctx.api)
request, _ := runtime.JSONRequest("GET", "/api/pets", nil)
v, ok := context.GetOk(request, ctxSecurityPrincipal)
assert.False(t, ok)
assert.Nil(t, v)
ri, ok := ctx.RouteInfo(request)
assert.True(t, ok)
p, err := ctx.Authorize(request, ri)
assert.Error(t, err)
assert.Nil(t, p)
v, ok = context.GetOk(request, ctxSecurityPrincipal)
assert.False(t, ok)
assert.Nil(t, v)
request.SetBasicAuth("wrong", "wrong")
p, err = ctx.Authorize(request, ri)
assert.Error(t, err)
assert.Nil(t, p)
v, ok = context.GetOk(request, ctxSecurityPrincipal)
assert.False(t, ok)
assert.Nil(t, v)
request.SetBasicAuth("admin", "admin")
p, err = ctx.Authorize(request, ri)
assert.NoError(t, err)
assert.Equal(t, "admin", p)
v, ok = context.GetOk(request, ctxSecurityPrincipal)
assert.True(t, ok)
assert.Equal(t, "admin", v)
request.SetBasicAuth("doesn't matter", "doesn't")
pp, rr := ctx.Authorize(request, ri)
assert.Equal(t, p, pp)
assert.Equal(t, err, rr)
}
func TestContextNegotiateContentType(t *testing.T) {
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
ctx.router = DefaultRouter(spec, ctx.api)
request, _ := http.NewRequest("POST", "/api/pets", nil)
// request.Header.Add("Accept", "*/*")
request.Header.Add("content-type", "text/html")
v, ok := context.GetOk(request, ctxBoundParams)
assert.False(t, ok)
assert.Nil(t, v)
ri, _ := ctx.RouteInfo(request)
res := NegotiateContentType(request, ri.Produces, "")
assert.Equal(t, "", res)
res2 := NegotiateContentType(request, ri.Produces, "text/plain")
assert.Equal(t, "text/plain", res2)
}
func TestContextBindAndValidate(t *testing.T) {
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
ctx.router = DefaultRouter(spec, ctx.api)
request, _ := http.NewRequest("POST", "/api/pets", nil)
request.Header.Add("Accept", "*/*")
request.Header.Add("content-type", "text/html")
request.ContentLength = 1
v, ok := context.GetOk(request, ctxBoundParams)
assert.False(t, ok)
assert.Nil(t, v)
ri, _ := ctx.RouteInfo(request)
data, result := ctx.BindAndValidate(request, ri) // this requires a much more thorough test
assert.NotNil(t, data)
assert.NotNil(t, result)
v, ok = context.GetOk(request, ctxBoundParams)
assert.True(t, ok)
assert.NotNil(t, v)
dd, rr := ctx.BindAndValidate(request, ri)
assert.Equal(t, data, dd)
assert.Equal(t, result, rr)
}
func TestContextRender(t *testing.T) {
ct := runtime.JSONMime
spec, api := petstore.NewAPI(t)
assert.NotNil(t, spec)
assert.NotNil(t, api)
ctx := NewContext(spec, api, nil)
ctx.router = DefaultRouter(spec, ctx.api)
request, _ := http.NewRequest("GET", "pets", nil)
request.Header.Set(runtime.HeaderAccept, ct)
ri, _ := ctx.RouteInfo(request)
recorder := httptest.NewRecorder()
ctx.Respond(recorder, request, []string{ct}, ri, map[string]interface{}{"name": "hello"})
assert.Equal(t, 200, recorder.Code)
assert.Equal(t, "{\"name\":\"hello\"}\n", recorder.Body.String())
recorder = httptest.NewRecorder()
ctx.Respond(recorder, request, []string{ct}, ri, errors.New("this went wrong"))
assert.Equal(t, 500, recorder.Code)
// recorder = httptest.NewRecorder()
// assert.Panics(t, func() { ctx.Respond(recorder, request, []string{ct}, ri, map[int]interface{}{1: "hello"}) })
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "pets", nil)
assert.Panics(t, func() { ctx.Respond(recorder, request, []string{}, ri, map[string]interface{}{"name": "hello"}) })
request, _ = http.NewRequest("GET", "/pets", nil)
request.Header.Set(runtime.HeaderAccept, ct)
ri, _ = ctx.RouteInfo(request)
recorder = httptest.NewRecorder()
ctx.Respond(recorder, request, []string{ct}, ri, map[string]interface{}{"name": "hello"})
assert.Equal(t, 200, recorder.Code)
assert.Equal(t, "{\"name\":\"hello\"}\n", recorder.Body.String())
recorder = httptest.NewRecorder()
ctx.Respond(recorder, request, []string{ct}, ri, errors.New("this went wrong"))
assert.Equal(t, 500, recorder.Code)
// recorder = httptest.NewRecorder()
// assert.Panics(t, func() { ctx.Respond(recorder, request, []string{ct}, ri, map[int]interface{}{1: "hello"}) })
// recorder = httptest.NewRecorder()
// request, _ = http.NewRequest("GET", "/pets", nil)
// assert.Panics(t, func() { ctx.Respond(recorder, request, []string{}, ri, map[string]interface{}{"name": "hello"}) })
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("DELETE", "/api/pets/1", nil)
ri, _ = ctx.RouteInfo(request)
ctx.Respond(recorder, request, ri.Produces, ri, nil)
assert.Equal(t, 204, recorder.Code)
}
func TestContextValidResponseFormat(t *testing.T) {
ct := "application/json"
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
ctx.router = DefaultRouter(spec, ctx.api)
request, _ := http.NewRequest("GET", "http://localhost:8080", nil)
request.Header.Set(runtime.HeaderAccept, ct)
// check there's nothing there
cached, ok := context.GetOk(request, ctxResponseFormat)
assert.False(t, ok)
assert.Empty(t, cached)
// trigger the parse
mt := ctx.ResponseFormat(request, []string{ct})
assert.Equal(t, ct, mt)
// check it was cached
cached, ok = context.GetOk(request, ctxResponseFormat)
assert.True(t, ok)
assert.Equal(t, ct, cached)
// check if the cast works and fetch from cache too
mt = ctx.ResponseFormat(request, []string{ct})
assert.Equal(t, ct, mt)
}
func TestContextInvalidResponseFormat(t *testing.T) {
ct := "application/x-yaml"
other := "application/sgml"
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
ctx.router = DefaultRouter(spec, ctx.api)
request, _ := http.NewRequest("GET", "http://localhost:8080", nil)
request.Header.Set(runtime.HeaderAccept, ct)
// check there's nothing there
cached, ok := context.GetOk(request, ctxResponseFormat)
assert.False(t, ok)
assert.Empty(t, cached)
// trigger the parse
mt := ctx.ResponseFormat(request, []string{other})
assert.Empty(t, mt)
// check it was cached
cached, ok = context.GetOk(request, ctxResponseFormat)
assert.False(t, ok)
assert.Empty(t, cached)
// check if the cast works and fetch from cache too
mt = ctx.ResponseFormat(request, []string{other})
assert.Empty(t, mt)
}
func TestContextValidRoute(t *testing.T) {
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
ctx.router = DefaultRouter(spec, ctx.api)
request, _ := http.NewRequest("GET", "/api/pets", nil)
// check there's nothing there
_, ok := context.GetOk(request, ctxMatchedRoute)
assert.False(t, ok)
matched, ok := ctx.RouteInfo(request)
assert.True(t, ok)
assert.NotNil(t, matched)
// check it was cached
_, ok = context.GetOk(request, ctxMatchedRoute)
assert.True(t, ok)
matched, ok = ctx.RouteInfo(request)
assert.True(t, ok)
assert.NotNil(t, matched)
}
func TestContextInvalidRoute(t *testing.T) {
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
ctx.router = DefaultRouter(spec, ctx.api)
request, _ := http.NewRequest("DELETE", "pets", nil)
// check there's nothing there
_, ok := context.GetOk(request, ctxMatchedRoute)
assert.False(t, ok)
matched, ok := ctx.RouteInfo(request)
assert.False(t, ok)
assert.Nil(t, matched)
// check it was cached
_, ok = context.GetOk(request, ctxMatchedRoute)
assert.False(t, ok)
matched, ok = ctx.RouteInfo(request)
assert.False(t, ok)
assert.Nil(t, matched)
}
func TestContextValidContentType(t *testing.T) {
ct := "application/json"
ctx := NewContext(nil, nil, nil)
request, _ := http.NewRequest("GET", "http://localhost:8080", nil)
request.Header.Set(runtime.HeaderContentType, ct)
// check there's nothing there
_, ok := context.GetOk(request, ctxContentType)
assert.False(t, ok)
// trigger the parse
mt, _, err := ctx.ContentType(request)
assert.NoError(t, err)
assert.Equal(t, ct, mt)
// check it was cached
_, ok = context.GetOk(request, ctxContentType)
assert.True(t, ok)
// check if the cast works and fetch from cache too
mt, _, err = ctx.ContentType(request)
assert.NoError(t, err)
assert.Equal(t, ct, mt)
}
func TestContextInvalidContentType(t *testing.T) {
ct := "application("
ctx := NewContext(nil, nil, nil)
request, _ := http.NewRequest("GET", "http://localhost:8080", nil)
request.Header.Set(runtime.HeaderContentType, ct)
// check there's nothing there
_, ok := context.GetOk(request, ctxContentType)
assert.False(t, ok)
// trigger the parse
mt, _, err := ctx.ContentType(request)
assert.Error(t, err)
assert.Empty(t, mt)
// check it was not cached
_, ok = context.GetOk(request, ctxContentType)
assert.False(t, ok)
// check if the failure continues
_, _, err = ctx.ContentType(request)
assert.Error(t, err)
}

View File

@@ -0,0 +1,19 @@
Copyright (c) 2014 Naoya Inada <naoina@kuune.org>
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.

View File

@@ -0,0 +1,180 @@
# Denco [![Build Status](https://travis-ci.org/naoina/denco.png?branch=master)](https://travis-ci.org/naoina/denco)
The fast and flexible HTTP request router for [Go](http://golang.org).
Denco is based on Double-Array implementation of [Kocha-urlrouter](https://github.com/naoina/kocha-urlrouter).
However, Denco is optimized and some features added.
## Features
* Fast (See [go-http-routing-benchmark](https://github.com/naoina/go-http-routing-benchmark))
* [URL patterns](#url-patterns) (`/foo/:bar` and `/foo/*wildcard`)
* Small (but enough) URL router API
* HTTP request multiplexer like `http.ServeMux`
## Installation
go get -u github.com/go-openapi/runtime/middleware/denco
## Using as HTTP request multiplexer
```go
package main
import (
"fmt"
"log"
"net/http"
"github.com/go-openapi/runtime/middleware/denco"
)
func Index(w http.ResponseWriter, r *http.Request, params denco.Params) {
fmt.Fprintf(w, "Welcome to Denco!\n")
}
func User(w http.ResponseWriter, r *http.Request, params denco.Params) {
fmt.Fprintf(w, "Hello %s!\n", params.Get("name"))
}
func main() {
mux := denco.NewMux()
handler, err := mux.Build([]denco.Handler{
mux.GET("/", Index),
mux.GET("/user/:name", User),
mux.POST("/user/:name", User),
})
if err != nil {
panic(err)
}
log.Fatal(http.ListenAndServe(":8080", handler))
}
```
## Using as URL router
```go
package main
import (
"fmt"
"github.com/go-openapi/runtime/middleware/denco"
)
type route struct {
name string
}
func main() {
router := denco.New()
router.Build([]denco.Record{
{"/", &route{"root"}},
{"/user/:id", &route{"user"}},
{"/user/:name/:id", &route{"username"}},
{"/static/*filepath", &route{"static"}},
})
data, params, found := router.Lookup("/")
// print `&main.route{name:"root"}, denco.Params(nil), true`.
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
data, params, found = router.Lookup("/user/hoge")
// print `&main.route{name:"user"}, denco.Params{denco.Param{Name:"id", Value:"hoge"}}, true`.
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
data, params, found = router.Lookup("/user/hoge/7")
// print `&main.route{name:"username"}, denco.Params{denco.Param{Name:"name", Value:"hoge"}, denco.Param{Name:"id", Value:"7"}}, true`.
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
data, params, found = router.Lookup("/static/path/to/file")
// print `&main.route{name:"static"}, denco.Params{denco.Param{Name:"filepath", Value:"path/to/file"}}, true`.
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
}
```
See [Godoc](http://godoc.org/github.com/go-openapi/runtime/middleware/denco) for more details.
## Getting the value of path parameter
You can get the value of path parameter by 2 ways.
1. Using [`denco.Params.Get`](http://godoc.org/github.com/go-openapi/runtime/middleware/denco#Params.Get) method
2. Find by loop
```go
package main
import (
"fmt"
"github.com/go-openapi/runtime/middleware/denco"
)
func main() {
router := denco.New()
if err := router.Build([]denco.Record{
{"/user/:name/:id", "route1"},
}); err != nil {
panic(err)
}
// 1. Using denco.Params.Get method.
_, params, _ := router.Lookup("/user/alice/1")
name := params.Get("name")
if name != "" {
fmt.Printf("Hello %s.\n", name) // prints "Hello alice.".
}
// 2. Find by loop.
for _, param := range params {
if param.Name == "name" {
fmt.Printf("Hello %s.\n", name) // prints "Hello alice.".
}
}
}
```
## URL patterns
Denco's route matching strategy is "most nearly matching".
When routes `/:name` and `/alice` have been built, URI `/alice` matches the route `/alice`, not `/:name`.
Because URI `/alice` is more match with the route `/alice` than `/:name`.
For more example, when routes below have been built:
```
/user/alice
/user/:name
/user/:name/:id
/user/alice/:id
/user/:id/bob
```
Routes matching are:
```
/user/alice => "/user/alice" (no match with "/user/:name")
/user/bob => "/user/:name"
/user/naoina/1 => "/user/:name/1"
/user/alice/1 => "/user/alice/:id" (no match with "/user/:name/:id")
/user/1/bob => "/user/:id/bob" (no match with "/user/:name/:id")
/user/alice/bob => "/user/alice/:id" (no match with "/user/:name/:id" and "/user/:id/bob")
```
## Limitation
Denco has some limitations below.
* Number of param records (such as `/:name`) must be less than 2^22
* Number of elements of internal slice must be less than 2^22
## Benchmarks
cd $GOPATH/github.com/go-openapi/runtime/middleware/denco
go test -bench . -benchmem
## License
Denco is licensed under the MIT License.

View File

@@ -0,0 +1,452 @@
// Package denco provides fast URL router.
package denco
import (
"fmt"
"sort"
"strings"
)
const (
// ParamCharacter is a special character for path parameter.
ParamCharacter = ':'
// WildcardCharacter is a special character for wildcard path parameter.
WildcardCharacter = '*'
// TerminationCharacter is a special character for end of path.
TerminationCharacter = '#'
// MaxSize is max size of records and internal slice.
MaxSize = (1 << 22) - 1
)
// Router represents a URL router.
type Router struct {
// SizeHint expects the maximum number of path parameters in records to Build.
// SizeHint will be used to determine the capacity of the memory to allocate.
// By default, SizeHint will be determined from given records to Build.
SizeHint int
static map[string]interface{}
param *doubleArray
}
// New returns a new Router.
func New() *Router {
return &Router{
SizeHint: -1,
static: make(map[string]interface{}),
param: newDoubleArray(),
}
}
// Lookup returns data and path parameters that associated with path.
// params is a slice of the Param that arranged in the order in which parameters appeared.
// e.g. when built routing path is "/path/to/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
func (rt *Router) Lookup(path string) (data interface{}, params Params, found bool) {
if data, found := rt.static[path]; found {
return data, nil, true
}
if len(rt.param.node) == 1 {
return nil, nil, false
}
nd, params, found := rt.param.lookup(path, make([]Param, 0, rt.SizeHint), 1)
if !found {
return nil, nil, false
}
for i := 0; i < len(params); i++ {
params[i].Name = nd.paramNames[i]
}
return nd.data, params, true
}
// Build builds URL router from records.
func (rt *Router) Build(records []Record) error {
statics, params := makeRecords(records)
if len(params) > MaxSize {
return fmt.Errorf("denco: too many records")
}
if rt.SizeHint < 0 {
rt.SizeHint = 0
for _, p := range params {
size := 0
for _, k := range p.Key {
if k == ParamCharacter || k == WildcardCharacter {
size++
}
}
if size > rt.SizeHint {
rt.SizeHint = size
}
}
}
for _, r := range statics {
rt.static[r.Key] = r.Value
}
if err := rt.param.build(params, 1, 0, make(map[int]struct{})); err != nil {
return err
}
return nil
}
// Param represents name and value of path parameter.
type Param struct {
Name string
Value string
}
// Params represents the name and value of path parameters.
type Params []Param
// Get gets the first value associated with the given name.
// If there are no values associated with the key, Get returns "".
func (ps Params) Get(name string) string {
for _, p := range ps {
if p.Name == name {
return p.Value
}
}
return ""
}
type doubleArray struct {
bc []baseCheck
node []*node
}
func newDoubleArray() *doubleArray {
return &doubleArray{
bc: []baseCheck{0},
node: []*node{nil}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
}
}
// baseCheck contains BASE, CHECK and Extra flags.
// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK.
//
// BASE (22bit) | Extra flags (2bit) | CHECK (8bit)
// |----------------------|--|--------|
// 32 10 8 0
type baseCheck uint32
func (bc baseCheck) Base() int {
return int(bc >> 10)
}
func (bc *baseCheck) SetBase(base int) {
*bc |= baseCheck(base) << 10
}
func (bc baseCheck) Check() byte {
return byte(bc)
}
func (bc *baseCheck) SetCheck(check byte) {
*bc |= baseCheck(check)
}
func (bc baseCheck) IsEmpty() bool {
return bc&0xfffffcff == 0
}
func (bc baseCheck) IsSingleParam() bool {
return bc&paramTypeSingle == paramTypeSingle
}
func (bc baseCheck) IsWildcardParam() bool {
return bc&paramTypeWildcard == paramTypeWildcard
}
func (bc baseCheck) IsAnyParam() bool {
return bc&paramTypeAny != 0
}
func (bc *baseCheck) SetSingleParam() {
*bc |= (1 << 8)
}
func (bc *baseCheck) SetWildcardParam() {
*bc |= (1 << 9)
}
const (
paramTypeSingle = 0x0100
paramTypeWildcard = 0x0200
paramTypeAny = 0x0300
)
func (da *doubleArray) lookup(path string, params []Param, idx int) (*node, []Param, bool) {
indices := make([]uint64, 0, 1)
for i := 0; i < len(path); i++ {
if da.bc[idx].IsAnyParam() {
indices = append(indices, (uint64(i)<<32)|(uint64(idx)&0xffffffff))
}
c := path[i]
if idx = nextIndex(da.bc[idx].Base(), c); idx >= len(da.bc) || da.bc[idx].Check() != c {
goto BACKTRACKING
}
}
if next := nextIndex(da.bc[idx].Base(), TerminationCharacter); next < len(da.bc) && da.bc[next].Check() == TerminationCharacter {
return da.node[da.bc[next].Base()], params, true
}
BACKTRACKING:
for j := len(indices) - 1; j >= 0; j-- {
i, idx := int(indices[j]>>32), int(indices[j]&0xffffffff)
if da.bc[idx].IsSingleParam() {
idx := nextIndex(da.bc[idx].Base(), ParamCharacter)
if idx >= len(da.bc) {
break
}
next := NextSeparator(path, i)
params := append(params, Param{Value: path[i:next]})
if nd, params, found := da.lookup(path[next:], params, idx); found {
return nd, params, true
}
}
if da.bc[idx].IsWildcardParam() {
idx := nextIndex(da.bc[idx].Base(), WildcardCharacter)
params := append(params, Param{Value: path[i:]})
return da.node[da.bc[idx].Base()], params, true
}
}
return nil, nil, false
}
// build builds double-array from records.
func (da *doubleArray) build(srcs []*record, idx, depth int, usedBase map[int]struct{}) error {
sort.Stable(recordSlice(srcs))
base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase)
if err != nil {
return err
}
if leaf != nil {
nd, err := makeNode(leaf)
if err != nil {
return err
}
da.bc[idx].SetBase(len(da.node))
da.node = append(da.node, nd)
}
for _, sib := range siblings {
da.setCheck(nextIndex(base, sib.c), sib.c)
}
for _, sib := range siblings {
records := srcs[sib.start:sib.end]
switch sib.c {
case ParamCharacter:
for _, r := range records {
next := NextSeparator(r.Key, depth+1)
name := r.Key[depth+1 : next]
r.paramNames = append(r.paramNames, name)
r.Key = r.Key[next:]
}
da.bc[idx].SetSingleParam()
if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
return err
}
case WildcardCharacter:
r := records[0]
name := r.Key[depth+1 : len(r.Key)-1]
r.paramNames = append(r.paramNames, name)
r.Key = ""
da.bc[idx].SetWildcardParam()
if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
return err
}
default:
if err := da.build(records, nextIndex(base, sib.c), depth+1, usedBase); err != nil {
return err
}
}
}
return nil
}
// setBase sets BASE.
func (da *doubleArray) setBase(i, base int) {
da.bc[i].SetBase(base)
}
// setCheck sets CHECK.
func (da *doubleArray) setCheck(i int, check byte) {
da.bc[i].SetCheck(check)
}
// findEmptyIndex returns an index of unused BASE/CHECK node.
func (da *doubleArray) findEmptyIndex(start int) int {
i := start
for ; i < len(da.bc); i++ {
if da.bc[i].IsEmpty() {
break
}
}
return i
}
// findBase returns good BASE.
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) {
for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) {
base = nextIndex(idx, firstChar)
if _, used := usedBase[base]; used {
continue
}
i := 0
for ; i < len(siblings); i++ {
next := nextIndex(base, siblings[i].c)
if len(da.bc) <= next {
da.bc = append(da.bc, make([]baseCheck, next-len(da.bc)+1)...)
}
if !da.bc[next].IsEmpty() {
break
}
}
if i == len(siblings) {
break
}
}
usedBase[base] = struct{}{}
return base
}
func (da *doubleArray) arrange(records []*record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) {
siblings, leaf, err = makeSiblings(records, depth)
if err != nil {
return -1, nil, nil, err
}
if len(siblings) < 1 {
return -1, nil, leaf, nil
}
base = da.findBase(siblings, idx, usedBase)
if base > MaxSize {
return -1, nil, nil, fmt.Errorf("denco: too many elements of internal slice")
}
da.setBase(idx, base)
return base, siblings, leaf, err
}
// node represents a node of Double-Array.
type node struct {
data interface{}
// Names of path parameters.
paramNames []string
}
// makeNode returns a new node from record.
func makeNode(r *record) (*node, error) {
dups := make(map[string]bool)
for _, name := range r.paramNames {
if dups[name] {
return nil, fmt.Errorf("denco: path parameter `%v' is duplicated in the key `%v'", name, r.Key)
}
dups[name] = true
}
return &node{data: r.Value, paramNames: r.paramNames}, nil
}
// sibling represents an intermediate data of build for Double-Array.
type sibling struct {
// An index of start of duplicated characters.
start int
// An index of end of duplicated characters.
end int
// A character of sibling.
c byte
}
// nextIndex returns a next index of array of BASE/CHECK.
func nextIndex(base int, c byte) int {
return base ^ int(c)
}
// makeSiblings returns slice of sibling.
func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, err error) {
var (
pc byte
n int
)
for i, r := range records {
if len(r.Key) <= depth {
leaf = r
continue
}
c := r.Key[depth]
switch {
case pc < c:
sib = append(sib, sibling{start: i, c: c})
case pc == c:
continue
default:
return nil, nil, fmt.Errorf("denco: BUG: routing table hasn't been sorted")
}
if n > 0 {
sib[n-1].end = i
}
pc = c
n++
}
if n == 0 {
return nil, leaf, nil
}
sib[n-1].end = len(records)
return sib, leaf, nil
}
// Record represents a record data for router construction.
type Record struct {
// Key for router construction.
Key string
// Result value for Key.
Value interface{}
}
// NewRecord returns a new Record.
func NewRecord(key string, value interface{}) Record {
return Record{
Key: key,
Value: value,
}
}
// record represents a record that use to build the Double-Array.
type record struct {
Record
paramNames []string
}
// makeRecords returns the records that use to build Double-Arrays.
func makeRecords(srcs []Record) (statics, params []*record) {
spChars := string([]byte{ParamCharacter, WildcardCharacter})
termChar := string(TerminationCharacter)
for _, r := range srcs {
if strings.ContainsAny(r.Key, spChars) {
r.Key += termChar
params = append(params, &record{Record: r})
} else {
statics = append(statics, &record{Record: r})
}
}
return statics, params
}
// recordSlice represents a slice of Record for sort and implements the sort.Interface.
type recordSlice []*record
// Len implements the sort.Interface.Len.
func (rs recordSlice) Len() int {
return len(rs)
}
// Less implements the sort.Interface.Less.
func (rs recordSlice) Less(i, j int) bool {
return rs[i].Key < rs[j].Key
}
// Swap implements the sort.Interface.Swap.
func (rs recordSlice) Swap(i, j int) {
rs[i], rs[j] = rs[j], rs[i]
}

View File

@@ -0,0 +1,178 @@
package denco_test
import (
"bytes"
"crypto/rand"
"fmt"
"math/big"
"testing"
"github.com/go-openapi/runtime/middleware/denco"
)
func BenchmarkRouterLookupStatic100(b *testing.B) {
benchmarkRouterLookupStatic(b, 100)
}
func BenchmarkRouterLookupStatic300(b *testing.B) {
benchmarkRouterLookupStatic(b, 300)
}
func BenchmarkRouterLookupStatic700(b *testing.B) {
benchmarkRouterLookupStatic(b, 700)
}
func BenchmarkRouterLookupSingleParam100(b *testing.B) {
records := makeTestSingleParamRecords(100)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingleParam300(b *testing.B) {
records := makeTestSingleParamRecords(300)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingleParam700(b *testing.B) {
records := makeTestSingleParamRecords(700)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingle2Param100(b *testing.B) {
records := makeTestSingle2ParamRecords(100)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingle2Param300(b *testing.B) {
records := makeTestSingle2ParamRecords(300)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingle2Param700(b *testing.B) {
records := makeTestSingle2ParamRecords(700)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterBuildStatic100(b *testing.B) {
records := makeTestStaticRecords(100)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildStatic300(b *testing.B) {
records := makeTestStaticRecords(300)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildStatic700(b *testing.B) {
records := makeTestStaticRecords(700)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingleParam100(b *testing.B) {
records := makeTestSingleParamRecords(100)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingleParam300(b *testing.B) {
records := makeTestSingleParamRecords(300)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingleParam700(b *testing.B) {
records := makeTestSingleParamRecords(700)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingle2Param100(b *testing.B) {
records := makeTestSingle2ParamRecords(100)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingle2Param300(b *testing.B) {
records := makeTestSingle2ParamRecords(300)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingle2Param700(b *testing.B) {
records := makeTestSingle2ParamRecords(700)
benchmarkRouterBuild(b, records)
}
func benchmarkRouterLookupStatic(b *testing.B, n int) {
b.StopTimer()
router := denco.New()
records := makeTestStaticRecords(n)
if err := router.Build(records); err != nil {
b.Fatal(err)
}
record := pickTestRecord(records)
b.StartTimer()
for i := 0; i < b.N; i++ {
if r, _, _ := router.Lookup(record.Key); r != record.Value {
b.Fail()
}
}
}
func benchmarkRouterLookupSingleParam(b *testing.B, records []denco.Record) {
router := denco.New()
if err := router.Build(records); err != nil {
b.Fatal(err)
}
record := pickTestRecord(records)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, _, found := router.Lookup(record.Key); !found {
b.Fail()
}
}
}
func benchmarkRouterBuild(b *testing.B, records []denco.Record) {
for i := 0; i < b.N; i++ {
router := denco.New()
if err := router.Build(records); err != nil {
b.Fatal(err)
}
}
}
func makeTestStaticRecords(n int) []denco.Record {
records := make([]denco.Record, n)
for i := 0; i < n; i++ {
records[i] = denco.NewRecord("/"+randomString(50), fmt.Sprintf("testroute%d", i))
}
return records
}
func makeTestSingleParamRecords(n int) []denco.Record {
records := make([]denco.Record, n)
for i := 0; i < len(records); i++ {
records[i] = denco.NewRecord(fmt.Sprintf("/user%d/:name", i), fmt.Sprintf("testroute%d", i))
}
return records
}
func makeTestSingle2ParamRecords(n int) []denco.Record {
records := make([]denco.Record, n)
for i := 0; i < len(records); i++ {
records[i] = denco.NewRecord(fmt.Sprintf("/user%d/:name/comment/:id", i), fmt.Sprintf("testroute%d", i))
}
return records
}
func pickTestRecord(records []denco.Record) denco.Record {
return records[len(records)/2]
}
func randomString(n int) string {
const srcStrings = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/"
var buf bytes.Buffer
for i := 0; i < n; i++ {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(srcStrings)-1)))
if err != nil {
panic(err)
}
buf.WriteByte(srcStrings[num.Int64()])
}
return buf.String()
}

View File

@@ -0,0 +1,524 @@
package denco_test
import (
"fmt"
"math/rand"
"reflect"
"testing"
"time"
"github.com/go-openapi/runtime/middleware/denco"
)
func routes() []denco.Record {
return []denco.Record{
{"/", "testroute0"},
{"/path/to/route", "testroute1"},
{"/path/to/other", "testroute2"},
{"/path/to/route/a", "testroute3"},
{"/path/to/:param", "testroute4"},
{"/gists/:param1/foo/:param2", "testroute12"},
{"/gists/:param1/foo/bar", "testroute11"},
{"/:param1/:param2/foo/:param3", "testroute13"},
{"/path/to/wildcard/*routepath", "testroute5"},
{"/path/to/:param1/:param2", "testroute6"},
{"/path/to/:param1/sep/:param2", "testroute7"},
{"/:year/:month/:day", "testroute8"},
{"/user/:id", "testroute9"},
{"/a/to/b/:param/*routepath", "testroute10"},
}
}
var realURIs = []denco.Record{
{"/authorizations", "/authorizations"},
{"/authorizations/:id", "/authorizations/:id"},
{"/applications/:client_id/tokens/:access_token", "/applications/:client_id/tokens/:access_token"},
{"/events", "/events"},
{"/repos/:owner/:repo/events", "/repos/:owner/:repo/events"},
{"/networks/:owner/:repo/events", "/networks/:owner/:repo/events"},
{"/orgs/:org/events", "/orgs/:org/events"},
{"/users/:user/received_events", "/users/:user/received_events"},
{"/users/:user/received_events/public", "/users/:user/received_events/public"},
{"/users/:user/events", "/users/:user/events"},
{"/users/:user/events/public", "/users/:user/events/public"},
{"/users/:user/events/orgs/:org", "/users/:user/events/orgs/:org"},
{"/feeds", "/feeds"},
{"/notifications", "/notifications"},
{"/repos/:owner/:repo/notifications", "/repos/:owner/:repo/notifications"},
{"/notifications/threads/:id", "/notifications/threads/:id"},
{"/notifications/threads/:id/subscription", "/notifications/threads/:id/subscription"},
{"/repos/:owner/:repo/stargazers", "/repos/:owner/:repo/stargazers"},
{"/users/:user/starred", "/users/:user/starred"},
{"/user/starred", "/user/starred"},
{"/user/starred/:owner/:repo", "/user/starred/:owner/:repo"},
{"/repos/:owner/:repo/subscribers", "/repos/:owner/:repo/subscribers"},
{"/users/:user/subscriptions", "/users/:user/subscriptions"},
{"/user/subscriptions", "/user/subscriptions"},
{"/repos/:owner/:repo/subscription", "/repos/:owner/:repo/subscription"},
{"/user/subscriptions/:owner/:repo", "/user/subscriptions/:owner/:repo"},
{"/users/:user/gists", "/users/:user/gists"},
{"/gists", "/gists"},
{"/gists/:id", "/gists/:id"},
{"/gists/:id/star", "/gists/:id/star"},
{"/repos/:owner/:repo/git/blobs/:sha", "/repos/:owner/:repo/git/blobs/:sha"},
{"/repos/:owner/:repo/git/commits/:sha", "/repos/:owner/:repo/git/commits/:sha"},
{"/repos/:owner/:repo/git/refs", "/repos/:owner/:repo/git/refs"},
{"/repos/:owner/:repo/git/tags/:sha", "/repos/:owner/:repo/git/tags/:sha"},
{"/repos/:owner/:repo/git/trees/:sha", "/repos/:owner/:repo/git/trees/:sha"},
{"/issues", "/issues"},
{"/user/issues", "/user/issues"},
{"/orgs/:org/issues", "/orgs/:org/issues"},
{"/repos/:owner/:repo/issues", "/repos/:owner/:repo/issues"},
{"/repos/:owner/:repo/issues/:number", "/repos/:owner/:repo/issues/:number"},
{"/repos/:owner/:repo/assignees", "/repos/:owner/:repo/assignees"},
{"/repos/:owner/:repo/assignees/:assignee", "/repos/:owner/:repo/assignees/:assignee"},
{"/repos/:owner/:repo/issues/:number/comments", "/repos/:owner/:repo/issues/:number/comments"},
{"/repos/:owner/:repo/issues/:number/events", "/repos/:owner/:repo/issues/:number/events"},
{"/repos/:owner/:repo/labels", "/repos/:owner/:repo/labels"},
{"/repos/:owner/:repo/labels/:name", "/repos/:owner/:repo/labels/:name"},
{"/repos/:owner/:repo/issues/:number/labels", "/repos/:owner/:repo/issues/:number/labels"},
{"/repos/:owner/:repo/milestones/:number/labels", "/repos/:owner/:repo/milestones/:number/labels"},
{"/repos/:owner/:repo/milestones", "/repos/:owner/:repo/milestones"},
{"/repos/:owner/:repo/milestones/:number", "/repos/:owner/:repo/milestones/:number"},
{"/emojis", "/emojis"},
{"/gitignore/templates", "/gitignore/templates"},
{"/gitignore/templates/:name", "/gitignore/templates/:name"},
{"/meta", "/meta"},
{"/rate_limit", "/rate_limit"},
{"/users/:user/orgs", "/users/:user/orgs"},
{"/user/orgs", "/user/orgs"},
{"/orgs/:org", "/orgs/:org"},
{"/orgs/:org/members", "/orgs/:org/members"},
{"/orgs/:org/members/:user", "/orgs/:org/members/:user"},
{"/orgs/:org/public_members", "/orgs/:org/public_members"},
{"/orgs/:org/public_members/:user", "/orgs/:org/public_members/:user"},
{"/orgs/:org/teams", "/orgs/:org/teams"},
{"/teams/:id", "/teams/:id"},
{"/teams/:id/members", "/teams/:id/members"},
{"/teams/:id/members/:user", "/teams/:id/members/:user"},
{"/teams/:id/repos", "/teams/:id/repos"},
{"/teams/:id/repos/:owner/:repo", "/teams/:id/repos/:owner/:repo"},
{"/user/teams", "/user/teams"},
{"/repos/:owner/:repo/pulls", "/repos/:owner/:repo/pulls"},
{"/repos/:owner/:repo/pulls/:number", "/repos/:owner/:repo/pulls/:number"},
{"/repos/:owner/:repo/pulls/:number/commits", "/repos/:owner/:repo/pulls/:number/commits"},
{"/repos/:owner/:repo/pulls/:number/files", "/repos/:owner/:repo/pulls/:number/files"},
{"/repos/:owner/:repo/pulls/:number/merge", "/repos/:owner/:repo/pulls/:number/merge"},
{"/repos/:owner/:repo/pulls/:number/comments", "/repos/:owner/:repo/pulls/:number/comments"},
{"/user/repos", "/user/repos"},
{"/users/:user/repos", "/users/:user/repos"},
{"/orgs/:org/repos", "/orgs/:org/repos"},
{"/repositories", "/repositories"},
{"/repos/:owner/:repo", "/repos/:owner/:repo"},
{"/repos/:owner/:repo/contributors", "/repos/:owner/:repo/contributors"},
{"/repos/:owner/:repo/languages", "/repos/:owner/:repo/languages"},
{"/repos/:owner/:repo/teams", "/repos/:owner/:repo/teams"},
{"/repos/:owner/:repo/tags", "/repos/:owner/:repo/tags"},
{"/repos/:owner/:repo/branches", "/repos/:owner/:repo/branches"},
{"/repos/:owner/:repo/branches/:branch", "/repos/:owner/:repo/branches/:branch"},
{"/repos/:owner/:repo/collaborators", "/repos/:owner/:repo/collaborators"},
{"/repos/:owner/:repo/collaborators/:user", "/repos/:owner/:repo/collaborators/:user"},
{"/repos/:owner/:repo/comments", "/repos/:owner/:repo/comments"},
{"/repos/:owner/:repo/commits/:sha/comments", "/repos/:owner/:repo/commits/:sha/comments"},
{"/repos/:owner/:repo/comments/:id", "/repos/:owner/:repo/comments/:id"},
{"/repos/:owner/:repo/commits", "/repos/:owner/:repo/commits"},
{"/repos/:owner/:repo/commits/:sha", "/repos/:owner/:repo/commits/:sha"},
{"/repos/:owner/:repo/readme", "/repos/:owner/:repo/readme"},
{"/repos/:owner/:repo/keys", "/repos/:owner/:repo/keys"},
{"/repos/:owner/:repo/keys/:id", "/repos/:owner/:repo/keys/:id"},
{"/repos/:owner/:repo/downloads", "/repos/:owner/:repo/downloads"},
{"/repos/:owner/:repo/downloads/:id", "/repos/:owner/:repo/downloads/:id"},
{"/repos/:owner/:repo/forks", "/repos/:owner/:repo/forks"},
{"/repos/:owner/:repo/hooks", "/repos/:owner/:repo/hooks"},
{"/repos/:owner/:repo/hooks/:id", "/repos/:owner/:repo/hooks/:id"},
{"/repos/:owner/:repo/releases", "/repos/:owner/:repo/releases"},
{"/repos/:owner/:repo/releases/:id", "/repos/:owner/:repo/releases/:id"},
{"/repos/:owner/:repo/releases/:id/assets", "/repos/:owner/:repo/releases/:id/assets"},
{"/repos/:owner/:repo/stats/contributors", "/repos/:owner/:repo/stats/contributors"},
{"/repos/:owner/:repo/stats/commit_activity", "/repos/:owner/:repo/stats/commit_activity"},
{"/repos/:owner/:repo/stats/code_frequency", "/repos/:owner/:repo/stats/code_frequency"},
{"/repos/:owner/:repo/stats/participation", "/repos/:owner/:repo/stats/participation"},
{"/repos/:owner/:repo/stats/punch_card", "/repos/:owner/:repo/stats/punch_card"},
{"/repos/:owner/:repo/statuses/:ref", "/repos/:owner/:repo/statuses/:ref"},
{"/search/repositories", "/search/repositories"},
{"/search/code", "/search/code"},
{"/search/issues", "/search/issues"},
{"/search/users", "/search/users"},
{"/legacy/issues/search/:owner/:repository/:state/:keyword", "/legacy/issues/search/:owner/:repository/:state/:keyword"},
{"/legacy/repos/search/:keyword", "/legacy/repos/search/:keyword"},
{"/legacy/user/search/:keyword", "/legacy/user/search/:keyword"},
{"/legacy/user/email/:email", "/legacy/user/email/:email"},
{"/users/:user", "/users/:user"},
{"/user", "/user"},
{"/users", "/users"},
{"/user/emails", "/user/emails"},
{"/users/:user/followers", "/users/:user/followers"},
{"/user/followers", "/user/followers"},
{"/users/:user/following", "/users/:user/following"},
{"/user/following", "/user/following"},
{"/user/following/:user", "/user/following/:user"},
{"/users/:user/following/:target_user", "/users/:user/following/:target_user"},
{"/users/:user/keys", "/users/:user/keys"},
{"/user/keys", "/user/keys"},
{"/user/keys/:id", "/user/keys/:id"},
{"/people/:userId", "/people/:userId"},
{"/people", "/people"},
{"/activities/:activityId/people/:collection", "/activities/:activityId/people/:collection"},
{"/people/:userId/people/:collection", "/people/:userId/people/:collection"},
{"/people/:userId/openIdConnect", "/people/:userId/openIdConnect"},
{"/people/:userId/activities/:collection", "/people/:userId/activities/:collection"},
{"/activities/:activityId", "/activities/:activityId"},
{"/activities", "/activities"},
{"/activities/:activityId/comments", "/activities/:activityId/comments"},
{"/comments/:commentId", "/comments/:commentId"},
{"/people/:userId/moments/:collection", "/people/:userId/moments/:collection"},
}
type testcase struct {
path string
value interface{}
params []denco.Param
found bool
}
func runLookupTest(t *testing.T, records []denco.Record, testcases []testcase) {
r := denco.New()
if err := r.Build(records); err != nil {
t.Fatal(err)
}
for _, testcase := range testcases {
data, params, found := r.Lookup(testcase.path)
if !reflect.DeepEqual(data, testcase.value) || !reflect.DeepEqual(params, denco.Params(testcase.params)) || !reflect.DeepEqual(found, testcase.found) {
t.Errorf("Router.Lookup(%q) => (%#v, %#v, %#v), want (%#v, %#v, %#v)", testcase.path, data, params, found, testcase.value, denco.Params(testcase.params), testcase.found)
}
}
}
func TestRouter_Lookup(t *testing.T) {
testcases := []testcase{
{"/", "testroute0", nil, true},
{"/gists/1323/foo/bar", "testroute11", []denco.Param{{"param1", "1323"}}, true},
{"/gists/1323/foo/133", "testroute12", []denco.Param{{"param1", "1323"}, {"param2", "133"}}, true},
{"/234/1323/foo/133", "testroute13", []denco.Param{{"param1", "234"}, {"param2", "1323"}, {"param3", "133"}}, true},
{"/path/to/route", "testroute1", nil, true},
{"/path/to/other", "testroute2", nil, true},
{"/path/to/route/a", "testroute3", nil, true},
{"/path/to/hoge", "testroute4", []denco.Param{{"param", "hoge"}}, true},
{"/path/to/wildcard/some/params", "testroute5", []denco.Param{{"routepath", "some/params"}}, true},
{"/path/to/o1/o2", "testroute6", []denco.Param{{"param1", "o1"}, {"param2", "o2"}}, true},
{"/path/to/p1/sep/p2", "testroute7", []denco.Param{{"param1", "p1"}, {"param2", "p2"}}, true},
{"/2014/01/06", "testroute8", []denco.Param{{"year", "2014"}, {"month", "01"}, {"day", "06"}}, true},
{"/user/777", "testroute9", []denco.Param{{"id", "777"}}, true},
{"/a/to/b/p1/some/wildcard/params", "testroute10", []denco.Param{{"param", "p1"}, {"routepath", "some/wildcard/params"}}, true},
{"/missing", nil, nil, false},
}
runLookupTest(t, routes(), testcases)
records := []denco.Record{
{"/", "testroute0"},
{"/:b", "testroute1"},
{"/*wildcard", "testroute2"},
}
testcases = []testcase{
{"/", "testroute0", nil, true},
{"/true", "testroute1", []denco.Param{{"b", "true"}}, true},
{"/foo/bar", "testroute2", []denco.Param{{"wildcard", "foo/bar"}}, true},
}
runLookupTest(t, records, testcases)
records = []denco.Record{
{"/networks/:owner/:repo/events", "testroute0"},
{"/orgs/:org/events", "testroute1"},
{"/notifications/threads/:id", "testroute2"},
}
testcases = []testcase{
{"/networks/:owner/:repo/events", "testroute0", []denco.Param{{"owner", ":owner"}, {"repo", ":repo"}}, true},
{"/orgs/:org/events", "testroute1", []denco.Param{{"org", ":org"}}, true},
{"/notifications/threads/:id", "testroute2", []denco.Param{{"id", ":id"}}, true},
}
runLookupTest(t, records, testcases)
runLookupTest(t, []denco.Record{
{"/", "route2"},
}, []testcase{
{"/user/alice", nil, nil, false},
})
runLookupTest(t, []denco.Record{
{"/user/:name", "route1"},
}, []testcase{
{"/", nil, nil, false},
})
runLookupTest(t, []denco.Record{
{"/*wildcard", "testroute0"},
{"/a/:b", "testroute1"},
}, []testcase{
{"/a", "testroute0", []denco.Param{{"wildcard", "a"}}, true},
})
}
func TestRouter_Lookup_withManyRoutes(t *testing.T) {
n := 1000
rand.Seed(time.Now().UnixNano())
records := make([]denco.Record, n)
for i := 0; i < n; i++ {
records[i] = denco.Record{Key: "/" + randomString(rand.Intn(50)+10), Value: fmt.Sprintf("route%d", i)}
}
router := denco.New()
if err := router.Build(records); err != nil {
t.Fatal(err)
}
for _, r := range records {
data, params, found := router.Lookup(r.Key)
if !reflect.DeepEqual(data, r.Value) || len(params) != 0 || !reflect.DeepEqual(found, true) {
t.Errorf("Router.Lookup(%q) => (%#v, %#v, %#v), want (%#v, %#v, %#v)", r.Key, data, len(params), found, r.Value, 0, true)
}
}
}
func TestRouter_Lookup_realURIs(t *testing.T) {
testcases := []testcase{
{"/authorizations", "/authorizations", nil, true},
{"/authorizations/1", "/authorizations/:id", []denco.Param{{"id", "1"}}, true},
{"/applications/1/tokens/zohRoo7e", "/applications/:client_id/tokens/:access_token", []denco.Param{{"client_id", "1"}, {"access_token", "zohRoo7e"}}, true},
{"/events", "/events", nil, true},
{"/repos/naoina/denco/events", "/repos/:owner/:repo/events", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/networks/naoina/denco/events", "/networks/:owner/:repo/events", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/orgs/something/events", "/orgs/:org/events", []denco.Param{{"org", "something"}}, true},
{"/users/naoina/received_events", "/users/:user/received_events", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/received_events/public", "/users/:user/received_events/public", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/events", "/users/:user/events", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/events/public", "/users/:user/events/public", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/events/orgs/something", "/users/:user/events/orgs/:org", []denco.Param{{"user", "naoina"}, {"org", "something"}}, true},
{"/feeds", "/feeds", nil, true},
{"/notifications", "/notifications", nil, true},
{"/repos/naoina/denco/notifications", "/repos/:owner/:repo/notifications", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/notifications/threads/1", "/notifications/threads/:id", []denco.Param{{"id", "1"}}, true},
{"/notifications/threads/2/subscription", "/notifications/threads/:id/subscription", []denco.Param{{"id", "2"}}, true},
{"/repos/naoina/denco/stargazers", "/repos/:owner/:repo/stargazers", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/users/naoina/starred", "/users/:user/starred", []denco.Param{{"user", "naoina"}}, true},
{"/user/starred", "/user/starred", nil, true},
{"/user/starred/naoina/denco", "/user/starred/:owner/:repo", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/subscribers", "/repos/:owner/:repo/subscribers", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/users/naoina/subscriptions", "/users/:user/subscriptions", []denco.Param{{"user", "naoina"}}, true},
{"/user/subscriptions", "/user/subscriptions", nil, true},
{"/repos/naoina/denco/subscription", "/repos/:owner/:repo/subscription", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/user/subscriptions/naoina/denco", "/user/subscriptions/:owner/:repo", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/users/naoina/gists", "/users/:user/gists", []denco.Param{{"user", "naoina"}}, true},
{"/gists", "/gists", nil, true},
{"/gists/1", "/gists/:id", []denco.Param{{"id", "1"}}, true},
{"/gists/2/star", "/gists/:id/star", []denco.Param{{"id", "2"}}, true},
{"/repos/naoina/denco/git/blobs/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/blobs/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/git/commits/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/commits/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/git/refs", "/repos/:owner/:repo/git/refs", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/git/tags/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/tags/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/git/trees/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/trees/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/issues", "/issues", nil, true},
{"/user/issues", "/user/issues", nil, true},
{"/orgs/something/issues", "/orgs/:org/issues", []denco.Param{{"org", "something"}}, true},
{"/repos/naoina/denco/issues", "/repos/:owner/:repo/issues", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/issues/1", "/repos/:owner/:repo/issues/:number", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/assignees", "/repos/:owner/:repo/assignees", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/assignees/foo", "/repos/:owner/:repo/assignees/:assignee", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"assignee", "foo"}}, true},
{"/repos/naoina/denco/issues/1/comments", "/repos/:owner/:repo/issues/:number/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/issues/1/events", "/repos/:owner/:repo/issues/:number/events", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/labels", "/repos/:owner/:repo/labels", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/labels/bug", "/repos/:owner/:repo/labels/:name", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"name", "bug"}}, true},
{"/repos/naoina/denco/issues/1/labels", "/repos/:owner/:repo/issues/:number/labels", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/milestones/1/labels", "/repos/:owner/:repo/milestones/:number/labels", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/milestones", "/repos/:owner/:repo/milestones", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/milestones/1", "/repos/:owner/:repo/milestones/:number", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/emojis", "/emojis", nil, true},
{"/gitignore/templates", "/gitignore/templates", nil, true},
{"/gitignore/templates/Go", "/gitignore/templates/:name", []denco.Param{{"name", "Go"}}, true},
{"/meta", "/meta", nil, true},
{"/rate_limit", "/rate_limit", nil, true},
{"/users/naoina/orgs", "/users/:user/orgs", []denco.Param{{"user", "naoina"}}, true},
{"/user/orgs", "/user/orgs", nil, true},
{"/orgs/something", "/orgs/:org", []denco.Param{{"org", "something"}}, true},
{"/orgs/something/members", "/orgs/:org/members", []denco.Param{{"org", "something"}}, true},
{"/orgs/something/members/naoina", "/orgs/:org/members/:user", []denco.Param{{"org", "something"}, {"user", "naoina"}}, true},
{"/orgs/something/public_members", "/orgs/:org/public_members", []denco.Param{{"org", "something"}}, true},
{"/orgs/something/public_members/naoina", "/orgs/:org/public_members/:user", []denco.Param{{"org", "something"}, {"user", "naoina"}}, true},
{"/orgs/something/teams", "/orgs/:org/teams", []denco.Param{{"org", "something"}}, true},
{"/teams/1", "/teams/:id", []denco.Param{{"id", "1"}}, true},
{"/teams/2/members", "/teams/:id/members", []denco.Param{{"id", "2"}}, true},
{"/teams/3/members/naoina", "/teams/:id/members/:user", []denco.Param{{"id", "3"}, {"user", "naoina"}}, true},
{"/teams/4/repos", "/teams/:id/repos", []denco.Param{{"id", "4"}}, true},
{"/teams/5/repos/naoina/denco", "/teams/:id/repos/:owner/:repo", []denco.Param{{"id", "5"}, {"owner", "naoina"}, {"repo", "denco"}}, true},
{"/user/teams", "/user/teams", nil, true},
{"/repos/naoina/denco/pulls", "/repos/:owner/:repo/pulls", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/pulls/1", "/repos/:owner/:repo/pulls/:number", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/pulls/1/commits", "/repos/:owner/:repo/pulls/:number/commits", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/pulls/1/files", "/repos/:owner/:repo/pulls/:number/files", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/pulls/1/merge", "/repos/:owner/:repo/pulls/:number/merge", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/pulls/1/comments", "/repos/:owner/:repo/pulls/:number/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/user/repos", "/user/repos", nil, true},
{"/users/naoina/repos", "/users/:user/repos", []denco.Param{{"user", "naoina"}}, true},
{"/orgs/something/repos", "/orgs/:org/repos", []denco.Param{{"org", "something"}}, true},
{"/repositories", "/repositories", nil, true},
{"/repos/naoina/denco", "/repos/:owner/:repo", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/contributors", "/repos/:owner/:repo/contributors", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/languages", "/repos/:owner/:repo/languages", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/teams", "/repos/:owner/:repo/teams", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/tags", "/repos/:owner/:repo/tags", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/branches", "/repos/:owner/:repo/branches", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/branches/master", "/repos/:owner/:repo/branches/:branch", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"branch", "master"}}, true},
{"/repos/naoina/denco/collaborators", "/repos/:owner/:repo/collaborators", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/collaborators/something", "/repos/:owner/:repo/collaborators/:user", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"user", "something"}}, true},
{"/repos/naoina/denco/comments", "/repos/:owner/:repo/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/commits/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9/comments", "/repos/:owner/:repo/commits/:sha/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/comments/1", "/repos/:owner/:repo/comments/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
{"/repos/naoina/denco/commits", "/repos/:owner/:repo/commits", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/commits/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/commits/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/readme", "/repos/:owner/:repo/readme", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/keys", "/repos/:owner/:repo/keys", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/keys/1", "/repos/:owner/:repo/keys/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
{"/repos/naoina/denco/downloads", "/repos/:owner/:repo/downloads", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/downloads/2", "/repos/:owner/:repo/downloads/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "2"}}, true},
{"/repos/naoina/denco/forks", "/repos/:owner/:repo/forks", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/hooks", "/repos/:owner/:repo/hooks", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/hooks/2", "/repos/:owner/:repo/hooks/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "2"}}, true},
{"/repos/naoina/denco/releases", "/repos/:owner/:repo/releases", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/releases/1", "/repos/:owner/:repo/releases/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
{"/repos/naoina/denco/releases/1/assets", "/repos/:owner/:repo/releases/:id/assets", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
{"/repos/naoina/denco/stats/contributors", "/repos/:owner/:repo/stats/contributors", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/stats/commit_activity", "/repos/:owner/:repo/stats/commit_activity", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/stats/code_frequency", "/repos/:owner/:repo/stats/code_frequency", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/stats/participation", "/repos/:owner/:repo/stats/participation", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/stats/punch_card", "/repos/:owner/:repo/stats/punch_card", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/statuses/master", "/repos/:owner/:repo/statuses/:ref", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"ref", "master"}}, true},
{"/search/repositories", "/search/repositories", nil, true},
{"/search/code", "/search/code", nil, true},
{"/search/issues", "/search/issues", nil, true},
{"/search/users", "/search/users", nil, true},
{"/legacy/issues/search/naoina/denco/closed/test", "/legacy/issues/search/:owner/:repository/:state/:keyword", []denco.Param{{"owner", "naoina"}, {"repository", "denco"}, {"state", "closed"}, {"keyword", "test"}}, true},
{"/legacy/repos/search/test", "/legacy/repos/search/:keyword", []denco.Param{{"keyword", "test"}}, true},
{"/legacy/user/search/test", "/legacy/user/search/:keyword", []denco.Param{{"keyword", "test"}}, true},
{"/legacy/user/email/naoina@kuune.org", "/legacy/user/email/:email", []denco.Param{{"email", "naoina@kuune.org"}}, true},
{"/users/naoina", "/users/:user", []denco.Param{{"user", "naoina"}}, true},
{"/user", "/user", nil, true},
{"/users", "/users", nil, true},
{"/user/emails", "/user/emails", nil, true},
{"/users/naoina/followers", "/users/:user/followers", []denco.Param{{"user", "naoina"}}, true},
{"/user/followers", "/user/followers", nil, true},
{"/users/naoina/following", "/users/:user/following", []denco.Param{{"user", "naoina"}}, true},
{"/user/following", "/user/following", nil, true},
{"/user/following/naoina", "/user/following/:user", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/following/target", "/users/:user/following/:target_user", []denco.Param{{"user", "naoina"}, {"target_user", "target"}}, true},
{"/users/naoina/keys", "/users/:user/keys", []denco.Param{{"user", "naoina"}}, true},
{"/user/keys", "/user/keys", nil, true},
{"/user/keys/1", "/user/keys/:id", []denco.Param{{"id", "1"}}, true},
{"/people/me", "/people/:userId", []denco.Param{{"userId", "me"}}, true},
{"/people", "/people", nil, true},
{"/activities/foo/people/vault", "/activities/:activityId/people/:collection", []denco.Param{{"activityId", "foo"}, {"collection", "vault"}}, true},
{"/people/me/people/vault", "/people/:userId/people/:collection", []denco.Param{{"userId", "me"}, {"collection", "vault"}}, true},
{"/people/me/openIdConnect", "/people/:userId/openIdConnect", []denco.Param{{"userId", "me"}}, true},
{"/people/me/activities/vault", "/people/:userId/activities/:collection", []denco.Param{{"userId", "me"}, {"collection", "vault"}}, true},
{"/activities/foo", "/activities/:activityId", []denco.Param{{"activityId", "foo"}}, true},
{"/activities", "/activities", nil, true},
{"/activities/foo/comments", "/activities/:activityId/comments", []denco.Param{{"activityId", "foo"}}, true},
{"/comments/hoge", "/comments/:commentId", []denco.Param{{"commentId", "hoge"}}, true},
{"/people/me/moments/vault", "/people/:userId/moments/:collection", []denco.Param{{"userId", "me"}, {"collection", "vault"}}, true},
}
runLookupTest(t, realURIs, testcases)
}
func TestRouter_Build(t *testing.T) {
// test for duplicate name of path parameters.
func() {
r := denco.New()
if err := r.Build([]denco.Record{
{"/:user/:id/:id", "testroute0"},
{"/:user/:user/:id", "testroute0"},
}); err == nil {
t.Errorf("no error returned by duplicate name of path parameters")
}
}()
}
func TestRouter_Build_withoutSizeHint(t *testing.T) {
for _, v := range []struct {
keys []string
sizeHint int
}{
{[]string{"/user"}, 0},
{[]string{"/user/:id"}, 1},
{[]string{"/user/:id/post"}, 1},
{[]string{"/user/:id/:group"}, 2},
{[]string{"/user/:id/post/:cid"}, 2},
{[]string{"/user/:id/post/:cid", "/admin/:id/post/:cid"}, 2},
{[]string{"/user/:id", "/admin/:id/post/:cid"}, 2},
{[]string{"/user/:id/post/:cid", "/admin/:id/post/:cid/:type"}, 3},
} {
r := denco.New()
actual := r.SizeHint
expect := -1
if !reflect.DeepEqual(actual, expect) {
t.Errorf(`before Build; Router.SizeHint => (%[1]T=%#[1]v); want (%[2]T=%#[2]v)`, actual, expect)
}
records := make([]denco.Record, len(v.keys))
for i, k := range v.keys {
records[i] = denco.Record{Key: k, Value: "value"}
}
if err := r.Build(records); err != nil {
t.Fatal(err)
}
actual = r.SizeHint
expect = v.sizeHint
if !reflect.DeepEqual(actual, expect) {
t.Errorf(`Router.Build(%#v); Router.SizeHint => (%[2]T=%#[2]v); want (%[3]T=%#[3]v)`, records, actual, expect)
}
}
}
func TestRouter_Build_withSizeHint(t *testing.T) {
for _, v := range []struct {
key string
sizeHint int
expect int
}{
{"/user", 0, 0},
{"/user", 1, 1},
{"/user", 2, 2},
{"/user/:id", 3, 3},
{"/user/:id/:group", 0, 0},
{"/user/:id/:group", 1, 1},
} {
r := denco.New()
r.SizeHint = v.sizeHint
records := []denco.Record{
{v.key, "value"},
}
if err := r.Build(records); err != nil {
t.Fatal(err)
}
actual := r.SizeHint
expect := v.expect
if !reflect.DeepEqual(actual, expect) {
t.Errorf(`Router.Build(%#v); Router.SizeHint => (%[2]T=%#[2]v); want (%[3]T=%#[3]v)`, records, actual, expect)
}
}
}
func TestParams_Get(t *testing.T) {
params := denco.Params([]denco.Param{
{"name1", "value1"},
{"name2", "value2"},
{"name3", "value3"},
{"name1", "value4"},
})
for _, v := range []struct{ value, expected string }{
{"name1", "value1"},
{"name2", "value2"},
{"name3", "value3"},
{"name4", ""},
} {
actual := params.Get(v.value)
expected := v.expected
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Params.Get(%q) => %#v, want %#v", v.value, actual, expected)
}
}
}

View File

@@ -0,0 +1,106 @@
package denco
import (
"net/http"
)
// Mux represents a multiplexer for HTTP request.
type Mux struct{}
// NewMux returns a new Mux.
func NewMux() *Mux {
return &Mux{}
}
// GET is shorthand of Mux.Handler("GET", path, handler).
func (m *Mux) GET(path string, handler HandlerFunc) Handler {
return m.Handler("GET", path, handler)
}
// POST is shorthand of Mux.Handler("POST", path, handler).
func (m *Mux) POST(path string, handler HandlerFunc) Handler {
return m.Handler("POST", path, handler)
}
// PUT is shorthand of Mux.Handler("PUT", path, handler).
func (m *Mux) PUT(path string, handler HandlerFunc) Handler {
return m.Handler("PUT", path, handler)
}
// HEAD is shorthand of Mux.Handler("HEAD", path, handler).
func (m *Mux) HEAD(path string, handler HandlerFunc) Handler {
return m.Handler("HEAD", path, handler)
}
// Handler returns a handler for HTTP method.
func (m *Mux) Handler(method, path string, handler HandlerFunc) Handler {
return Handler{
Method: method,
Path: path,
Func: handler,
}
}
// Build builds a http.Handler.
func (m *Mux) Build(handlers []Handler) (http.Handler, error) {
recordMap := make(map[string][]Record)
for _, h := range handlers {
recordMap[h.Method] = append(recordMap[h.Method], NewRecord(h.Path, h.Func))
}
mux := newServeMux()
for m, records := range recordMap {
router := New()
if err := router.Build(records); err != nil {
return nil, err
}
mux.routers[m] = router
}
return mux, nil
}
// Handler represents a handler of HTTP request.
type Handler struct {
// Method is an HTTP method.
Method string
// Path is a routing path for handler.
Path string
// Func is a function of handler of HTTP request.
Func HandlerFunc
}
// The HandlerFunc type is aliased to type of handler function.
type HandlerFunc func(w http.ResponseWriter, r *http.Request, params Params)
type serveMux struct {
routers map[string]*Router
}
func newServeMux() *serveMux {
return &serveMux{
routers: make(map[string]*Router),
}
}
// ServeHTTP implements http.Handler interface.
func (mux *serveMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
handler, params := mux.handler(r.Method, r.URL.Path)
handler(w, r, params)
}
func (mux *serveMux) handler(method, path string) (HandlerFunc, []Param) {
if router, found := mux.routers[method]; found {
if handler, params, found := router.Lookup(path); found {
return handler.(HandlerFunc), params
}
}
return NotFound, nil
}
// NotFound replies to the request with an HTTP 404 not found error.
// NotFound is called when unknown HTTP method or a handler not found.
// If you want to use the your own NotFound handler, please overwrite this variable.
var NotFound = func(w http.ResponseWriter, r *http.Request, _ Params) {
http.NotFound(w, r)
}

View File

@@ -0,0 +1,106 @@
package denco_test
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-openapi/runtime/middleware/denco"
)
func testHandlerFunc(w http.ResponseWriter, r *http.Request, params denco.Params) {
fmt.Fprintf(w, "method: %s, path: %s, params: %v", r.Method, r.URL.Path, params)
}
func TestMux(t *testing.T) {
mux := denco.NewMux()
handler, err := mux.Build([]denco.Handler{
mux.GET("/", testHandlerFunc),
mux.GET("/user/:name", testHandlerFunc),
mux.POST("/user/:name", testHandlerFunc),
mux.HEAD("/user/:name", testHandlerFunc),
mux.PUT("/user/:name", testHandlerFunc),
mux.Handler("GET", "/user/handler", testHandlerFunc),
mux.Handler("POST", "/user/handler", testHandlerFunc),
{"PUT", "/user/inference", testHandlerFunc},
})
if err != nil {
t.Fatal(err)
}
server := httptest.NewServer(handler)
defer server.Close()
for _, v := range []struct {
status int
method, path, expected string
}{
{200, "GET", "/", "method: GET, path: /, params: []"},
{200, "GET", "/user/alice", "method: GET, path: /user/alice, params: [{name alice}]"},
{200, "POST", "/user/bob", "method: POST, path: /user/bob, params: [{name bob}]"},
{200, "HEAD", "/user/alice", ""},
{200, "PUT", "/user/bob", "method: PUT, path: /user/bob, params: [{name bob}]"},
{404, "POST", "/", "404 page not found\n"},
{404, "GET", "/unknown", "404 page not found\n"},
{404, "POST", "/user/alice/1", "404 page not found\n"},
{200, "GET", "/user/handler", "method: GET, path: /user/handler, params: []"},
{200, "POST", "/user/handler", "method: POST, path: /user/handler, params: []"},
{200, "PUT", "/user/inference", "method: PUT, path: /user/inference, params: []"},
} {
req, err := http.NewRequest(v.method, server.URL+v.path, nil)
if err != nil {
t.Error(err)
continue
}
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Error(err)
continue
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
continue
}
actual := string(body)
expected := v.expected
if res.StatusCode != v.status || actual != expected {
t.Errorf(`%s "%s" => %#v %#v, want %#v %#v`, v.method, v.path, res.StatusCode, actual, v.status, expected)
}
}
}
func TestNotFound(t *testing.T) {
mux := denco.NewMux()
handler, err := mux.Build([]denco.Handler{})
if err != nil {
t.Fatal(err)
}
server := httptest.NewServer(handler)
defer server.Close()
origNotFound := denco.NotFound
defer func() {
denco.NotFound = origNotFound
}()
denco.NotFound = func(w http.ResponseWriter, r *http.Request, params denco.Params) {
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "method: %s, path: %s, params: %v", r.Method, r.URL.Path, params)
}
res, err := http.Get(server.URL)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
actual := string(body)
expected := "method: GET, path: /, params: []"
if res.StatusCode != http.StatusServiceUnavailable || actual != expected {
t.Errorf(`GET "/" => %#v %#v, want %#v %#v`, res.StatusCode, actual, http.StatusServiceUnavailable, expected)
}
}

View File

@@ -0,0 +1,12 @@
package denco
// NextSeparator returns an index of next separator in path.
func NextSeparator(path string, start int) int {
for start < len(path) {
if c := path[start]; c == '/' || c == TerminationCharacter {
break
}
start++
}
return start
}

View File

@@ -0,0 +1,31 @@
package denco_test
import (
"reflect"
"testing"
"github.com/go-openapi/runtime/middleware/denco"
)
func TestNextSeparator(t *testing.T) {
for _, testcase := range []struct {
path string
start int
expected interface{}
}{
{"/path/to/route", 0, 0},
{"/path/to/route", 1, 5},
{"/path/to/route", 9, 14},
{"/path.html", 1, 10},
{"/foo/bar.html", 1, 4},
{"/foo/bar.html/baz.png", 5, 13},
{"/foo/bar.html/baz.png", 14, 21},
{"path#", 0, 4},
} {
actual := denco.NextSeparator(testcase.path, testcase.start)
expected := testcase.expected
if !reflect.DeepEqual(actual, expected) {
t.Errorf("path = %q, start = %v expect %v, but %v", testcase.path, testcase.start, expected, actual)
}
}
}

65
vendor/github.com/go-openapi/runtime/middleware/doc.go generated vendored Normal file
View File

@@ -0,0 +1,65 @@
// 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 middleware provides the library with helper functions for serving swagger APIs.
Pseudo middleware handler
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/gorilla/context"
)
func newCompleteMiddleware(ctx *Context) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
defer context.Clear(r)
// use context to lookup routes
if matched, ok := ctx.RouteInfo(r); ok {
if len(matched.Authenticators) > 0 {
if _, err := ctx.Authorize(r, matched); err != nil {
ctx.Respond(rw, r, matched.Produces, matched, err)
return
}
}
bound, validation := ctx.BindAndValidate(r, matched)
if validation != nil {
ctx.Respond(rw, r, matched.Produces, matched, validation)
return
}
result, err := matched.Handler.Handle(bound)
if err != nil {
ctx.Respond(rw, r, matched.Produces, matched, err)
return
}
ctx.Respond(rw, r, matched.Produces, matched, result)
return
}
// Not found, check if it exists in the other methods first
if others := ctx.AllowedMethods(r); len(others) > 0 {
ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others))
return
}
ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.Path))
})
}
*/
package middleware

View File

@@ -0,0 +1,299 @@
// Copyright 2013 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 or at
// https://developers.google.com/open-source/licenses/bsd.
// this file was taken from the github.com/golang/gddo repository
// Package header provides functions for parsing HTTP headers.
package header
import (
"net/http"
"strings"
"time"
)
// Octet types from RFC 2616.
var octetTypes [256]octetType
type octetType byte
const (
isToken octetType = 1 << iota
isSpace
)
func init() {
// OCTET = <any 8-bit sequence of data>
// CHAR = <any US-ASCII character (octets 0 - 127)>
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
// CR = <US-ASCII CR, carriage return (13)>
// LF = <US-ASCII LF, linefeed (10)>
// SP = <US-ASCII SP, space (32)>
// HT = <US-ASCII HT, horizontal-tab (9)>
// <"> = <US-ASCII double-quote mark (34)>
// CRLF = CR LF
// LWS = [CRLF] 1*( SP | HT )
// TEXT = <any OCTET except CTLs, but including LWS>
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
// token = 1*<any CHAR except CTLs or separators>
// qdtext = <any TEXT except <">>
for c := 0; c < 256; c++ {
var t octetType
isCtl := c <= 31 || c == 127
isChar := 0 <= c && c <= 127
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
t |= isSpace
}
if isChar && !isCtl && !isSeparator {
t |= isToken
}
octetTypes[c] = t
}
}
// Copy returns a shallow copy of the header.
func Copy(header http.Header) http.Header {
h := make(http.Header)
for k, vs := range header {
h[k] = vs
}
return h
}
var timeLayouts = []string{"Mon, 02 Jan 2006 15:04:05 GMT", time.RFC850, time.ANSIC}
// ParseTime parses the header as time. The zero value is returned if the
// header is not present or there is an error parsing the
// header.
func ParseTime(header http.Header, key string) time.Time {
if s := header.Get(key); s != "" {
for _, layout := range timeLayouts {
if t, err := time.Parse(layout, s); err == nil {
return t.UTC()
}
}
}
return time.Time{}
}
// ParseList parses a comma separated list of values. Commas are ignored in
// quoted strings. Quoted values are not unescaped or unquoted. Whitespace is
// trimmed.
func ParseList(header http.Header, key string) []string {
var result []string
for _, s := range header[http.CanonicalHeaderKey(key)] {
begin := 0
end := 0
escape := false
quote := false
for i := 0; i < len(s); i++ {
b := s[i]
switch {
case escape:
escape = false
end = i + 1
case quote:
switch b {
case '\\':
escape = true
case '"':
quote = false
}
end = i + 1
case b == '"':
quote = true
end = i + 1
case octetTypes[b]&isSpace != 0:
if begin == end {
begin = i + 1
end = begin
}
case b == ',':
if begin < end {
result = append(result, s[begin:end])
}
begin = i + 1
end = begin
default:
end = i + 1
}
}
if begin < end {
result = append(result, s[begin:end])
}
}
return result
}
// ParseValueAndParams parses a comma separated list of values with optional
// semicolon separated name-value pairs. Content-Type and Content-Disposition
// headers are in this format.
func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) {
params = make(map[string]string)
s := header.Get(key)
value, s = expectTokenSlash(s)
if value == "" {
return
}
value = strings.ToLower(value)
s = skipSpace(s)
for strings.HasPrefix(s, ";") {
var pkey string
pkey, s = expectToken(skipSpace(s[1:]))
if pkey == "" {
return
}
if !strings.HasPrefix(s, "=") {
return
}
var pvalue string
pvalue, s = expectTokenOrQuoted(s[1:])
if pvalue == "" {
return
}
pkey = strings.ToLower(pkey)
params[pkey] = pvalue
s = skipSpace(s)
}
return
}
type AcceptSpec struct {
Value string
Q float64
}
// ParseAccept parses Accept* headers.
func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
loop:
for _, s := range header[key] {
for {
var spec AcceptSpec
spec.Value, s = expectTokenSlash(s)
if spec.Value == "" {
continue loop
}
spec.Q = 1.0
s = skipSpace(s)
if strings.HasPrefix(s, ";") {
s = skipSpace(s[1:])
if !strings.HasPrefix(s, "q=") {
continue loop
}
spec.Q, s = expectQuality(s[2:])
if spec.Q < 0.0 {
continue loop
}
}
specs = append(specs, spec)
s = skipSpace(s)
if !strings.HasPrefix(s, ",") {
continue loop
}
s = skipSpace(s[1:])
}
}
return
}
func skipSpace(s string) (rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isSpace == 0 {
break
}
}
return s[i:]
}
func expectToken(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isToken == 0 {
break
}
}
return s[:i], s[i:]
}
func expectTokenSlash(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
b := s[i]
if (octetTypes[b]&isToken == 0) && b != '/' {
break
}
}
return s[:i], s[i:]
}
func expectQuality(s string) (q float64, rest string) {
switch {
case len(s) == 0:
return -1, ""
case s[0] == '0':
q = 0
case s[0] == '1':
q = 1
default:
return -1, ""
}
s = s[1:]
if !strings.HasPrefix(s, ".") {
return q, s
}
s = s[1:]
i := 0
n := 0
d := 1
for ; i < len(s); i++ {
b := s[i]
if b < '0' || b > '9' {
break
}
n = n*10 + int(b) - '0'
d *= 10
}
return q + float64(n)/float64(d), s[i:]
}
func expectTokenOrQuoted(s string) (value string, rest string) {
if !strings.HasPrefix(s, "\"") {
return expectToken(s)
}
s = s[1:]
for i := 0; i < len(s); i++ {
switch s[i] {
case '"':
return s[:i], s[i+1:]
case '\\':
p := make([]byte, len(s)-1)
j := copy(p, s[:i])
escape := true
for i = i + 1; i < len(s); i++ {
b := s[i]
switch {
case escape:
escape = false
p[j] = b
j += 1
case b == '\\':
escape = true
case b == '"':
return string(p[:j]), s[i+1:]
default:
p[j] = b
j += 1
}
}
return "", ""
}
}
return "", ""
}

View File

@@ -0,0 +1,82 @@
// Copyright 2013 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 or at
// https://developers.google.com/open-source/licenses/bsd.
// this file was taken from the github.com/golang/gddo repository
package middleware
import (
"net/http"
"strings"
"github.com/go-openapi/runtime/middleware/header"
)
// NegotiateContentEncoding returns the best offered content encoding for the
// request's Accept-Encoding header. If two offers match with equal weight and
// then the offer earlier in the list is preferred. If no offers are
// acceptable, then "" is returned.
func NegotiateContentEncoding(r *http.Request, offers []string) string {
bestOffer := "identity"
bestQ := -1.0
specs := header.ParseAccept(r.Header, "Accept-Encoding")
for _, offer := range offers {
for _, spec := range specs {
if spec.Q > bestQ &&
(spec.Value == "*" || spec.Value == offer) {
bestQ = spec.Q
bestOffer = offer
}
}
}
if bestQ == 0 {
bestOffer = ""
}
return bestOffer
}
// NegotiateContentType returns the best offered content type for the request's
// Accept header. If two offers match with equal weight, then the more specific
// offer is preferred. For example, text/* trumps */*. If two offers match
// with equal weight and specificity, then the offer earlier in the list is
// preferred. If no offers match, then defaultOffer is returned.
func NegotiateContentType(r *http.Request, offers []string, defaultOffer string) string {
bestOffer := defaultOffer
bestQ := -1.0
bestWild := 3
specs := header.ParseAccept(r.Header, "Accept")
for _, offer := range offers {
for _, spec := range specs {
switch {
case spec.Q == 0.0:
// ignore
case spec.Q < bestQ:
// better match found
case spec.Value == "*/*":
if spec.Q > bestQ || bestWild > 2 {
bestQ = spec.Q
bestWild = 2
bestOffer = offer
}
case strings.HasSuffix(spec.Value, "/*"):
if strings.HasPrefix(offer, spec.Value[:len(spec.Value)-1]) &&
(spec.Q > bestQ || bestWild > 1) {
bestQ = spec.Q
bestWild = 1
bestOffer = offer
}
default:
if spec.Value == offer &&
(spec.Q > bestQ || bestWild > 0) {
bestQ = spec.Q
bestWild = 0
bestOffer = offer
}
}
}
}
return bestOffer
}

View File

@@ -0,0 +1,70 @@
// Copyright 2013 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 or at
// https://developers.google.com/open-source/licenses/bsd.
package middleware
import (
"net/http"
"testing"
)
var negotiateContentEncodingTests = []struct {
s string
offers []string
expect string
}{
{"", []string{"identity", "gzip"}, "identity"},
{"*;q=0", []string{"identity", "gzip"}, ""},
{"gzip", []string{"identity", "gzip"}, "gzip"},
}
func TestNegotiateContentEnoding(t *testing.T) {
for _, tt := range negotiateContentEncodingTests {
r := &http.Request{Header: http.Header{"Accept-Encoding": {tt.s}}}
actual := NegotiateContentEncoding(r, tt.offers)
if actual != tt.expect {
t.Errorf("NegotiateContentEncoding(%q, %#v)=%q, want %q", tt.s, tt.offers, actual, tt.expect)
}
}
}
var negotiateContentTypeTests = []struct {
s string
offers []string
defaultOffer string
expect string
}{
{"text/html, */*;q=0", []string{"x/y"}, "", ""},
{"text/html, */*", []string{"x/y"}, "", "x/y"},
{"text/html, image/png", []string{"text/html", "image/png"}, "", "text/html"},
{"text/html, image/png", []string{"image/png", "text/html"}, "", "image/png"},
{"text/html, image/png; q=0.5", []string{"image/png"}, "", "image/png"},
{"text/html, image/png; q=0.5", []string{"text/html"}, "", "text/html"},
{"text/html, image/png; q=0.5", []string{"foo/bar"}, "", ""},
{"text/html, image/png; q=0.5", []string{"image/png", "text/html"}, "", "text/html"},
{"text/html, image/png; q=0.5", []string{"text/html", "image/png"}, "", "text/html"},
{"text/html;q=0.5, image/png", []string{"image/png"}, "", "image/png"},
{"text/html;q=0.5, image/png", []string{"text/html"}, "", "text/html"},
{"text/html;q=0.5, image/png", []string{"image/png", "text/html"}, "", "image/png"},
{"text/html;q=0.5, image/png", []string{"text/html", "image/png"}, "", "image/png"},
{"image/png, image/*;q=0.5", []string{"image/jpg", "image/png"}, "", "image/png"},
{"image/png, image/*;q=0.5", []string{"image/jpg"}, "", "image/jpg"},
{"image/png, image/*;q=0.5", []string{"image/jpg", "image/gif"}, "", "image/jpg"},
{"image/png, image/*", []string{"image/jpg", "image/gif"}, "", "image/jpg"},
{"image/png, image/*", []string{"image/gif", "image/jpg"}, "", "image/gif"},
{"image/png, image/*", []string{"image/gif", "image/png"}, "", "image/png"},
{"image/png, image/*", []string{"image/png", "image/gif"}, "", "image/png"},
}
func TestNegotiateContentType(t *testing.T) {
for _, tt := range negotiateContentTypeTests {
r := &http.Request{Header: http.Header{"Accept": {tt.s}}}
actual := NegotiateContentType(r, tt.offers, tt.defaultOffer)
if actual != tt.expect {
t.Errorf("NegotiateContentType(%q, %#v, %q)=%q, want %q", tt.s, tt.offers, tt.defaultOffer, actual, tt.expect)
}
}
}

View File

@@ -0,0 +1,48 @@
// 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 middleware
import (
"net/http"
"github.com/go-openapi/runtime"
)
type errorResp struct {
code int
response interface{}
headers http.Header
}
func (e *errorResp) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
for k, v := range e.headers {
for _, val := range v {
rw.Header().Add(k, val)
}
}
if e.code > 0 {
rw.WriteHeader(e.code)
} else {
rw.WriteHeader(http.StatusInternalServerError)
}
if err := producer.Produce(rw, e.response); err != nil {
panic(err)
}
}
// NotImplemented the error response when the response is not implemented
func NotImplemented(message string) Responder {
return &errorResp{http.StatusNotImplemented, message, make(http.Header)}
}

View File

@@ -0,0 +1,26 @@
// 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 middleware
import "net/http"
// NewOperationExecutor creates a context aware middleware that handles the operations after routing
func NewOperationExecutor(ctx *Context) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// use context to lookup routes
route, _ := ctx.RouteInfo(r)
route.Handler.ServeHTTP(rw, r)
})
}

View File

@@ -0,0 +1,64 @@
// 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 middleware
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/internal/testing/petstore"
"github.com/stretchr/testify/assert"
)
func TestOperationExecutor(t *testing.T) {
spec, api := petstore.NewAPI(t)
api.RegisterOperation("get", "/pets", runtime.OperationHandlerFunc(func(params interface{}) (interface{}, error) {
return []interface{}{
map[string]interface{}{"id": 1, "name": "a dog"},
}, nil
}))
context := NewContext(spec, api, nil)
context.router = DefaultRouter(spec, context.api)
mw := NewOperationExecutor(context)
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/api/pets", nil)
request.Header.Add("Accept", "application/json")
request.SetBasicAuth("admin", "admin")
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
assert.Equal(t, `[{"id":1,"name":"a dog"}]`+"\n", recorder.Body.String())
spec, api = petstore.NewAPI(t)
api.RegisterOperation("get", "/pets", runtime.OperationHandlerFunc(func(params interface{}) (interface{}, error) {
return nil, errors.New(422, "expected")
}))
context = NewContext(spec, api, nil)
context.router = DefaultRouter(spec, context.api)
mw = NewOperationExecutor(context)
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/api/pets", nil)
request.Header.Add("Accept", "application/json")
request.SetBasicAuth("admin", "admin")
mw.ServeHTTP(recorder, request)
assert.Equal(t, 422, recorder.Code)
assert.Equal(t, `{"code":422,"message":"expected"}`, recorder.Body.String())
}

View File

@@ -0,0 +1,480 @@
// 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 middleware
import (
"encoding"
"encoding/base64"
"fmt"
"io"
"net/http"
"reflect"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
const defaultMaxMemory = 32 << 20
var textUnmarshalType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
func newUntypedParamBinder(param spec.Parameter, spec *spec.Swagger, formats strfmt.Registry) *untypedParamBinder {
binder := new(untypedParamBinder)
binder.Name = param.Name
binder.parameter = &param
binder.formats = formats
if param.In != "body" {
binder.validator = validate.NewParamValidator(&param, formats)
} else {
binder.validator = validate.NewSchemaValidator(param.Schema, spec, param.Name, formats)
}
return binder
}
type untypedParamBinder struct {
parameter *spec.Parameter
formats strfmt.Registry
Name string
validator validate.EntityValidator
}
func (p *untypedParamBinder) Type() reflect.Type {
return p.typeForSchema(p.parameter.Type, p.parameter.Format, p.parameter.Items)
}
func (p *untypedParamBinder) typeForSchema(tpe, format string, items *spec.Items) reflect.Type {
switch tpe {
case "boolean":
return reflect.TypeOf(true)
case "string":
if tt, ok := p.formats.GetType(format); ok {
return tt
}
return reflect.TypeOf("")
case "integer":
switch format {
case "int8":
return reflect.TypeOf(int8(0))
case "int16":
return reflect.TypeOf(int16(0))
case "int32":
return reflect.TypeOf(int32(0))
case "int64":
return reflect.TypeOf(int64(0))
default:
return reflect.TypeOf(int64(0))
}
case "number":
switch format {
case "float":
return reflect.TypeOf(float32(0))
case "double":
return reflect.TypeOf(float64(0))
}
case "array":
if items == nil {
return nil
}
itemsType := p.typeForSchema(items.Type, items.Format, items.Items)
if itemsType == nil {
return nil
}
return reflect.MakeSlice(reflect.SliceOf(itemsType), 0, 0).Type()
case "file":
return reflect.TypeOf(&runtime.File{}).Elem()
case "object":
return reflect.TypeOf(map[string]interface{}{})
}
return nil
}
func (p *untypedParamBinder) allowsMulti() bool {
return p.parameter.In == "query" || p.parameter.In == "formData"
}
func (p *untypedParamBinder) readValue(values runtime.Gettable, target reflect.Value) ([]string, bool, bool, error) {
name, in, cf, tpe := p.parameter.Name, p.parameter.In, p.parameter.CollectionFormat, p.parameter.Type
if tpe == "array" {
if cf == "multi" {
if !p.allowsMulti() {
return nil, false, false, errors.InvalidCollectionFormat(name, in, cf)
}
vv, hasKey, _ := values.GetOK(name)
return vv, false, hasKey, nil
}
v, hk, hv := values.GetOK(name)
if !hv {
return nil, false, hk, nil
}
d, c, e := p.readFormattedSliceFieldValue(v[len(v)-1], target)
return d, c, hk, e
}
vv, hk, _ := values.GetOK(name)
return vv, false, hk, nil
}
func (p *untypedParamBinder) Bind(request *http.Request, routeParams RouteParams, consumer runtime.Consumer, target reflect.Value) error {
// fmt.Println("binding", p.name, "as", p.Type())
switch p.parameter.In {
case "query":
data, custom, hasKey, err := p.readValue(runtime.Values(request.URL.Query()), target)
if err != nil {
return err
}
if custom {
return nil
}
return p.bindValue(data, hasKey, target)
case "header":
data, custom, hasKey, err := p.readValue(runtime.Values(request.Header), target)
if err != nil {
return err
}
if custom {
return nil
}
return p.bindValue(data, hasKey, target)
case "path":
data, custom, hasKey, err := p.readValue(routeParams, target)
if err != nil {
return err
}
if custom {
return nil
}
return p.bindValue(data, hasKey, target)
case "formData":
var err error
var mt string
mt, _, e := runtime.ContentType(request.Header)
if e != nil {
// because of the interface conversion go thinks the error is not nil
// so we first check for nil and then set the err var if it's not nil
err = e
}
if err != nil {
return errors.InvalidContentType("", []string{"multipart/form-data", "application/x-www-form-urlencoded"})
}
if mt != "multipart/form-data" && mt != "application/x-www-form-urlencoded" {
return errors.InvalidContentType(mt, []string{"multipart/form-data", "application/x-www-form-urlencoded"})
}
if mt == "multipart/form-data" {
if err := request.ParseMultipartForm(defaultMaxMemory); err != nil {
return errors.NewParseError(p.Name, p.parameter.In, "", err)
}
}
if err := request.ParseForm(); err != nil {
return errors.NewParseError(p.Name, p.parameter.In, "", err)
}
if p.parameter.Type == "file" {
file, header, err := request.FormFile(p.parameter.Name)
if err != nil {
return errors.NewParseError(p.Name, p.parameter.In, "", err)
}
target.Set(reflect.ValueOf(runtime.File{Data: file, Header: header}))
return nil
}
if request.MultipartForm != nil {
data, custom, hasKey, err := p.readValue(runtime.Values(request.MultipartForm.Value), target)
if err != nil {
return err
}
if custom {
return nil
}
return p.bindValue(data, hasKey, target)
}
data, custom, hasKey, err := p.readValue(runtime.Values(request.PostForm), target)
if err != nil {
return err
}
if custom {
return nil
}
return p.bindValue(data, hasKey, target)
case "body":
newValue := reflect.New(target.Type())
if !runtime.HasBody(request) {
if p.parameter.Default != nil {
target.Set(reflect.ValueOf(p.parameter.Default))
}
return nil
}
if err := consumer.Consume(request.Body, newValue.Interface()); err != nil {
if err == io.EOF && p.parameter.Default != nil {
target.Set(reflect.ValueOf(p.parameter.Default))
return nil
}
tpe := p.parameter.Type
if p.parameter.Format != "" {
tpe = p.parameter.Format
}
return errors.InvalidType(p.Name, p.parameter.In, tpe, nil)
}
target.Set(reflect.Indirect(newValue))
return nil
default:
return errors.New(500, fmt.Sprintf("invalid parameter location %q", p.parameter.In))
}
}
func (p *untypedParamBinder) bindValue(data []string, hasKey bool, target reflect.Value) error {
if p.parameter.Type == "array" {
return p.setSliceFieldValue(target, p.parameter.Default, data, hasKey)
}
var d string
if len(data) > 0 {
d = data[len(data)-1]
}
return p.setFieldValue(target, p.parameter.Default, d, hasKey)
}
func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue interface{}, data string, hasKey bool) error {
tpe := p.parameter.Type
if p.parameter.Format != "" {
tpe = p.parameter.Format
}
if (!hasKey || (!p.parameter.AllowEmptyValue && data == "")) && p.parameter.Required && p.parameter.Default == nil {
return errors.Required(p.Name, p.parameter.In)
}
ok, err := p.tryUnmarshaler(target, defaultValue, data)
if err != nil {
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
}
if ok {
return nil
}
defVal := reflect.Zero(target.Type())
if defaultValue != nil {
defVal = reflect.ValueOf(defaultValue)
}
if tpe == "byte" {
if data == "" {
if target.CanSet() {
target.SetBytes(defVal.Bytes())
}
return nil
}
b, err := base64.StdEncoding.DecodeString(data)
if err != nil {
b, err = base64.URLEncoding.DecodeString(data)
if err != nil {
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
}
}
if target.CanSet() {
target.SetBytes(b)
}
return nil
}
switch target.Kind() {
case reflect.Bool:
if data == "" {
if target.CanSet() {
target.SetBool(defVal.Bool())
}
return nil
}
b, err := swag.ConvertBool(data)
if err != nil {
return err
}
if target.CanSet() {
target.SetBool(b)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if data == "" {
if target.CanSet() {
rd := defVal.Convert(reflect.TypeOf(int64(0)))
target.SetInt(rd.Int())
}
return nil
}
i, err := strconv.ParseInt(data, 10, 64)
if err != nil {
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
}
if target.OverflowInt(i) {
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
}
if target.CanSet() {
target.SetInt(i)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if data == "" {
if target.CanSet() {
rd := defVal.Convert(reflect.TypeOf(uint64(0)))
target.SetUint(rd.Uint())
}
return nil
}
u, err := strconv.ParseUint(data, 10, 64)
if err != nil {
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
}
if target.OverflowUint(u) {
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
}
if target.CanSet() {
target.SetUint(u)
}
case reflect.Float32, reflect.Float64:
if data == "" {
if target.CanSet() {
rd := defVal.Convert(reflect.TypeOf(float64(0)))
target.SetFloat(rd.Float())
}
return nil
}
f, err := strconv.ParseFloat(data, 64)
if err != nil {
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
}
if target.OverflowFloat(f) {
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
}
if target.CanSet() {
target.SetFloat(f)
}
case reflect.String:
value := data
if value == "" {
value = defVal.String()
}
// validate string
if target.CanSet() {
target.SetString(value)
}
case reflect.Ptr:
if data == "" && defVal.Kind() == reflect.Ptr {
if target.CanSet() {
target.Set(defVal)
}
return nil
}
newVal := reflect.New(target.Type().Elem())
if err := p.setFieldValue(reflect.Indirect(newVal), defVal, data, hasKey); err != nil {
return err
}
if target.CanSet() {
target.Set(newVal)
}
default:
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
}
return nil
}
func (p *untypedParamBinder) tryUnmarshaler(target reflect.Value, defaultValue interface{}, data string) (bool, error) {
if !target.CanSet() {
return false, nil
}
// When a type implements encoding.TextUnmarshaler we'll use that instead of reflecting some more
if reflect.PtrTo(target.Type()).Implements(textUnmarshalType) {
if defaultValue != nil && len(data) == 0 {
target.Set(reflect.ValueOf(defaultValue))
return true, nil
}
value := reflect.New(target.Type())
if err := value.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(data)); err != nil {
return true, err
}
target.Set(reflect.Indirect(value))
return true, nil
}
return false, nil
}
func (p *untypedParamBinder) readFormattedSliceFieldValue(data string, target reflect.Value) ([]string, bool, error) {
ok, err := p.tryUnmarshaler(target, p.parameter.Default, data)
if err != nil {
return nil, true, err
}
if ok {
return nil, true, nil
}
return swag.SplitByFormat(data, p.parameter.CollectionFormat), false, nil
}
func (p *untypedParamBinder) setSliceFieldValue(target reflect.Value, defaultValue interface{}, data []string, hasKey bool) error {
sz := len(data)
if (!hasKey || (!p.parameter.AllowEmptyValue && (sz == 0 || (sz == 1 && data[0] == "")))) && p.parameter.Required && defaultValue == nil {
return errors.Required(p.Name, p.parameter.In)
}
defVal := reflect.Zero(target.Type())
if defaultValue != nil {
defVal = reflect.ValueOf(defaultValue)
}
if !target.CanSet() {
return nil
}
if sz == 0 {
target.Set(defVal)
return nil
}
value := reflect.MakeSlice(reflect.SliceOf(target.Type().Elem()), sz, sz)
for i := 0; i < sz; i++ {
if err := p.setFieldValue(value.Index(i), nil, data[i], hasKey); err != nil {
return err
}
}
target.Set(value)
return nil
}

View File

@@ -0,0 +1,340 @@
// 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 middleware
import (
"math"
"net/url"
"reflect"
"strconv"
"testing"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
)
type email struct {
Address string
}
type paramFactory func(string) *spec.Parameter
var paramFactories = []paramFactory{
spec.QueryParam,
spec.HeaderParam,
spec.PathParam,
spec.FormDataParam,
}
func np(param *spec.Parameter) *untypedParamBinder {
return newUntypedParamBinder(*param, new(spec.Swagger), strfmt.Default)
}
var stringItems = new(spec.Items)
func init() {
stringItems.Type = "string"
}
func testCollectionFormat(t *testing.T, param *spec.Parameter, valid bool) {
binder := &untypedParamBinder{
parameter: param,
}
_, _, _, err := binder.readValue(runtime.Values(nil), reflect.ValueOf(nil))
if valid {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, errors.InvalidCollectionFormat(param.Name, param.In, param.CollectionFormat), err)
}
}
func requiredError(param *spec.Parameter) *errors.Validation {
return errors.Required(param.Name, param.In)
}
func validateRequiredTest(t *testing.T, param *spec.Parameter, value reflect.Value) {
binder := np(param)
err := binder.bindValue([]string{}, true, value)
assert.Error(t, err)
assert.NotNil(t, param)
assert.EqualError(t, requiredError(param), err.Error())
err = binder.bindValue([]string{""}, true, value)
if assert.Error(t, err) {
assert.EqualError(t, requiredError(param), err.Error())
}
// should be impossible data, but let's go with it
err = binder.bindValue([]string{"a"}, false, value)
assert.Error(t, err)
assert.EqualError(t, requiredError(param), err.Error())
err = binder.bindValue([]string{""}, false, value)
assert.Error(t, err)
assert.EqualError(t, requiredError(param), err.Error())
}
func validateRequiredAllowEmptyTest(t *testing.T, param *spec.Parameter, value reflect.Value) {
param.AllowEmptyValue = true
binder := np(param)
err := binder.bindValue([]string{}, true, value)
assert.NoError(t, err)
if assert.NotNil(t, param) {
err = binder.bindValue([]string{""}, true, value)
assert.NoError(t, err)
err = binder.bindValue([]string{"1"}, false, value)
assert.Error(t, err)
assert.EqualError(t, requiredError(param), err.Error())
err = binder.bindValue([]string{""}, false, value)
assert.Error(t, err)
assert.EqualError(t, requiredError(param), err.Error())
}
}
func TestRequiredValidation(t *testing.T) {
strParam := spec.QueryParam("name").Typed("string", "").AsRequired()
validateRequiredTest(t, strParam, reflect.ValueOf(""))
validateRequiredAllowEmptyTest(t, strParam, reflect.ValueOf(""))
intParam := spec.QueryParam("id").Typed("integer", "int32").AsRequired()
validateRequiredTest(t, intParam, reflect.ValueOf(int32(0)))
validateRequiredAllowEmptyTest(t, intParam, reflect.ValueOf(int32(0)))
longParam := spec.QueryParam("id").Typed("integer", "int64").AsRequired()
validateRequiredTest(t, longParam, reflect.ValueOf(int64(0)))
validateRequiredAllowEmptyTest(t, longParam, reflect.ValueOf(int64(0)))
floatParam := spec.QueryParam("score").Typed("number", "float").AsRequired()
validateRequiredTest(t, floatParam, reflect.ValueOf(float32(0)))
validateRequiredAllowEmptyTest(t, floatParam, reflect.ValueOf(float32(0)))
doubleParam := spec.QueryParam("score").Typed("number", "double").AsRequired()
validateRequiredTest(t, doubleParam, reflect.ValueOf(float64(0)))
validateRequiredAllowEmptyTest(t, doubleParam, reflect.ValueOf(float64(0)))
dateTimeParam := spec.QueryParam("registered").Typed("string", "date-time").AsRequired()
validateRequiredTest(t, dateTimeParam, reflect.ValueOf(strfmt.DateTime{}))
// validateRequiredAllowEmptyTest(t, dateTimeParam, reflect.ValueOf(strfmt.DateTime{}))
dateParam := spec.QueryParam("registered").Typed("string", "date").AsRequired()
validateRequiredTest(t, dateParam, reflect.ValueOf(strfmt.Date{}))
// validateRequiredAllowEmptyTest(t, dateParam, reflect.ValueOf(strfmt.DateTime{}))
sliceParam := spec.QueryParam("tags").CollectionOf(stringItems, "").AsRequired()
validateRequiredTest(t, sliceParam, reflect.MakeSlice(reflect.TypeOf([]string{}), 0, 0))
validateRequiredAllowEmptyTest(t, sliceParam, reflect.MakeSlice(reflect.TypeOf([]string{}), 0, 0))
}
func TestInvalidCollectionFormat(t *testing.T) {
validCf1 := spec.QueryParam("validFmt").CollectionOf(stringItems, "multi")
validCf2 := spec.FormDataParam("validFmt2").CollectionOf(stringItems, "multi")
invalidCf1 := spec.HeaderParam("invalidHdr").CollectionOf(stringItems, "multi")
invalidCf2 := spec.PathParam("invalidPath").CollectionOf(stringItems, "multi")
testCollectionFormat(t, validCf1, true)
testCollectionFormat(t, validCf2, true)
testCollectionFormat(t, invalidCf1, false)
testCollectionFormat(t, invalidCf2, false)
}
func invalidTypeError(param *spec.Parameter, data interface{}) *errors.Validation {
tpe := param.Type
if param.Format != "" {
tpe = param.Format
}
return errors.InvalidType(param.Name, param.In, tpe, data)
}
func TestTypeValidation(t *testing.T) {
for _, newParam := range paramFactories {
intParam := newParam("badInt").Typed("integer", "int32")
value := reflect.ValueOf(int32(0))
binder := np(intParam)
err := binder.bindValue([]string{"yada"}, true, value)
// fails for invalid string
assert.Error(t, err)
assert.Equal(t, invalidTypeError(intParam, "yada"), err)
// fails for overflow
val := int64(math.MaxInt32)
str := strconv.FormatInt(val, 10) + "0"
v := int32(0)
value = reflect.ValueOf(&v).Elem()
binder = np(intParam)
err = binder.bindValue([]string{str}, true, value)
assert.Error(t, err)
assert.Equal(t, invalidTypeError(intParam, str), err)
longParam := newParam("badLong").Typed("integer", "int64")
value = reflect.ValueOf(int64(0))
binder = np(longParam)
err = binder.bindValue([]string{"yada"}, true, value)
// fails for invalid string
assert.Error(t, err)
assert.Equal(t, invalidTypeError(longParam, "yada"), err)
// fails for overflow
str2 := strconv.FormatInt(math.MaxInt64, 10) + "0"
v2 := int64(0)
vv2 := reflect.ValueOf(&v2).Elem()
binder = np(longParam)
err = binder.bindValue([]string{str2}, true, vv2)
assert.Error(t, err)
assert.Equal(t, invalidTypeError(longParam, str2), err)
floatParam := newParam("badFloat").Typed("number", "float")
value = reflect.ValueOf(float64(0))
binder = np(floatParam)
err = binder.bindValue([]string{"yada"}, true, value)
// fails for invalid string
assert.Error(t, err)
assert.Equal(t, invalidTypeError(floatParam, "yada"), err)
// fails for overflow
str3 := strconv.FormatFloat(math.MaxFloat64, 'f', 5, 64)
v3 := reflect.TypeOf(float32(0))
value = reflect.New(v3).Elem()
binder = np(floatParam)
err = binder.bindValue([]string{str3}, true, value)
assert.Error(t, err)
assert.Equal(t, invalidTypeError(floatParam, str3), err)
doubleParam := newParam("badDouble").Typed("number", "double")
value = reflect.ValueOf(float64(0))
binder = np(doubleParam)
err = binder.bindValue([]string{"yada"}, true, value)
// fails for invalid string
assert.Error(t, err)
assert.Equal(t, invalidTypeError(doubleParam, "yada"), err)
// fails for overflow
str4 := "9" + strconv.FormatFloat(math.MaxFloat64, 'f', 5, 64)
v4 := reflect.TypeOf(float64(0))
value = reflect.New(v4).Elem()
binder = np(doubleParam)
err = binder.bindValue([]string{str4}, true, value)
assert.Error(t, err)
assert.Equal(t, invalidTypeError(doubleParam, str4), err)
dateParam := newParam("badDate").Typed("string", "date")
value = reflect.ValueOf(strfmt.Date{})
binder = np(dateParam)
err = binder.bindValue([]string{"yada"}, true, value)
// fails for invalid string
assert.Error(t, err)
assert.Equal(t, invalidTypeError(dateParam, "yada"), err)
dateTimeParam := newParam("badDateTime").Typed("string", "date-time")
value = reflect.ValueOf(strfmt.DateTime{})
binder = np(dateTimeParam)
err = binder.bindValue([]string{"yada"}, true, value)
// fails for invalid string
assert.Error(t, err)
assert.Equal(t, invalidTypeError(dateTimeParam, "yada"), err)
byteParam := newParam("badByte").Typed("string", "byte")
values := url.Values(map[string][]string{})
values.Add("badByte", "yaüda")
v5 := []byte{}
value = reflect.ValueOf(&v5).Elem()
binder = np(byteParam)
err = binder.bindValue([]string{"yaüda"}, true, value)
// fails for invalid string
assert.Error(t, err)
assert.Equal(t, invalidTypeError(byteParam, "yaüda"), err)
}
}
func TestTypeDetectionInvalidItems(t *testing.T) {
withoutItems := spec.QueryParam("without").CollectionOf(nil, "")
binder := &untypedParamBinder{
Name: "without",
parameter: withoutItems,
}
assert.Nil(t, binder.Type())
items := new(spec.Items)
items.Type = "array"
withInvalidItems := spec.QueryParam("invalidItems").CollectionOf(items, "")
binder = &untypedParamBinder{
Name: "invalidItems",
parameter: withInvalidItems,
}
assert.Nil(t, binder.Type())
noType := spec.QueryParam("invalidType")
noType.Type = "invalid"
binder = &untypedParamBinder{
Name: "invalidType",
parameter: noType,
}
assert.Nil(t, binder.Type())
}
// type emailStrFmt struct {
// name string
// tpe reflect.Type
// validator FormatValidator
// }
//
// func (e *emailStrFmt) Name() string {
// return e.name
// }
//
// func (e *emailStrFmt) Type() reflect.Type {
// return e.tpe
// }
//
// func (e *emailStrFmt) Matches(str string) bool {
// return e.validator(str)
// }
//
// func TestTypeDetectionValid(t *testing.T) {
// // emlFmt := &emailStrFmt{
// // name: "email",
// // tpe: reflect.TypeOf(email{}),
// // }
// // formats := []StringFormat{emlFmt}
//
// expected := map[string]reflect.Type{
// "name": reflect.TypeOf(""),
// "id": reflect.TypeOf(int64(0)),
// "age": reflect.TypeOf(int32(0)),
// "score": reflect.TypeOf(float32(0)),
// "factor": reflect.TypeOf(float64(0)),
// "friend": reflect.TypeOf(map[string]interface{}{}),
// "X-Request-Id": reflect.TypeOf(int64(0)),
// "tags": reflect.TypeOf([]string{}),
// "confirmed": reflect.TypeOf(true),
// "planned": reflect.TypeOf(swagger.Date{}),
// "delivered": reflect.TypeOf(swagger.DateTime{}),
// "email": reflect.TypeOf(email{}),
// "picture": reflect.TypeOf([]byte{}),
// "file": reflect.TypeOf(&swagger.File{}).Elem(),
// }
//
// params := parametersForAllTypes("")
// emailParam := spec.QueryParam("email").Typed("string", "email")
// params["email"] = *emailParam
//
// fileParam := spec.FileParam("file")
// params["file"] = *fileParam
//
// for _, v := range params {
// binder := &paramBinder{
// formats: formats,
// name: v.Name,
// parameter: &v,
// }
// assert.Equal(t, expected[v.Name], binder.Type(), "name: %s", v.Name)
// }
// }

View File

@@ -0,0 +1,101 @@
package middleware
import (
"bytes"
"fmt"
"html/template"
"net/http"
"path"
)
// RedocOpts configures the Redoc middlewares
type RedocOpts struct {
// BasePath for the UI path, defaults to: /
BasePath string
// Path combines with BasePath for the full UI path, defaults to: docs
Path string
// SpecURL the url to find the spec for
SpecURL string
// RedocURL for the js that generates the redoc site, defaults to: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js
RedocURL string
// Title for the documentation site, default to: API documentation
Title string
}
// EnsureDefaults in case some options are missing
func (r *RedocOpts) EnsureDefaults() {
if r.BasePath == "" {
r.BasePath = "/"
}
if r.Path == "" {
r.Path = "docs"
}
if r.SpecURL == "" {
r.SpecURL = "/swagger.json"
}
if r.RedocURL == "" {
r.RedocURL = redocLatest
}
if r.Title == "" {
r.Title = "API documentation"
}
}
// Redoc creates a middleware to serve a documentation site for a swagger spec.
// This allows for altering the spec before starting the http listener.
//
func Redoc(opts RedocOpts, next http.Handler) http.Handler {
opts.EnsureDefaults()
pth := path.Join(opts.BasePath, opts.Path)
tmpl := template.Must(template.New("redoc").Parse(redocTemplate))
buf := bytes.NewBuffer(nil)
tmpl.Execute(buf, opts)
b := buf.Bytes()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Path == pth {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.WriteHeader(http.StatusOK)
rw.Write(b)
return
}
if next == nil {
rw.Header().Set("Content-Type", "text/plain")
rw.WriteHeader(http.StatusNotFound)
rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
return
}
next.ServeHTTP(rw, r)
})
}
const (
redocLatest = "https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"
redocTemplate = `<!DOCTYPE html>
<html>
<head>
<title>{{ .Title }}</title>
<!-- needed for adaptive design -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='{{ .SpecURL }}'></redoc>
<script src="{{ .RedocURL }}"> </script>
</body>
</html>
`
)

View File

@@ -0,0 +1,22 @@
package middleware
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRedocMiddleware(t *testing.T) {
redoc := Redoc(RedocOpts{}, nil)
req, _ := http.NewRequest("GET", "/docs", nil)
recorder := httptest.NewRecorder()
redoc.ServeHTTP(recorder, req)
assert.Equal(t, 200, recorder.Code)
assert.Equal(t, "text/html; charset=utf-8", recorder.Header().Get("Content-Type"))
assert.Contains(t, recorder.Body.String(), "<title>API documentation</title>")
assert.Contains(t, recorder.Body.String(), "<redoc spec-url='/swagger.json'></redoc>")
assert.Contains(t, recorder.Body.String(), redocLatest)
}

View File

@@ -0,0 +1,104 @@
// 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 middleware
import (
"net/http"
"reflect"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
)
// RequestBinder binds and validates the data from a http request
type untypedRequestBinder struct {
Spec *spec.Swagger
Parameters map[string]spec.Parameter
Formats strfmt.Registry
paramBinders map[string]*untypedParamBinder
}
// NewRequestBinder creates a new binder for reading a request.
func newUntypedRequestBinder(parameters map[string]spec.Parameter, spec *spec.Swagger, formats strfmt.Registry) *untypedRequestBinder {
binders := make(map[string]*untypedParamBinder)
for fieldName, param := range parameters {
binders[fieldName] = newUntypedParamBinder(param, spec, formats)
}
return &untypedRequestBinder{
Parameters: parameters,
paramBinders: binders,
Spec: spec,
Formats: formats,
}
}
// Bind perform the databinding and validation
func (o *untypedRequestBinder) Bind(request *http.Request, routeParams RouteParams, consumer runtime.Consumer, data interface{}) error {
val := reflect.Indirect(reflect.ValueOf(data))
isMap := val.Kind() == reflect.Map
var result []error
debugLog("binding %d parameters for %s %s", len(o.Parameters), request.Method, request.URL.EscapedPath())
for fieldName, param := range o.Parameters {
binder := o.paramBinders[fieldName]
debugLog("binding paramter %s for %s %s", fieldName, request.Method, request.URL.EscapedPath())
var target reflect.Value
if !isMap {
binder.Name = fieldName
target = val.FieldByName(fieldName)
}
if isMap {
tpe := binder.Type()
if tpe == nil {
if param.Schema.Type.Contains("array") {
tpe = reflect.TypeOf([]interface{}{})
} else {
tpe = reflect.TypeOf(map[string]interface{}{})
}
}
target = reflect.Indirect(reflect.New(tpe))
}
if !target.IsValid() {
result = append(result, errors.New(500, "parameter name %q is an unknown field", binder.Name))
continue
}
if err := binder.Bind(request, routeParams, consumer, target); err != nil {
result = append(result, err)
continue
}
if binder.validator != nil {
rr := binder.validator.Validate(target.Interface())
if rr != nil && rr.HasErrors() {
result = append(result, rr.AsError())
}
}
if isMap {
val.SetMapIndex(reflect.ValueOf(param.Name), target)
}
}
if len(result) > 0 {
return errors.CompositeValidationError(result...)
}
return nil
}

View File

@@ -0,0 +1,481 @@
// 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 middleware
import (
"bytes"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"strings"
"testing"
"time"
"github.com/go-openapi/runtime"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
)
type stubConsumer struct {
}
func (s *stubConsumer) Consume(_ io.Reader, _ interface{}) error {
return nil
}
type friend struct {
Name string `json:"name"`
Age int `json:"age"`
}
type jsonRequestParams struct {
ID int64 // path
Name string // query
Friend friend // body
RequestID int64 // header
Tags []string // csv
}
type jsonRequestPtr struct {
ID int64 // path
Name string // query
RequestID int64 // header
Tags []string // csv
Friend *friend
}
type jsonRequestSlice struct {
ID int64 // path
Name string // query
RequestID int64 // header
Tags []string // csv
Friend []friend
}
type jsonRequestAllTypes struct {
Confirmed bool
Planned strfmt.Date
Delivered strfmt.DateTime
Age int32
ID int64
Score float32
Factor float64
Friend friend
Name string
Tags []string
Picture []byte
RequestID int64
}
func parametersForAllTypes(fmt string) map[string]spec.Parameter {
if fmt == "" {
fmt = "csv"
}
nameParam := spec.QueryParam("name").Typed("string", "")
idParam := spec.PathParam("id").Typed("integer", "int64")
ageParam := spec.QueryParam("age").Typed("integer", "int32")
scoreParam := spec.QueryParam("score").Typed("number", "float")
factorParam := spec.QueryParam("factor").Typed("number", "double")
friendSchema := new(spec.Schema).Typed("object", "")
friendParam := spec.BodyParam("friend", friendSchema)
requestIDParam := spec.HeaderParam("X-Request-Id").Typed("integer", "int64")
requestIDParam.Extensions = spec.Extensions(map[string]interface{}{})
requestIDParam.Extensions.Add("go-name", "RequestID")
items := new(spec.Items)
items.Type = "string"
tagsParam := spec.QueryParam("tags").CollectionOf(items, fmt)
confirmedParam := spec.QueryParam("confirmed").Typed("boolean", "")
plannedParam := spec.QueryParam("planned").Typed("string", "date")
deliveredParam := spec.QueryParam("delivered").Typed("string", "date-time")
pictureParam := spec.QueryParam("picture").Typed("string", "byte") // base64 encoded during transport
return map[string]spec.Parameter{
"ID": *idParam,
"Name": *nameParam,
"RequestID": *requestIDParam,
"Friend": *friendParam,
"Tags": *tagsParam,
"Age": *ageParam,
"Score": *scoreParam,
"Factor": *factorParam,
"Confirmed": *confirmedParam,
"Planned": *plannedParam,
"Delivered": *deliveredParam,
"Picture": *pictureParam,
}
}
func parametersForJSONRequestParams(fmt string) map[string]spec.Parameter {
if fmt == "" {
fmt = "csv"
}
nameParam := spec.QueryParam("name").Typed("string", "")
idParam := spec.PathParam("id").Typed("integer", "int64")
friendSchema := new(spec.Schema).Typed("object", "")
friendParam := spec.BodyParam("friend", friendSchema)
requestIDParam := spec.HeaderParam("X-Request-Id").Typed("integer", "int64")
requestIDParam.Extensions = spec.Extensions(map[string]interface{}{})
requestIDParam.Extensions.Add("go-name", "RequestID")
items := new(spec.Items)
items.Type = "string"
tagsParam := spec.QueryParam("tags").CollectionOf(items, fmt)
return map[string]spec.Parameter{
"ID": *idParam,
"Name": *nameParam,
"RequestID": *requestIDParam,
"Friend": *friendParam,
"Tags": *tagsParam,
}
}
func parametersForJSONRequestSliceParams(fmt string) map[string]spec.Parameter {
if fmt == "" {
fmt = "csv"
}
nameParam := spec.QueryParam("name").Typed("string", "")
idParam := spec.PathParam("id").Typed("integer", "int64")
friendSchema := new(spec.Schema).Typed("object", "")
friendParam := spec.BodyParam("friend", spec.ArrayProperty(friendSchema))
requestIDParam := spec.HeaderParam("X-Request-Id").Typed("integer", "int64")
requestIDParam.Extensions = spec.Extensions(map[string]interface{}{})
requestIDParam.Extensions.Add("go-name", "RequestID")
items := new(spec.Items)
items.Type = "string"
tagsParam := spec.QueryParam("tags").CollectionOf(items, fmt)
return map[string]spec.Parameter{
"ID": *idParam,
"Name": *nameParam,
"RequestID": *requestIDParam,
"Friend": *friendParam,
"Tags": *tagsParam,
}
}
func TestRequestBindingDefaultValue(t *testing.T) {
confirmed := true
name := "thomas"
friend := map[string]interface{}{"name": "toby", "age": float64(32)}
id, age, score, factor := int64(7575), int32(348), float32(5.309), float64(37.403)
requestID := 19394858
tags := []string{"one", "two", "three"}
dt1 := time.Date(2014, 8, 9, 0, 0, 0, 0, time.UTC)
planned := strfmt.Date(dt1)
dt2 := time.Date(2014, 10, 12, 8, 5, 5, 0, time.UTC)
delivered := strfmt.DateTime(dt2)
uri, _ := url.Parse("http://localhost:8002/hello")
defaults := map[string]interface{}{
"id": id,
"age": age,
"score": score,
"factor": factor,
"name": name,
"friend": friend,
"X-Request-Id": requestID,
"tags": tags,
"confirmed": confirmed,
"planned": planned,
"delivered": delivered,
"picture": []byte("hello"),
}
op2 := parametersForAllTypes("")
op3 := make(map[string]spec.Parameter)
for k, p := range op2 {
p.Default = defaults[p.Name]
op3[k] = p
}
req, _ := http.NewRequest("POST", uri.String(), bytes.NewBuffer(nil))
req.Header.Set("Content-Type", "application/json")
binder := newUntypedRequestBinder(op3, new(spec.Swagger), strfmt.Default)
data := make(map[string]interface{})
err := binder.Bind(req, RouteParams(nil), runtime.JSONConsumer(), &data)
assert.NoError(t, err)
assert.Equal(t, defaults["id"], data["id"])
assert.Equal(t, name, data["name"])
assert.Equal(t, friend, data["friend"])
assert.EqualValues(t, requestID, data["X-Request-Id"])
assert.Equal(t, tags, data["tags"])
assert.Equal(t, planned, data["planned"])
assert.Equal(t, delivered, data["delivered"])
assert.Equal(t, confirmed, data["confirmed"])
assert.Equal(t, age, data["age"])
assert.Equal(t, factor, data["factor"])
assert.Equal(t, score, data["score"])
assert.Equal(t, "hello", string(data["picture"].(strfmt.Base64)))
}
func TestRequestBindingForInvalid(t *testing.T) {
invalidParam := spec.QueryParam("some")
op1 := map[string]spec.Parameter{"Some": *invalidParam}
binder := newUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default)
req, _ := http.NewRequest("GET", "http://localhost:8002/hello?name=the-name", nil)
err := binder.Bind(req, nil, new(stubConsumer), new(jsonRequestParams))
assert.Error(t, err)
op2 := parametersForJSONRequestParams("")
binder = newUntypedRequestBinder(op2, new(spec.Swagger), strfmt.Default)
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{"name":"toby","age":32}`)))
req.Header.Set("Content-Type", "application(")
data := jsonRequestParams{}
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
assert.Error(t, err)
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{]`)))
req.Header.Set("Content-Type", "application/json")
data = jsonRequestParams{}
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
assert.Error(t, err)
invalidMultiParam := spec.HeaderParam("tags").CollectionOf(new(spec.Items), "multi")
op3 := map[string]spec.Parameter{"Tags": *invalidMultiParam}
binder = newUntypedRequestBinder(op3, new(spec.Swagger), strfmt.Default)
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{}`)))
req.Header.Set("Content-Type", "application/json")
data = jsonRequestParams{}
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
assert.Error(t, err)
invalidMultiParam = spec.PathParam("").CollectionOf(new(spec.Items), "multi")
op4 := map[string]spec.Parameter{"Tags": *invalidMultiParam}
binder = newUntypedRequestBinder(op4, new(spec.Swagger), strfmt.Default)
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{}`)))
req.Header.Set("Content-Type", "application/json")
data = jsonRequestParams{}
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
assert.Error(t, err)
invalidInParam := spec.HeaderParam("tags").Typed("string", "")
invalidInParam.In = "invalid"
op5 := map[string]spec.Parameter{"Tags": *invalidInParam}
binder = newUntypedRequestBinder(op5, new(spec.Swagger), strfmt.Default)
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{}`)))
req.Header.Set("Content-Type", "application/json")
data = jsonRequestParams{}
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
assert.Error(t, err)
}
func TestRequestBindingForValid(t *testing.T) {
for _, fmt := range []string{"csv", "pipes", "tsv", "ssv", "multi"} {
op1 := parametersForJSONRequestParams(fmt)
binder := newUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default)
lval := []string{"one", "two", "three"}
queryString := ""
switch fmt {
case "multi":
queryString = strings.Join(lval, "&tags=")
case "ssv":
queryString = strings.Join(lval, " ")
case "pipes":
queryString = strings.Join(lval, "|")
case "tsv":
queryString = strings.Join(lval, "\t")
default:
queryString = strings.Join(lval, ",")
}
urlStr := "http://localhost:8002/hello/1?name=the-name&tags=" + queryString
req, _ := http.NewRequest("POST", urlStr, bytes.NewBuffer([]byte(`{"name":"toby","age":32}`)))
req.Header.Set("Content-Type", "application/json;charset=utf-8")
req.Header.Set("X-Request-Id", "1325959595")
data := jsonRequestParams{}
err := binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
expected := jsonRequestParams{
ID: 1,
Name: "the-name",
Friend: friend{"toby", 32},
RequestID: 1325959595,
Tags: []string{"one", "two", "three"},
}
assert.NoError(t, err)
assert.Equal(t, expected, data)
}
op1 := parametersForJSONRequestParams("")
binder := newUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default)
urlStr := "http://localhost:8002/hello/1?name=the-name&tags=one,two,three"
req, _ := http.NewRequest("POST", urlStr, bytes.NewBuffer([]byte(`{"name":"toby","age":32}`)))
req.Header.Set("Content-Type", "application/json;charset=utf-8")
req.Header.Set("X-Request-Id", "1325959595")
data2 := jsonRequestPtr{}
err := binder.Bind(req, []RouteParam{{"id", "1"}}, runtime.JSONConsumer(), &data2)
expected2 := jsonRequestPtr{
Friend: &friend{"toby", 32},
Tags: []string{"one", "two", "three"},
}
assert.NoError(t, err)
if data2.Friend == nil {
t.Fatal("friend is nil")
}
assert.Equal(t, *expected2.Friend, *data2.Friend)
assert.Equal(t, expected2.Tags, data2.Tags)
req, _ = http.NewRequest("POST", urlStr, bytes.NewBuffer([]byte(`[{"name":"toby","age":32}]`)))
req.Header.Set("Content-Type", "application/json;charset=utf-8")
req.Header.Set("X-Request-Id", "1325959595")
op2 := parametersForJSONRequestSliceParams("")
binder = newUntypedRequestBinder(op2, new(spec.Swagger), strfmt.Default)
data3 := jsonRequestSlice{}
err = binder.Bind(req, []RouteParam{{"id", "1"}}, runtime.JSONConsumer(), &data3)
expected3 := jsonRequestSlice{
Friend: []friend{{"toby", 32}},
Tags: []string{"one", "two", "three"},
}
assert.NoError(t, err)
assert.Equal(t, expected3.Friend, data3.Friend)
assert.Equal(t, expected3.Tags, data3.Tags)
}
type formRequest struct {
Name string
Age int
}
func parametersForFormUpload() map[string]spec.Parameter {
nameParam := spec.FormDataParam("name").Typed("string", "")
ageParam := spec.FormDataParam("age").Typed("integer", "int32")
return map[string]spec.Parameter{"Name": *nameParam, "Age": *ageParam}
}
func TestFormUpload(t *testing.T) {
params := parametersForFormUpload()
binder := newUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
urlStr := "http://localhost:8002/hello"
req, _ := http.NewRequest("POST", urlStr, bytes.NewBufferString(`name=the-name&age=32`))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
data := formRequest{}
res := binder.Bind(req, nil, runtime.JSONConsumer(), &data)
assert.NoError(t, res)
assert.Equal(t, "the-name", data.Name)
assert.Equal(t, 32, data.Age)
req, _ = http.NewRequest("POST", urlStr, bytes.NewBufferString(`name=%3&age=32`))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
data = formRequest{}
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
}
type fileRequest struct {
Name string // body
File runtime.File // upload
}
func paramsForFileUpload() *untypedRequestBinder {
nameParam := spec.FormDataParam("name").Typed("string", "")
fileParam := spec.FileParam("file")
params := map[string]spec.Parameter{"Name": *nameParam, "File": *fileParam}
return newUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
}
func TestBindingFileUpload(t *testing.T) {
binder := paramsForFileUpload()
body := bytes.NewBuffer(nil)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "plain-jane.txt")
assert.NoError(t, err)
part.Write([]byte("the file contents"))
writer.WriteField("name", "the-name")
assert.NoError(t, writer.Close())
urlStr := "http://localhost:8002/hello"
req, _ := http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
data := fileRequest{}
assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
assert.Equal(t, "the-name", data.Name)
assert.NotNil(t, data.File)
assert.NotNil(t, data.File.Header)
assert.Equal(t, "plain-jane.txt", data.File.Header.Filename)
bb, err := ioutil.ReadAll(data.File.Data)
assert.NoError(t, err)
assert.Equal(t, []byte("the file contents"), bb)
req, _ = http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", "application/json")
data = fileRequest{}
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
req, _ = http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", "application(")
data = fileRequest{}
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
body = bytes.NewBuffer(nil)
writer = multipart.NewWriter(body)
part, err = writer.CreateFormFile("bad-name", "plain-jane.txt")
assert.NoError(t, err)
part.Write([]byte("the file contents"))
writer.WriteField("name", "the-name")
assert.NoError(t, writer.Close())
req, _ = http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
data = fileRequest{}
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
req, _ = http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
req.MultipartReader()
data = fileRequest{}
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
}

View File

@@ -0,0 +1,38 @@
// 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 middleware
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRouteParams(t *testing.T) {
coll1 := RouteParams([]RouteParam{
{"blah", "foo"},
{"abc", "bar"},
{"ccc", "efg"},
})
v := coll1.Get("blah")
assert.Equal(t, v, "foo")
v2 := coll1.Get("abc")
assert.Equal(t, v2, "bar")
v3 := coll1.Get("ccc")
assert.Equal(t, v3, "efg")
v4 := coll1.Get("ydkdk")
assert.Empty(t, v4)
}

View File

@@ -0,0 +1,262 @@
// 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 middleware
import (
"net/http"
fpath "path"
"regexp"
"strings"
"github.com/go-openapi/analysis"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware/denco"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/gorilla/context"
)
// RouteParam is a object to capture route params in a framework agnostic way.
// implementations of the muxer should use these route params to communicate with the
// swagger framework
type RouteParam struct {
Name string
Value string
}
// RouteParams the collection of route params
type RouteParams []RouteParam
// Get gets the value for the route param for the specified key
func (r RouteParams) Get(name string) string {
vv, _, _ := r.GetOK(name)
if len(vv) > 0 {
return vv[len(vv)-1]
}
return ""
}
// GetOK gets the value but also returns booleans to indicate if a key or value
// is present. This aids in validation and satisfies an interface in use there
//
// The returned values are: data, has key, has value
func (r RouteParams) GetOK(name string) ([]string, bool, bool) {
for _, p := range r {
if p.Name == name {
return []string{p.Value}, true, p.Value != ""
}
}
return nil, false, false
}
// NewRouter creates a new context aware router middleware
func NewRouter(ctx *Context, next http.Handler) http.Handler {
if ctx.router == nil {
ctx.router = DefaultRouter(ctx.spec, ctx.api)
}
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
defer context.Clear(r)
if _, ok := ctx.RouteInfo(r); ok {
next.ServeHTTP(rw, r)
return
}
// Not found, check if it exists in the other methods first
if others := ctx.AllowedMethods(r); len(others) > 0 {
ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others))
return
}
ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.EscapedPath()))
})
}
// RoutableAPI represents an interface for things that can serve
// as a provider of implementations for the swagger router
type RoutableAPI interface {
HandlerFor(string, string) (http.Handler, bool)
ServeErrorFor(string) func(http.ResponseWriter, *http.Request, error)
ConsumersFor([]string) map[string]runtime.Consumer
ProducersFor([]string) map[string]runtime.Producer
AuthenticatorsFor(map[string]spec.SecurityScheme) map[string]runtime.Authenticator
Formats() strfmt.Registry
DefaultProduces() string
DefaultConsumes() string
}
// Router represents a swagger aware router
type Router interface {
Lookup(method, path string) (*MatchedRoute, bool)
OtherMethods(method, path string) []string
}
type defaultRouteBuilder struct {
spec *loads.Document
analyzer *analysis.Spec
api RoutableAPI
records map[string][]denco.Record
}
type defaultRouter struct {
spec *loads.Document
api RoutableAPI
routers map[string]*denco.Router
}
func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI) *defaultRouteBuilder {
return &defaultRouteBuilder{
spec: spec,
analyzer: analysis.New(spec.Spec()),
api: api,
records: make(map[string][]denco.Record),
}
}
// DefaultRouter creates a default implemenation of the router
func DefaultRouter(spec *loads.Document, api RoutableAPI) Router {
builder := newDefaultRouteBuilder(spec, api)
if spec != nil {
for method, paths := range builder.analyzer.Operations() {
for path, operation := range paths {
fp := fpath.Join(spec.BasePath(), path)
debugLog("adding route %s %s %q", method, fp, operation.ID)
builder.AddRoute(method, fp, operation)
}
}
}
return builder.Build()
}
type routeEntry struct {
PathPattern string
BasePath string
Operation *spec.Operation
Consumes []string
Consumers map[string]runtime.Consumer
Produces []string
Producers map[string]runtime.Producer
Parameters map[string]spec.Parameter
Handler http.Handler
Formats strfmt.Registry
Binder *untypedRequestBinder
Authenticators map[string]runtime.Authenticator
Scopes map[string][]string
}
// MatchedRoute represents the route that was matched in this request
type MatchedRoute struct {
routeEntry
Params RouteParams
Consumer runtime.Consumer
Producer runtime.Producer
}
func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) {
mth := strings.ToUpper(method)
debugLog("looking up route for %s %s", method, path)
if Debug {
if len(d.routers) == 0 {
debugLog("there are no known routers")
}
for meth := range d.routers {
debugLog("got a router for %s", meth)
}
}
if router, ok := d.routers[mth]; ok {
if m, rp, ok := router.Lookup(path); ok && m != nil {
if entry, ok := m.(*routeEntry); ok {
debugLog("found a route for %s %s with %d parameters", method, path, len(entry.Parameters))
var params RouteParams
for _, p := range rp {
params = append(params, RouteParam{Name: p.Name, Value: p.Value})
}
return &MatchedRoute{routeEntry: *entry, Params: params}, true
}
} else {
debugLog("couldn't find a route by path for %s %s", method, path)
}
} else {
debugLog("couldn't find a route by method for %s %s", method, path)
}
return nil, false
}
func (d *defaultRouter) OtherMethods(method, path string) []string {
mn := strings.ToUpper(method)
var methods []string
for k, v := range d.routers {
if k != mn {
if _, _, ok := v.Lookup(path); ok {
methods = append(methods, k)
continue
}
}
}
return methods
}
var pathConverter = regexp.MustCompile(`{(\w+)}`)
func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Operation) {
mn := strings.ToUpper(method)
bp := fpath.Clean(d.spec.BasePath())
if len(bp) > 0 && bp[len(bp)-1] == '/' {
bp = bp[:len(bp)-1]
}
if handler, ok := d.api.HandlerFor(method, strings.TrimPrefix(path, bp)); ok {
consumes := d.analyzer.ConsumesFor(operation)
produces := d.analyzer.ProducesFor(operation)
parameters := d.analyzer.ParamsFor(method, strings.TrimPrefix(path, bp))
definitions := d.analyzer.SecurityDefinitionsFor(operation)
requirements := d.analyzer.SecurityRequirementsFor(operation)
scopes := make(map[string][]string, len(requirements))
for _, v := range requirements {
scopes[v.Name] = v.Scopes
}
record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{
Operation: operation,
Handler: handler,
Consumes: consumes,
Produces: produces,
Consumers: d.api.ConsumersFor(consumes),
Producers: d.api.ProducersFor(produces),
Parameters: parameters,
Formats: d.api.Formats(),
Binder: newUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()),
Authenticators: d.api.AuthenticatorsFor(definitions),
Scopes: scopes,
})
d.records[mn] = append(d.records[mn], record)
}
}
func (d *defaultRouteBuilder) Build() *defaultRouter {
routers := make(map[string]*denco.Router)
for method, records := range d.records {
router := denco.New()
router.Build(records)
routers[method] = router
}
return &defaultRouter{
spec: d.spec,
routers: routers,
}
}

View File

@@ -0,0 +1,206 @@
// 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 middleware
import (
"net/http"
"net/http/httptest"
"sort"
"strings"
"testing"
"github.com/go-openapi/analysis"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime/internal/testing/petstore"
"github.com/go-openapi/runtime/middleware/untyped"
"github.com/stretchr/testify/assert"
)
func terminator(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusOK)
}
func TestRouterMiddleware(t *testing.T) {
spec, api := petstore.NewAPI(t)
context := NewContext(spec, api, nil)
mw := NewRouter(context, http.HandlerFunc(terminator))
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/api/pets", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("DELETE", "/api/pets", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusMethodNotAllowed, recorder.Code)
methods := strings.Split(recorder.Header().Get("Allow"), ",")
sort.Sort(sort.StringSlice(methods))
assert.Equal(t, "GET,POST", strings.Join(methods, ","))
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/nopets", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusNotFound, recorder.Code)
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/pets", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusNotFound, recorder.Code)
spec, api = petstore.NewRootAPI(t)
context = NewContext(spec, api, nil)
mw = NewRouter(context, http.HandlerFunc(terminator))
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/pets", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("DELETE", "/pets", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusMethodNotAllowed, recorder.Code)
methods = strings.Split(recorder.Header().Get("Allow"), ",")
sort.Sort(sort.StringSlice(methods))
assert.Equal(t, "GET,POST", strings.Join(methods, ","))
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/nopets", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusNotFound, recorder.Code)
}
func TestRouterBuilder(t *testing.T) {
spec, api := petstore.NewAPI(t)
analyzed := analysis.New(spec.Spec())
assert.Len(t, analyzed.RequiredConsumes(), 3)
assert.Len(t, analyzed.RequiredProduces(), 5)
assert.Len(t, analyzed.OperationIDs(), 4)
// context := NewContext(spec, api)
builder := petAPIRouterBuilder(spec, api, analyzed)
getRecords := builder.records["GET"]
postRecords := builder.records["POST"]
deleteRecords := builder.records["DELETE"]
assert.Len(t, getRecords, 2)
assert.Len(t, postRecords, 1)
assert.Len(t, deleteRecords, 1)
assert.Empty(t, builder.records["PATCH"])
assert.Empty(t, builder.records["OPTIONS"])
assert.Empty(t, builder.records["HEAD"])
assert.Empty(t, builder.records["PUT"])
rec := postRecords[0]
assert.Equal(t, rec.Key, "/pets")
val := rec.Value.(*routeEntry)
assert.Len(t, val.Consumers, 1)
assert.Len(t, val.Producers, 1)
assert.Len(t, val.Consumes, 1)
assert.Len(t, val.Produces, 1)
assert.Len(t, val.Parameters, 1)
recG := getRecords[0]
assert.Equal(t, recG.Key, "/pets")
valG := recG.Value.(*routeEntry)
assert.Len(t, valG.Consumers, 2)
assert.Len(t, valG.Producers, 4)
assert.Len(t, valG.Consumes, 2)
assert.Len(t, valG.Produces, 4)
assert.Len(t, valG.Parameters, 2)
}
func TestRouterCanonicalBasePath(t *testing.T) {
spec, api := petstore.NewAPI(t)
spec.Spec().BasePath = "/api///"
context := NewContext(spec, api, nil)
mw := NewRouter(context, http.HandlerFunc(terminator))
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/api/pets", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
}
func TestRouter_EscapedPath(t *testing.T) {
spec, api := petstore.NewAPI(t)
spec.Spec().BasePath = "/api/"
context := NewContext(spec, api, nil)
mw := NewRouter(context, http.HandlerFunc(terminator))
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/api/pets/123", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/api/pets/abc%2Fdef", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
ri, _ := context.RouteInfo(request)
if assert.NotNil(t, ri) {
if assert.NotNil(t, ri.Params) {
assert.Equal(t, "abc%2Fdef", ri.Params.Get("id"))
}
}
}
func TestRouterStruct(t *testing.T) {
spec, api := petstore.NewAPI(t)
router := DefaultRouter(spec, newRoutableUntypedAPI(spec, api, new(Context)))
methods := router.OtherMethods("post", "/api/pets/{id}")
assert.Len(t, methods, 2)
entry, ok := router.Lookup("delete", "/api/pets/{id}")
assert.True(t, ok)
assert.NotNil(t, entry)
assert.Len(t, entry.Params, 1)
assert.Equal(t, "id", entry.Params[0].Name)
_, ok = router.Lookup("delete", "/pets")
assert.False(t, ok)
_, ok = router.Lookup("post", "/no-pets")
assert.False(t, ok)
}
func petAPIRouterBuilder(spec *loads.Document, api *untyped.API, analyzed *analysis.Spec) *defaultRouteBuilder {
builder := newDefaultRouteBuilder(spec, newRoutableUntypedAPI(spec, api, new(Context)))
builder.AddRoute("GET", "/pets", analyzed.AllPaths()["/pets"].Get)
builder.AddRoute("POST", "/pets", analyzed.AllPaths()["/pets"].Post)
builder.AddRoute("DELETE", "/pets/{id}", analyzed.AllPaths()["/pets/{id}"].Delete)
builder.AddRoute("GET", "/pets/{id}", analyzed.AllPaths()["/pets/{id}"].Get)
return builder
}

View File

@@ -0,0 +1,34 @@
// 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 middleware
import "net/http"
func newSecureAPI(ctx *Context, next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
route, _ := ctx.RouteInfo(r)
if route != nil && len(route.Authenticators) == 0 {
next.ServeHTTP(rw, r)
return
}
if _, err := ctx.Authorize(r, route); err != nil {
ctx.Respond(rw, r, route.Produces, route, err)
return
}
next.ServeHTTP(rw, r)
})
}

View File

@@ -0,0 +1,58 @@
// 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 middleware
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/go-openapi/runtime/internal/testing/petstore"
"github.com/stretchr/testify/assert"
)
func TestSecurityMiddleware(t *testing.T) {
spec, api := petstore.NewAPI(t)
context := NewContext(spec, api, nil)
context.router = DefaultRouter(spec, context.api)
mw := newSecureAPI(context, http.HandlerFunc(terminator))
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/api/pets", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, 401, recorder.Code)
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/api/pets", nil)
request.SetBasicAuth("admin", "wrong")
mw.ServeHTTP(recorder, request)
assert.Equal(t, 401, recorder.Code)
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "/api/pets", nil)
request.SetBasicAuth("admin", "admin")
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "//apipets/1", nil)
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
}

View File

@@ -0,0 +1,47 @@
// 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 middleware
import (
"net/http"
"path"
)
// Spec creates a middleware to serve a swagger spec.
// This allows for altering the spec before starting the http listener.
// This can be useful if you want to serve the swagger spec from another path than /swagger.json
//
func Spec(basePath string, b []byte, next http.Handler) http.Handler {
if basePath == "" {
basePath = "/"
}
pth := path.Join(basePath, "swagger.json")
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Path == pth {
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
rw.Write(b)
return
}
if next == nil {
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusNotFound)
return
}
next.ServeHTTP(rw, r)
})
}

View File

@@ -0,0 +1,56 @@
// 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 middleware
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/internal/testing/petstore"
"github.com/stretchr/testify/assert"
)
func TestServeSpecMiddleware(t *testing.T) {
spec, api := petstore.NewAPI(t)
ctx := NewContext(spec, api, nil)
handler := Spec("", ctx.spec.Raw(), nil)
// serves spec
request, _ := http.NewRequest("GET", "/swagger.json", nil)
request.Header.Add(runtime.HeaderContentType, runtime.JSONMime)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
// returns 404 when no next handler
request, _ = http.NewRequest("GET", "/api/pets", nil)
request.Header.Add(runtime.HeaderContentType, runtime.JSONMime)
recorder = httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
assert.Equal(t, 404, recorder.Code)
// forwards to next handler for other url
handler = Spec("", ctx.spec.Raw(), http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
request, _ = http.NewRequest("GET", "/api/pets", nil)
request.Header.Add(runtime.HeaderContentType, runtime.JSONMime)
recorder = httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
}

View File

@@ -0,0 +1,301 @@
// 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 middleware
import (
"errors"
"reflect"
"strings"
"testing"
"time"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/stretchr/testify/assert"
)
var evaluatesAsTrue = []string{"true", "1", "yes", "ok", "y", "on", "selected", "checked", "t", "enabled"}
type unmarshallerSlice []string
func (u *unmarshallerSlice) UnmarshalText(data []byte) error {
if len(data) == 0 {
return errors.New("an error")
}
*u = strings.Split(string(data), ",")
return nil
}
type SomeOperationParams struct {
Name string
ID int64
Confirmed bool
Age int
Visits int32
Count int16
Seq int8
UID uint64
UAge uint
UVisits uint32
UCount uint16
USeq uint8
Score float32
Rate float64
Timestamp strfmt.DateTime
Birthdate strfmt.Date
LastFailure *strfmt.DateTime
Unsupported struct{}
Tags []string
Prefs []int32
Categories unmarshallerSlice
}
func FloatParamTest(t *testing.T, fName, pName, format string, val reflect.Value, defVal, expectedDef interface{}, actual func() interface{}) {
fld := val.FieldByName(pName)
binder := &untypedParamBinder{
parameter: spec.QueryParam(pName).Typed("number", "double").WithDefault(defVal),
Name: pName,
}
err := binder.setFieldValue(fld, defVal, "5", true)
assert.NoError(t, err)
assert.EqualValues(t, 5, actual())
err = binder.setFieldValue(fld, defVal, "", true)
assert.NoError(t, err)
assert.EqualValues(t, expectedDef, actual())
err = binder.setFieldValue(fld, defVal, "yada", true)
assert.Error(t, err)
}
func IntParamTest(t *testing.T, pName string, val reflect.Value, defVal, expectedDef interface{}, actual func() interface{}) {
fld := val.FieldByName(pName)
binder := &untypedParamBinder{
parameter: spec.QueryParam(pName).Typed("integer", "int64").WithDefault(defVal),
Name: pName,
}
err := binder.setFieldValue(fld, defVal, "5", true)
assert.NoError(t, err)
assert.EqualValues(t, 5, actual())
err = binder.setFieldValue(fld, defVal, "", true)
assert.NoError(t, err)
assert.EqualValues(t, expectedDef, actual())
err = binder.setFieldValue(fld, defVal, "yada", true)
assert.Error(t, err)
}
func TestParamBinding(t *testing.T) {
actual := new(SomeOperationParams)
val := reflect.ValueOf(actual).Elem()
pName := "Name"
fld := val.FieldByName(pName)
binder := &untypedParamBinder{
parameter: spec.QueryParam(pName).Typed("string", "").WithDefault("some-name"),
Name: pName,
}
err := binder.setFieldValue(fld, "some-name", "the name value", true)
assert.NoError(t, err)
assert.Equal(t, "the name value", actual.Name)
err = binder.setFieldValue(fld, "some-name", "", true)
assert.NoError(t, err)
assert.Equal(t, "some-name", actual.Name)
IntParamTest(t, "ID", val, 1, 1, func() interface{} { return actual.ID })
IntParamTest(t, "ID", val, nil, 0, func() interface{} { return actual.ID })
IntParamTest(t, "Age", val, 1, 1, func() interface{} { return actual.Age })
IntParamTest(t, "Age", val, nil, 0, func() interface{} { return actual.Age })
IntParamTest(t, "Visits", val, 1, 1, func() interface{} { return actual.Visits })
IntParamTest(t, "Visits", val, nil, 0, func() interface{} { return actual.Visits })
IntParamTest(t, "Count", val, 1, 1, func() interface{} { return actual.Count })
IntParamTest(t, "Count", val, nil, 0, func() interface{} { return actual.Count })
IntParamTest(t, "Seq", val, 1, 1, func() interface{} { return actual.Seq })
IntParamTest(t, "Seq", val, nil, 0, func() interface{} { return actual.Seq })
IntParamTest(t, "UID", val, uint64(1), 1, func() interface{} { return actual.UID })
IntParamTest(t, "UID", val, uint64(0), 0, func() interface{} { return actual.UID })
IntParamTest(t, "UAge", val, uint(1), 1, func() interface{} { return actual.UAge })
IntParamTest(t, "UAge", val, nil, 0, func() interface{} { return actual.UAge })
IntParamTest(t, "UVisits", val, uint32(1), 1, func() interface{} { return actual.UVisits })
IntParamTest(t, "UVisits", val, nil, 0, func() interface{} { return actual.UVisits })
IntParamTest(t, "UCount", val, uint16(1), 1, func() interface{} { return actual.UCount })
IntParamTest(t, "UCount", val, nil, 0, func() interface{} { return actual.UCount })
IntParamTest(t, "USeq", val, uint8(1), 1, func() interface{} { return actual.USeq })
IntParamTest(t, "USeq", val, nil, 0, func() interface{} { return actual.USeq })
FloatParamTest(t, "score", "Score", "float", val, 1.0, 1, func() interface{} { return actual.Score })
FloatParamTest(t, "score", "Score", "float", val, nil, 0, func() interface{} { return actual.Score })
FloatParamTest(t, "rate", "Rate", "double", val, 1.0, 1, func() interface{} { return actual.Rate })
FloatParamTest(t, "rate", "Rate", "double", val, nil, 0, func() interface{} { return actual.Rate })
pName = "Confirmed"
confirmedField := val.FieldByName(pName)
binder = &untypedParamBinder{
parameter: spec.QueryParam(pName).Typed("boolean", "").WithDefault(true),
Name: pName,
}
for _, tv := range evaluatesAsTrue {
err = binder.setFieldValue(confirmedField, true, tv, true)
assert.NoError(t, err)
assert.True(t, actual.Confirmed)
}
err = binder.setFieldValue(confirmedField, true, "", true)
assert.NoError(t, err)
assert.True(t, actual.Confirmed)
err = binder.setFieldValue(confirmedField, true, "0", true)
assert.NoError(t, err)
assert.False(t, actual.Confirmed)
pName = "Timestamp"
timeField := val.FieldByName(pName)
dt := strfmt.DateTime(time.Date(2014, 3, 19, 2, 9, 0, 0, time.UTC))
binder = &untypedParamBinder{
parameter: spec.QueryParam(pName).Typed("string", "date-time").WithDefault(dt),
Name: pName,
}
exp := strfmt.DateTime(time.Date(2014, 5, 14, 2, 9, 0, 0, time.UTC))
err = binder.setFieldValue(timeField, dt, exp.String(), true)
assert.NoError(t, err)
assert.Equal(t, exp, actual.Timestamp)
err = binder.setFieldValue(timeField, dt, "", true)
assert.NoError(t, err)
assert.Equal(t, dt, actual.Timestamp)
err = binder.setFieldValue(timeField, dt, "yada", true)
assert.Error(t, err)
ddt := strfmt.Date(time.Date(2014, 3, 19, 0, 0, 0, 0, time.UTC))
pName = "Birthdate"
dateField := val.FieldByName(pName)
binder = &untypedParamBinder{
parameter: spec.QueryParam(pName).Typed("string", "date").WithDefault(ddt),
Name: pName,
}
expd := strfmt.Date(time.Date(2014, 5, 14, 0, 0, 0, 0, time.UTC))
err = binder.setFieldValue(dateField, ddt, expd.String(), true)
assert.NoError(t, err)
assert.Equal(t, expd, actual.Birthdate)
err = binder.setFieldValue(dateField, ddt, "", true)
assert.NoError(t, err)
assert.Equal(t, ddt, actual.Birthdate)
err = binder.setFieldValue(dateField, ddt, "yada", true)
assert.Error(t, err)
dt = strfmt.DateTime(time.Date(2014, 3, 19, 2, 9, 0, 0, time.UTC))
fdt := &dt
pName = "LastFailure"
ftimeField := val.FieldByName(pName)
binder = &untypedParamBinder{
parameter: spec.QueryParam(pName).Typed("string", "date").WithDefault(fdt),
Name: pName,
}
exp = strfmt.DateTime(time.Date(2014, 5, 14, 2, 9, 0, 0, time.UTC))
fexp := &exp
err = binder.setFieldValue(ftimeField, fdt, fexp.String(), true)
assert.NoError(t, err)
assert.Equal(t, fexp, actual.LastFailure)
err = binder.setFieldValue(ftimeField, fdt, "", true)
assert.NoError(t, err)
assert.Equal(t, fdt, actual.LastFailure)
err = binder.setFieldValue(ftimeField, fdt, "", true)
assert.NoError(t, err)
assert.Equal(t, fdt, actual.LastFailure)
actual.LastFailure = nil
err = binder.setFieldValue(ftimeField, fdt, "yada", true)
assert.Error(t, err)
assert.Nil(t, actual.LastFailure)
pName = "Unsupported"
unsupportedField := val.FieldByName(pName)
binder = &untypedParamBinder{
parameter: spec.QueryParam(pName).Typed("string", ""),
Name: pName,
}
err = binder.setFieldValue(unsupportedField, nil, "", true)
assert.Error(t, err)
}
func TestSliceConversion(t *testing.T) {
actual := new(SomeOperationParams)
val := reflect.ValueOf(actual).Elem()
// prefsField := val.FieldByName("Prefs")
// cData := "yada,2,3"
// _, _, err := readFormattedSliceFieldValue("Prefs", prefsField, cData, "csv", nil)
// assert.Error(t, err)
sliced := []string{"some", "string", "values"}
seps := map[string]string{"ssv": " ", "tsv": "\t", "pipes": "|", "csv": ",", "": ","}
tagsField := val.FieldByName("Tags")
for k, sep := range seps {
binder := &untypedParamBinder{
Name: "Tags",
parameter: spec.QueryParam("tags").CollectionOf(stringItems, k),
}
actual.Tags = nil
cData := strings.Join(sliced, sep)
tags, _, err := binder.readFormattedSliceFieldValue(cData, tagsField)
assert.NoError(t, err)
assert.Equal(t, sliced, tags)
cData = strings.Join(sliced, " "+sep+" ")
tags, _, err = binder.readFormattedSliceFieldValue(cData, tagsField)
assert.NoError(t, err)
assert.Equal(t, sliced, tags)
tags, _, err = binder.readFormattedSliceFieldValue("", tagsField)
assert.NoError(t, err)
assert.Empty(t, tags)
}
assert.Nil(t, swag.SplitByFormat("yada", "multi"))
assert.Nil(t, swag.SplitByFormat("", ""))
categoriesField := val.FieldByName("Categories")
binder := &untypedParamBinder{
Name: "Categories",
parameter: spec.QueryParam("categories").CollectionOf(stringItems, "csv"),
}
cData := strings.Join(sliced, ",")
categories, custom, err := binder.readFormattedSliceFieldValue(cData, categoriesField)
assert.NoError(t, err)
assert.EqualValues(t, sliced, actual.Categories)
assert.True(t, custom)
assert.Empty(t, categories)
categories, custom, err = binder.readFormattedSliceFieldValue("", categoriesField)
assert.Error(t, err)
assert.True(t, custom)
assert.Empty(t, categories)
}

View File

@@ -0,0 +1,275 @@
// 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 untyped
import (
"fmt"
"net/http"
"sort"
"strings"
"github.com/go-openapi/analysis"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
)
// NewAPI creates the default untyped API
func NewAPI(spec *loads.Document) *API {
var an *analysis.Spec
if spec != nil && spec.Spec() != nil {
an = analysis.New(spec.Spec())
}
api := &API{
spec: spec,
analyzer: an,
consumers: make(map[string]runtime.Consumer, 10),
producers: make(map[string]runtime.Producer, 10),
authenticators: make(map[string]runtime.Authenticator),
operations: make(map[string]map[string]runtime.OperationHandler),
ServeError: errors.ServeError,
Models: make(map[string]func() interface{}),
formats: strfmt.NewFormats(),
}
return api.WithJSONDefaults()
}
// API represents an untyped mux for a swagger spec
type API struct {
spec *loads.Document
analyzer *analysis.Spec
DefaultProduces string
DefaultConsumes string
consumers map[string]runtime.Consumer
producers map[string]runtime.Producer
authenticators map[string]runtime.Authenticator
operations map[string]map[string]runtime.OperationHandler
ServeError func(http.ResponseWriter, *http.Request, error)
Models map[string]func() interface{}
formats strfmt.Registry
}
// WithJSONDefaults loads the json defaults for this api
func (d *API) WithJSONDefaults() *API {
d.DefaultConsumes = runtime.JSONMime
d.DefaultProduces = runtime.JSONMime
d.consumers[runtime.JSONMime] = runtime.JSONConsumer()
d.producers[runtime.JSONMime] = runtime.JSONProducer()
return d
}
// WithoutJSONDefaults clears the json defaults for this api
func (d *API) WithoutJSONDefaults() *API {
d.DefaultConsumes = ""
d.DefaultProduces = ""
delete(d.consumers, runtime.JSONMime)
delete(d.producers, runtime.JSONMime)
return d
}
// Formats returns the registered string formats
func (d *API) Formats() strfmt.Registry {
if d.formats == nil {
d.formats = strfmt.NewFormats()
}
return d.formats
}
// RegisterFormat registers a custom format validator
func (d *API) RegisterFormat(name string, format strfmt.Format, validator strfmt.Validator) {
if d.formats == nil {
d.formats = strfmt.NewFormats()
}
d.formats.Add(name, format, validator)
}
// RegisterAuth registers an auth handler in this api
func (d *API) RegisterAuth(scheme string, handler runtime.Authenticator) {
if d.authenticators == nil {
d.authenticators = make(map[string]runtime.Authenticator)
}
d.authenticators[scheme] = handler
}
// RegisterConsumer registers a consumer for a media type.
func (d *API) RegisterConsumer(mediaType string, handler runtime.Consumer) {
if d.consumers == nil {
d.consumers = make(map[string]runtime.Consumer, 10)
}
d.consumers[strings.ToLower(mediaType)] = handler
}
// RegisterProducer registers a producer for a media type
func (d *API) RegisterProducer(mediaType string, handler runtime.Producer) {
if d.producers == nil {
d.producers = make(map[string]runtime.Producer, 10)
}
d.producers[strings.ToLower(mediaType)] = handler
}
// RegisterOperation registers an operation handler for an operation name
func (d *API) RegisterOperation(method, path string, handler runtime.OperationHandler) {
if d.operations == nil {
d.operations = make(map[string]map[string]runtime.OperationHandler, 30)
}
um := strings.ToUpper(method)
if b, ok := d.operations[um]; !ok || b == nil {
d.operations[um] = make(map[string]runtime.OperationHandler)
}
d.operations[um][path] = handler
}
// OperationHandlerFor returns the operation handler for the specified id if it can be found
func (d *API) OperationHandlerFor(method, path string) (runtime.OperationHandler, bool) {
if d.operations == nil {
return nil, false
}
if pi, ok := d.operations[strings.ToUpper(method)]; ok {
h, ok := pi[path]
return h, ok
}
return nil, false
}
// ConsumersFor gets the consumers for the specified media types
func (d *API) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
result := make(map[string]runtime.Consumer)
for _, mt := range mediaTypes {
if consumer, ok := d.consumers[mt]; ok {
result[mt] = consumer
}
}
return result
}
// ProducersFor gets the producers for the specified media types
func (d *API) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
result := make(map[string]runtime.Producer)
for _, mt := range mediaTypes {
if producer, ok := d.producers[mt]; ok {
result[mt] = producer
}
}
return result
}
// AuthenticatorsFor gets the authenticators for the specified security schemes
func (d *API) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
result := make(map[string]runtime.Authenticator)
for k := range schemes {
if a, ok := d.authenticators[k]; ok {
result[k] = a
}
}
return result
}
// Validate validates this API for any missing items
func (d *API) Validate() error {
return d.validate()
}
// validateWith validates the registrations in this API against the provided spec analyzer
func (d *API) validate() error {
var consumes []string
for k := range d.consumers {
consumes = append(consumes, k)
}
var produces []string
for k := range d.producers {
produces = append(produces, k)
}
var authenticators []string
for k := range d.authenticators {
authenticators = append(authenticators, k)
}
var operations []string
for m, v := range d.operations {
for p := range v {
operations = append(operations, fmt.Sprintf("%s %s", strings.ToUpper(m), p))
}
}
var definedAuths []string
for k := range d.spec.Spec().SecurityDefinitions {
definedAuths = append(definedAuths, k)
}
if err := d.verify("consumes", consumes, d.analyzer.RequiredConsumes()); err != nil {
return err
}
if err := d.verify("produces", produces, d.analyzer.RequiredProduces()); err != nil {
return err
}
if err := d.verify("operation", operations, d.analyzer.OperationMethodPaths()); err != nil {
return err
}
requiredAuths := d.analyzer.RequiredSecuritySchemes()
if err := d.verify("auth scheme", authenticators, requiredAuths); err != nil {
return err
}
if err := d.verify("security definitions", definedAuths, requiredAuths); err != nil {
return err
}
return nil
}
func (d *API) verify(name string, registrations []string, expectations []string) error {
sort.Sort(sort.StringSlice(registrations))
sort.Sort(sort.StringSlice(expectations))
expected := map[string]struct{}{}
seen := map[string]struct{}{}
for _, v := range expectations {
expected[v] = struct{}{}
}
var unspecified []string
for _, v := range registrations {
seen[v] = struct{}{}
if _, ok := expected[v]; !ok {
unspecified = append(unspecified, v)
}
}
for k := range seen {
delete(expected, k)
}
var unregistered []string
for k := range expected {
unregistered = append(unregistered, k)
}
sort.Sort(sort.StringSlice(unspecified))
sort.Sort(sort.StringSlice(unregistered))
if len(unregistered) > 0 || len(unspecified) > 0 {
return &errors.APIVerificationFailed{
Section: name,
MissingSpecification: unspecified,
MissingRegistration: unregistered,
}
}
return nil
}

View File

@@ -0,0 +1,276 @@
// 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 untyped
import (
"io"
"sort"
"testing"
"github.com/go-openapi/analysis"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
swaggerspec "github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)
func stubAutenticator() runtime.Authenticator {
return runtime.AuthenticatorFunc(func(_ interface{}) (bool, interface{}, error) { return false, nil, nil })
}
type stubConsumer struct {
}
func (s *stubConsumer) Consume(_ io.Reader, _ interface{}) error {
return nil
}
type stubProducer struct {
}
func (s *stubProducer) Produce(_ io.Writer, _ interface{}) error {
return nil
}
type stubOperationHandler struct {
}
func (s *stubOperationHandler) ParameterModel() interface{} {
return nil
}
func (s *stubOperationHandler) Handle(params interface{}) (interface{}, error) {
return nil, nil
}
func TestUntypedAPIRegistrations(t *testing.T) {
api := NewAPI(new(loads.Document)).WithJSONDefaults()
api.RegisterConsumer("application/yada", new(stubConsumer))
api.RegisterProducer("application/yada-2", new(stubProducer))
api.RegisterOperation("get", "/{someId}", new(stubOperationHandler))
api.RegisterAuth("basic", stubAutenticator())
assert.NotEmpty(t, api.authenticators)
_, ok := api.authenticators["basic"]
assert.True(t, ok)
_, ok = api.consumers["application/yada"]
assert.True(t, ok)
_, ok = api.producers["application/yada-2"]
assert.True(t, ok)
_, ok = api.consumers["application/json"]
assert.True(t, ok)
_, ok = api.producers["application/json"]
assert.True(t, ok)
_, ok = api.operations["GET"]["/{someId}"]
assert.True(t, ok)
h, ok := api.OperationHandlerFor("get", "/{someId}")
assert.True(t, ok)
assert.NotNil(t, h)
_, ok = api.OperationHandlerFor("doesntExist", "/{someId}")
assert.False(t, ok)
}
func TestUntypedAppValidation(t *testing.T) {
invalidSpecStr := `{
"consumes": ["application/json"],
"produces": ["application/json"],
"security": [
{"apiKey":[]}
],
"parameters": {
"format": {
"in": "query",
"name": "format",
"type": "string"
}
},
"paths": {
"/": {
"parameters": [
{
"name": "limit",
"type": "integer",
"format": "int32",
"x-go-name": "Limit"
}
],
"get": {
"consumes": ["application/x-yaml"],
"produces": ["application/x-yaml"],
"security": [
{"basic":[]}
],
"parameters": [
{
"name": "skip",
"type": "integer",
"format": "int32"
}
]
}
}
}
}`
specStr := `{
"consumes": ["application/json"],
"produces": ["application/json"],
"security": [
{"apiKey":[]}
],
"securityDefinitions": {
"basic": { "type": "basic" },
"apiKey": { "type": "apiKey", "in":"header", "name":"X-API-KEY" }
},
"parameters": {
"format": {
"in": "query",
"name": "format",
"type": "string"
}
},
"paths": {
"/": {
"parameters": [
{
"name": "limit",
"type": "integer",
"format": "int32",
"x-go-name": "Limit"
}
],
"get": {
"consumes": ["application/x-yaml"],
"produces": ["application/x-yaml"],
"security": [
{"basic":[]}
],
"parameters": [
{
"name": "skip",
"type": "integer",
"format": "int32"
}
]
}
}
}
}`
validSpec, err := loads.Analyzed([]byte(specStr), "")
assert.NoError(t, err)
assert.NotNil(t, validSpec)
spec, err := loads.Analyzed([]byte(invalidSpecStr), "")
assert.NoError(t, err)
assert.NotNil(t, spec)
analyzed := analysis.New(spec.Spec())
analyzedValid := analysis.New(validSpec.Spec())
cons := analyzed.ConsumesFor(analyzed.AllPaths()["/"].Get)
assert.Len(t, cons, 1)
prods := analyzed.RequiredProduces()
assert.Len(t, prods, 2)
api1 := NewAPI(spec)
err = api1.Validate()
assert.Error(t, err)
assert.Equal(t, "missing [application/x-yaml] consumes registrations", err.Error())
api1.RegisterConsumer("application/x-yaml", new(stubConsumer))
err = api1.validate()
assert.Error(t, err)
assert.Equal(t, "missing [application/x-yaml] produces registrations", err.Error())
api1.RegisterProducer("application/x-yaml", new(stubProducer))
err = api1.validate()
assert.Error(t, err)
assert.Equal(t, "missing [GET /] operation registrations", err.Error())
api1.RegisterOperation("get", "/", new(stubOperationHandler))
err = api1.validate()
assert.Error(t, err)
assert.Equal(t, "missing [apiKey, basic] auth scheme registrations", err.Error())
api1.RegisterAuth("basic", stubAutenticator())
api1.RegisterAuth("apiKey", stubAutenticator())
err = api1.validate()
assert.Error(t, err)
assert.Equal(t, "missing [apiKey, basic] security definitions registrations", err.Error())
api3 := NewAPI(validSpec)
api3.RegisterConsumer("application/x-yaml", new(stubConsumer))
api3.RegisterProducer("application/x-yaml", new(stubProducer))
api3.RegisterOperation("get", "/", new(stubOperationHandler))
api3.RegisterAuth("basic", stubAutenticator())
api3.RegisterAuth("apiKey", stubAutenticator())
err = api3.validate()
assert.NoError(t, err)
api3.RegisterConsumer("application/something", new(stubConsumer))
err = api3.validate()
assert.Error(t, err)
assert.Equal(t, "missing from spec file [application/something] consumes", err.Error())
api2 := NewAPI(spec)
api2.RegisterConsumer("application/something", new(stubConsumer))
err = api2.validate()
assert.Error(t, err)
assert.Equal(t, "missing [application/x-yaml] consumes registrations\nmissing from spec file [application/something] consumes", err.Error())
api2.RegisterConsumer("application/x-yaml", new(stubConsumer))
delete(api2.consumers, "application/something")
api2.RegisterProducer("application/something", new(stubProducer))
err = api2.validate()
assert.Error(t, err)
assert.Equal(t, "missing [application/x-yaml] produces registrations\nmissing from spec file [application/something] produces", err.Error())
delete(api2.producers, "application/something")
api2.RegisterProducer("application/x-yaml", new(stubProducer))
expected := []string{"application/x-yaml"}
sort.Sort(sort.StringSlice(expected))
consumes := analyzed.ConsumesFor(analyzed.AllPaths()["/"].Get)
sort.Sort(sort.StringSlice(consumes))
assert.Equal(t, expected, consumes)
consumers := api1.ConsumersFor(consumes)
assert.Len(t, consumers, 1)
produces := analyzed.ProducesFor(analyzed.AllPaths()["/"].Get)
sort.Sort(sort.StringSlice(produces))
assert.Equal(t, expected, produces)
producers := api1.ProducersFor(produces)
assert.Len(t, producers, 1)
definitions := analyzedValid.SecurityDefinitionsFor(analyzedValid.AllPaths()["/"].Get)
expectedSchemes := map[string]swaggerspec.SecurityScheme{"basic": *swaggerspec.BasicAuth()}
assert.Equal(t, expectedSchemes, definitions)
authenticators := api3.AuthenticatorsFor(definitions)
assert.Len(t, authenticators, 1)
opHandler := runtime.OperationHandlerFunc(func(data interface{}) (interface{}, error) {
return data, nil
})
d, err := opHandler.Handle(1)
assert.NoError(t, err)
assert.Equal(t, 1, d)
authenticator := runtime.AuthenticatorFunc(func(params interface{}) (bool, interface{}, error) {
if str, ok := params.(string); ok {
return ok, str, nil
}
return true, nil, errors.Unauthenticated("authenticator")
})
ok, p, err := authenticator.Authenticate("hello")
assert.True(t, ok)
assert.NoError(t, err)
assert.Equal(t, "hello", p)
}

View File

@@ -0,0 +1,164 @@
// 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 middleware
import (
"bytes"
"encoding/base64"
"encoding/json"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"strings"
"testing"
"time"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
)
func TestUntypedFormPost(t *testing.T) {
params := parametersForFormUpload()
binder := newUntypedRequestBinder(params, nil, strfmt.Default)
urlStr := "http://localhost:8002/hello"
req, _ := http.NewRequest("POST", urlStr, bytes.NewBufferString(`name=the-name&age=32`))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
data := make(map[string]interface{})
assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
assert.Equal(t, "the-name", data["name"])
assert.EqualValues(t, 32, data["age"])
req, _ = http.NewRequest("POST", urlStr, bytes.NewBufferString(`name=%3&age=32`))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
data = make(map[string]interface{})
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
}
func TestUntypedFileUpload(t *testing.T) {
binder := paramsForFileUpload()
body := bytes.NewBuffer(nil)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "plain-jane.txt")
assert.NoError(t, err)
part.Write([]byte("the file contents"))
writer.WriteField("name", "the-name")
assert.NoError(t, writer.Close())
urlStr := "http://localhost:8002/hello"
req, _ := http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
data := make(map[string]interface{})
assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
assert.Equal(t, "the-name", data["name"])
assert.NotNil(t, data["file"])
assert.IsType(t, runtime.File{}, data["file"])
file := data["file"].(runtime.File)
assert.NotNil(t, file.Header)
assert.Equal(t, "plain-jane.txt", file.Header.Filename)
bb, err := ioutil.ReadAll(file.Data)
assert.NoError(t, err)
assert.Equal(t, []byte("the file contents"), bb)
req, _ = http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", "application/json")
data = make(map[string]interface{})
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
req, _ = http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", "application(")
data = make(map[string]interface{})
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
body = bytes.NewBuffer(nil)
writer = multipart.NewWriter(body)
part, err = writer.CreateFormFile("bad-name", "plain-jane.txt")
assert.NoError(t, err)
part.Write([]byte("the file contents"))
writer.WriteField("name", "the-name")
assert.NoError(t, writer.Close())
req, _ = http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
data = make(map[string]interface{})
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
req, _ = http.NewRequest("POST", urlStr, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
req.MultipartReader()
data = make(map[string]interface{})
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
}
func TestUntypedBindingTypesForValid(t *testing.T) {
op2 := parametersForAllTypes("")
binder := newUntypedRequestBinder(op2, nil, strfmt.Default)
confirmed := true
name := "thomas"
friend := map[string]interface{}{"name": "toby", "age": json.Number("32")}
id, age, score, factor := int64(7575), int32(348), float32(5.309), float64(37.403)
requestID := 19394858
tags := []string{"one", "two", "three"}
dt1 := time.Date(2014, 8, 9, 0, 0, 0, 0, time.UTC)
planned := strfmt.Date(dt1)
dt2 := time.Date(2014, 10, 12, 8, 5, 5, 0, time.UTC)
delivered := strfmt.DateTime(dt2)
picture := base64.URLEncoding.EncodeToString([]byte("hello"))
uri, _ := url.Parse("http://localhost:8002/hello/7575")
qs := uri.Query()
qs.Add("name", name)
qs.Add("confirmed", "true")
qs.Add("age", "348")
qs.Add("score", "5.309")
qs.Add("factor", "37.403")
qs.Add("tags", strings.Join(tags, ","))
qs.Add("planned", planned.String())
qs.Add("delivered", delivered.String())
qs.Add("picture", picture)
req, _ := http.NewRequest("POST", uri.String()+"?"+qs.Encode(), bytes.NewBuffer([]byte(`{"name":"toby","age":32}`)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Request-Id", "19394858")
data := make(map[string]interface{})
err := binder.Bind(req, RouteParams([]RouteParam{{"id", "7575"}}), runtime.JSONConsumer(), &data)
assert.NoError(t, err)
assert.Equal(t, id, data["id"])
assert.Equal(t, name, data["name"])
assert.Equal(t, friend, data["friend"])
assert.EqualValues(t, requestID, data["X-Request-Id"])
assert.Equal(t, tags, data["tags"])
assert.Equal(t, planned, data["planned"])
assert.Equal(t, delivered, data["delivered"])
assert.Equal(t, confirmed, data["confirmed"])
assert.Equal(t, age, data["age"])
assert.Equal(t, factor, data["factor"])
assert.Equal(t, score, data["score"])
pb, _ := base64.URLEncoding.DecodeString(picture)
assert.EqualValues(t, pb, data["picture"].(strfmt.Base64))
}

View File

@@ -0,0 +1,141 @@
// 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 middleware
import (
"mime"
"net/http"
"strings"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
)
// NewValidation starts a new validation middleware
func newValidation(ctx *Context, next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
matched, _ := ctx.RouteInfo(r)
if matched == nil {
ctx.NotFound(rw, r)
return
}
_, result := ctx.BindAndValidate(r, matched)
if result != nil {
ctx.Respond(rw, r, matched.Produces, matched, result)
return
}
debugLog("no result for %s %s", r.Method, r.URL.EscapedPath())
next.ServeHTTP(rw, r)
})
}
type validation struct {
context *Context
result []error
request *http.Request
route *MatchedRoute
bound map[string]interface{}
}
type untypedBinder map[string]interface{}
func (ub untypedBinder) BindRequest(r *http.Request, route *MatchedRoute, consumer runtime.Consumer) error {
if err := route.Binder.Bind(r, route.Params, consumer, ub); err != nil {
return err
}
return nil
}
// ContentType validates the content type of a request
func validateContentType(allowed []string, actual string) error {
debugLog("validating content type for %q against [%s]", actual, strings.Join(allowed, ", "))
if len(allowed) == 0 {
return nil
}
mt, _, err := mime.ParseMediaType(actual)
if err != nil {
return errors.InvalidContentType(actual, allowed)
}
if swag.ContainsStringsCI(allowed, mt) {
return nil
}
return errors.InvalidContentType(actual, allowed)
}
func validateRequest(ctx *Context, request *http.Request, route *MatchedRoute) *validation {
debugLog("validating request %s %s", request.Method, request.URL.EscapedPath())
validate := &validation{
context: ctx,
request: request,
route: route,
bound: make(map[string]interface{}),
}
validate.contentType()
if len(validate.result) == 0 {
validate.responseFormat()
}
if len(validate.result) == 0 {
validate.parameters()
}
return validate
}
func (v *validation) parameters() {
debugLog("validating request parameters for %s %s", v.request.Method, v.request.URL.EscapedPath())
if result := v.route.Binder.Bind(v.request, v.route.Params, v.route.Consumer, v.bound); result != nil {
if result.Error() == "validation failure list" {
for _, e := range result.(*errors.Validation).Value.([]interface{}) {
v.result = append(v.result, e.(error))
}
return
}
v.result = append(v.result, result)
}
}
func (v *validation) contentType() {
if len(v.result) == 0 && runtime.HasBody(v.request) {
debugLog("validating body content type for %s %s", v.request.Method, v.request.URL.EscapedPath())
ct, _, err := v.context.ContentType(v.request)
if err != nil {
v.result = append(v.result, err)
}
if len(v.result) == 0 {
if err := validateContentType(v.route.Consumes, ct); err != nil {
v.result = append(v.result, err)
}
}
if ct != "" && v.route.Consumer == nil {
cons, ok := v.route.Consumers[ct]
if !ok {
v.result = append(v.result, errors.New(500, "no consumer registered for %s", ct))
} else {
v.route.Consumer = cons
}
}
}
}
func (v *validation) responseFormat() {
if str := v.context.ResponseFormat(v.request, v.route.Produces); str == "" && runtime.HasBody(v.request) {
v.result = append(v.result, errors.InvalidResponseFormat(v.request.Header.Get(runtime.HeaderAccept), v.route.Produces))
}
}

View File

@@ -0,0 +1,130 @@
// 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 middleware
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/internal/testing/petstore"
"github.com/stretchr/testify/assert"
)
func TestContentTypeValidation(t *testing.T) {
spec, api := petstore.NewAPI(t)
context := NewContext(spec, api, nil)
context.router = DefaultRouter(spec, context.api)
mw := newValidation(context, http.HandlerFunc(terminator))
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/api/pets", nil)
request.Header.Add("Accept", "*/*")
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusOK, recorder.Code)
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("POST", "/api/pets", nil)
request.Header.Add("content-type", "application(")
request.ContentLength = 1
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusBadRequest, recorder.Code)
assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("POST", "/api/pets", nil)
request.Header.Add("Accept", "application/json")
request.Header.Add("content-type", "text/html")
request.ContentLength = 1
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusUnsupportedMediaType, recorder.Code)
assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("POST", "/api/pets", nil)
request.Header.Add("Accept", "application/json")
request.Header.Add("content-type", "text/html")
request.TransferEncoding = []string{"chunked"}
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusUnsupportedMediaType, recorder.Code)
assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("POST", "/api/pets", nil)
request.Header.Add("Accept", "application/json")
request.Header.Add("content-type", "text/html")
mw.ServeHTTP(recorder, request)
assert.Equal(t, 422, recorder.Code)
assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
}
func TestResponseFormatValidation(t *testing.T) {
spec, api := petstore.NewAPI(t)
context := NewContext(spec, api, nil)
context.router = DefaultRouter(spec, context.api)
mw := newValidation(context, http.HandlerFunc(terminator))
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("POST", "/api/pets", bytes.NewBuffer([]byte(`name: Dog`)))
request.Header.Set(runtime.HeaderContentType, "application/x-yaml")
request.Header.Set(runtime.HeaderAccept, "application/x-yaml")
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code, recorder.Body.String())
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("POST", "/api/pets", bytes.NewBuffer([]byte(`name: Dog`)))
request.Header.Set(runtime.HeaderContentType, "application/x-yaml")
request.Header.Set(runtime.HeaderAccept, "application/sml")
mw.ServeHTTP(recorder, request)
assert.Equal(t, http.StatusNotAcceptable, recorder.Code)
}
func TestValidateContentType(t *testing.T) {
data := []struct {
hdr string
allowed []string
err *errors.Validation
}{
{"application/json", []string{"application/json"}, nil},
{"application/json", []string{"application/x-yaml", "text/html"}, errors.InvalidContentType("application/json", []string{"application/x-yaml", "text/html"})},
{"text/html; charset=utf-8", []string{"text/html"}, nil},
{"text/html;charset=utf-8", []string{"text/html"}, nil},
{"", []string{"application/json"}, errors.InvalidContentType("", []string{"application/json"})},
{"text/html; charset=utf-8", []string{"application/json"}, errors.InvalidContentType("text/html; charset=utf-8", []string{"application/json"})},
{"application(", []string{"application/json"}, errors.InvalidContentType("application(", []string{"application/json"})},
{"application/json;char*", []string{"application/json"}, errors.InvalidContentType("application/json;char*", []string{"application/json"})},
}
for _, v := range data {
err := validateContentType(v.allowed, v.hdr)
if v.err == nil {
assert.NoError(t, err, "input: %q", v.hdr)
} else {
assert.Error(t, err, "input: %q", v.hdr)
assert.IsType(t, &errors.Validation{}, err, "input: %q", v.hdr)
assert.Equal(t, v.err.Error(), err.Error(), "input: %q", v.hdr)
assert.EqualValues(t, http.StatusUnsupportedMediaType, err.(*errors.Validation).Code())
}
}
}

77
vendor/github.com/go-openapi/runtime/request.go generated vendored Normal file
View File

@@ -0,0 +1,77 @@
// 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 runtime
import (
"io"
"net/http"
"strings"
"github.com/go-openapi/swag"
)
// CanHaveBody returns true if this method can have a body
func CanHaveBody(method string) bool {
mn := strings.ToUpper(method)
return mn == "POST" || mn == "PUT" || mn == "PATCH" || mn == "DELETE"
}
// IsSafe returns true if this is a request with a safe method
func IsSafe(r *http.Request) bool {
mn := strings.ToUpper(r.Method)
return mn == "GET" || mn == "HEAD"
}
// AllowsBody returns true if the request allows for a body
func AllowsBody(r *http.Request) bool {
mn := strings.ToUpper(r.Method)
return mn != "HEAD"
}
// HasBody returns true if this method needs a content-type
func HasBody(r *http.Request) bool {
return len(r.TransferEncoding) > 0 || r.ContentLength > 0
}
// JSONRequest creates a new http request with json headers set
func JSONRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, urlStr, body)
if err != nil {
return nil, err
}
req.Header.Add(HeaderContentType, JSONMime)
req.Header.Add(HeaderAccept, JSONMime)
return req, nil
}
// Gettable for things with a method GetOK(string) (data string, hasKey bool, hasValue bool)
type Gettable interface {
GetOK(string) ([]string, bool, bool)
}
// ReadSingleValue reads a single value from the source
func ReadSingleValue(values Gettable, name string) string {
vv, _, hv := values.GetOK(name)
if hv {
return vv[len(vv)-1]
}
return ""
}
// ReadCollectionValue reads a collection value from a string data source
func ReadCollectionValue(values Gettable, name, collectionFormat string) []string {
v := ReadSingleValue(values, name)
return swag.SplitByFormat(v, collectionFormat)
}

122
vendor/github.com/go-openapi/runtime/request_test.go generated vendored Normal file
View File

@@ -0,0 +1,122 @@
// 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 runtime
import (
"net/url"
"testing"
"github.com/stretchr/testify/assert"
)
/*
type tstreadcloser struct {
closed bool
}
func (t *tstreadcloser) Read(p []byte) (int, error) { return 0, nil }
func (t *tstreadcloser) Close() error {
t.closed = true
return nil
}
func TestPeekingReader(t *testing.T) {
// just passes to original reader when nothing called
exp1 := []byte("original")
pr1 := &peekingReader{rdr: ioutil.NopCloser(bytes.NewReader(exp1))}
b1, err := ioutil.ReadAll(pr1)
if assert.NoError(t, err) {
assert.Equal(t, exp1, b1)
}
// uses actual when there was some buffering
exp2 := []byte("actual")
pt1, pt2 := []byte("a"), []byte("ctual")
pr2 := &peekingReader{
rdr: ioutil.NopCloser(bytes.NewReader(exp1)),
actual: io.MultiReader(bytes.NewReader(pt1), bytes.NewReader(pt2)),
peeked: pt1,
}
b2, err := ioutil.ReadAll(pr2)
if assert.NoError(t, err) {
assert.Equal(t, exp2, b2)
}
// closes original reader
tr := new(tstreadcloser)
pr3 := &peekingReader{
rdr: tr,
actual: ioutil.NopCloser(bytes.NewBuffer(nil)),
peeked: pt1,
}
// returns true when peeked previously with data
// returns true when peeked with data
}
*/
func TestJSONRequest(t *testing.T) {
req, err := JSONRequest("GET", "/swagger.json", nil)
assert.NoError(t, err)
assert.Equal(t, "GET", req.Method)
assert.Equal(t, JSONMime, req.Header.Get(HeaderContentType))
assert.Equal(t, JSONMime, req.Header.Get(HeaderAccept))
req, err = JSONRequest("GET", "%2", nil)
assert.Error(t, err)
assert.Nil(t, req)
}
//func TestCanHaveBody(t *testing.T) {
//assert.True(t, CanHaveBody("put"))
//assert.True(t, CanHaveBody("post"))
//assert.True(t, CanHaveBody("patch"))
//assert.True(t, CanHaveBody("delete"))
//assert.False(t, CanHaveBody(""))
//assert.False(t, CanHaveBody("get"))
//assert.False(t, CanHaveBody("options"))
//assert.False(t, CanHaveBody("head"))
//assert.False(t, CanHaveBody("invalid"))
//}
func TestReadSingle(t *testing.T) {
values := url.Values(make(map[string][]string))
values.Add("something", "the thing")
assert.Equal(t, "the thing", ReadSingleValue(tv(values), "something"))
assert.Empty(t, ReadSingleValue(tv(values), "notthere"))
}
func TestReadCollection(t *testing.T) {
values := url.Values(make(map[string][]string))
values.Add("something", "value1,value2")
assert.Equal(t, []string{"value1", "value2"}, ReadCollectionValue(tv(values), "something", "csv"))
assert.Empty(t, ReadCollectionValue(tv(values), "notthere", ""))
}
type tv map[string][]string
func (v tv) GetOK(key string) (value []string, hasKey bool, hasValue bool) {
value, hasKey = v[key]
if !hasKey {
return
}
if len(value) == 0 {
return
}
hasValue = true
return
}

View File

@@ -0,0 +1,94 @@
// 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 security
import (
"net/http"
"testing"
"github.com/go-openapi/errors"
"github.com/stretchr/testify/assert"
)
var tokenAuth = TokenAuthentication(func(token string) (interface{}, error) {
if token == "token123" {
return "admin", nil
}
return nil, errors.Unauthenticated("token")
})
func TestInvalidApiKeyAuthInitialization(t *testing.T) {
assert.Panics(t, func() { APIKeyAuth("api_key", "qery", tokenAuth) })
}
func TestValidApiKeyAuth(t *testing.T) {
ta := APIKeyAuth("api_key", "query", tokenAuth)
ta2 := APIKeyAuth("X-API-KEY", "header", tokenAuth)
req1, _ := http.NewRequest("GET", "/blah?api_key=token123", nil)
ok, usr, err := ta.Authenticate(req1)
assert.True(t, ok)
assert.Equal(t, "admin", usr)
assert.NoError(t, err)
req2, _ := http.NewRequest("GET", "/blah", nil)
req2.Header.Set("X-API-KEY", "token123")
ok, usr, err = ta2.Authenticate(req2)
assert.True(t, ok)
assert.Equal(t, "admin", usr)
assert.NoError(t, err)
}
func TestInvalidApiKeyAuth(t *testing.T) {
ta := APIKeyAuth("api_key", "query", tokenAuth)
ta2 := APIKeyAuth("X-API-KEY", "header", tokenAuth)
req1, _ := http.NewRequest("GET", "/blah?api_key=token124", nil)
ok, usr, err := ta.Authenticate(req1)
assert.True(t, ok)
assert.Equal(t, nil, usr)
assert.Error(t, err)
req2, _ := http.NewRequest("GET", "/blah", nil)
req2.Header.Set("X-API-KEY", "token124")
ok, usr, err = ta2.Authenticate(req2)
assert.True(t, ok)
assert.Equal(t, nil, usr)
assert.Error(t, err)
}
func TestMissingApiKeyAuth(t *testing.T) {
ta := APIKeyAuth("api_key", "query", tokenAuth)
ta2 := APIKeyAuth("X-API-KEY", "header", tokenAuth)
req1, _ := http.NewRequest("GET", "/blah", nil)
req1.Header.Set("X-API-KEY", "token123")
ok, usr, err := ta.Authenticate(req1)
assert.False(t, ok)
assert.Equal(t, nil, usr)
assert.NoError(t, err)
req2, _ := http.NewRequest("GET", "/blah?api_key=token123", nil)
ok, usr, err = ta2.Authenticate(req2)
assert.False(t, ok)
assert.Equal(t, nil, usr)
assert.NoError(t, err)
}

View File

@@ -0,0 +1,128 @@
// 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 security
import (
"net/http"
"strings"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
)
// HttpAuthenticator is a function that authenticates a HTTP request
func HttpAuthenticator(handler func(*http.Request) (bool, interface{}, error)) runtime.Authenticator {
return runtime.AuthenticatorFunc(func(params interface{}) (bool, interface{}, error) {
if request, ok := params.(*http.Request); ok {
return handler(request)
}
if scoped, ok := params.(*ScopedAuthRequest); ok {
return handler(scoped.Request)
}
return false, nil, nil
})
}
// ScopedAuthenticator is a function that authenticates a HTTP request against a list of valid scopes
func ScopedAuthenticator(handler func(*ScopedAuthRequest) (bool, interface{}, error)) runtime.Authenticator {
return runtime.AuthenticatorFunc(func(params interface{}) (bool, interface{}, error) {
if request, ok := params.(*ScopedAuthRequest); ok {
return handler(request)
}
return false, nil, nil
})
}
// UserPassAuthentication authentication function
type UserPassAuthentication func(string, string) (interface{}, error)
// TokenAuthentication authentication function
type TokenAuthentication func(string) (interface{}, error)
// ScopedTokenAuthentication authentication function
type ScopedTokenAuthentication func(string, []string) (interface{}, error)
// BasicAuth creates a basic auth authenticator with the provided authentication function
func BasicAuth(authenticate UserPassAuthentication) runtime.Authenticator {
return HttpAuthenticator(func(r *http.Request) (bool, interface{}, error) {
if usr, pass, ok := r.BasicAuth(); ok {
p, err := authenticate(usr, pass)
return true, p, err
}
return false, nil, nil
})
}
// APIKeyAuth creates an authenticator that uses a token for authorization.
// This token can be obtained from either a header or a query string
func APIKeyAuth(name, in string, authenticate TokenAuthentication) runtime.Authenticator {
inl := strings.ToLower(in)
if inl != "query" && inl != "header" {
// panic because this is most likely a typo
panic(errors.New(500, "api key auth: in value needs to be either \"query\" or \"header\"."))
}
var getToken func(*http.Request) string
switch inl {
case "header":
getToken = func(r *http.Request) string { return r.Header.Get(name) }
case "query":
getToken = func(r *http.Request) string { return r.URL.Query().Get(name) }
}
return HttpAuthenticator(func(r *http.Request) (bool, interface{}, error) {
token := getToken(r)
if token == "" {
return false, nil, nil
}
p, err := authenticate(token)
return true, p, err
})
}
// ScopedAuthRequest contains both a http request and the required scopes for a particular operation
type ScopedAuthRequest struct {
Request *http.Request
RequiredScopes []string
}
// BearerAuth for use with oauth2 flows
func BearerAuth(name string, authenticate ScopedTokenAuthentication) runtime.Authenticator {
const prefix = "Bearer "
return ScopedAuthenticator(func(r *ScopedAuthRequest) (bool, interface{}, error) {
var token string
hdr := r.Request.Header.Get("Authorization")
if strings.HasPrefix(hdr, prefix) {
token = strings.TrimPrefix(hdr, prefix)
}
if token == "" {
qs := r.Request.URL.Query()
token = qs.Get("access_token")
}
ct, _, _ := runtime.ContentType(r.Request.Header)
if token == "" && (ct == "application/x-www-form-urlencoded" || ct == "multipart/form-data") {
token = r.Request.FormValue("access_token")
}
if token == "" {
return false, nil, nil
}
p, err := authenticate(token, r.RequiredScopes)
return true, p, err
})
}

View File

@@ -0,0 +1,75 @@
// 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 security
import (
"net/http"
"testing"
"github.com/go-openapi/errors"
"github.com/stretchr/testify/assert"
)
var basicAuthHandler = UserPassAuthentication(func(user, pass string) (interface{}, error) {
if user == "admin" && pass == "123456" {
return "admin", nil
}
return "", errors.Unauthenticated("basic")
})
func TestValidBasicAuth(t *testing.T) {
ba := BasicAuth(basicAuthHandler)
req, _ := http.NewRequest("GET", "/blah", nil)
req.SetBasicAuth("admin", "123456")
ok, usr, err := ba.Authenticate(req)
assert.NoError(t, err)
assert.True(t, ok)
assert.Equal(t, "admin", usr)
}
func TestInvalidBasicAuth(t *testing.T) {
ba := BasicAuth(basicAuthHandler)
req, _ := http.NewRequest("GET", "/blah", nil)
req.SetBasicAuth("admin", "admin")
ok, usr, err := ba.Authenticate(req)
assert.Error(t, err)
assert.True(t, ok)
assert.Equal(t, "", usr)
}
func TestMissingbasicAuth(t *testing.T) {
ba := BasicAuth(basicAuthHandler)
req, _ := http.NewRequest("GET", "/blah", nil)
ok, usr, err := ba.Authenticate(req)
assert.NoError(t, err)
assert.False(t, ok)
assert.Equal(t, nil, usr)
}
func TestNoRequestBasicAuth(t *testing.T) {
ba := BasicAuth(basicAuthHandler)
ok, usr, err := ba.Authenticate("token")
assert.NoError(t, err)
assert.False(t, ok)
assert.Nil(t, usr)
}

View File

@@ -0,0 +1,143 @@
package security
import (
"bytes"
"mime/multipart"
"net/http"
"net/url"
"strings"
"testing"
"github.com/go-openapi/errors"
"github.com/stretchr/testify/assert"
)
var bearerAuth = ScopedTokenAuthentication(func(token string, requiredScopes []string) (interface{}, error) {
if token == "token123" {
return "admin", nil
}
return nil, errors.Unauthenticated("bearer")
})
func TestValidBearerAuth(t *testing.T) {
ba := BearerAuth("owners_auth", bearerAuth)
req1, _ := http.NewRequest("GET", "/blah?access_token=token123", nil)
ok, usr, err := ba.Authenticate(&ScopedAuthRequest{Request: req1})
assert.True(t, ok)
assert.Equal(t, "admin", usr)
assert.NoError(t, err)
req2, _ := http.NewRequest("GET", "/blah", nil)
req2.Header.Set("Authorization", "Bearer token123")
ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req2})
assert.True(t, ok)
assert.Equal(t, "admin", usr)
assert.NoError(t, err)
body := url.Values(map[string][]string{})
body.Set("access_token", "token123")
req3, _ := http.NewRequest("POST", "/blah", strings.NewReader(body.Encode()))
req3.Header.Set("Content-Type", "application/x-www-form-urlencoded")
ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req3})
assert.True(t, ok)
assert.Equal(t, "admin", usr)
assert.NoError(t, err)
mpbody := bytes.NewBuffer(nil)
writer := multipart.NewWriter(mpbody)
writer.WriteField("access_token", "token123")
writer.Close()
req4, _ := http.NewRequest("POST", "/blah", mpbody)
req4.Header.Set("Content-Type", writer.FormDataContentType())
ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req4})
assert.True(t, ok)
assert.Equal(t, "admin", usr)
assert.NoError(t, err)
}
func TestInvalidBearerAuth(t *testing.T) {
ba := BearerAuth("owners_auth", bearerAuth)
req1, _ := http.NewRequest("GET", "/blah?access_token=token124", nil)
ok, usr, err := ba.Authenticate(&ScopedAuthRequest{Request: req1})
assert.True(t, ok)
assert.Equal(t, nil, usr)
assert.Error(t, err)
req2, _ := http.NewRequest("GET", "/blah", nil)
req2.Header.Set("Authorization", "Bearer token124")
ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req2})
assert.True(t, ok)
assert.Equal(t, nil, usr)
assert.Error(t, err)
body := url.Values(map[string][]string{})
body.Set("access_token", "token124")
req3, _ := http.NewRequest("POST", "/blah", strings.NewReader(body.Encode()))
req3.Header.Set("Content-Type", "application/x-www-form-urlencoded")
ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req3})
assert.True(t, ok)
assert.Equal(t, nil, usr)
assert.Error(t, err)
mpbody := bytes.NewBuffer(nil)
writer := multipart.NewWriter(mpbody)
writer.WriteField("access_token", "token124")
writer.Close()
req4, _ := http.NewRequest("POST", "/blah", mpbody)
req4.Header.Set("Content-Type", writer.FormDataContentType())
ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req4})
assert.True(t, ok)
assert.Equal(t, nil, usr)
assert.Error(t, err)
}
func TestMissingBearerAuth(t *testing.T) {
ba := BearerAuth("owners_auth", bearerAuth)
req1, _ := http.NewRequest("GET", "/blah?access_toke=token123", nil)
ok, usr, err := ba.Authenticate(&ScopedAuthRequest{Request: req1})
assert.False(t, ok)
assert.Equal(t, nil, usr)
assert.NoError(t, err)
req2, _ := http.NewRequest("GET", "/blah", nil)
req2.Header.Set("Authorization", "Beare token123")
ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req2})
assert.False(t, ok)
assert.Equal(t, nil, usr)
assert.NoError(t, err)
body := url.Values(map[string][]string{})
body.Set("access_toke", "token123")
req3, _ := http.NewRequest("POST", "/blah", strings.NewReader(body.Encode()))
req3.Header.Set("Content-Type", "application/x-www-form-urlencoded")
ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req3})
assert.False(t, ok)
assert.Equal(t, nil, usr)
assert.NoError(t, err)
mpbody := bytes.NewBuffer(nil)
writer := multipart.NewWriter(mpbody)
writer.WriteField("access_toke", "token123")
writer.Close()
req4, _ := http.NewRequest("POST", "/blah", mpbody)
req4.Header.Set("Content-Type", writer.FormDataContentType())
ok, usr, err = ba.Authenticate(&ScopedAuthRequest{Request: req4})
assert.False(t, ok)
assert.Equal(t, nil, usr)
assert.NoError(t, err)
}

90
vendor/github.com/go-openapi/runtime/statuses.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
// 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 runtime
// Statuses lists the most common HTTP status codes to default message
// taken from https://httpstatuses.com/
var Statuses = map[int]string{
100: "Continue",
101: "Switching Protocols",
102: "Processing",
103: "Checkpoint",
122: "URI too long",
200: "OK",
201: "Created",
202: "Accepted",
203: "Request Processed",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
207: "Multi-Status",
208: "Already Reported",
226: "IM Used",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found",
303: "See Other",
304: "Not Modified",
305: "Use Proxy",
306: "Switch Proxy",
307: "Temporary Redirect",
308: "Permanent Redirect",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Request Entity Too Large",
414: "Request-URI Too Long",
415: "Unsupported Media Type",
416: "Request Range Not Satisfiable",
417: "Expectation Failed",
418: "I'm a teapot",
420: "Enhance Your Calm",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
426: "Upgrade Required",
428: "Precondition Required",
429: "Too Many Requests",
431: "Request Header Fields Too Large",
444: "No Response",
449: "Retry With",
450: "Blocked by Windows Parental Controls",
451: "Wrong Exchange Server",
499: "Client Closed Request",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported",
506: "Variant Also Negotiates",
507: "Insufficient Storage",
508: "Loop Detected",
509: "Bandwidth Limit Exceeded",
510: "Not Extended",
511: "Network Authentication Required",
598: "Network read timeout error",
599: "Network connect timeout error",
}

96
vendor/github.com/go-openapi/runtime/text.go generated vendored Normal file
View File

@@ -0,0 +1,96 @@
// 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 runtime
import (
"bytes"
"encoding"
"errors"
"fmt"
"io"
"reflect"
)
// TextConsumer creates a new text consumer
func TextConsumer() Consumer {
return ConsumerFunc(func(reader io.Reader, data interface{}) error {
if reader == nil {
return errors.New("TextConsumer requires a reader") // early exit
}
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(reader)
if err != nil {
return err
}
b := buf.Bytes()
if tu, ok := data.(encoding.TextUnmarshaler); ok {
err := tu.UnmarshalText(b)
if err != nil {
return fmt.Errorf("text consumer: %v", err)
}
return nil
}
t := reflect.TypeOf(data)
if data != nil && t.Kind() == reflect.Ptr {
v := reflect.Indirect(reflect.ValueOf(data))
if t.Elem().Kind() == reflect.String {
v.SetString(string(b))
return nil
}
}
return fmt.Errorf("%v (%T) is not supported by the TextConsumer, %s",
data, data, "can be resolved by supporting TextUnmarshaler interface")
})
}
// TextProducer creates a new text producer
func TextProducer() Producer {
return ProducerFunc(func(writer io.Writer, data interface{}) error {
if writer == nil {
return errors.New("TextProducer requires a writer") // early exit
}
if data == nil {
return errors.New("no data given to produce text from")
}
if tm, ok := data.(encoding.TextMarshaler); ok {
txt, err := tm.MarshalText()
if err != nil {
return fmt.Errorf("text producer: %v", err)
}
_, err = writer.Write(txt)
return err
}
if str, ok := data.(fmt.Stringer); ok {
_, err := writer.Write([]byte(str.String()))
return err
}
v := reflect.Indirect(reflect.ValueOf(data))
if v.Kind() != reflect.String {
return fmt.Errorf("%T is not a supported type by the TextProducer", data)
}
_, err := writer.Write([]byte(v.String()))
return err
})
}

151
vendor/github.com/go-openapi/runtime/text_test.go generated vendored Normal file
View File

@@ -0,0 +1,151 @@
// 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 runtime
import (
"bytes"
"errors"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
var consProdText = `The quick brown fox jumped over the lazy dog.`
func TestTextConsumer(t *testing.T) {
cons := TextConsumer()
// can consume as a string
var str string
err1 := cons.Consume(bytes.NewBuffer([]byte(consProdText)), &str)
assert.NoError(t, err1)
assert.Equal(t, consProdText, str)
var tu textUnmarshalDummy
// can consume as a TextUnmarshaler
err3 := cons.Consume(bytes.NewBuffer([]byte(consProdText)), &tu)
assert.NoError(t, err3)
assert.Equal(t, consProdText, tu.str)
// text unmarshal objects can return an error as well, this will be propagated
assert.Error(t, cons.Consume(bytes.NewBuffer(nil), &tu))
// when readers can't be read, those errors will be propogated as well
assert.Error(t, cons.Consume(new(nopReader), &tu))
// readers can also not be nil
assert.Error(t, cons.Consume(nil, &tu))
// can't consume nil ptr's or unsupported types
assert.Error(t, cons.Consume(bytes.NewBuffer([]byte(consProdText)), nil))
assert.Error(t, cons.Consume(bytes.NewBuffer([]byte(consProdText)), 42))
assert.Error(t, cons.Consume(bytes.NewBuffer([]byte(consProdText)), &struct{}{}))
}
type textUnmarshalDummy struct {
str string
}
func (t *textUnmarshalDummy) UnmarshalText(b []byte) error {
if len(b) == 0 {
return errors.New("no text given")
}
t.str = string(b)
return nil
}
type nopReader struct{}
func (n *nopReader) Read(p []byte) (int, error) {
return 0, errors.New("nop")
}
func TestTextProducer(t *testing.T) {
prod := TextProducer()
rw := httptest.NewRecorder()
err := prod.Produce(rw, consProdText)
assert.NoError(t, err)
assert.Equal(t, consProdText, rw.Body.String())
rw2 := httptest.NewRecorder()
err2 := prod.Produce(rw2, &consProdText)
assert.NoError(t, err2)
assert.Equal(t, consProdText, rw2.Body.String())
// should always work with type aliases
// as an alias is sometimes given by generated go-swagger code
type alias string
aliasProdText := alias(consProdText)
rw3 := httptest.NewRecorder()
err3 := prod.Produce(rw3, aliasProdText)
assert.NoError(t, err3)
assert.Equal(t, consProdText, rw3.Body.String())
rw4 := httptest.NewRecorder()
err4 := prod.Produce(rw4, &aliasProdText)
assert.NoError(t, err4)
assert.Equal(t, consProdText, rw4.Body.String())
const answer = "42"
// Should always work with objects implementing Stringer interface
rw5 := httptest.NewRecorder()
err5 := prod.Produce(rw5, &stringerDummy{answer})
assert.NoError(t, err5)
assert.Equal(t, answer, rw5.Body.String())
// Should always work with objects implementing TextMarshaler interface
rw6 := httptest.NewRecorder()
err6 := prod.Produce(rw6, &textMarshalDummy{answer})
assert.NoError(t, err6)
assert.Equal(t, answer, rw6.Body.String())
// should not work with anything that's not (indirectly) a string
rw7 := httptest.NewRecorder()
err7 := prod.Produce(rw7, 42)
assert.Error(t, err7)
// nil values should also be safely caught with an error
rw8 := httptest.NewRecorder()
err8 := prod.Produce(rw8, nil)
assert.Error(t, err8)
// writer can not be nil
assert.Error(t, prod.Produce(nil, &textMarshalDummy{answer}))
// should not work for a textMarshaler that returns an error during marshalling
rw9 := httptest.NewRecorder()
err9 := prod.Produce(rw9, new(textMarshalDummy))
assert.Error(t, err9)
}
type stringerDummy struct {
str string
}
func (t *stringerDummy) String() string {
return t.str
}
type textMarshalDummy struct {
str string
}
func (t *textMarshalDummy) MarshalText() ([]byte, error) {
if t.str == "" {
return nil, errors.New("no text set")
}
return []byte(t.str), nil
}

19
vendor/github.com/go-openapi/runtime/values.go generated vendored Normal file
View File

@@ -0,0 +1,19 @@
package runtime
// Values typically represent parameters on a http request.
type Values map[string][]string
// GetOK returns the values collection for the given key.
// When the key is present in the map it will return true for hasKey.
// When the value is not empty it will return true for hasValue.
func (v Values) GetOK(key string) (value []string, hasKey bool, hasValue bool) {
value, hasKey = v[key]
if !hasKey {
return
}
if len(value) == 0 {
return
}
hasValue = true
return
}

36
vendor/github.com/go-openapi/runtime/xml.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
// 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 runtime
import (
"encoding/xml"
"io"
)
// XMLConsumer creates a new XML consumer
func XMLConsumer() Consumer {
return ConsumerFunc(func(reader io.Reader, data interface{}) error {
dec := xml.NewDecoder(reader)
return dec.Decode(data)
})
}
// XMLProducer creates a new XML producer
func XMLProducer() Producer {
return ProducerFunc(func(writer io.Writer, data interface{}) error {
enc := xml.NewEncoder(writer)
return enc.Encode(data)
})
}

53
vendor/github.com/go-openapi/runtime/xml_test.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
// 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 runtime
import (
"bytes"
"encoding/xml"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
var consProdXML = `<person><name>Somebody</name><id>1</id></person>`
func TestXMLConsumer(t *testing.T) {
cons := XMLConsumer()
var data struct {
XMLName xml.Name `xml:"person"`
Name string `xml:"name"`
ID int `xml:"id"`
}
err := cons.Consume(bytes.NewBuffer([]byte(consProdXML)), &data)
assert.NoError(t, err)
assert.Equal(t, "Somebody", data.Name)
assert.Equal(t, 1, data.ID)
}
func TestXMLProducer(t *testing.T) {
prod := XMLProducer()
data := struct {
XMLName xml.Name `xml:"person"`
Name string `xml:"name"`
ID int `xml:"id"`
}{Name: "Somebody", ID: 1}
rw := httptest.NewRecorder()
err := prod.Produce(rw, data)
assert.NoError(t, err)
assert.Equal(t, consProdXML, rw.Body.String())
}

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