feat(server): return request error message in json

This commit is contained in:
MuXiu1997
2023-01-26 14:12:59 +08:00
parent 61383468b2
commit 4c1eac941d
15 changed files with 1144 additions and 9 deletions

1
go.mod
View File

@@ -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
View File

@@ -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=

View File

@@ -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()
}

View File

@@ -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"`
}
}

View File

@@ -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()

View File

@@ -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
View 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
View 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
View 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
View File

@@ -0,0 +1,294 @@
# Sling [![GoDoc](https://pkg.go.dev/badge/github.com/dghubble/sling.svg)](https://pkg.go.dev/github.com/dghubble/sling) [![Workflow](https://github.com/dghubble/sling/actions/workflows/test.yaml/badge.svg)](https://github.com/dghubble/sling/actions/workflows/test.yaml?query=branch%3Amain) [![Coverage](https://gocover.io/_badge/github.com/dghubble/sling)](https://gocover.io/github.com/dghubble/sling) [![Sponsors](https://img.shields.io/github/sponsors/dghubble?logo=github)](https://github.com/sponsors/dghubble) [![Twitter](https://img.shields.io/badge/twitter-follow-1da1f2?logo=twitter)](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
View 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
View 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
View 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
View 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
View File

@@ -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