mirror of
https://github.com/MuXiu1997/traefik-github-oauth-plugin
synced 2025-12-17 18:31:27 +00:00
feat(server): return request error message in json
This commit is contained in:
1
go.mod
1
go.mod
@@ -3,6 +3,7 @@ module github.com/muxiu1997/traefik-github-oauth-plugin
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/dghubble/sling v1.4.1
|
||||
github.com/gin-gonic/gin v1.8.2
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3
|
||||
github.com/google/go-github/v49 v49.1.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -2,6 +2,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dghubble/sling v1.4.1 h1:AxjTubpVyozMvbBCtXcsWEyGGgUZutC5YGrfxPNVOcQ=
|
||||
github.com/dghubble/sling v1.4.1/go.mod h1:QoMB1KL3GAo+7HsD8Itd6S+6tW91who8BGZzuLvpOyc=
|
||||
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
|
||||
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/muxiu1997/traefik-github-oauth-plugin/internal/app/traefik-github-oauth-server/model"
|
||||
"github.com/muxiu1997/traefik-github-oauth-plugin/internal/pkg/constant"
|
||||
)
|
||||
|
||||
@@ -17,7 +18,9 @@ func NewApiSecretKeyMiddleware(apiSecretKey string) gin.HandlerFunc {
|
||||
}
|
||||
reqSecretKey := c.GetHeader(constant.HTTP_HEADER_AUTHORIZATION)
|
||||
if reqSecretKey != fmt.Sprintf("%s %s", constant.AUTHORIZATION_PREFIX_TOKEN, apiSecretKey) {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
c.JSON(http.StatusUnauthorized, model.ResponseError{
|
||||
Message: "invalid api secret key",
|
||||
})
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@@ -24,9 +24,13 @@ type ResponseGetAuthResult struct {
|
||||
GitHubUserLogin string `json:"github_user_login"`
|
||||
}
|
||||
|
||||
type ResponseError struct {
|
||||
Message string `json:"msg"`
|
||||
}
|
||||
|
||||
type AuthRequest struct {
|
||||
RedirectURI string `json:"redirect_uri"`
|
||||
AuthURL string `json:"auth_url"`
|
||||
GitHubUserID string `json:"github_user_id"`
|
||||
GitHubUserLogin string `json:"github_user_login"`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,20 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidApiBaseURL = fmt.Errorf("invalid api base url")
|
||||
ErrInvalidRID = fmt.Errorf("invalid rid")
|
||||
ErrInvalidAuthURL = fmt.Errorf("invalid auth url")
|
||||
)
|
||||
|
||||
func generateOAuthPageURL(app *server.App) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
body := model.RequestGenerateOAuthPageURL{}
|
||||
err := c.BindJSON(&body)
|
||||
err := c.ShouldBindJSON(&body)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.ResponseError{
|
||||
Message: fmt.Sprintf("invalid request: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -30,7 +39,9 @@ func generateOAuthPageURL(app *server.App) gin.HandlerFunc {
|
||||
|
||||
redirectURI, err := buildRedirectURI(app.Config.ApiBaseURL, rid)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
c.JSON(http.StatusInternalServerError, model.ResponseError{
|
||||
Message: fmt.Sprintf("[server]%s: %s", err.Error(), app.Config.ApiBaseURL),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,7 +69,7 @@ func redirect(app *server.App) gin.HandlerFunc {
|
||||
|
||||
authRequest, found := app.AuthRequestManager.Get(query.RID)
|
||||
if !found {
|
||||
c.String(http.StatusBadRequest, "invalid rid")
|
||||
c.String(http.StatusBadRequest, ErrInvalidRID.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -73,7 +84,7 @@ func redirect(app *server.App) gin.HandlerFunc {
|
||||
|
||||
authURL, err := url.Parse(authRequest.AuthURL)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "invalid auth url: %s", authRequest.AuthURL)
|
||||
c.String(http.StatusInternalServerError, "%s: %s", ErrInvalidAuthURL.Error(), authRequest.AuthURL)
|
||||
return
|
||||
}
|
||||
authURLQuery := authURL.Query()
|
||||
@@ -87,14 +98,19 @@ func redirect(app *server.App) gin.HandlerFunc {
|
||||
func getAuthResult(app *server.App) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
query := model.RequestGetAuthResult{}
|
||||
err := c.BindQuery(&query)
|
||||
err := c.ShouldBindQuery(&query)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, model.ResponseError{
|
||||
Message: fmt.Sprintf("invalid request: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
authRequest, found := app.AuthRequestManager.Pop(query.RID)
|
||||
if !found {
|
||||
c.String(http.StatusBadRequest, "invalid rid")
|
||||
c.JSON(http.StatusBadRequest, model.ResponseError{
|
||||
Message: ErrInvalidRID.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -126,7 +142,7 @@ func oAuthCodeToUser(ctx context.Context, oAuthConfig *oauth2.Config, code strin
|
||||
func buildRedirectURI(apiBaseUrl, rid string) (string, error) {
|
||||
redirectURI, err := url.Parse(apiBaseUrl)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid api base url in server config: %w", err)
|
||||
return "", ErrInvalidApiBaseURL
|
||||
}
|
||||
redirectURI = redirectURI.JoinPath(constant.ROUTER_GROUP_PATH_OAUTH, constant.ROUTER_PATH_OAUTH_REDIRECT)
|
||||
redirectURLQuery := redirectURI.Query()
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
server "github.com/muxiu1997/traefik-github-oauth-plugin/internal/app/traefik-github-oauth-server"
|
||||
"github.com/muxiu1997/traefik-github-oauth-plugin/internal/pkg/constant"
|
||||
)
|
||||
@@ -8,6 +11,10 @@ import (
|
||||
func RegisterRoutes(app *server.App) {
|
||||
apiSecretKeyMiddleware := server.NewApiSecretKeyMiddleware(app.Config.ApiSecretKey)
|
||||
|
||||
app.Engine.GET("/", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "Traefik GitHub OAuth Server")
|
||||
})
|
||||
|
||||
app.Engine.GET(constant.ROUTER_PATH_OAUTH_HEALTH, healthCheck(app))
|
||||
|
||||
oauthGroup := app.Engine.Group(constant.ROUTER_GROUP_PATH_OAUTH)
|
||||
|
||||
77
vendor/github.com/dghubble/sling/CHANGES.md
generated
vendored
Normal file
77
vendor/github.com/dghubble/sling/CHANGES.md
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
# Sling Changelog
|
||||
|
||||
Notable changes between releases.
|
||||
|
||||
## Latest
|
||||
|
||||
## v1.4.1
|
||||
|
||||
* Update minimum Go version to v1.18 ([#76](https://github.com/dghubble/sling/pull/76))
|
||||
|
||||
## v1.4.0
|
||||
|
||||
* `Do` reads Body to reuse HTTP/1.x "keep-alive" TCP connections ([#59](https://github.com/dghubble/sling/pull/59))
|
||||
* `Receive` skips decoding if status is 204 (no content) ([#63](https://github.com/dghubble/sling/pull/63))
|
||||
|
||||
## v1.3.0
|
||||
|
||||
* Add Sling `ResponseDecoder` setter for receiving responses with a custom `ResponseDecoder` ([#49](https://github.com/dghubble/sling/pull/49))
|
||||
* Add Go module support (i.e. `go.mod`). Exclude `examples` (multi-module). ([#52](https://github.com/dghubble/sling/pull/52))
|
||||
|
||||
## v1.2.0
|
||||
|
||||
* Add `Connect`, `Options`, and `Trace` HTTP methods ([c51967](https://github.com/dghubble/sling/commit/c519674860ff275e0ceb12caf5d87b31765c4e71))
|
||||
* Skip receiving (i.e. decoding) `204 No Content` responses ([#31](https://github.com/dghubble/sling/pull/31))
|
||||
|
||||
## v1.1.0
|
||||
|
||||
* Allow JSON decoding, regardless of response Content-Type (#26)
|
||||
* Add `BodyProvider` interface and setter so request Body encoding can be customized (#23)
|
||||
* Add `Doer` interface and setter so request sending behavior can be customized (#21)
|
||||
* Add `SetBasicAuth` setter for Authorization headers (#16)
|
||||
* Add Sling `Body` setter to set an `io.Reader` on the Request (#9)
|
||||
|
||||
## v1.0.0
|
||||
|
||||
* Added support for receiving and decoding error JSON structs
|
||||
* Renamed Sling `JsonBody` setter to `BodyJSON` (breaking)
|
||||
* Renamed Sling `BodyStruct` setter to `BodyForm` (breaking)
|
||||
* Renamed Sling fields `httpClient`, `method`, `rawURL`, and `header` to be internal (breaking)
|
||||
* Changed `Do` and `Receive` to skip response JSON decoding if "application/json" Content-Type is missing
|
||||
* Changed `Sling.Receive(v interface{})` to `Sling.Receive(successV, failureV interface{})` (breaking)
|
||||
* Previously `Receive` attempted to decode the response Body in all cases
|
||||
* Updated `Receive` will decode the response Body into successV for 2XX responses or decode the Body into failureV for other status codes. Pass a nil `successV` or `failureV` to skip JSON decoding into that value.
|
||||
* To upgrade, pass nil for the `failureV` argument or consider defining a JSON tagged struct appropriate for the API endpoint. (e.g. `s.Receive(&issue, nil)`, `s.Receive(&issue, &githubError)`)
|
||||
* To retain the old behavior, duplicate the first argument (e.g. s.Receive(&tweet, &tweet))
|
||||
* Changed `Sling.Do(http.Request, v interface{})` to `Sling.Do(http.Request, successV, failureV interface{})` (breaking)
|
||||
* See the changelog entry about `Receive`, the upgrade path is the same.
|
||||
* Removed HEAD, GET, POST, PUT, PATCH, DELETE constants, no reason to export them (breaking)
|
||||
|
||||
## v0.4.0
|
||||
|
||||
* Improved golint compliance
|
||||
* Fixed typos and test printouts
|
||||
|
||||
## v0.3.0
|
||||
|
||||
* Added BodyStruct method for setting a url encoded form body on the Request
|
||||
* Added Add and Set methods for adding or setting Request Headers
|
||||
* Added JsonBody method for setting JSON Request Body
|
||||
* Improved examples and documentation
|
||||
|
||||
## v0.2.0
|
||||
|
||||
* Added http.Client setter
|
||||
* Added Sling.New() method to return a copy of a Sling
|
||||
* Added Base setter and Path extension support
|
||||
* Added method setters (Get, Post, Put, Patch, Delete, Head)
|
||||
* Added support for encoding URL Query parameters
|
||||
* Added example tiny Github API
|
||||
* Changed v0.1.0 method signatures and names (breaking)
|
||||
* Removed Go 1.0 support
|
||||
|
||||
## v0.1.0
|
||||
|
||||
* Support decoding JSON responses.
|
||||
|
||||
|
||||
21
vendor/github.com/dghubble/sling/LICENSE
generated
vendored
Normal file
21
vendor/github.com/dghubble/sling/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Dalton Hubble
|
||||
|
||||
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.
|
||||
15
vendor/github.com/dghubble/sling/Makefile
generated
vendored
Normal file
15
vendor/github.com/dghubble/sling/Makefile
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
.PHONY: all
|
||||
all: test vet fmt
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@go test . -cover
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
@go vet -all .
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@test -z $$(go fmt ./...)
|
||||
|
||||
294
vendor/github.com/dghubble/sling/README.md
generated
vendored
Normal file
294
vendor/github.com/dghubble/sling/README.md
generated
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
# Sling [](https://pkg.go.dev/github.com/dghubble/sling) [](https://github.com/dghubble/sling/actions/workflows/test.yaml?query=branch%3Amain) [](https://gocover.io/github.com/dghubble/sling) [](https://github.com/sponsors/dghubble) [](https://twitter.com/dghubble)
|
||||
|
||||
<img align="right" src="https://storage.googleapis.com/dghubble/small-gopher-with-sling.png">
|
||||
|
||||
Sling is a Go HTTP client library for creating and sending API requests.
|
||||
|
||||
Slings store HTTP Request properties to simplify sending requests and decoding responses. Check [usage](#usage) or the [examples](examples) to learn how to compose a Sling into your API client.
|
||||
|
||||
### Features
|
||||
|
||||
* Method Setters: Get/Post/Put/Patch/Delete/Head
|
||||
* Add or Set Request Headers
|
||||
* Base/Path: Extend a Sling for different endpoints
|
||||
* Encode structs into URL query parameters
|
||||
* Encode a form or JSON into the Request Body
|
||||
* Receive JSON success or failure responses
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
go get github.com/dghubble/sling
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Read [GoDoc](https://godoc.org/github.com/dghubble/sling)
|
||||
|
||||
## Usage
|
||||
|
||||
Use a Sling to set path, method, header, query, or body properties and create an `http.Request`.
|
||||
|
||||
```go
|
||||
type Params struct {
|
||||
Count int `url:"count,omitempty"`
|
||||
}
|
||||
params := &Params{Count: 5}
|
||||
|
||||
req, err := sling.New().Get("https://example.com").QueryStruct(params).Request()
|
||||
client.Do(req)
|
||||
```
|
||||
|
||||
### Path
|
||||
|
||||
Use `Path` to set or extend the URL for created Requests. Extension means the path will be resolved relative to the existing URL.
|
||||
|
||||
```go
|
||||
// creates a GET request to https://example.com/foo/bar
|
||||
req, err := sling.New().Base("https://example.com/").Path("foo/").Path("bar").Request()
|
||||
```
|
||||
|
||||
Use `Get`, `Post`, `Put`, `Patch`, `Delete`, `Head`, `Options`, `Trace`, or `Connect` which are exactly the same as `Path` except they set the HTTP method too.
|
||||
|
||||
```go
|
||||
req, err := sling.New().Post("http://upload.com/gophers")
|
||||
```
|
||||
|
||||
### Headers
|
||||
|
||||
`Add` or `Set` headers for requests created by a Sling.
|
||||
|
||||
```go
|
||||
s := sling.New().Base(baseUrl).Set("User-Agent", "Gophergram API Client")
|
||||
req, err := s.New().Get("gophergram/list").Request()
|
||||
```
|
||||
|
||||
### Query
|
||||
|
||||
#### QueryStruct
|
||||
|
||||
Define [url tagged structs](https://godoc.org/github.com/google/go-querystring/query). Use `QueryStruct` to encode a struct as query parameters on requests.
|
||||
|
||||
```go
|
||||
// Github Issue Parameters
|
||||
type IssueParams struct {
|
||||
Filter string `url:"filter,omitempty"`
|
||||
State string `url:"state,omitempty"`
|
||||
Labels string `url:"labels,omitempty"`
|
||||
Sort string `url:"sort,omitempty"`
|
||||
Direction string `url:"direction,omitempty"`
|
||||
Since string `url:"since,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
githubBase := sling.New().Base("https://api.github.com/").Client(httpClient)
|
||||
|
||||
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)
|
||||
params := &IssueParams{Sort: "updated", State: "open"}
|
||||
req, err := githubBase.New().Get(path).QueryStruct(params).Request()
|
||||
```
|
||||
|
||||
### Body
|
||||
|
||||
#### JSON Body
|
||||
|
||||
Define [JSON tagged structs](https://golang.org/pkg/encoding/json/). Use `BodyJSON` to JSON encode a struct as the Body on requests.
|
||||
|
||||
```go
|
||||
type IssueRequest struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
Milestone int `json:"milestone,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
githubBase := sling.New().Base("https://api.github.com/").Client(httpClient)
|
||||
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)
|
||||
|
||||
body := &IssueRequest{
|
||||
Title: "Test title",
|
||||
Body: "Some issue",
|
||||
}
|
||||
req, err := githubBase.New().Post(path).BodyJSON(body).Request()
|
||||
```
|
||||
|
||||
Requests will include an `application/json` Content-Type header.
|
||||
|
||||
#### Form Body
|
||||
|
||||
Define [url tagged structs](https://godoc.org/github.com/google/go-querystring/query). Use `BodyForm` to form url encode a struct as the Body on requests.
|
||||
|
||||
```go
|
||||
type StatusUpdateParams struct {
|
||||
Status string `url:"status,omitempty"`
|
||||
InReplyToStatusId int64 `url:"in_reply_to_status_id,omitempty"`
|
||||
MediaIds []int64 `url:"media_ids,omitempty,comma"`
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
tweetParams := &StatusUpdateParams{Status: "writing some Go"}
|
||||
req, err := twitterBase.New().Post(path).BodyForm(tweetParams).Request()
|
||||
```
|
||||
|
||||
Requests will include an `application/x-www-form-urlencoded` Content-Type header.
|
||||
|
||||
#### Plain Body
|
||||
|
||||
Use `Body` to set a plain `io.Reader` on requests created by a Sling.
|
||||
|
||||
```go
|
||||
body := strings.NewReader("raw body")
|
||||
req, err := sling.New().Base("https://example.com").Body(body).Request()
|
||||
```
|
||||
|
||||
Set a content type header, if desired (e.g. `Set("Content-Type", "text/plain")`).
|
||||
|
||||
### Extend a Sling
|
||||
|
||||
Each Sling creates a standard `http.Request` (e.g. with some path and query
|
||||
params) each time `Request()` is called. You may wish to extend an existing Sling to minimize duplication (e.g. a common client or base url).
|
||||
|
||||
Each Sling instance provides a `New()` method which creates an independent copy, so setting properties on the child won't mutate the parent Sling.
|
||||
|
||||
```go
|
||||
const twitterApi = "https://api.twitter.com/1.1/"
|
||||
base := sling.New().Base(twitterApi).Client(authClient)
|
||||
|
||||
// statuses/show.json Sling
|
||||
tweetShowSling := base.New().Get("statuses/show.json").QueryStruct(params)
|
||||
req, err := tweetShowSling.Request()
|
||||
|
||||
// statuses/update.json Sling
|
||||
tweetPostSling := base.New().Post("statuses/update.json").BodyForm(params)
|
||||
req, err := tweetPostSling.Request()
|
||||
```
|
||||
|
||||
Without the calls to `base.New()`, `tweetShowSling` and `tweetPostSling` would reference the base Sling and POST to
|
||||
"https://api.twitter.com/1.1/statuses/show.json/statuses/update.json", which
|
||||
is undesired.
|
||||
|
||||
Recap: If you wish to *extend* a Sling, create a new child copy with `New()`.
|
||||
|
||||
### Sending
|
||||
|
||||
#### Receive
|
||||
|
||||
Define a JSON struct to decode a type from 2XX success responses. Use `ReceiveSuccess(successV interface{})` to send a new Request and decode the response body into `successV` if it succeeds.
|
||||
|
||||
```go
|
||||
// Github Issue (abbreviated)
|
||||
type Issue struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
issues := new([]Issue)
|
||||
resp, err := githubBase.New().Get(path).QueryStruct(params).ReceiveSuccess(issues)
|
||||
fmt.Println(issues, resp, err)
|
||||
```
|
||||
|
||||
Most APIs return failure responses with JSON error details. To decode these, define success and failure JSON structs. Use `Receive(successV, failureV interface{})` to send a new Request that will automatically decode the response into the `successV` for 2XX responses or into `failureV` for non-2XX responses.
|
||||
|
||||
```go
|
||||
type GithubError struct {
|
||||
Message string `json:"message"`
|
||||
Errors []struct {
|
||||
Resource string `json:"resource"`
|
||||
Field string `json:"field"`
|
||||
Code string `json:"code"`
|
||||
} `json:"errors"`
|
||||
DocumentationURL string `json:"documentation_url"`
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
issues := new([]Issue)
|
||||
githubError := new(GithubError)
|
||||
resp, err := githubBase.New().Get(path).QueryStruct(params).Receive(issues, githubError)
|
||||
fmt.Println(issues, githubError, resp, err)
|
||||
```
|
||||
|
||||
Pass a nil `successV` or `failureV` argument to skip JSON decoding into that value.
|
||||
|
||||
### Modify a Request
|
||||
|
||||
Sling provides the raw http.Request so modifications can be made using standard net/http features. For example, in Go 1.7+ , add HTTP tracing to a request with a context:
|
||||
|
||||
```go
|
||||
req, err := sling.New().Get("https://example.com").QueryStruct(params).Request()
|
||||
// handle error
|
||||
|
||||
trace := &httptrace.ClientTrace{
|
||||
DNSDone: func(dnsInfo httptrace.DNSDoneInfo) {
|
||||
fmt.Printf("DNS Info: %+v\n", dnsInfo)
|
||||
},
|
||||
GotConn: func(connInfo httptrace.GotConnInfo) {
|
||||
fmt.Printf("Got Conn: %+v\n", connInfo)
|
||||
},
|
||||
}
|
||||
|
||||
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
|
||||
client.Do(req)
|
||||
```
|
||||
|
||||
### Build an API
|
||||
|
||||
APIs typically define an endpoint (also called a service) for each type of resource. For example, here is a tiny Github IssueService which [lists](https://developer.github.com/v3/issues/#list-issues-for-a-repository) repository issues.
|
||||
|
||||
```go
|
||||
const baseURL = "https://api.github.com/"
|
||||
|
||||
type IssueService struct {
|
||||
sling *sling.Sling
|
||||
}
|
||||
|
||||
func NewIssueService(httpClient *http.Client) *IssueService {
|
||||
return &IssueService{
|
||||
sling: sling.New().Client(httpClient).Base(baseURL),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IssueService) ListByRepo(owner, repo string, params *IssueListParams) ([]Issue, *http.Response, error) {
|
||||
issues := new([]Issue)
|
||||
githubError := new(GithubError)
|
||||
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)
|
||||
resp, err := s.sling.New().Get(path).QueryStruct(params).Receive(issues, githubError)
|
||||
if err == nil {
|
||||
err = githubError
|
||||
}
|
||||
return *issues, resp, err
|
||||
}
|
||||
```
|
||||
|
||||
## Example APIs using Sling
|
||||
|
||||
* Digits [dghubble/go-digits](https://github.com/dghubble/go-digits)
|
||||
* GoSquared [drinkin/go-gosquared](https://github.com/drinkin/go-gosquared)
|
||||
* Kala [ajvb/kala](https://github.com/ajvb/kala)
|
||||
* Parse [fergstar/go-parse](https://github.com/fergstar/go-parse)
|
||||
* Swagger Generator [swagger-api/swagger-codegen](https://github.com/swagger-api/swagger-codegen)
|
||||
* Twitter [dghubble/go-twitter](https://github.com/dghubble/go-twitter)
|
||||
* Stacksmith [jesustinoco/go-smith](https://github.com/jesustinoco/go-smith)
|
||||
|
||||
Create a Pull Request to add a link to your own API.
|
||||
|
||||
## Motivation
|
||||
|
||||
Many client libraries follow the lead of [google/go-github](https://github.com/google/go-github) (our inspiration!), but do so by reimplementing logic common to all clients.
|
||||
|
||||
This project borrows and abstracts those ideas into a Sling, an agnostic component any API client can use for creating and sending requests.
|
||||
|
||||
## Contributing
|
||||
|
||||
See the [Contributing Guide](https://gist.github.com/dghubble/be682c123727f70bcfe7).
|
||||
|
||||
## License
|
||||
|
||||
[MIT License](LICENSE)
|
||||
68
vendor/github.com/dghubble/sling/body.go
generated
vendored
Normal file
68
vendor/github.com/dghubble/sling/body.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
package sling
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
goquery "github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
// BodyProvider provides Body content for http.Request attachment.
|
||||
type BodyProvider interface {
|
||||
// ContentType returns the Content-Type of the body.
|
||||
ContentType() string
|
||||
// Body returns the io.Reader body.
|
||||
Body() (io.Reader, error)
|
||||
}
|
||||
|
||||
// bodyProvider provides the wrapped body value as a Body for reqests.
|
||||
type bodyProvider struct {
|
||||
body io.Reader
|
||||
}
|
||||
|
||||
func (p bodyProvider) ContentType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p bodyProvider) Body() (io.Reader, error) {
|
||||
return p.body, nil
|
||||
}
|
||||
|
||||
// jsonBodyProvider encodes a JSON tagged struct value as a Body for requests.
|
||||
// See https://golang.org/pkg/encoding/json/#MarshalIndent for details.
|
||||
type jsonBodyProvider struct {
|
||||
payload interface{}
|
||||
}
|
||||
|
||||
func (p jsonBodyProvider) ContentType() string {
|
||||
return jsonContentType
|
||||
}
|
||||
|
||||
func (p jsonBodyProvider) Body() (io.Reader, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
err := json.NewEncoder(buf).Encode(p.payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// formBodyProvider encodes a url tagged struct value as Body for requests.
|
||||
// See https://godoc.org/github.com/google/go-querystring/query for details.
|
||||
type formBodyProvider struct {
|
||||
payload interface{}
|
||||
}
|
||||
|
||||
func (p formBodyProvider) ContentType() string {
|
||||
return formContentType
|
||||
}
|
||||
|
||||
func (p formBodyProvider) Body() (io.Reader, error) {
|
||||
values, err := goquery.Values(p.payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return strings.NewReader(values.Encode()), nil
|
||||
}
|
||||
179
vendor/github.com/dghubble/sling/doc.go
generated
vendored
Normal file
179
vendor/github.com/dghubble/sling/doc.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
Package sling is a Go HTTP client library for creating and sending API requests.
|
||||
|
||||
Slings store HTTP Request properties to simplify sending requests and decoding
|
||||
responses. Check the examples to learn how to compose a Sling into your API
|
||||
client.
|
||||
|
||||
# Usage
|
||||
|
||||
Use a Sling to set path, method, header, query, or body properties and create an
|
||||
http.Request.
|
||||
|
||||
type Params struct {
|
||||
Count int `url:"count,omitempty"`
|
||||
}
|
||||
params := &Params{Count: 5}
|
||||
|
||||
req, err := sling.New().Get("https://example.com").QueryStruct(params).Request()
|
||||
client.Do(req)
|
||||
|
||||
# Path
|
||||
|
||||
Use Path to set or extend the URL for created Requests. Extension means the
|
||||
path will be resolved relative to the existing URL.
|
||||
|
||||
// creates a GET request to https://example.com/foo/bar
|
||||
req, err := sling.New().Base("https://example.com/").Path("foo/").Path("bar").Request()
|
||||
|
||||
Use Get, Post, Put, Patch, Delete, or Head which are exactly the same as Path
|
||||
except they set the HTTP method too.
|
||||
|
||||
req, err := sling.New().Post("http://upload.com/gophers")
|
||||
|
||||
# Headers
|
||||
|
||||
Add or Set headers for requests created by a Sling.
|
||||
|
||||
s := sling.New().Base(baseUrl).Set("User-Agent", "Gophergram API Client")
|
||||
req, err := s.New().Get("gophergram/list").Request()
|
||||
|
||||
# QueryStruct
|
||||
|
||||
Define url parameter structs (https://godoc.org/github.com/google/go-querystring/query).
|
||||
Use QueryStruct to encode a struct as query parameters on requests.
|
||||
|
||||
// Github Issue Parameters
|
||||
type IssueParams struct {
|
||||
Filter string `url:"filter,omitempty"`
|
||||
State string `url:"state,omitempty"`
|
||||
Labels string `url:"labels,omitempty"`
|
||||
Sort string `url:"sort,omitempty"`
|
||||
Direction string `url:"direction,omitempty"`
|
||||
Since string `url:"since,omitempty"`
|
||||
}
|
||||
|
||||
githubBase := sling.New().Base("https://api.github.com/").Client(httpClient)
|
||||
|
||||
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)
|
||||
params := &IssueParams{Sort: "updated", State: "open"}
|
||||
req, err := githubBase.New().Get(path).QueryStruct(params).Request()
|
||||
|
||||
# Json Body
|
||||
|
||||
Define JSON tagged structs (https://golang.org/pkg/encoding/json/).
|
||||
Use BodyJSON to JSON encode a struct as the Body on requests.
|
||||
|
||||
type IssueRequest struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
Milestone int `json:"milestone,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
githubBase := sling.New().Base("https://api.github.com/").Client(httpClient)
|
||||
path := fmt.Sprintf("repos/%s/%s/issues", owner, repo)
|
||||
|
||||
body := &IssueRequest{
|
||||
Title: "Test title",
|
||||
Body: "Some issue",
|
||||
}
|
||||
req, err := githubBase.New().Post(path).BodyJSON(body).Request()
|
||||
|
||||
Requests will include an "application/json" Content-Type header.
|
||||
|
||||
# Form Body
|
||||
|
||||
Define url tagged structs (https://godoc.org/github.com/google/go-querystring/query).
|
||||
Use BodyForm to form url encode a struct as the Body on requests.
|
||||
|
||||
type StatusUpdateParams struct {
|
||||
Status string `url:"status,omitempty"`
|
||||
InReplyToStatusId int64 `url:"in_reply_to_status_id,omitempty"`
|
||||
MediaIds []int64 `url:"media_ids,omitempty,comma"`
|
||||
}
|
||||
|
||||
tweetParams := &StatusUpdateParams{Status: "writing some Go"}
|
||||
req, err := twitterBase.New().Post(path).BodyForm(tweetParams).Request()
|
||||
|
||||
Requests will include an "application/x-www-form-urlencoded" Content-Type
|
||||
header.
|
||||
|
||||
# Plain Body
|
||||
|
||||
Use Body to set a plain io.Reader on requests created by a Sling.
|
||||
|
||||
body := strings.NewReader("raw body")
|
||||
req, err := sling.New().Base("https://example.com").Body(body).Request()
|
||||
|
||||
Set a content type header, if desired (e.g. Set("Content-Type", "text/plain")).
|
||||
|
||||
# Extend a Sling
|
||||
|
||||
Each Sling generates an http.Request (say with some path and query params)
|
||||
each time Request() is called, based on its state. When creating
|
||||
different slings, you may wish to extend an existing Sling to minimize
|
||||
duplication (e.g. a common client).
|
||||
|
||||
Each Sling instance provides a New() method which creates an independent copy,
|
||||
so setting properties on the child won't mutate the parent Sling.
|
||||
|
||||
const twitterApi = "https://api.twitter.com/1.1/"
|
||||
base := sling.New().Base(twitterApi).Client(authClient)
|
||||
|
||||
// statuses/show.json Sling
|
||||
tweetShowSling := base.New().Get("statuses/show.json").QueryStruct(params)
|
||||
req, err := tweetShowSling.Request()
|
||||
|
||||
// statuses/update.json Sling
|
||||
tweetPostSling := base.New().Post("statuses/update.json").BodyForm(params)
|
||||
req, err := tweetPostSling.Request()
|
||||
|
||||
Without the calls to base.New(), tweetShowSling and tweetPostSling would
|
||||
reference the base Sling and POST to
|
||||
"https://api.twitter.com/1.1/statuses/show.json/statuses/update.json", which
|
||||
is undesired.
|
||||
|
||||
Recap: If you wish to extend a Sling, create a new child copy with New().
|
||||
|
||||
# Receive
|
||||
|
||||
Define a JSON struct to decode a type from 2XX success responses. Use
|
||||
ReceiveSuccess(successV interface{}) to send a new Request and decode the
|
||||
response body into successV if it succeeds.
|
||||
|
||||
// Github Issue (abbreviated)
|
||||
type Issue struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
issues := new([]Issue)
|
||||
resp, err := githubBase.New().Get(path).QueryStruct(params).ReceiveSuccess(issues)
|
||||
fmt.Println(issues, resp, err)
|
||||
|
||||
Most APIs return failure responses with JSON error details. To decode these,
|
||||
define success and failure JSON structs. Use
|
||||
Receive(successV, failureV interface{}) to send a new Request that will
|
||||
automatically decode the response into the successV for 2XX responses or into
|
||||
failureV for non-2XX responses.
|
||||
|
||||
type GithubError struct {
|
||||
Message string `json:"message"`
|
||||
Errors []struct {
|
||||
Resource string `json:"resource"`
|
||||
Field string `json:"field"`
|
||||
Code string `json:"code"`
|
||||
} `json:"errors"`
|
||||
DocumentationURL string `json:"documentation_url"`
|
||||
}
|
||||
|
||||
issues := new([]Issue)
|
||||
githubError := new(GithubError)
|
||||
resp, err := githubBase.New().Get(path).QueryStruct(params).Receive(issues, githubError)
|
||||
fmt.Println(issues, githubError, resp, err)
|
||||
|
||||
Pass a nil successV or failureV argument to skip JSON decoding into that value.
|
||||
*/
|
||||
package sling
|
||||
22
vendor/github.com/dghubble/sling/response.go
generated
vendored
Normal file
22
vendor/github.com/dghubble/sling/response.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package sling
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ResponseDecoder decodes http responses into struct values.
|
||||
type ResponseDecoder interface {
|
||||
// Decode decodes the response into the value pointed to by v.
|
||||
Decode(resp *http.Response, v interface{}) error
|
||||
}
|
||||
|
||||
// jsonDecoder decodes http response JSON into a JSON-tagged struct value.
|
||||
type jsonDecoder struct {
|
||||
}
|
||||
|
||||
// Decode decodes the Response Body into the value pointed to by v.
|
||||
// Caller must provide a non-nil v and close the resp.Body.
|
||||
func (d jsonDecoder) Decode(resp *http.Response, v interface{}) error {
|
||||
return json.NewDecoder(resp.Body).Decode(v)
|
||||
}
|
||||
423
vendor/github.com/dghubble/sling/sling.go
generated
vendored
Normal file
423
vendor/github.com/dghubble/sling/sling.go
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
package sling
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
goquery "github.com/google/go-querystring/query"
|
||||
)
|
||||
|
||||
const (
|
||||
contentType = "Content-Type"
|
||||
jsonContentType = "application/json"
|
||||
formContentType = "application/x-www-form-urlencoded"
|
||||
)
|
||||
|
||||
// Doer executes http requests. It is implemented by *http.Client. You can
|
||||
// wrap *http.Client with layers of Doers to form a stack of client-side
|
||||
// middleware.
|
||||
type Doer interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Sling is an HTTP Request builder and sender.
|
||||
type Sling struct {
|
||||
// http Client for doing requests
|
||||
httpClient Doer
|
||||
// HTTP method (GET, POST, etc.)
|
||||
method string
|
||||
// raw url string for requests
|
||||
rawURL string
|
||||
// stores key-values pairs to add to request's Headers
|
||||
header http.Header
|
||||
// url tagged query structs
|
||||
queryStructs []interface{}
|
||||
// body provider
|
||||
bodyProvider BodyProvider
|
||||
// response decoder
|
||||
responseDecoder ResponseDecoder
|
||||
}
|
||||
|
||||
// New returns a new Sling with an http DefaultClient.
|
||||
func New() *Sling {
|
||||
return &Sling{
|
||||
httpClient: http.DefaultClient,
|
||||
method: "GET",
|
||||
header: make(http.Header),
|
||||
queryStructs: make([]interface{}, 0),
|
||||
responseDecoder: jsonDecoder{},
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a copy of a Sling for creating a new Sling with properties
|
||||
// from a parent Sling. For example,
|
||||
//
|
||||
// parentSling := sling.New().Client(client).Base("https://api.io/")
|
||||
// fooSling := parentSling.New().Get("foo/")
|
||||
// barSling := parentSling.New().Get("bar/")
|
||||
//
|
||||
// fooSling and barSling will both use the same client, but send requests to
|
||||
// https://api.io/foo/ and https://api.io/bar/ respectively.
|
||||
//
|
||||
// Note that query and body values are copied so if pointer values are used,
|
||||
// mutating the original value will mutate the value within the child Sling.
|
||||
func (s *Sling) New() *Sling {
|
||||
// copy Headers pairs into new Header map
|
||||
headerCopy := make(http.Header)
|
||||
for k, v := range s.header {
|
||||
headerCopy[k] = v
|
||||
}
|
||||
return &Sling{
|
||||
httpClient: s.httpClient,
|
||||
method: s.method,
|
||||
rawURL: s.rawURL,
|
||||
header: headerCopy,
|
||||
queryStructs: append([]interface{}{}, s.queryStructs...),
|
||||
bodyProvider: s.bodyProvider,
|
||||
responseDecoder: s.responseDecoder,
|
||||
}
|
||||
}
|
||||
|
||||
// Http Client
|
||||
|
||||
// Client sets the http Client used to do requests. If a nil client is given,
|
||||
// the http.DefaultClient will be used.
|
||||
func (s *Sling) Client(httpClient *http.Client) *Sling {
|
||||
if httpClient == nil {
|
||||
return s.Doer(http.DefaultClient)
|
||||
}
|
||||
return s.Doer(httpClient)
|
||||
}
|
||||
|
||||
// Doer sets the custom Doer implementation used to do requests.
|
||||
// If a nil client is given, the http.DefaultClient will be used.
|
||||
func (s *Sling) Doer(doer Doer) *Sling {
|
||||
if doer == nil {
|
||||
s.httpClient = http.DefaultClient
|
||||
} else {
|
||||
s.httpClient = doer
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Method
|
||||
|
||||
// Head sets the Sling method to HEAD and sets the given pathURL.
|
||||
func (s *Sling) Head(pathURL string) *Sling {
|
||||
s.method = "HEAD"
|
||||
return s.Path(pathURL)
|
||||
}
|
||||
|
||||
// Get sets the Sling method to GET and sets the given pathURL.
|
||||
func (s *Sling) Get(pathURL string) *Sling {
|
||||
s.method = "GET"
|
||||
return s.Path(pathURL)
|
||||
}
|
||||
|
||||
// Post sets the Sling method to POST and sets the given pathURL.
|
||||
func (s *Sling) Post(pathURL string) *Sling {
|
||||
s.method = "POST"
|
||||
return s.Path(pathURL)
|
||||
}
|
||||
|
||||
// Put sets the Sling method to PUT and sets the given pathURL.
|
||||
func (s *Sling) Put(pathURL string) *Sling {
|
||||
s.method = "PUT"
|
||||
return s.Path(pathURL)
|
||||
}
|
||||
|
||||
// Patch sets the Sling method to PATCH and sets the given pathURL.
|
||||
func (s *Sling) Patch(pathURL string) *Sling {
|
||||
s.method = "PATCH"
|
||||
return s.Path(pathURL)
|
||||
}
|
||||
|
||||
// Delete sets the Sling method to DELETE and sets the given pathURL.
|
||||
func (s *Sling) Delete(pathURL string) *Sling {
|
||||
s.method = "DELETE"
|
||||
return s.Path(pathURL)
|
||||
}
|
||||
|
||||
// Options sets the Sling method to OPTIONS and sets the given pathURL.
|
||||
func (s *Sling) Options(pathURL string) *Sling {
|
||||
s.method = "OPTIONS"
|
||||
return s.Path(pathURL)
|
||||
}
|
||||
|
||||
// Trace sets the Sling method to TRACE and sets the given pathURL.
|
||||
func (s *Sling) Trace(pathURL string) *Sling {
|
||||
s.method = "TRACE"
|
||||
return s.Path(pathURL)
|
||||
}
|
||||
|
||||
// Connect sets the Sling method to CONNECT and sets the given pathURL.
|
||||
func (s *Sling) Connect(pathURL string) *Sling {
|
||||
s.method = "CONNECT"
|
||||
return s.Path(pathURL)
|
||||
}
|
||||
|
||||
// Header
|
||||
|
||||
// Add adds the key, value pair in Headers, appending values for existing keys
|
||||
// to the key's values. Header keys are canonicalized.
|
||||
func (s *Sling) Add(key, value string) *Sling {
|
||||
s.header.Add(key, value)
|
||||
return s
|
||||
}
|
||||
|
||||
// Set sets the key, value pair in Headers, replacing existing values
|
||||
// associated with key. Header keys are canonicalized.
|
||||
func (s *Sling) Set(key, value string) *Sling {
|
||||
s.header.Set(key, value)
|
||||
return s
|
||||
}
|
||||
|
||||
// SetBasicAuth sets the Authorization header to use HTTP Basic Authentication
|
||||
// with the provided username and password. With HTTP Basic Authentication
|
||||
// the provided username and password are not encrypted.
|
||||
func (s *Sling) SetBasicAuth(username, password string) *Sling {
|
||||
return s.Set("Authorization", "Basic "+basicAuth(username, password))
|
||||
}
|
||||
|
||||
// basicAuth returns the base64 encoded username:password for basic auth copied
|
||||
// from net/http.
|
||||
func basicAuth(username, password string) string {
|
||||
auth := username + ":" + password
|
||||
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
}
|
||||
|
||||
// Url
|
||||
|
||||
// Base sets the rawURL. If you intend to extend the url with Path,
|
||||
// baseUrl should be specified with a trailing slash.
|
||||
func (s *Sling) Base(rawURL string) *Sling {
|
||||
s.rawURL = rawURL
|
||||
return s
|
||||
}
|
||||
|
||||
// Path extends the rawURL with the given path by resolving the reference to
|
||||
// an absolute URL. If parsing errors occur, the rawURL is left unmodified.
|
||||
func (s *Sling) Path(path string) *Sling {
|
||||
baseURL, baseErr := url.Parse(s.rawURL)
|
||||
pathURL, pathErr := url.Parse(path)
|
||||
if baseErr == nil && pathErr == nil {
|
||||
s.rawURL = baseURL.ResolveReference(pathURL).String()
|
||||
return s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// QueryStruct appends the queryStruct to the Sling's queryStructs. The value
|
||||
// pointed to by each queryStruct will be encoded as url query parameters on
|
||||
// new requests (see Request()).
|
||||
// The queryStruct argument should be a pointer to a url tagged struct. See
|
||||
// https://godoc.org/github.com/google/go-querystring/query for details.
|
||||
func (s *Sling) QueryStruct(queryStruct interface{}) *Sling {
|
||||
if queryStruct != nil {
|
||||
s.queryStructs = append(s.queryStructs, queryStruct)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Body
|
||||
|
||||
// Body sets the Sling's body. The body value will be set as the Body on new
|
||||
// requests (see Request()).
|
||||
// If the provided body is also an io.Closer, the request Body will be closed
|
||||
// by http.Client methods.
|
||||
func (s *Sling) Body(body io.Reader) *Sling {
|
||||
if body == nil {
|
||||
return s
|
||||
}
|
||||
return s.BodyProvider(bodyProvider{body: body})
|
||||
}
|
||||
|
||||
// BodyProvider sets the Sling's body provider.
|
||||
func (s *Sling) BodyProvider(body BodyProvider) *Sling {
|
||||
if body == nil {
|
||||
return s
|
||||
}
|
||||
s.bodyProvider = body
|
||||
|
||||
ct := body.ContentType()
|
||||
if ct != "" {
|
||||
s.Set(contentType, ct)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// BodyJSON sets the Sling's bodyJSON. The value pointed to by the bodyJSON
|
||||
// will be JSON encoded as the Body on new requests (see Request()).
|
||||
// The bodyJSON argument should be a pointer to a JSON tagged struct. See
|
||||
// https://golang.org/pkg/encoding/json/#MarshalIndent for details.
|
||||
func (s *Sling) BodyJSON(bodyJSON interface{}) *Sling {
|
||||
if bodyJSON == nil {
|
||||
return s
|
||||
}
|
||||
return s.BodyProvider(jsonBodyProvider{payload: bodyJSON})
|
||||
}
|
||||
|
||||
// BodyForm sets the Sling's bodyForm. The value pointed to by the bodyForm
|
||||
// will be url encoded as the Body on new requests (see Request()).
|
||||
// The bodyForm argument should be a pointer to a url tagged struct. See
|
||||
// https://godoc.org/github.com/google/go-querystring/query for details.
|
||||
func (s *Sling) BodyForm(bodyForm interface{}) *Sling {
|
||||
if bodyForm == nil {
|
||||
return s
|
||||
}
|
||||
return s.BodyProvider(formBodyProvider{payload: bodyForm})
|
||||
}
|
||||
|
||||
// Requests
|
||||
|
||||
// Request returns a new http.Request created with the Sling properties.
|
||||
// Returns any errors parsing the rawURL, encoding query structs, encoding
|
||||
// the body, or creating the http.Request.
|
||||
func (s *Sling) Request() (*http.Request, error) {
|
||||
reqURL, err := url.Parse(s.rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = addQueryStructs(reqURL, s.queryStructs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
if s.bodyProvider != nil {
|
||||
body, err = s.bodyProvider.Body()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
req, err := http.NewRequest(s.method, reqURL.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addHeaders(req, s.header)
|
||||
return req, err
|
||||
}
|
||||
|
||||
// addQueryStructs parses url tagged query structs using go-querystring to
|
||||
// encode them to url.Values and format them onto the url.RawQuery. Any
|
||||
// query parsing or encoding errors are returned.
|
||||
func addQueryStructs(reqURL *url.URL, queryStructs []interface{}) error {
|
||||
urlValues, err := url.ParseQuery(reqURL.RawQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// encodes query structs into a url.Values map and merges maps
|
||||
for _, queryStruct := range queryStructs {
|
||||
queryValues, err := goquery.Values(queryStruct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, values := range queryValues {
|
||||
for _, value := range values {
|
||||
urlValues.Add(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
// url.Values format to a sorted "url encoded" string, e.g. "key=val&foo=bar"
|
||||
reqURL.RawQuery = urlValues.Encode()
|
||||
return nil
|
||||
}
|
||||
|
||||
// addHeaders adds the key, value pairs from the given http.Header to the
|
||||
// request. Values for existing keys are appended to the keys values.
|
||||
func addHeaders(req *http.Request, header http.Header) {
|
||||
for key, values := range header {
|
||||
for _, value := range values {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sending
|
||||
|
||||
// ResponseDecoder sets the Sling's response decoder.
|
||||
func (s *Sling) ResponseDecoder(decoder ResponseDecoder) *Sling {
|
||||
if decoder == nil {
|
||||
return s
|
||||
}
|
||||
s.responseDecoder = decoder
|
||||
return s
|
||||
}
|
||||
|
||||
// ReceiveSuccess creates a new HTTP request and returns the response. Success
|
||||
// responses (2XX) are JSON decoded into the value pointed to by successV.
|
||||
// Any error creating the request, sending it, or decoding a 2XX response
|
||||
// is returned.
|
||||
func (s *Sling) ReceiveSuccess(successV interface{}) (*http.Response, error) {
|
||||
return s.Receive(successV, nil)
|
||||
}
|
||||
|
||||
// Receive creates a new HTTP request and returns the response. Success
|
||||
// responses (2XX) are JSON decoded into the value pointed to by successV and
|
||||
// other responses are JSON decoded into the value pointed to by failureV.
|
||||
// If the status code of response is 204(no content) or the Content-Lenght is 0,
|
||||
// decoding is skipped. Any error creating the request, sending it, or decoding
|
||||
// the response is returned.
|
||||
// Receive is shorthand for calling Request and Do.
|
||||
func (s *Sling) Receive(successV, failureV interface{}) (*http.Response, error) {
|
||||
req, err := s.Request()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.Do(req, successV, failureV)
|
||||
}
|
||||
|
||||
// Do sends an HTTP request and returns the response. Success responses (2XX)
|
||||
// are JSON decoded into the value pointed to by successV and other responses
|
||||
// are JSON decoded into the value pointed to by failureV.
|
||||
// If the status code of response is 204(no content) or the Content-Length is 0,
|
||||
// decoding is skipped. Any error sending the request or decoding the response
|
||||
// is returned.
|
||||
func (s *Sling) Do(req *http.Request, successV, failureV interface{}) (*http.Response, error) {
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
// when err is nil, resp contains a non-nil resp.Body which must be closed
|
||||
defer resp.Body.Close()
|
||||
|
||||
// The default HTTP client's Transport may not
|
||||
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
|
||||
// not read to completion and closed.
|
||||
// See: https://golang.org/pkg/net/http/#Response
|
||||
defer io.Copy(ioutil.Discard, resp.Body)
|
||||
|
||||
// Don't try to decode on 204s or Content-Length is 0
|
||||
if resp.StatusCode == http.StatusNoContent || resp.ContentLength == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Decode from json
|
||||
if successV != nil || failureV != nil {
|
||||
err = decodeResponse(resp, s.responseDecoder, successV, failureV)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// decodeResponse decodes response Body into the value pointed to by successV
|
||||
// if the response is a success (2XX) or into the value pointed to by failureV
|
||||
// otherwise. If the successV or failureV argument to decode into is nil,
|
||||
// decoding is skipped.
|
||||
// Caller is responsible for closing the resp.Body.
|
||||
func decodeResponse(resp *http.Response, decoder ResponseDecoder, successV, failureV interface{}) error {
|
||||
if code := resp.StatusCode; 200 <= code && code <= 299 {
|
||||
if successV != nil {
|
||||
return decoder.Decode(resp, successV)
|
||||
}
|
||||
} else {
|
||||
if failureV != nil {
|
||||
return decoder.Decode(resp, failureV)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -1,6 +1,9 @@
|
||||
# github.com/davecgh/go-spew v1.1.1
|
||||
## explicit
|
||||
github.com/davecgh/go-spew/spew
|
||||
# github.com/dghubble/sling v1.4.1
|
||||
## explicit; go 1.18
|
||||
github.com/dghubble/sling
|
||||
# github.com/gin-contrib/sse v0.1.0
|
||||
## explicit; go 1.12
|
||||
github.com/gin-contrib/sse
|
||||
|
||||
Reference in New Issue
Block a user