Compare commits

...

2 Commits

Author SHA1 Message Date
AJ ONeal 155c006740 update deps 2020-05-13 06:12:33 +00:00
AJ ONeal aab56909cb add mailgun test 2020-05-13 06:11:06 +00:00
85 changed files with 18777 additions and 0 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
.env
/public-jwks
/go-mockid

65
cmd/mailer/mailer.go Normal file
View File

@ -0,0 +1,65 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"time"
mailgun "github.com/mailgun/mailgun-go/v3"
_ "github.com/joho/godotenv/autoload"
)
func main() {
/*
MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MAILGUN_DOMAIN=mail.example.com
MAILGUN_FROM="Rob the Robot <rob.the.robot@mail.example.com>"
*/
to := flag.String("to", "", "message recipient in the format of 'John Doe <john@example.com>'")
replyTo := flag.String("reply-to", "", "reply-to in the format of 'John Doe <john@example.com>'")
subject := flag.String("subject", "Test Subject", "the utf8-encoded subject of the email")
text := flag.String(
"text",
"Testing some Mailgun awesomeness!",
"the body of the email as utf8-encoded plain-text format",
)
flag.Parse()
if 0 == len(*to) {
flag.Usage()
os.Exit(1)
}
domain := os.Getenv("MAILGUN_DOMAIN")
apiKey := os.Getenv("MAILGUN_API_KEY")
from := os.Getenv("MAILGUN_FROM")
if 0 == len(*text) {
*text = "Testing some Mailgun awesomeness!"
}
msgId, err := SendSimpleMessage(domain, apiKey, *to, from, *subject, *text, *replyTo)
if nil != err {
panic(err)
}
fmt.Printf("Queued with Message ID %q\n", msgId)
}
func SendSimpleMessage(domain, apiKey, to, from, subject, text, replyTo string) (string, error) {
mg := mailgun.NewMailgun(domain, apiKey)
m := mg.NewMessage(from, subject, text, to)
if 0 != len(replyTo) {
// mailgun's required "h:" prefix is added by the library
m.AddHeader("Reply-To", replyTo)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
_, id, err := mg.Send(ctx, m)
return id, err
}

1
go.mod
View File

@ -5,4 +5,5 @@ go 1.13
require (
git.rootprojects.org/root/keypairs v0.5.2
github.com/joho/godotenv v1.3.0
github.com/mailgun/mailgun-go/v3 v3.6.4
)

16
go.sum
View File

@ -1,4 +1,20 @@
git.rootprojects.org/root/keypairs v0.5.2 h1:jr+drUUm/REaCDJTl5gT3kF2PwlXygcLsBZlqoKTZZw=
git.rootprojects.org/root/keypairs v0.5.2/go.mod h1:WGI8PadOp+4LjUuI+wNlSwcJwFtY8L9XuNjuO3213HA=
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/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuYrgaRcnW4=
github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/mailgun/mailgun-go/v3 v3.6.4 h1:+cvbZRgLSHivbz/w1iWLmxVl6Bqf4geD2D7QMj4+8PE=
github.com/mailgun/mailgun-go/v3 v3.6.4/go.mod h1:ZjVnH8S0dR2BLjvkZc/rxwerdcirzlA12LQDuGAadR0=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

3
vendor/github.com/go-chi/chi/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
.idea
*.sw?
.vscode

17
vendor/github.com/go-chi/chi/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,17 @@
language: go
go:
- 1.10.x
- 1.11.x
script:
- go get -d -t ./...
- go vet ./...
- go test ./...
- >
go_version=$(go version);
if [ ${go_version:13:4} = "1.11" ]; then
go get -u golang.org/x/tools/cmd/goimports;
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :;
fi

139
vendor/github.com/go-chi/chi/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,139 @@
# Changelog
## v4.0.0 (2019-01-10)
- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8
- router: respond with 404 on router with no routes (#362)
- router: additional check to ensure wildcard is at the end of a url pattern (#333)
- middleware: deprecate use of http.CloseNotifier (#347)
- middleware: fix RedirectSlashes to include query params on redirect (#334)
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0
## v3.3.4 (2019-01-07)
- Minor middleware improvements. No changes to core library/router. Moving v3 into its
- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4
## v3.3.3 (2018-08-27)
- Minor release
- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3
## v3.3.2 (2017-12-22)
- Support to route trailing slashes on mounted sub-routers (#281)
- middleware: new `ContentCharset` to check matching charsets. Thank you
@csucu for your community contribution!
## v3.3.1 (2017-11-20)
- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types
- middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value
- Minor bug fixes
## v3.3.0 (2017-10-10)
- New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage
- Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function
## v3.2.1 (2017-08-31)
- Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface
and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path
- Add new `RouteMethod` to `*Context`
- Add new `Routes` pointer to `*Context`
- Add new `middleware.GetHead` to route missing HEAD requests to GET handler
- Updated benchmarks (see README)
## v3.1.5 (2017-08-02)
- Setup golint and go vet for the project
- As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler`
to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler`
## v3.1.0 (2017-07-10)
- Fix a few minor issues after v3 release
- Move `docgen` sub-pkg to https://github.com/go-chi/docgen
- Move `render` sub-pkg to https://github.com/go-chi/render
- Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime
suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in
https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage.
## v3.0.0 (2017-06-21)
- Major update to chi library with many exciting updates, but also some *breaking changes*
- URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as
`/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the
same router
- Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example:
`r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")`
- Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as
`r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like
in `_examples/custom-handler`
- Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their
own using file handler with the stdlib, see `_examples/fileserver` for an example
- Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()`
- Moved the chi project to its own organization, to allow chi-related community packages to
be easily discovered and supported, at: https://github.com/go-chi
- *NOTE:* please update your import paths to `"github.com/go-chi/chi"`
- *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2
## v2.1.0 (2017-03-30)
- Minor improvements and update to the chi core library
- Introduced a brand new `chi/render` sub-package to complete the story of building
APIs to offer a pattern for managing well-defined request / response payloads. Please
check out the updated `_examples/rest` example for how it works.
- Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface
## v2.0.0 (2017-01-06)
- After many months of v2 being in an RC state with many companies and users running it in
production, the inclusion of some improvements to the middlewares, we are very pleased to
announce v2.0.0 of chi.
## v2.0.0-rc1 (2016-07-26)
- Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular
community `"net/context"` package has been included in the standard library as `"context"` and
utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other
request-scoped values. We're very excited about the new context addition and are proud to
introduce chi v2, a minimal and powerful routing package for building large HTTP services,
with zero external dependencies. Chi focuses on idiomatic design and encourages the use of
stdlib HTTP handlers and middlwares.
- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc`
- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()`
- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`,
which provides direct access to URL routing parameters, the routing path and the matching
routing patterns.
- Users upgrading from chi v1 to v2, need to:
1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to
the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)`
2. Use `chi.URLParam(r *http.Request, paramKey string) string`
or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value
## v1.0.0 (2016-07-01)
- Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older.
## v0.9.0 (2016-03-31)
- Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33)
- BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters
has changed to: `chi.URLParam(ctx, "id")`

31
vendor/github.com/go-chi/chi/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,31 @@
# Contributing
## Prerequisites
1. [Install Go][go-install].
2. Download the sources and switch the working directory:
```bash
go get -u -d github.com/go-chi/chi
cd $GOPATH/src/github.com/go-chi/chi
```
## Submitting a Pull Request
A typical workflow is:
1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip]
2. [Create a topic branch.][branch]
3. Add tests for your change.
4. Run `go test`. If your tests pass, return to the step 3.
5. Implement the change and ensure the steps from the previous step pass.
6. Run `goimports -w .`, to ensure the new code conforms to Go formatting guideline.
7. [Add, commit and push your changes.][git-help]
8. [Submit a pull request.][pull-req]
[go-install]: https://golang.org/doc/install
[go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html
[fork]: https://help.github.com/articles/fork-a-repo
[branch]: http://learn.github.com/p/branching.html
[git-help]: https://guides.github.com
[pull-req]: https://help.github.com/articles/using-pull-requests

20
vendor/github.com/go-chi/chi/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

438
vendor/github.com/go-chi/chi/README.md generated vendored Normal file
View File

@ -0,0 +1,438 @@
# <img alt="chi" src="https://cdn.rawgit.com/go-chi/chi/master/_examples/chi.svg" width="220" />
[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis]
`chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's
especially good at helping you write large REST API services that are kept maintainable as your
project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to
handle signaling, cancelation and request-scoped values across a handler chain.
The focus of the project has been to seek out an elegant and comfortable design for writing
REST API servers, written during the development of the Pressly API service that powers our
public API service, which in turn powers all of our client-side applications.
The key considerations of chi's design are: project structure, maintainability, standard http
handlers (stdlib-only), developer productivity, and deconstructing a large system into many small
parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also
included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!
## Install
`go get -u github.com/go-chi/chi`
## Features
* **Lightweight** - cloc'd in ~1000 LOC for the chi router
* **Fast** - yes, see [benchmarks](#benchmarks)
* **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http`
* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting
* **Context control** - built on new `context` package, providing value chaining, cancelations and timeouts
* **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91))
* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown
* **No external dependencies** - plain ol' Go stdlib + net/http
## Examples
See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples.
**As easy as:**
```go
package main
import (
"net/http"
"github.com/go-chi/chi"
)
func main() {
r := chi.NewRouter()
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
http.ListenAndServe(":3000", r)
}
```
**REST Preview:**
Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs
in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in
Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)).
I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed
above, they will show you all the features of chi and serve as a good form of documentation.
```go
import (
//...
"context"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
func main() {
r := chi.NewRouter()
// A good base middleware stack
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Set a timeout value on the request context (ctx), that will signal
// through ctx.Done() that the request has timed out and further
// processing should be stopped.
r.Use(middleware.Timeout(60 * time.Second))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hi"))
})
// RESTy routes for "articles" resource
r.Route("/articles", func(r chi.Router) {
r.With(paginate).Get("/", listArticles) // GET /articles
r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017
r.Post("/", createArticle) // POST /articles
r.Get("/search", searchArticles) // GET /articles/search
// Regexp url parameters:
r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto
// Subrouters:
r.Route("/{articleID}", func(r chi.Router) {
r.Use(ArticleCtx)
r.Get("/", getArticle) // GET /articles/123
r.Put("/", updateArticle) // PUT /articles/123
r.Delete("/", deleteArticle) // DELETE /articles/123
})
})
// Mount the admin sub-router
r.Mount("/admin", adminRouter())
http.ListenAndServe(":3333", r)
}
func ArticleCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
articleID := chi.URLParam(r, "articleID")
article, err := dbGetArticle(articleID)
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
ctx := context.WithValue(r.Context(), "article", article)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getArticle(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
article, ok := ctx.Value("article").(*Article)
if !ok {
http.Error(w, http.StatusText(422), 422)
return
}
w.Write([]byte(fmt.Sprintf("title:%s", article.Title)))
}
// A completely separate router for administrator routes
func adminRouter() http.Handler {
r := chi.NewRouter()
r.Use(AdminOnly)
r.Get("/", adminIndex)
r.Get("/accounts", adminListAccounts)
return r
}
func AdminOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
perm, ok := ctx.Value("acl.permission").(YourPermissionType)
if !ok || !perm.IsAdmin() {
http.Error(w, http.StatusText(403), 403)
return
}
next.ServeHTTP(w, r)
})
}
```
## Router design
chi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree).
The router is fully compatible with `net/http`.
Built on top of the tree is the `Router` interface:
```go
// Router consisting of the core routing methods used by chi's Mux,
// using only the standard net/http.
type Router interface {
http.Handler
Routes
// Use appends one of more middlewares onto the Router stack.
Use(middlewares ...func(http.Handler) http.Handler)
// With adds inline middlewares for an endpoint handler.
With(middlewares ...func(http.Handler) http.Handler) Router
// Group adds a new inline-Router along the current routing
// path, with a fresh middleware stack for the inline-Router.
Group(fn func(r Router)) Router
// Route mounts a sub-Router along a `pattern`` string.
Route(pattern string, fn func(r Router)) Router
// Mount attaches another http.Handler along ./pattern/*
Mount(pattern string, h http.Handler)
// Handle and HandleFunc adds routes for `pattern` that matches
// all HTTP methods.
Handle(pattern string, h http.Handler)
HandleFunc(pattern string, h http.HandlerFunc)
// Method and MethodFunc adds routes for `pattern` that matches
// the `method` HTTP method.
Method(method, pattern string, h http.Handler)
MethodFunc(method, pattern string, h http.HandlerFunc)
// HTTP-method routing along `pattern`
Connect(pattern string, h http.HandlerFunc)
Delete(pattern string, h http.HandlerFunc)
Get(pattern string, h http.HandlerFunc)
Head(pattern string, h http.HandlerFunc)
Options(pattern string, h http.HandlerFunc)
Patch(pattern string, h http.HandlerFunc)
Post(pattern string, h http.HandlerFunc)
Put(pattern string, h http.HandlerFunc)
Trace(pattern string, h http.HandlerFunc)
// NotFound defines a handler to respond whenever a route could
// not be found.
NotFound(h http.HandlerFunc)
// MethodNotAllowed defines a handler to respond whenever a method is
// not allowed.
MethodNotAllowed(h http.HandlerFunc)
}
// Routes interface adds two methods for router traversal, which is also
// used by the github.com/go-chi/docgen package to generate documentation for Routers.
type Routes interface {
// Routes returns the routing tree in an easily traversable structure.
Routes() []Route
// Middlewares returns the list of middlewares in use by the router.
Middlewares() Middlewares
// Match searches the routing tree for a handler that matches
// the method/path - similar to routing a http request, but without
// executing the handler thereafter.
Match(rctx *Context, method, path string) bool
}
```
Each routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern
supports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters
can be fetched at runtime by calling `chi.URLParam(r, "userID")` for named parameters
and `chi.URLParam(r, "*")` for a wildcard parameter.
### Middleware handlers
chi's middlewares are just stdlib net/http middleware handlers. There is nothing special
about them, which means the router and all the tooling is designed to be compatible and
friendly with any middleware in the community. This offers much better extensibility and reuse
of packages and is at the heart of chi's purpose.
Here is an example of a standard net/http middleware handler using the new request context
available in Go. This middleware sets a hypothetical user identifier on the request
context and calls the next handler in the chain.
```go
// HTTP middleware setting a value on the request context
func MyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "user", "123")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
```
### Request handlers
chi uses standard net/http request handlers. This little snippet is an example of a http.Handler
func that reads a user identifier from the request context - hypothetically, identifying
the user sending an authenticated request, validated+set by a previous middleware handler.
```go
// HTTP handler accessing data from the request context.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user").(string)
w.Write([]byte(fmt.Sprintf("hi %s", user)))
}
```
### URL parameters
chi's router parses and stores URL parameters right onto the request context. Here is
an example of how to access URL params in your net/http handlers. And of course, middlewares
are able to access the same information.
```go
// HTTP handler accessing the url routing parameters.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID") // from a route like /users/{userID}
ctx := r.Context()
key := ctx.Value("key").(string)
w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key)))
}
```
## Middlewares
chi comes equipped with an optional `middleware` package, providing a suite of standard
`net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible
with `net/http` can be used with chi's mux.
### Core middlewares
-----------------------------------------------------------------------------------------------------------
| chi/middleware Handler | description |
|:----------------------|:---------------------------------------------------------------------------------
| AllowContentType | Explicit whitelist of accepted request Content-Types |
| Compress | Gzip compression for clients that accept compressed responses |
| GetHead | Automatically route undefined HEAD requests to GET handlers |
| Heartbeat | Monitoring endpoint to check the servers pulse |
| Logger | Logs the start and end of each request with the elapsed processing time |
| NoCache | Sets response headers to prevent clients from caching |
| Profiler | Easily attach net/http/pprof to your routers |
| RealIP | Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP |
| Recoverer | Gracefully absorb panics and prints the stack trace |
| RequestID | Injects a request ID into the context of each request |
| RedirectSlashes | Redirect slashes on routing paths |
| SetHeader | Short-hand middleware to set a response header key/value |
| StripSlashes | Strip slashes on routing paths |
| Throttle | Puts a ceiling on the number of concurrent requests |
| Timeout | Signals to the request context when the timeout deadline is reached |
| URLFormat | Parse extension from url and put it on request context |
| WithValue | Short-hand middleware to set a key/value on the request context |
-----------------------------------------------------------------------------------------------------------
### Auxiliary middlewares & packages
Please see https://github.com/go-chi for additional packages.
--------------------------------------------------------------------------------------------------------------------
| package | description |
|:---------------------------------------------------|:-------------------------------------------------------------
| [cors](https://github.com/go-chi/cors) | Cross-origin resource sharing (CORS) |
| [docgen](https://github.com/go-chi/docgen) | Print chi.Router routes at runtime |
| [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication |
| [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing |
| [httpcoala](https://github.com/go-chi/httpcoala) | HTTP request coalescer |
| [chi-authz](https://github.com/casbin/chi-authz) | Request ACL via https://github.com/hsluoyz/casbin |
| [phi](https://github.com/fate-lovely/phi) | Port chi to [fasthttp](https://github.com/valyala/fasthttp) |
--------------------------------------------------------------------------------------------------------------------
please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware
## context?
`context` is a tiny pkg that provides simple interface to signal context across call stacks
and goroutines. It was originally written by [Sameer Ajmani](https://github.com/Sajmani)
and is available in stdlib since go1.7.
Learn more at https://blog.golang.org/context
and..
* Docs: https://golang.org/pkg/context
* Source: https://github.com/golang/go/tree/master/src/context
## Benchmarks
The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark
Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop
```shell
BenchmarkChi_Param 3000000 475 ns/op 432 B/op 3 allocs/op
BenchmarkChi_Param5 2000000 696 ns/op 432 B/op 3 allocs/op
BenchmarkChi_Param20 1000000 1275 ns/op 432 B/op 3 allocs/op
BenchmarkChi_ParamWrite 3000000 505 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GithubStatic 3000000 508 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GithubParam 2000000 669 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GithubAll 10000 134627 ns/op 87699 B/op 609 allocs/op
BenchmarkChi_GPlusStatic 3000000 402 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GPlusParam 3000000 500 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GPlus2Params 3000000 586 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GPlusAll 200000 7237 ns/op 5616 B/op 39 allocs/op
BenchmarkChi_ParseStatic 3000000 408 ns/op 432 B/op 3 allocs/op
BenchmarkChi_ParseParam 3000000 488 ns/op 432 B/op 3 allocs/op
BenchmarkChi_Parse2Params 3000000 551 ns/op 432 B/op 3 allocs/op
BenchmarkChi_ParseAll 100000 13508 ns/op 11232 B/op 78 allocs/op
BenchmarkChi_StaticAll 20000 81933 ns/op 67826 B/op 471 allocs/op
```
Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc
NOTE: the allocs in the benchmark above are from the calls to http.Request's
`WithContext(context.Context)` method that clones the http.Request, sets the `Context()`
on the duplicated (alloc'd) request and returns it the new request object. This is just
how setting context on a request in Go works.
## Credits
* Carl Jackson for https://github.com/zenazn/goji
* Parts of chi's thinking comes from goji, and chi's middleware package
sources from goji.
* Armon Dadgar for https://github.com/armon/go-radix
* Contributions: [@VojtechVitek](https://github.com/VojtechVitek)
We'll be more than happy to see [your contributions](./CONTRIBUTING.md)!
## Beyond REST
chi is just a http router that lets you decompose request handling into many smaller layers.
Many companies including Pressly.com (of course) use chi to write REST services for their public
APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces
required to write a complete client-server system or network of microservices.
Looking ahead beyond REST, I also recommend some newer works in the field coming from
[gRPC](https://github.com/grpc/grpc-go), [NATS](https://nats.io), [go-kit](https://github.com/go-kit/kit)
and even [graphql](https://github.com/graphql-go/graphql). They're all pretty cool with their
own unique approaches and benefits. Specifically, I'd look at gRPC since it makes client-server
communication feel like a single program on a single computer, no need to hand-write a client library
and the request/response payloads are typed contracts. NATS is pretty amazing too as a super
fast and lightweight pub-sub transport that can speak protobufs, with nice service discovery -
an excellent combination with gRPC.
## License
Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka)
Licensed under [MIT License](./LICENSE)
[GoDoc]: https://godoc.org/github.com/go-chi/chi
[GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg
[Travis]: https://travis-ci.org/go-chi/chi
[Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master

49
vendor/github.com/go-chi/chi/chain.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package chi
import "net/http"
// Chain returns a Middlewares type from a slice of middleware handlers.
func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares {
return Middlewares(middlewares)
}
// Handler builds and returns a http.Handler from the chain of middlewares,
// with `h http.Handler` as the final handler.
func (mws Middlewares) Handler(h http.Handler) http.Handler {
return &ChainHandler{mws, h, chain(mws, h)}
}
// HandlerFunc builds and returns a http.Handler from the chain of middlewares,
// with `h http.Handler` as the final handler.
func (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler {
return &ChainHandler{mws, h, chain(mws, h)}
}
// ChainHandler is a http.Handler with support for handler composition and
// execution.
type ChainHandler struct {
Middlewares Middlewares
Endpoint http.Handler
chain http.Handler
}
func (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.chain.ServeHTTP(w, r)
}
// chain builds a http.Handler composed of an inline middleware stack and endpoint
// handler in the order they are passed.
func chain(middlewares []func(http.Handler) http.Handler, endpoint http.Handler) http.Handler {
// Return ahead of time if there aren't any middlewares for the chain
if len(middlewares) == 0 {
return endpoint
}
// Wrap the end handler with the middleware chain
h := middlewares[len(middlewares)-1](endpoint)
for i := len(middlewares) - 2; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}

134
vendor/github.com/go-chi/chi/chi.go generated vendored Normal file
View File

@ -0,0 +1,134 @@
//
// Package chi is a small, idiomatic and composable router for building HTTP services.
//
// chi requires Go 1.7 or newer.
//
// Example:
// package main
//
// import (
// "net/http"
//
// "github.com/go-chi/chi"
// "github.com/go-chi/chi/middleware"
// )
//
// func main() {
// r := chi.NewRouter()
// r.Use(middleware.Logger)
// r.Use(middleware.Recoverer)
//
// r.Get("/", func(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte("root."))
// })
//
// http.ListenAndServe(":3333", r)
// }
//
// See github.com/go-chi/chi/_examples/ for more in-depth examples.
//
// URL patterns allow for easy matching of path components in HTTP
// requests. The matching components can then be accessed using
// chi.URLParam(). All patterns must begin with a slash.
//
// A simple named placeholder {name} matches any sequence of characters
// up to the next / or the end of the URL. Trailing slashes on paths must
// be handled explicitly.
//
// A placeholder with a name followed by a colon allows a regular
// expression match, for example {number:\\d+}. The regular expression
// syntax is Go's normal regexp RE2 syntax, except that regular expressions
// including { or } are not supported, and / will never be
// matched. An anonymous regexp pattern is allowed, using an empty string
// before the colon in the placeholder, such as {:\\d+}
//
// The special placeholder of asterisk matches the rest of the requested
// URL. Any trailing characters in the pattern are ignored. This is the only
// placeholder which will match / characters.
//
// Examples:
// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/"
// "/user/{name}/info" matches "/user/jsmith/info"
// "/page/*" matches "/page/intro/latest"
// "/page/*/index" also matches "/page/intro/latest"
// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01"
//
package chi
import "net/http"
// NewRouter returns a new Mux object that implements the Router interface.
func NewRouter() *Mux {
return NewMux()
}
// Router consisting of the core routing methods used by chi's Mux,
// using only the standard net/http.
type Router interface {
http.Handler
Routes
// Use appends one of more middlewares onto the Router stack.
Use(middlewares ...func(http.Handler) http.Handler)
// With adds inline middlewares for an endpoint handler.
With(middlewares ...func(http.Handler) http.Handler) Router
// Group adds a new inline-Router along the current routing
// path, with a fresh middleware stack for the inline-Router.
Group(fn func(r Router)) Router
// Route mounts a sub-Router along a `pattern`` string.
Route(pattern string, fn func(r Router)) Router
// Mount attaches another http.Handler along ./pattern/*
Mount(pattern string, h http.Handler)
// Handle and HandleFunc adds routes for `pattern` that matches
// all HTTP methods.
Handle(pattern string, h http.Handler)
HandleFunc(pattern string, h http.HandlerFunc)
// Method and MethodFunc adds routes for `pattern` that matches
// the `method` HTTP method.
Method(method, pattern string, h http.Handler)
MethodFunc(method, pattern string, h http.HandlerFunc)
// HTTP-method routing along `pattern`
Connect(pattern string, h http.HandlerFunc)
Delete(pattern string, h http.HandlerFunc)
Get(pattern string, h http.HandlerFunc)
Head(pattern string, h http.HandlerFunc)
Options(pattern string, h http.HandlerFunc)
Patch(pattern string, h http.HandlerFunc)
Post(pattern string, h http.HandlerFunc)
Put(pattern string, h http.HandlerFunc)
Trace(pattern string, h http.HandlerFunc)
// NotFound defines a handler to respond whenever a route could
// not be found.
NotFound(h http.HandlerFunc)
// MethodNotAllowed defines a handler to respond whenever a method is
// not allowed.
MethodNotAllowed(h http.HandlerFunc)
}
// Routes interface adds two methods for router traversal, which is also
// used by the `docgen` subpackage to generation documentation for Routers.
type Routes interface {
// Routes returns the routing tree in an easily traversable structure.
Routes() []Route
// Middlewares returns the list of middlewares in use by the router.
Middlewares() Middlewares
// Match searches the routing tree for a handler that matches
// the method/path - similar to routing a http request, but without
// executing the handler thereafter.
Match(rctx *Context, method, path string) bool
}
// Middlewares type is a slice of standard middleware handlers with methods
// to compose middleware chains and http.Handler's.
type Middlewares []func(http.Handler) http.Handler

161
vendor/github.com/go-chi/chi/context.go generated vendored Normal file
View File

@ -0,0 +1,161 @@
package chi
import (
"context"
"net"
"net/http"
"strings"
)
var (
// RouteCtxKey is the context.Context key to store the request context.
RouteCtxKey = &contextKey{"RouteContext"}
)
// Context is the default routing context set on the root node of a
// request context to track route patterns, URL parameters and
// an optional routing path.
type Context struct {
Routes Routes
// Routing path/method override used during the route search.
// See Mux#routeHTTP method.
RoutePath string
RouteMethod string
// Routing pattern stack throughout the lifecycle of the request,
// across all connected routers. It is a record of all matching
// patterns across a stack of sub-routers.
RoutePatterns []string
// URLParams are the stack of routeParams captured during the
// routing lifecycle across a stack of sub-routers.
URLParams RouteParams
// The endpoint routing pattern that matched the request URI path
// or `RoutePath` of the current sub-router. This value will update
// during the lifecycle of a request passing through a stack of
// sub-routers.
routePattern string
// Route parameters matched for the current sub-router. It is
// intentionally unexported so it cant be tampered.
routeParams RouteParams
// methodNotAllowed hint
methodNotAllowed bool
}
// NewRouteContext returns a new routing Context object.
func NewRouteContext() *Context {
return &Context{}
}
// Reset a routing context to its initial state.
func (x *Context) Reset() {
x.Routes = nil
x.RoutePath = ""
x.RouteMethod = ""
x.RoutePatterns = x.RoutePatterns[:0]
x.URLParams.Keys = x.URLParams.Keys[:0]
x.URLParams.Values = x.URLParams.Values[:0]
x.routePattern = ""
x.routeParams.Keys = x.routeParams.Keys[:0]
x.routeParams.Values = x.routeParams.Values[:0]
x.methodNotAllowed = false
}
// URLParam returns the corresponding URL parameter value from the request
// routing context.
func (x *Context) URLParam(key string) string {
for k := len(x.URLParams.Keys) - 1; k >= 0; k-- {
if x.URLParams.Keys[k] == key {
return x.URLParams.Values[k]
}
}
return ""
}
// RoutePattern builds the routing pattern string for the particular
// request, at the particular point during routing. This means, the value
// will change throughout the execution of a request in a router. That is
// why its advised to only use this value after calling the next handler.
//
// For example,
//
// func Instrument(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// next.ServeHTTP(w, r)
// routePattern := chi.RouteContext(r.Context()).RoutePattern()
// measure(w, r, routePattern)
// })
// }
func (x *Context) RoutePattern() string {
routePattern := strings.Join(x.RoutePatterns, "")
return strings.Replace(routePattern, "/*/", "/", -1)
}
// RouteContext returns chi's routing Context object from a
// http.Request Context.
func RouteContext(ctx context.Context) *Context {
return ctx.Value(RouteCtxKey).(*Context)
}
// URLParam returns the url parameter from a http.Request object.
func URLParam(r *http.Request, key string) string {
if rctx := RouteContext(r.Context()); rctx != nil {
return rctx.URLParam(key)
}
return ""
}
// URLParamFromCtx returns the url parameter from a http.Request Context.
func URLParamFromCtx(ctx context.Context, key string) string {
if rctx := RouteContext(ctx); rctx != nil {
return rctx.URLParam(key)
}
return ""
}
// RouteParams is a structure to track URL routing parameters efficiently.
type RouteParams struct {
Keys, Values []string
}
// Add will append a URL parameter to the end of the route param
func (s *RouteParams) Add(key, value string) {
(*s).Keys = append((*s).Keys, key)
(*s).Values = append((*s).Values, value)
}
// ServerBaseContext wraps an http.Handler to set the request context to the
// `baseCtx`.
func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler {
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
baseCtx := baseCtx
// Copy over default net/http server context keys
if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok {
baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v)
}
if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok {
baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v)
}
h.ServeHTTP(w, r.WithContext(baseCtx))
})
return fn
}
// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation. This technique
// for defining context keys was copied from Go 1.7's new use of context in net/http.
type contextKey struct {
name string
}
func (k *contextKey) String() string {
return "chi context value " + k.name
}

460
vendor/github.com/go-chi/chi/mux.go generated vendored Normal file
View File

@ -0,0 +1,460 @@
package chi
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
)
var _ Router = &Mux{}
// Mux is a simple HTTP route multiplexer that parses a request path,
// records any URL params, and executes an end handler. It implements
// the http.Handler interface and is friendly with the standard library.
//
// Mux is designed to be fast, minimal and offer a powerful API for building
// modular and composable HTTP services with a large set of handlers. It's
// particularly useful for writing large REST API services that break a handler
// into many smaller parts composed of middlewares and end handlers.
type Mux struct {
// The radix trie router
tree *node
// The middleware stack
middlewares []func(http.Handler) http.Handler
// Controls the behaviour of middleware chain generation when a mux
// is registered as an inline group inside another mux.
inline bool
parent *Mux
// The computed mux handler made of the chained middleware stack and
// the tree router
handler http.Handler
// Routing context pool
pool *sync.Pool
// Custom route not found handler
notFoundHandler http.HandlerFunc
// Custom method not allowed handler
methodNotAllowedHandler http.HandlerFunc
}
// NewMux returns a newly initialized Mux object that implements the Router
// interface.
func NewMux() *Mux {
mux := &Mux{tree: &node{}, pool: &sync.Pool{}}
mux.pool.New = func() interface{} {
return NewRouteContext()
}
return mux
}
// ServeHTTP is the single method of the http.Handler interface that makes
// Mux interoperable with the standard library. It uses a sync.Pool to get and
// reuse routing contexts for each request.
func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Ensure the mux has some routes defined on the mux
if mx.handler == nil {
mx.NotFoundHandler().ServeHTTP(w, r)
return
}
// Check if a routing context already exists from a parent router.
rctx, _ := r.Context().Value(RouteCtxKey).(*Context)
if rctx != nil {
mx.handler.ServeHTTP(w, r)
return
}
// Fetch a RouteContext object from the sync pool, and call the computed
// mx.handler that is comprised of mx.middlewares + mx.routeHTTP.
// Once the request is finished, reset the routing context and put it back
// into the pool for reuse from another request.
rctx = mx.pool.Get().(*Context)
rctx.Reset()
rctx.Routes = mx
r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
mx.handler.ServeHTTP(w, r)
mx.pool.Put(rctx)
}
// Use appends a middleware handler to the Mux middleware stack.
//
// The middleware stack for any Mux will execute before searching for a matching
// route to a specific handler, which provides opportunity to respond early,
// change the course of the request execution, or set request-scoped values for
// the next http.Handler.
func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) {
if mx.handler != nil {
panic("chi: all middlewares must be defined before routes on a mux")
}
mx.middlewares = append(mx.middlewares, middlewares...)
}
// Handle adds the route `pattern` that matches any http method to
// execute the `handler` http.Handler.
func (mx *Mux) Handle(pattern string, handler http.Handler) {
mx.handle(mALL, pattern, handler)
}
// HandleFunc adds the route `pattern` that matches any http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mALL, pattern, handlerFn)
}
// Method adds the route `pattern` that matches `method` http method to
// execute the `handler` http.Handler.
func (mx *Mux) Method(method, pattern string, handler http.Handler) {
m, ok := methodMap[strings.ToUpper(method)]
if !ok {
panic(fmt.Sprintf("chi: '%s' http method is not supported.", method))
}
mx.handle(m, pattern, handler)
}
// MethodFunc adds the route `pattern` that matches `method` http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) {
mx.Method(method, pattern, handlerFn)
}
// Connect adds the route `pattern` that matches a CONNECT http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mCONNECT, pattern, handlerFn)
}
// Delete adds the route `pattern` that matches a DELETE http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mDELETE, pattern, handlerFn)
}
// Get adds the route `pattern` that matches a GET http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mGET, pattern, handlerFn)
}
// Head adds the route `pattern` that matches a HEAD http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mHEAD, pattern, handlerFn)
}
// Options adds the route `pattern` that matches a OPTIONS http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mOPTIONS, pattern, handlerFn)
}
// Patch adds the route `pattern` that matches a PATCH http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mPATCH, pattern, handlerFn)
}
// Post adds the route `pattern` that matches a POST http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mPOST, pattern, handlerFn)
}
// Put adds the route `pattern` that matches a PUT http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mPUT, pattern, handlerFn)
}
// Trace adds the route `pattern` that matches a TRACE http method to
// execute the `handlerFn` http.HandlerFunc.
func (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) {
mx.handle(mTRACE, pattern, handlerFn)
}
// NotFound sets a custom http.HandlerFunc for routing paths that could
// not be found. The default 404 handler is `http.NotFound`.
func (mx *Mux) NotFound(handlerFn http.HandlerFunc) {
// Build NotFound handler chain
m := mx
hFn := handlerFn
if mx.inline && mx.parent != nil {
m = mx.parent
hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP
}
// Update the notFoundHandler from this point forward
m.notFoundHandler = hFn
m.updateSubRoutes(func(subMux *Mux) {
if subMux.notFoundHandler == nil {
subMux.NotFound(hFn)
}
})
}
// MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the
// method is unresolved. The default handler returns a 405 with an empty body.
func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) {
// Build MethodNotAllowed handler chain
m := mx
hFn := handlerFn
if mx.inline && mx.parent != nil {
m = mx.parent
hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP
}
// Update the methodNotAllowedHandler from this point forward
m.methodNotAllowedHandler = hFn
m.updateSubRoutes(func(subMux *Mux) {
if subMux.methodNotAllowedHandler == nil {
subMux.MethodNotAllowed(hFn)
}
})
}
// With adds inline middlewares for an endpoint handler.
func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
// Similarly as in handle(), we must build the mux handler once further
// middleware registration isn't allowed for this stack, like now.
if !mx.inline && mx.handler == nil {
mx.buildRouteHandler()
}
// Copy middlewares from parent inline muxs
var mws Middlewares
if mx.inline {
mws = make(Middlewares, len(mx.middlewares))
copy(mws, mx.middlewares)
}
mws = append(mws, middlewares...)
im := &Mux{pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws}
return im
}
// Group creates a new inline-Mux with a fresh middleware stack. It's useful
// for a group of handlers along the same routing path that use an additional
// set of middlewares. See _examples/.
func (mx *Mux) Group(fn func(r Router)) Router {
im := mx.With().(*Mux)
if fn != nil {
fn(im)
}
return im
}
// Route creates a new Mux with a fresh middleware stack and mounts it
// along the `pattern` as a subrouter. Effectively, this is a short-hand
// call to Mount. See _examples/.
func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
subRouter := NewRouter()
if fn != nil {
fn(subRouter)
}
mx.Mount(pattern, subRouter)
return subRouter
}
// Mount attaches another http.Handler or chi Router as a subrouter along a routing
// path. It's very useful to split up a large API as many independent routers and
// compose them as a single service using Mount. See _examples/.
//
// Note that Mount() simply sets a wildcard along the `pattern` that will continue
// routing at the `handler`, which in most cases is another chi.Router. As a result,
// if you define two Mount() routes on the exact same pattern the mount will panic.
func (mx *Mux) Mount(pattern string, handler http.Handler) {
// Provide runtime safety for ensuring a pattern isn't mounted on an existing
// routing pattern.
if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") {
panic(fmt.Sprintf("chi: attempting to Mount() a handler on an existing path, '%s'", pattern))
}
// Assign sub-Router's with the parent not found & method not allowed handler if not specified.
subr, ok := handler.(*Mux)
if ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil {
subr.NotFound(mx.notFoundHandler)
}
if ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil {
subr.MethodNotAllowed(mx.methodNotAllowedHandler)
}
// Wrap the sub-router in a handlerFunc to scope the request path for routing.
mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rctx := RouteContext(r.Context())
rctx.RoutePath = mx.nextRoutePath(rctx)
handler.ServeHTTP(w, r)
})
if pattern == "" || pattern[len(pattern)-1] != '/' {
mx.handle(mALL|mSTUB, pattern, mountHandler)
mx.handle(mALL|mSTUB, pattern+"/", mountHandler)
pattern += "/"
}
method := mALL
subroutes, _ := handler.(Routes)
if subroutes != nil {
method |= mSTUB
}
n := mx.handle(method, pattern+"*", mountHandler)
if subroutes != nil {
n.subroutes = subroutes
}
}
// Routes returns a slice of routing information from the tree,
// useful for traversing available routes of a router.
func (mx *Mux) Routes() []Route {
return mx.tree.routes()
}
// Middlewares returns a slice of middleware handler functions.
func (mx *Mux) Middlewares() Middlewares {
return mx.middlewares
}
// Match searches the routing tree for a handler that matches the method/path.
// It's similar to routing a http request, but without executing the handler
// thereafter.
//
// Note: the *Context state is updated during execution, so manage
// the state carefully or make a NewRouteContext().
func (mx *Mux) Match(rctx *Context, method, path string) bool {
m, ok := methodMap[method]
if !ok {
return false
}
node, _, h := mx.tree.FindRoute(rctx, m, path)
if node != nil && node.subroutes != nil {
rctx.RoutePath = mx.nextRoutePath(rctx)
return node.subroutes.Match(rctx, method, rctx.RoutePath)
}
return h != nil
}
// NotFoundHandler returns the default Mux 404 responder whenever a route
// cannot be found.
func (mx *Mux) NotFoundHandler() http.HandlerFunc {
if mx.notFoundHandler != nil {
return mx.notFoundHandler
}
return http.NotFound
}
// MethodNotAllowedHandler returns the default Mux 405 responder whenever
// a method cannot be resolved for a route.
func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc {
if mx.methodNotAllowedHandler != nil {
return mx.methodNotAllowedHandler
}
return methodNotAllowedHandler
}
// buildRouteHandler builds the single mux handler that is a chain of the middleware
// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this
// point, no other middlewares can be registered on this Mux's stack. But you can still
// compose additional middlewares via Group()'s or using a chained middleware handler.
func (mx *Mux) buildRouteHandler() {
mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP))
}
// handle registers a http.Handler in the routing tree for a particular http method
// and routing pattern.
func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node {
if len(pattern) == 0 || pattern[0] != '/' {
panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern))
}
// Build the final routing handler for this Mux.
if !mx.inline && mx.handler == nil {
mx.buildRouteHandler()
}
// Build endpoint handler with inline middlewares for the route
var h http.Handler
if mx.inline {
mx.handler = http.HandlerFunc(mx.routeHTTP)
h = Chain(mx.middlewares...).Handler(handler)
} else {
h = handler
}
// Add the endpoint to the tree and return the node
return mx.tree.InsertRoute(method, pattern, h)
}
// routeHTTP routes a http.Request through the Mux routing tree to serve
// the matching handler for a particular http method.
func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {
// Grab the route context object
rctx := r.Context().Value(RouteCtxKey).(*Context)
// The request routing path
routePath := rctx.RoutePath
if routePath == "" {
if r.URL.RawPath != "" {
routePath = r.URL.RawPath
} else {
routePath = r.URL.Path
}
}
// Check if method is supported by chi
if rctx.RouteMethod == "" {
rctx.RouteMethod = r.Method
}
method, ok := methodMap[rctx.RouteMethod]
if !ok {
mx.MethodNotAllowedHandler().ServeHTTP(w, r)
return
}
// Find the route
if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
h.ServeHTTP(w, r)
return
}
if rctx.methodNotAllowed {
mx.MethodNotAllowedHandler().ServeHTTP(w, r)
} else {
mx.NotFoundHandler().ServeHTTP(w, r)
}
}
func (mx *Mux) nextRoutePath(rctx *Context) string {
routePath := "/"
nx := len(rctx.routeParams.Keys) - 1 // index of last param in list
if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx {
routePath += rctx.routeParams.Values[nx]
}
return routePath
}
// Recursively update data on child routers.
func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) {
for _, r := range mx.tree.routes() {
subMux, ok := r.SubRoutes.(*Mux)
if !ok {
continue
}
fn(subMux)
}
}
// methodNotAllowedHandler is a helper function to respond with a 405,
// method not allowed.
func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(405)
w.Write(nil)
}

847
vendor/github.com/go-chi/chi/tree.go generated vendored Normal file
View File

@ -0,0 +1,847 @@
package chi
// Radix tree implementation below is a based on the original work by
// Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go
// (MIT licensed). It's been heavily modified for use as a HTTP routing tree.
import (
"fmt"
"math"
"net/http"
"regexp"
"sort"
"strconv"
"strings"
)
type methodTyp int
const (
mSTUB methodTyp = 1 << iota
mCONNECT
mDELETE
mGET
mHEAD
mOPTIONS
mPATCH
mPOST
mPUT
mTRACE
)
var mALL = mCONNECT | mDELETE | mGET | mHEAD |
mOPTIONS | mPATCH | mPOST | mPUT | mTRACE
var methodMap = map[string]methodTyp{
http.MethodConnect: mCONNECT,
http.MethodDelete: mDELETE,
http.MethodGet: mGET,
http.MethodHead: mHEAD,
http.MethodOptions: mOPTIONS,
http.MethodPatch: mPATCH,
http.MethodPost: mPOST,
http.MethodPut: mPUT,
http.MethodTrace: mTRACE,
}
// RegisterMethod adds support for custom HTTP method handlers, available
// via Router#Method and Router#MethodFunc
func RegisterMethod(method string) {
if method == "" {
return
}
method = strings.ToUpper(method)
if _, ok := methodMap[method]; ok {
return
}
n := len(methodMap)
if n > strconv.IntSize {
panic(fmt.Sprintf("chi: max number of methods reached (%d)", strconv.IntSize))
}
mt := methodTyp(math.Exp2(float64(n)))
methodMap[method] = mt
mALL |= mt
}
type nodeTyp uint8
const (
ntStatic nodeTyp = iota // /home
ntRegexp // /{id:[0-9]+}
ntParam // /{user}
ntCatchAll // /api/v1/*
)
type node struct {
// node type: static, regexp, param, catchAll
typ nodeTyp
// first byte of the prefix
label byte
// first byte of the child prefix
tail byte
// prefix is the common prefix we ignore
prefix string
// regexp matcher for regexp nodes
rex *regexp.Regexp
// HTTP handler endpoints on the leaf node
endpoints endpoints
// subroutes on the leaf node
subroutes Routes
// child nodes should be stored in-order for iteration,
// in groups of the node type.
children [ntCatchAll + 1]nodes
}
// endpoints is a mapping of http method constants to handlers
// for a given route.
type endpoints map[methodTyp]*endpoint
type endpoint struct {
// endpoint handler
handler http.Handler
// pattern is the routing pattern for handler nodes
pattern string
// parameter keys recorded on handler nodes
paramKeys []string
}
func (s endpoints) Value(method methodTyp) *endpoint {
mh, ok := s[method]
if !ok {
mh = &endpoint{}
s[method] = mh
}
return mh
}
func (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node {
var parent *node
search := pattern
for {
// Handle key exhaustion
if len(search) == 0 {
// Insert or update the node's leaf handler
n.setEndpoint(method, handler, pattern)
return n
}
// We're going to be searching for a wild node next,
// in this case, we need to get the tail
var label = search[0]
var segTail byte
var segEndIdx int
var segTyp nodeTyp
var segRexpat string
if label == '{' || label == '*' {
segTyp, _, segRexpat, segTail, _, segEndIdx = patNextSegment(search)
}
var prefix string
if segTyp == ntRegexp {
prefix = segRexpat
}
// Look for the edge to attach to
parent = n
n = n.getEdge(segTyp, label, segTail, prefix)
// No edge, create one
if n == nil {
child := &node{label: label, tail: segTail, prefix: search}
hn := parent.addChild(child, search)
hn.setEndpoint(method, handler, pattern)
return hn
}
// Found an edge to match the pattern
if n.typ > ntStatic {
// We found a param node, trim the param from the search path and continue.
// This param/wild pattern segment would already be on the tree from a previous
// call to addChild when creating a new node.
search = search[segEndIdx:]
continue
}
// Static nodes fall below here.
// Determine longest prefix of the search key on match.
commonPrefix := longestPrefix(search, n.prefix)
if commonPrefix == len(n.prefix) {
// the common prefix is as long as the current node's prefix we're attempting to insert.
// keep the search going.
search = search[commonPrefix:]
continue
}
// Split the node
child := &node{
typ: ntStatic,
prefix: search[:commonPrefix],
}
parent.replaceChild(search[0], segTail, child)
// Restore the existing node
n.label = n.prefix[commonPrefix]
n.prefix = n.prefix[commonPrefix:]
child.addChild(n, n.prefix)
// If the new key is a subset, set the method/handler on this node and finish.
search = search[commonPrefix:]
if len(search) == 0 {
child.setEndpoint(method, handler, pattern)
return child
}
// Create a new edge for the node
subchild := &node{
typ: ntStatic,
label: search[0],
prefix: search,
}
hn := child.addChild(subchild, search)
hn.setEndpoint(method, handler, pattern)
return hn
}
}
// addChild appends the new `child` node to the tree using the `pattern` as the trie key.
// For a URL router like chi's, we split the static, param, regexp and wildcard segments
// into different nodes. In addition, addChild will recursively call itself until every
// pattern segment is added to the url pattern tree as individual nodes, depending on type.
func (n *node) addChild(child *node, prefix string) *node {
search := prefix
// handler leaf node added to the tree is the child.
// this may be overridden later down the flow
hn := child
// Parse next segment
segTyp, _, segRexpat, segTail, segStartIdx, segEndIdx := patNextSegment(search)
// Add child depending on next up segment
switch segTyp {
case ntStatic:
// Search prefix is all static (that is, has no params in path)
// noop
default:
// Search prefix contains a param, regexp or wildcard
if segTyp == ntRegexp {
rex, err := regexp.Compile(segRexpat)
if err != nil {
panic(fmt.Sprintf("chi: invalid regexp pattern '%s' in route param", segRexpat))
}
child.prefix = segRexpat
child.rex = rex
}
if segStartIdx == 0 {
// Route starts with a param
child.typ = segTyp
if segTyp == ntCatchAll {
segStartIdx = -1
} else {
segStartIdx = segEndIdx
}
if segStartIdx < 0 {
segStartIdx = len(search)
}
child.tail = segTail // for params, we set the tail
if segStartIdx != len(search) {
// add static edge for the remaining part, split the end.
// its not possible to have adjacent param nodes, so its certainly
// going to be a static node next.
search = search[segStartIdx:] // advance search position
nn := &node{
typ: ntStatic,
label: search[0],
prefix: search,
}
hn = child.addChild(nn, search)
}
} else if segStartIdx > 0 {
// Route has some param
// starts with a static segment
child.typ = ntStatic
child.prefix = search[:segStartIdx]
child.rex = nil
// add the param edge node
search = search[segStartIdx:]
nn := &node{
typ: segTyp,
label: search[0],
tail: segTail,
}
hn = child.addChild(nn, search)
}
}
n.children[child.typ] = append(n.children[child.typ], child)
n.children[child.typ].Sort()
return hn
}
func (n *node) replaceChild(label, tail byte, child *node) {
for i := 0; i < len(n.children[child.typ]); i++ {
if n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail {
n.children[child.typ][i] = child
n.children[child.typ][i].label = label
n.children[child.typ][i].tail = tail
return
}
}
panic("chi: replacing missing child")
}
func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node {
nds := n.children[ntyp]
for i := 0; i < len(nds); i++ {
if nds[i].label == label && nds[i].tail == tail {
if ntyp == ntRegexp && nds[i].prefix != prefix {
continue
}
return nds[i]
}
}
return nil
}
func (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern string) {
// Set the handler for the method type on the node
if n.endpoints == nil {
n.endpoints = make(endpoints, 0)
}
paramKeys := patParamKeys(pattern)
if method&mSTUB == mSTUB {
n.endpoints.Value(mSTUB).handler = handler
}
if method&mALL == mALL {
h := n.endpoints.Value(mALL)
h.handler = handler
h.pattern = pattern
h.paramKeys = paramKeys
for _, m := range methodMap {
h := n.endpoints.Value(m)
h.handler = handler
h.pattern = pattern
h.paramKeys = paramKeys
}
} else {
h := n.endpoints.Value(method)
h.handler = handler
h.pattern = pattern
h.paramKeys = paramKeys
}
}
func (n *node) FindRoute(rctx *Context, method methodTyp, path string) (*node, endpoints, http.Handler) {
// Reset the context routing pattern and params
rctx.routePattern = ""
rctx.routeParams.Keys = rctx.routeParams.Keys[:0]
rctx.routeParams.Values = rctx.routeParams.Values[:0]
// Find the routing handlers for the path
rn := n.findRoute(rctx, method, path)
if rn == nil {
return nil, nil, nil
}
// Record the routing params in the request lifecycle
rctx.URLParams.Keys = append(rctx.URLParams.Keys, rctx.routeParams.Keys...)
rctx.URLParams.Values = append(rctx.URLParams.Values, rctx.routeParams.Values...)
// Record the routing pattern in the request lifecycle
if rn.endpoints[method].pattern != "" {
rctx.routePattern = rn.endpoints[method].pattern
rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.routePattern)
}
return rn, rn.endpoints, rn.endpoints[method].handler
}
// Recursive edge traversal by checking all nodeTyp groups along the way.
// It's like searching through a multi-dimensional radix trie.
func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {
nn := n
search := path
for t, nds := range nn.children {
ntyp := nodeTyp(t)
if len(nds) == 0 {
continue
}
var xn *node
xsearch := search
var label byte
if search != "" {
label = search[0]
}
switch ntyp {
case ntStatic:
xn = nds.findEdge(label)
if xn == nil || !strings.HasPrefix(xsearch, xn.prefix) {
continue
}
xsearch = xsearch[len(xn.prefix):]
case ntParam, ntRegexp:
// short-circuit and return no matching route for empty param values
if xsearch == "" {
continue
}
// serially loop through each node grouped by the tail delimiter
for idx := 0; idx < len(nds); idx++ {
xn = nds[idx]
// label for param nodes is the delimiter byte
p := strings.IndexByte(xsearch, xn.tail)
if p < 0 {
if xn.tail == '/' {
p = len(xsearch)
} else {
continue
}
}
if ntyp == ntRegexp && xn.rex != nil {
if xn.rex.Match([]byte(xsearch[:p])) == false {
continue
}
} else if strings.IndexByte(xsearch[:p], '/') != -1 {
// avoid a match across path segments
continue
}
rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p])
xsearch = xsearch[p:]
break
}
default:
// catch-all nodes
rctx.routeParams.Values = append(rctx.routeParams.Values, search)
xn = nds[0]
xsearch = ""
}
if xn == nil {
continue
}
// did we find it yet?
if len(xsearch) == 0 {
if xn.isLeaf() {
h, _ := xn.endpoints[method]
if h != nil && h.handler != nil {
rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...)
return xn
}
// flag that the routing context found a route, but not a corresponding
// supported method
rctx.methodNotAllowed = true
}
}
// recursively find the next node..
fin := xn.findRoute(rctx, method, xsearch)
if fin != nil {
return fin
}
// Did not find final handler, let's remove the param here if it was set
if xn.typ > ntStatic {
if len(rctx.routeParams.Values) > 0 {
rctx.routeParams.Values = rctx.routeParams.Values[:len(rctx.routeParams.Values)-1]
}
}
}
return nil
}
func (n *node) findEdge(ntyp nodeTyp, label byte) *node {
nds := n.children[ntyp]
num := len(nds)
idx := 0
switch ntyp {
case ntStatic, ntParam, ntRegexp:
i, j := 0, num-1
for i <= j {
idx = i + (j-i)/2
if label > nds[idx].label {
i = idx + 1
} else if label < nds[idx].label {
j = idx - 1
} else {
i = num // breaks cond
}
}
if nds[idx].label != label {
return nil
}
return nds[idx]
default: // catch all
return nds[idx]
}
}
func (n *node) isEmpty() bool {
for _, nds := range n.children {
if len(nds) > 0 {
return false
}
}
return true
}
func (n *node) isLeaf() bool {
return n.endpoints != nil
}
func (n *node) findPattern(pattern string) bool {
nn := n
for _, nds := range nn.children {
if len(nds) == 0 {
continue
}
n = nn.findEdge(nds[0].typ, pattern[0])
if n == nil {
continue
}
var idx int
var xpattern string
switch n.typ {
case ntStatic:
idx = longestPrefix(pattern, n.prefix)
if idx < len(n.prefix) {
continue
}
case ntParam, ntRegexp:
idx = strings.IndexByte(pattern, '}') + 1
case ntCatchAll:
idx = longestPrefix(pattern, "*")
default:
panic("chi: unknown node type")
}
xpattern = pattern[idx:]
if len(xpattern) == 0 {
return true
}
return n.findPattern(xpattern)
}
return false
}
func (n *node) routes() []Route {
rts := []Route{}
n.walk(func(eps endpoints, subroutes Routes) bool {
if eps[mSTUB] != nil && eps[mSTUB].handler != nil && subroutes == nil {
return false
}
// Group methodHandlers by unique patterns
pats := make(map[string]endpoints, 0)
for mt, h := range eps {
if h.pattern == "" {
continue
}
p, ok := pats[h.pattern]
if !ok {
p = endpoints{}
pats[h.pattern] = p
}
p[mt] = h
}
for p, mh := range pats {
hs := make(map[string]http.Handler, 0)
if mh[mALL] != nil && mh[mALL].handler != nil {
hs["*"] = mh[mALL].handler
}
for mt, h := range mh {
if h.handler == nil {
continue
}
m := methodTypString(mt)
if m == "" {
continue
}
hs[m] = h.handler
}
rt := Route{p, hs, subroutes}
rts = append(rts, rt)
}
return false
})
return rts
}
func (n *node) walk(fn func(eps endpoints, subroutes Routes) bool) bool {
// Visit the leaf values if any
if (n.endpoints != nil || n.subroutes != nil) && fn(n.endpoints, n.subroutes) {
return true
}
// Recurse on the children
for _, ns := range n.children {
for _, cn := range ns {
if cn.walk(fn) {
return true
}
}
}
return false
}
// patNextSegment returns the next segment details from a pattern:
// node type, param key, regexp string, param tail byte, param starting index, param ending index
func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) {
ps := strings.Index(pattern, "{")
ws := strings.Index(pattern, "*")
if ps < 0 && ws < 0 {
return ntStatic, "", "", 0, 0, len(pattern) // we return the entire thing
}
// Sanity check
if ws >= 0 && ws != len(pattern)-1 {
panic("chi: wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead")
}
if ps >= 0 && ws >= 0 && ws < ps {
panic("chi: wildcard '*' must be the last pattern in a route, otherwise use a '{param}'")
}
var tail byte = '/' // Default endpoint tail to / byte
if ps >= 0 {
// Param/Regexp pattern is next
nt := ntParam
// Read to closing } taking into account opens and closes in curl count (cc)
cc := 0
pe := ps
for i, c := range pattern[ps:] {
if c == '{' {
cc++
} else if c == '}' {
cc--
if cc == 0 {
pe = ps + i
break
}
}
}
if pe == ps {
panic("chi: route param closing delimiter '}' is missing")
}
key := pattern[ps+1 : pe]
pe++ // set end to next position
if pe < len(pattern) {
tail = pattern[pe]
}
var rexpat string
if idx := strings.Index(key, ":"); idx >= 0 {
nt = ntRegexp
rexpat = key[idx+1:]
key = key[:idx]
}
if len(rexpat) > 0 {
if rexpat[0] != '^' {
rexpat = "^" + rexpat
}
if rexpat[len(rexpat)-1] != '$' {
rexpat = rexpat + "$"
}
}
return nt, key, rexpat, tail, ps, pe
}
// Wildcard pattern as finale
// TODO: should we panic if there is stuff after the * ???
return ntCatchAll, "*", "", 0, ws, len(pattern)
}
func patParamKeys(pattern string) []string {
pat := pattern
paramKeys := []string{}
for {
ptyp, paramKey, _, _, _, e := patNextSegment(pat)
if ptyp == ntStatic {
return paramKeys
}
for i := 0; i < len(paramKeys); i++ {
if paramKeys[i] == paramKey {
panic(fmt.Sprintf("chi: routing pattern '%s' contains duplicate param key, '%s'", pattern, paramKey))
}
}
paramKeys = append(paramKeys, paramKey)
pat = pat[e:]
}
}
// longestPrefix finds the length of the shared prefix
// of two strings
func longestPrefix(k1, k2 string) int {
max := len(k1)
if l := len(k2); l < max {
max = l
}
var i int
for i = 0; i < max; i++ {
if k1[i] != k2[i] {
break
}
}
return i
}
func methodTypString(method methodTyp) string {
for s, t := range methodMap {
if method == t {
return s
}
}
return ""
}
type nodes []*node
// Sort the list of nodes by label
func (ns nodes) Sort() { sort.Sort(ns); ns.tailSort() }
func (ns nodes) Len() int { return len(ns) }
func (ns nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label }
// tailSort pushes nodes with '/' as the tail to the end of the list for param nodes.
// The list order determines the traversal order.
func (ns nodes) tailSort() {
for i := len(ns) - 1; i >= 0; i-- {
if ns[i].typ > ntStatic && ns[i].tail == '/' {
ns.Swap(i, len(ns)-1)
return
}
}
}
func (ns nodes) findEdge(label byte) *node {
num := len(ns)
idx := 0
i, j := 0, num-1
for i <= j {
idx = i + (j-i)/2
if label > ns[idx].label {
i = idx + 1
} else if label < ns[idx].label {
j = idx - 1
} else {
i = num // breaks cond
}
}
if ns[idx].label != label {
return nil
}
return ns[idx]
}
// Route describes the details of a routing handler.
type Route struct {
Pattern string
Handlers map[string]http.Handler
SubRoutes Routes
}
// WalkFunc is the type of the function called for each method and route visited by Walk.
type WalkFunc func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error
// Walk walks any router tree that implements Routes interface.
func Walk(r Routes, walkFn WalkFunc) error {
return walk(r, walkFn, "")
}
func walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.Handler) http.Handler) error {
for _, route := range r.Routes() {
mws := make([]func(http.Handler) http.Handler, len(parentMw))
copy(mws, parentMw)
mws = append(mws, r.Middlewares()...)
if route.SubRoutes != nil {
if err := walk(route.SubRoutes, walkFn, parentRoute+route.Pattern, mws...); err != nil {
return err
}
continue
}
for method, handler := range route.Handlers {
if method == "*" {
// Ignore a "catchAll" method, since we pass down all the specific methods for each route.
continue
}
fullRoute := parentRoute + route.Pattern
if chain, ok := handler.(*ChainHandler); ok {
if err := walkFn(method, fullRoute, chain.Endpoint, append(mws, chain.Middlewares...)...); err != nil {
return err
}
} else {
if err := walkFn(method, fullRoute, handler, mws...); err != nil {
return err
}
}
}
}
return nil
}

3
vendor/github.com/mailgun/mailgun-go/v3/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
.DS_Store
.idea/
cmd/mailgun/mailgun

7
vendor/github.com/mailgun/mailgun-go/v3/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,7 @@
language: go
env:
- GO111MODULE=on
go:
- 1.11.x

175
vendor/github.com/mailgun/mailgun-go/v3/CHANGELOG generated vendored Normal file
View File

@ -0,0 +1,175 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.6.3] - 2019-12-03
### Changes
* Calls to get stats now use epoch as the time format
## [3.6.2] - 2019-11-18
### Added
* Added `AddTemplateVariable()` to make adding variables to templates
less confusing and error prone.
## [3.6.1] - 2019-10-24
### Added
* Added `VerifyWebhookSignature()` to mailgun interface
## [3.6.1-rc.3] - 2019-07-16
### Added
* APIBaseEU and APIBaseUS to help customers change regions
* Documented how to change regions in the README
## [3.6.1-rc.2] - 2019-07-01
### Changes
* Fix the JSON response for `GetMember()`
* Typo in format string in max number of tags error
## [3.6.0] - 2019-06-26
### Added
* Added UpdateClickTracking() to modify click tracking for a domain
* Added UpdateUnsubscribeTracking() to modify unsubscribe tracking for a domain
* Added UpdateOpenTracking() to modify open tracking for a domain
## [3.5.0] - 2019-05-21
### Added
* Added notice in README about go dep bug.
* Added endpoints for webhooks in mock server
### Changes
* Change names of some parameters on public methods to make their use clearer.
* Changed signature of `GetWebhook()` now returns []string.
* Changed signature of `ListWebhooks()` now returns map[string][]string.
* Both `GetWebhooks()` and `ListWebhooks()` now handle new and legacy webhooks properly.
## [3.4.0] - 2019-04-23
### Added
* Added `Message.SetTemplate()` to allow sending with the body of a template.
### Changes
* Changed signature of `CreateDomain()` moved password into `CreateDomainOptions`
## [3.4.0] - 2019-04-23
### Added
* Added `Message.SetTemplate()` to allow sending with the body of a template.
### Changes
* Changed signature of `CreateDomain()` moved password into `CreateDomainOptions`
## [3.3.2] - 2019-03-28
### Changes
* Uncommented DeliveryStatus.Code and change it to an integer (See #175)
* Added UserVariables to all Message events (See #176)
## [3.3.1] - 2019-03-13
### Changes
* Updated Template calls to reflect the most recent Template API changes.
* GetStoredMessage() now accepts a URL instead of an id
* Deprecated GetStoredMessageForURL()
* Deprecated GetStoredMessageRawForURL()
* Fixed GetUnsubscribed()
### Added
* Added `GetStoredAttachment()`
### Removed
* Method `DeleteStoredMessage()` mailgun API no long allows this call
## [3.3.0] - 2019-01-28
### Changes
* Changed signature of CreateDomain() Now returns JSON response
* Changed signature of GetDomain() Now returns a single DomainResponse
* Clarified installation notes for non golang module users
* Changed 'Public Key' to 'Public Validation Key' in readme
* Fixed issue with Next() for limit/skip based iterators
### Added
* Added VerifyDomain()
## [3.2.0] - 2019-01-21
### Changes
* Deprecated mg.VerifyWebhookRequest()
### Added
* Added mailgun.ParseEvent()
* Added mailgun.ParseEvents()
* Added mg.VerifyWebhookSignature()
## [3.1.0] - 2019-01-16
### Changes
* Removed context.Context from ListDomains() signature
* ListEventOptions.Begin and End are no longer pointers to time.Time
### Added
* Added mg.ReSend() to public Mailgun interface
* Added Message.SetSkipVerification()
* Added Message.SetRequireTLS()
## [3.0.0] - 2019-01-15
### Added
* Added CHANGELOG
* Added `AddDomainIP()`
* Added `ListDomainIPS()`
* Added `DeleteDomainIP()`
* Added `ListIPS()`
* Added `GetIP()`
* Added `GetDomainTracking()`
* Added `GetDomainConnection()`
* Added `UpdateDomainConnection()`
* Added `CreateExport()`
* Added `ListExports()`
* Added `GetExports()`
* Added `GetExportLink()`
* Added `CreateTemplate()`
* Added `GetTemplate()`
* Added `UpdateTemplate()`
* Added `DeleteTemplate()`
* Added `ListTemplates()`
* Added `AddTemplateVersion()`
* Added `GetTemplateVersion()`
* Added `UpdateTemplateVersion()`
* Added `DeleteTemplateVersion()`
* Added `ListTemplateVersions()`
### Changed
* Added a `mailgun.MockServer` which duplicates part of the mailgun API; suitable for testing
* `ListMailingLists()` now uses the `/pages` API and returns an iterator
* `ListMembers()` now uses the `/pages` API and returns an iterator
* Renamed public interface methods to be consistent. IE: `GetThing(), ListThing(), CreateThing()`
* Moved event objects into the `mailgun/events` package, so names like
`MailingList` returned by API calls and `MailingList` as an event object
don't conflict and confuse users.
* Now using context.Context for all network operations
* Test suite will run without MG_ env vars defined
* ListRoutes() now uses the iterator interface
* Added SkipNetworkTest()
* Renamed GetStatsTotals() to GetStats()
* Renamed GetUnsubscribes to ListUnsubscribes()
* Renamed Unsubscribe() to CreateUnsubscribe()
* Renamed RemoveUnsubscribe() to DeleteUnsubscribe()
* GetStats() now takes an `*opt` argument to pass optional parameters
* Modified GetUnsubscribe() to follow the API
* Now using golang modules
* ListCredentials() now returns an iterator
* ListUnsubscribes() now returns an paging iterator
* CreateDomain now accepts CreateDomainOption{}
* CreateDomain() now supports all optional parameters not just spam_action and wildcard.
* ListComplaints() now returns a page iterator
* Renamed `TagItem` to `Tag`
* ListBounces() now returns a page iterator
* API responses with CreatedAt fields are now unmarshalled into RFC2822
* DomainList() now returns an iterator
* Updated godoc documentation
* Renamed ApiBase to APIBase
* Updated copyright to 2019
* `ListEvents()` now returns a list of typed events
### Removed
* Removed more deprecated types
* Removed gobuffalo/envy dependency
* Remove mention of the CLI in the README
* Removed mailgun cli from project
* Removed GetCode() from `Bounce` struct. Verified API returns 'string' and not 'int'
* Removed deprecated methods NewMessage and NewMIMEMessage
* Removed ginkgo and gomega tests
* Removed GetStats() As the /stats endpoint is depreciated

27
vendor/github.com/mailgun/mailgun-go/v3/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2013-2016, Michael Banzon
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the names of Mailgun, Michael Banzon, nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
vendor/github.com/mailgun/mailgun-go/v3/Makefile generated vendored Normal file
View File

@ -0,0 +1,21 @@
.PHONY: all
.DEFAULT_GOAL := all
PACKAGE := github.com/mailgun/mailgun-go
gen:
rm events/events_easyjson.go
easyjson --all events/events.go
rm events/objects_easyjson.go
easyjson --all events/objects.go
all:
export GO111MODULE=on; go test . -v
godoc:
mkdir -p /tmp/tmpgoroot/doc
-rm -rf /tmp/tmpgopath/src/${PACKAGE}
mkdir -p /tmp/tmpgopath/src/${PACKAGE}
tar -c --exclude='.git' --exclude='tmp' . | tar -x -C /tmp/tmpgopath/src/${PACKAGE}
echo -e "open http://localhost:6060/pkg/${PACKAGE}\n"
GOROOT=/tmp/tmpgoroot/ GOPATH=/tmp/tmpgopath/ godoc -http=localhost:6060

364
vendor/github.com/mailgun/mailgun-go/v3/README.md generated vendored Normal file
View File

@ -0,0 +1,364 @@
# Mailgun with Go
[![GoDoc](https://godoc.org/github.com/mailgun/mailgun-go?status.svg)](https://godoc.org/github.com/mailgun/mailgun-go)
[![Build Status](https://img.shields.io/travis/mailgun/mailgun-go/master.svg)](https://travis-ci.org/mailgun/mailgun-go)
Go library for interacting with the [Mailgun](https://mailgun.com/) [API](https://documentation.mailgun.com/api_reference.html).
**NOTE: Backward compatibility has been broken with the v3.0 release which includes versioned paths required by 1.11
go modules (See [Releasing Modules](https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher)).
Pin your dependencies to the v1.1.1 or v2.0 tag if you are not ready for v3.0**
## Usage
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/mailgun/mailgun-go/v3"
)
// Your available domain names can be found here:
// (https://app.mailgun.com/app/domains)
var yourDomain string = "your-domain-name" // e.g. mg.yourcompany.com
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
var privateAPIKey string = "your-private-key"
func main() {
// Create an instance of the Mailgun Client
mg := mailgun.NewMailgun(yourDomain, privateAPIKey)
sender := "sender@example.com"
subject := "Fancy subject!"
body := "Hello from Mailgun Go!"
recipient := "recipient@example.com"
// The message object allows you to add attachments and Bcc recipients
message := mg.NewMessage(sender, subject, body, recipient)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Send the message with a 10 second timeout
resp, id, err := mg.Send(ctx, message)
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %s Resp: %s\n", id, resp)
}
```
## Get Events
```go
package main
import (
"context"
"fmt"
"time"
"github.com/mailgun/mailgun-go/v3"
"github.com/mailgun/mailgun-go/v3/events"
)
func main() {
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
mg := mailgun.NewMailgun("your-domain.com", "your-private-key")
it := mg.ListEvents(&mailgun.ListEventOptions{Limit: 100})
var page []mailgun.Event
// The entire operation should not take longer than 30 seconds
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
// For each page of 100 events
for it.Next(ctx, &page) {
for _, e := range page {
// You can access some fields via the interface
fmt.Printf("Event: '%s' TimeStamp: '%s'\n", e.GetName(), e.GetTimestamp())
// and you can act upon each event by type
switch event := e.(type) {
case *events.Accepted:
fmt.Printf("Accepted: auth: %t\n", event.Flags.IsAuthenticated)
case *events.Delivered:
fmt.Printf("Delivered transport: %s\n", event.Envelope.Transport)
case *events.Failed:
fmt.Printf("Failed reason: %s\n", event.Reason)
case *events.Clicked:
fmt.Printf("Clicked GeoLocation: %s\n", event.GeoLocation.Country)
case *events.Opened:
fmt.Printf("Opened GeoLocation: %s\n", event.GeoLocation.Country)
case *events.Rejected:
fmt.Printf("Rejected reason: %s\n", event.Reject.Reason)
case *events.Stored:
fmt.Printf("Stored URL: %s\n", event.Storage.URL)
case *events.Unsubscribed:
fmt.Printf("Unsubscribed client OS: %s\n", event.ClientInfo.ClientOS)
}
}
}
}
```
## Event Polling
The mailgun library has built-in support for polling the events api
```go
package main
import (
"context"
"time"
"github.com/mailgun/mailgun-go/v3"
)
func main() {
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
mg := mailgun.NewMailgun("your-domain.com", "your-private-key")
begin := time.Now().Add(time.Second * -3)
// Very short poll interval
it := mg.PollEvents(&mailgun.ListEventOptions{
// Only events with a timestamp after this date/time will be returned
Begin: &begin,
// How often we poll the api for new events
PollInterval: time.Second * 30,
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Poll until our email event arrives
var page []mailgun.Event
for it.Poll(ctx, &page) {
for _, e := range page {
// Do something with event
}
}
}
```
# Email Validations
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/mailgun/mailgun-go/v3"
)
// If your plan does not include email validations but you have an account,
// use your Public Validation api key. If your plan does include email validations,
// use your Private API key. You can find both the Private and
// Public Validation API Keys in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
var apiKey string = "your-api-key"
func main() {
// Create an instance of the Validator
v := mailgun.NewEmailValidator(apiKey)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
email, err := v.ValidateEmail(ctx, "recipient@example.com", false)
if err != nil {
panic(err)
}
fmt.Printf("Valid: %t\n", email.IsValid)
}
```
## Webhook Handling
```go
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"github.com/mailgun/mailgun-go/v3"
"github.com/mailgun/mailgun-go/v3/events"
)
func main() {
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
mg := mailgun.NewMailgun("your-domain.com", "private-api-key")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var payload mailgun.WebhookPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
fmt.Printf("decode JSON error: %s", err)
w.WriteHeader(http.StatusNotAcceptable)
return
}
verified, err := mg.VerifyWebhookSignature(payload.Signature)
if err != nil {
fmt.Printf("verify error: %s\n", err)
w.WriteHeader(http.StatusNotAcceptable)
return
}
if !verified {
w.WriteHeader(http.StatusNotAcceptable)
fmt.Printf("failed verification %+v\n", payload.Signature)
return
}
fmt.Printf("Verified Signature\n")
// Parse the event provided by the webhook payload
e, err := mailgun.ParseEvent(payload.EventData)
if err != nil {
fmt.Printf("parse event error: %s\n", err)
return
}
switch event := e.(type) {
case *events.Accepted:
fmt.Printf("Accepted: auth: %t\n", event.Flags.IsAuthenticated)
case *events.Delivered:
fmt.Printf("Delivered transport: %s\n", event.Envelope.Transport)
}
})
fmt.Println("Serve on :9090...")
if err := http.ListenAndServe(":9090", nil); err != nil {
fmt.Printf("serve error: %s\n", err)
os.Exit(1)
}
}
```
## Using Templates
Templates enable you to create message templates on your Mailgun account and then populate the data variables at send-time. This allows you to have your layout and design managed on the server and handle the data on the client. The template variables are added as a JSON stringified `X-Mailgun-Variables` header. For example, if you have a template to send a password reset link, you could do the following:
```go
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/mailgun/mailgun-go/v3"
)
// Your available domain names can be found here:
// (https://app.mailgun.com/app/domains)
var yourDomain string = "your-domain-name" // e.g. mg.yourcompany.com
// You can find the Private API Key in your Account Menu, under "Settings":
// (https://app.mailgun.com/app/account/security)
var privateAPIKey string = "your-private-key"
func main() {
// Create an instance of the Mailgun Client
mg := mailgun.NewMailgun(yourDomain, privateAPIKey)
sender := "sender@example.com"
subject := "Fancy subject!"
body := ""
recipient := "recipient@example.com"
// The message object allows you to add attachments and Bcc recipients
message := mg.NewMessage(sender, subject, body, recipient)
message.SetTemplate("passwordReset")
message.AddTemplateVariable("passwordResetLink", "some link to your site unique to your user")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Send the message with a 10 second timeout
resp, id, err := mg.Send(ctx, message)
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID: %s Resp: %s\n", id, resp)
}
```
The official mailgun documentation includes examples using this library. Go
[here](https://documentation.mailgun.com/en/latest/api_reference.html#api-reference)
and click on the "Go" button at the top of the page.
### EU Region
European customers will need to change the default API Base to access your domains
```go
mg := mailgun.NewMailgun("your-domain.com", "private-api-key")
mg.SetAPIBase(mailgun.APIBaseEU)
```
## Installation
If you are using [golang modules](https://github.com/golang/go/wiki/Modules) make sure you
include the `/v3` at the end of your import paths
```bash
$ go get github.com/mailgun/mailgun-go/v3
```
If you are **not** using golang modules, you can drop the `/v3` at the end of the import path.
As long as you are using the latest 1.10 or 1.11 golang release, import paths that end in `/v3`
in your code should work fine even if you do not have golang modules enabled for your project.
```bash
$ go get github.com/mailgun/mailgun-go
```
**NOTE for go dep users**
Using version 3 of the mailgun-go library with go dep currently results in the following error
```
"github.com/mailgun/mailgun-go/v3/events", which contains malformed code: no package exists at ...
```
This is a known bug in go dep. You can follow the PR to fix this bug [here](https://github.com/golang/dep/pull/1963)
Until this bug is fixed, the best way to use version 3 of the mailgun-go library is to use the golang community
supported [golang modules](https://github.com/golang/go/wiki/Modules).
## Testing
*WARNING* - running the tests will cost you money!
To run the tests various environment variables must be set. These are:
* `MG_DOMAIN` is the domain name - this is a value registered in the Mailgun admin interface.
* `MG_PUBLIC_API_KEY` is the Public Validation API key - you can get this value from the Mailgun [security page](https://app.mailgun.com/app/account/security)
* `MG_API_KEY` is the Private API key - you can get this value from the Mailgun [security page](https://app.mailgun.com/app/account/security)
* `MG_EMAIL_TO` is the email address used in various sending tests.
and finally
* `MG_SPEND_MONEY` if this value is set the part of the test that use the API to actually send email will be run - be aware *this will count on your quota* and *this _will_ cost you money*.
The code is released under a 3-clause BSD license. See the LICENSE file for more information.

202
vendor/github.com/mailgun/mailgun-go/v3/bounces.go generated vendored Normal file
View File

@ -0,0 +1,202 @@
package mailgun
import (
"context"
"strconv"
)
// Bounce aggregates data relating to undeliverable messages to a specific intended recipient,
// identified by Address.
type Bounce struct {
// The time at which Mailgun detected the bounce.
CreatedAt RFC2822Time `json:"created_at"`
// Code provides the SMTP error code that caused the bounce
Code string `json:"code"`
// Address the bounce is for
Address string `json:"address"`
// human readable reason why
Error string `json:"error"`
}
type Paging struct {
First string `json:"first,omitempty"`
Next string `json:"next,omitempty"`
Previous string `json:"previous,omitempty"`
Last string `json:"last,omitempty"`
}
type bouncesListResponse struct {
Items []Bounce `json:"items"`
Paging Paging `json:"paging"`
}
// ListBounces returns a complete set of bounces logged against the sender's domain, if any.
// The results include the total number of bounces (regardless of skip or limit settings),
// and the slice of bounces specified, if successful.
// Note that the length of the slice may be smaller than the total number of bounces.
func (mg *MailgunImpl) ListBounces(opts *ListOptions) *BouncesIterator {
r := newHTTPRequest(generateApiUrl(mg, bouncesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
if opts != nil {
if opts.Limit != 0 {
r.addParameter("limit", strconv.Itoa(opts.Limit))
}
}
url, err := r.generateUrlWithParameters()
return &BouncesIterator{
mg: mg,
bouncesListResponse: bouncesListResponse{Paging: Paging{Next: url, First: url}},
err: err,
}
}
type BouncesIterator struct {
bouncesListResponse
mg Mailgun
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ci *BouncesIterator) Err() error {
return ci.err
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (ci *BouncesIterator) Next(ctx context.Context, items *[]Bounce) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Next)
if ci.err != nil {
return false
}
cpy := make([]Bounce, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
if len(ci.Items) == 0 {
return false
}
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (ci *BouncesIterator) First(ctx context.Context, items *[]Bounce) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.First)
if ci.err != nil {
return false
}
cpy := make([]Bounce, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (ci *BouncesIterator) Last(ctx context.Context, items *[]Bounce) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Last)
if ci.err != nil {
return false
}
cpy := make([]Bounce, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (ci *BouncesIterator) Previous(ctx context.Context, items *[]Bounce) bool {
if ci.err != nil {
return false
}
if ci.Paging.Previous == "" {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Previous)
if ci.err != nil {
return false
}
cpy := make([]Bounce, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
if len(ci.Items) == 0 {
return false
}
return true
}
func (ci *BouncesIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(ci.mg.Client())
r.setBasicAuth(basicAuthUser, ci.mg.APIKey())
return getResponseFromJSON(ctx, r, &ci.bouncesListResponse)
}
// GetBounce retrieves a single bounce record, if any exist, for the given recipient address.
func (mg *MailgunImpl) GetBounce(ctx context.Context, address string) (Bounce, error) {
r := newHTTPRequest(generateApiUrl(mg, bouncesEndpoint) + "/" + address)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var response Bounce
err := getResponseFromJSON(ctx, r, &response)
return response, err
}
// AddBounce files a bounce report.
// Address identifies the intended recipient of the message that bounced.
// Code corresponds to the numeric response given by the e-mail server which rejected the message.
// Error providees the corresponding human readable reason for the problem.
// For example,
// here's how the these two fields relate.
// Suppose the SMTP server responds with an error, as below.
// Then, . . .
//
// 550 Requested action not taken: mailbox unavailable
// \___/\_______________________________________________/
// | |
// `-- Code `-- Error
//
// Note that both code and error exist as strings, even though
// code will report as a number.
func (mg *MailgunImpl) AddBounce(ctx context.Context, address, code, error string) error {
r := newHTTPRequest(generateApiUrl(mg, bouncesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("address", address)
if code != "" {
payload.addValue("code", code)
}
if error != "" {
payload.addValue("error", error)
}
_, err := makePostRequest(ctx, r, payload)
return err
}
// DeleteBounce removes all bounces associted with the provided e-mail address.
func (mg *MailgunImpl) DeleteBounce(ctx context.Context, address string) error {
r := newHTTPRequest(generateApiUrl(mg, bouncesEndpoint) + "/" + address)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}

216
vendor/github.com/mailgun/mailgun-go/v3/credentials.go generated vendored Normal file
View File

@ -0,0 +1,216 @@
package mailgun
import (
"context"
"fmt"
"strconv"
)
// A Credential structure describes a principle allowed to send or receive mail at the domain.
type Credential struct {
CreatedAt RFC2822Time `json:"created_at"`
Login string `json:"login"`
Password string `json:"password"`
}
type credentialsListResponse struct {
// is -1 if Next() or First() have not been called
TotalCount int `json:"total_count"`
Items []Credential `json:"items"`
}
// Returned when a required parameter is missing.
var ErrEmptyParam = fmt.Errorf("empty or illegal parameter")
// ListCredentials returns the (possibly zero-length) list of credentials associated with your domain.
func (mg *MailgunImpl) ListCredentials(opts *ListOptions) *CredentialsIterator {
var limit int
if opts != nil {
limit = opts.Limit
}
if limit == 0 {
limit = 100
}
return &CredentialsIterator{
mg: mg,
url: generateCredentialsUrl(mg, ""),
credentialsListResponse: credentialsListResponse{TotalCount: -1},
limit: limit,
}
}
type CredentialsIterator struct {
credentialsListResponse
limit int
mg Mailgun
offset int
url string
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ri *CredentialsIterator) Err() error {
return ri.err
}
// Offset returns the current offset of the iterator
func (ri *CredentialsIterator) Offset() int {
return ri.offset
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (ri *CredentialsIterator) Next(ctx context.Context, items *[]Credential) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Credential, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
ri.offset = ri.offset + len(ri.Items)
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (ri *CredentialsIterator) First(ctx context.Context, items *[]Credential) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, 0, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Credential, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
ri.offset = len(ri.Items)
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (ri *CredentialsIterator) Last(ctx context.Context, items *[]Credential) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.TotalCount - ri.limit
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Credential, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (ri *CredentialsIterator) Previous(ctx context.Context, items *[]Credential) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.offset - (ri.limit * 2)
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Credential, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
return true
}
func (ri *CredentialsIterator) fetch(ctx context.Context, skip, limit int) error {
r := newHTTPRequest(ri.url)
r.setBasicAuth(basicAuthUser, ri.mg.APIKey())
r.setClient(ri.mg.Client())
if skip != 0 {
r.addParameter("skip", strconv.Itoa(skip))
}
if limit != 0 {
r.addParameter("limit", strconv.Itoa(limit))
}
return getResponseFromJSON(ctx, r, &ri.credentialsListResponse)
}
// CreateCredential attempts to create associate a new principle with your domain.
func (mg *MailgunImpl) CreateCredential(ctx context.Context, login, password string) error {
if (login == "") || (password == "") {
return ErrEmptyParam
}
r := newHTTPRequest(generateCredentialsUrl(mg, ""))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("login", login)
p.addValue("password", password)
_, err := makePostRequest(ctx, r, p)
return err
}
// ChangeCredentialPassword attempts to alter the indicated credential's password.
func (mg *MailgunImpl) ChangeCredentialPassword(ctx context.Context, login, password string) error {
if (login == "") || (password == "") {
return ErrEmptyParam
}
r := newHTTPRequest(generateCredentialsUrl(mg, login))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("password", password)
_, err := makePutRequest(ctx, r, p)
return err
}
// DeleteCredential attempts to remove the indicated principle from the domain.
func (mg *MailgunImpl) DeleteCredential(ctx context.Context, login string) error {
if login == "" {
return ErrEmptyParam
}
r := newHTTPRequest(generateCredentialsUrl(mg, login))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}

386
vendor/github.com/mailgun/mailgun-go/v3/domains.go generated vendored Normal file
View File

@ -0,0 +1,386 @@
package mailgun
import (
"context"
"strconv"
"strings"
)
// Use these to specify a spam action when creating a new domain.
const (
// Tag the received message with headers providing a measure of its spamness.
SpamActionTag = SpamAction("tag")
// Prevents Mailgun from taking any action on what it perceives to be spam.
SpamActionDisabled = SpamAction("disabled")
// instructs Mailgun to just block or delete the message all-together.
SpamActionDelete = SpamAction("delete")
)
type SpamAction string
// A Domain structure holds information about a domain used when sending mail.
type Domain struct {
CreatedAt RFC2822Time `json:"created_at"`
SMTPLogin string `json:"smtp_login"`
Name string `json:"name"`
SMTPPassword string `json:"smtp_password"`
Wildcard bool `json:"wildcard"`
SpamAction SpamAction `json:"spam_action"`
State string `json:"state"`
}
// DNSRecord structures describe intended records to properly configure your domain for use with Mailgun.
// Note that Mailgun does not host DNS records.
type DNSRecord struct {
Priority string
RecordType string `json:"record_type"`
Valid string
Name string
Value string
}
type DomainResponse struct {
Domain Domain `json:"domain"`
ReceivingDNSRecords []DNSRecord `json:"receiving_dns_records"`
SendingDNSRecords []DNSRecord `json:"sending_dns_records"`
}
type domainConnectionResponse struct {
Connection DomainConnection `json:"connection"`
}
type domainsListResponse struct {
// is -1 if Next() or First() have not been called
TotalCount int `json:"total_count"`
Items []Domain `json:"items"`
}
// Specify the domain connection options
type DomainConnection struct {
RequireTLS bool `json:"require_tls"`
SkipVerification bool `json:"skip_verification"`
}
// Specify the domain tracking options
type DomainTracking struct {
Click TrackingStatus `json:"click"`
Open TrackingStatus `json:"open"`
Unsubscribe TrackingStatus `json:"unsubscribe"`
}
// The tracking status of a domain
type TrackingStatus struct {
Active bool `json:"active"`
HTMLFooter string `json:"html_footer"`
TextFooter string `json:"text_footer"`
}
type domainTrackingResponse struct {
Tracking DomainTracking `json:"tracking"`
}
// ListDomains retrieves a set of domains from Mailgun.
func (mg *MailgunImpl) ListDomains(opts *ListOptions) *DomainsIterator {
var limit int
if opts != nil {
limit = opts.Limit
}
if limit == 0 {
limit = 100
}
return &DomainsIterator{
mg: mg,
url: generatePublicApiUrl(mg, domainsEndpoint),
domainsListResponse: domainsListResponse{TotalCount: -1},
limit: limit,
}
}
type DomainsIterator struct {
domainsListResponse
limit int
mg Mailgun
offset int
url string
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ri *DomainsIterator) Err() error {
return ri.err
}
// Offset returns the current offset of the iterator
func (ri *DomainsIterator) Offset() int {
return ri.offset
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (ri *DomainsIterator) Next(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
ri.offset = ri.offset + len(ri.Items)
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (ri *DomainsIterator) First(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, 0, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
ri.offset = len(ri.Items)
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (ri *DomainsIterator) Last(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.TotalCount - ri.limit
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (ri *DomainsIterator) Previous(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.offset - (ri.limit * 2)
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
return true
}
func (ri *DomainsIterator) fetch(ctx context.Context, skip, limit int) error {
r := newHTTPRequest(ri.url)
r.setBasicAuth(basicAuthUser, ri.mg.APIKey())
r.setClient(ri.mg.Client())
if skip != 0 {
r.addParameter("skip", strconv.Itoa(skip))
}
if limit != 0 {
r.addParameter("limit", strconv.Itoa(limit))
}
return getResponseFromJSON(ctx, r, &ri.domainsListResponse)
}
// GetDomain retrieves detailed information about the named domain.
func (mg *MailgunImpl) GetDomain(ctx context.Context, domain string) (DomainResponse, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp DomainResponse
err := getResponseFromJSON(ctx, r, &resp)
return resp, err
}
func (mg *MailgunImpl) VerifyDomain(ctx context.Context, domain string) (string, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/verify")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
var resp DomainResponse
err := putResponseFromJSON(ctx, r, payload, &resp)
return resp.Domain.State, err
}
// Optional parameters when creating a domain
type CreateDomainOptions struct {
Password string
SpamAction SpamAction
Wildcard bool
ForceDKIMAuthority bool
DKIMKeySize int
IPS []string
}
// CreateDomain instructs Mailgun to create a new domain for your account.
// The name parameter identifies the domain.
// The smtpPassword parameter provides an access credential for the domain.
// The spamAction domain must be one of Delete, Tag, or Disabled.
// The wildcard parameter instructs Mailgun to treat all subdomains of this domain uniformly if true,
// and as different domains if false.
func (mg *MailgunImpl) CreateDomain(ctx context.Context, name string, opts *CreateDomainOptions) (DomainResponse, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("name", name)
if opts != nil {
if opts.SpamAction != "" {
payload.addValue("spam_action", string(opts.SpamAction))
}
if opts.Wildcard {
payload.addValue("wildcard", boolToString(opts.Wildcard))
}
if opts.ForceDKIMAuthority {
payload.addValue("force_dkim_authority", boolToString(opts.ForceDKIMAuthority))
}
if opts.DKIMKeySize != 0 {
payload.addValue("dkim_key_size", strconv.Itoa(opts.DKIMKeySize))
}
if len(opts.IPS) != 0 {
payload.addValue("ips", strings.Join(opts.IPS, ","))
}
if len(opts.Password) != 0 {
payload.addValue("smtp_password", opts.Password)
}
}
var resp DomainResponse
err := postResponseFromJSON(ctx, r, payload, &resp)
return resp, err
}
// GetDomainConnection returns delivery connection settings for the defined domain
func (mg *MailgunImpl) GetDomainConnection(ctx context.Context, domain string) (DomainConnection, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/connection")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp domainConnectionResponse
err := getResponseFromJSON(ctx, r, &resp)
return resp.Connection, err
}
// Updates the specified delivery connection settings for the defined domain
func (mg *MailgunImpl) UpdateDomainConnection(ctx context.Context, domain string, settings DomainConnection) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/connection")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("require_tls", boolToString(settings.RequireTLS))
payload.addValue("skip_verification", boolToString(settings.SkipVerification))
_, err := makePutRequest(ctx, r, payload)
return err
}
// DeleteDomain instructs Mailgun to dispose of the named domain name
func (mg *MailgunImpl) DeleteDomain(ctx context.Context, name string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + name)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetDomainTracking returns tracking settings for a domain
func (mg *MailgunImpl) GetDomainTracking(ctx context.Context, domain string) (DomainTracking, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/tracking")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp domainTrackingResponse
err := getResponseFromJSON(ctx, r, &resp)
return resp.Tracking, err
}
func (mg *MailgunImpl) UpdateClickTracking(ctx context.Context, domain, active string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/tracking/click")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("active", active)
_, err := makePutRequest(ctx, r, payload)
return err
}
func (mg *MailgunImpl) UpdateUnsubscribeTracking(ctx context.Context, domain, active, htmlFooter, textFooter string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/tracking/unsubscribe")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("active", active)
payload.addValue("html_footer", htmlFooter)
payload.addValue("text_footer", textFooter)
_, err := makePutRequest(ctx, r, payload)
return err
}
func (mg *MailgunImpl) UpdateOpenTracking(ctx context.Context, domain, active string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/tracking/open")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("active", active)
_, err := makePutRequest(ctx, r, payload)
return err
}
func boolToString(b bool) string {
if b {
return "true"
}
return "false"
}

View File

@ -0,0 +1,171 @@
package mailgun
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"github.com/pkg/errors"
)
// The EmailVerificationParts structure breaks out the basic elements of an email address.
// LocalPart includes everything up to the '@' in an e-mail address.
// Domain includes everything after the '@'.
// DisplayName is no longer used, and will appear as "".
type EmailVerificationParts struct {
LocalPart string `json:"local_part"`
Domain string `json:"domain"`
DisplayName string `json:"display_name"`
}
// EmailVerification records basic facts about a validated e-mail address.
// See the ValidateEmail method and example for more details.
//
type EmailVerification struct {
// Indicates whether an email address conforms to IETF RFC standards.
IsValid bool `json:"is_valid"`
// Indicates whether an email address is deliverable.
MailboxVerification string `json:"mailbox_verification"`
// Parts records the different subfields of the parsed email address
Parts EmailVerificationParts `json:"parts"`
// Echoes the address provided.
Address string `json:"address"`
// Provides a simple recommendation in case the address is invalid or
// Mailgun thinks you might have a typo. May be empty, in which case
// Mailgun has no recommendation to give.
DidYouMean string `json:"did_you_mean"`
// Indicates whether Mailgun thinks the address is from a known
// disposable mailbox provider.
IsDisposableAddress bool `json:"is_disposable_address"`
// Indicates whether Mailgun thinks the address is an email distribution list.
IsRoleAddress bool `json:"is_role_address"`
// A human readable reason the address is reported as invalid
Reason string `json:"reason"`
}
type addressParseResult struct {
Parsed []string `json:"parsed"`
Unparseable []string `json:"unparseable"`
}
type EmailValidator interface {
ValidateEmail(ctx context.Context, email string, mailBoxVerify bool) (EmailVerification, error)
ParseAddresses(ctx context.Context, addresses ...string) ([]string, []string, error)
}
type EmailValidatorImpl struct {
client *http.Client
isPublicKey bool
apiBase string
apiKey string
}
// Creates a new validation instance.
// * If a public key is provided, uses the public validation endpoints
// * If a private key is provided, uses the private validation endpoints
func NewEmailValidator(apiKey string) *EmailValidatorImpl {
isPublicKey := false
// Did the user pass in a public key?
if strings.HasPrefix(apiKey, "pubkey-") {
isPublicKey = true
}
return &EmailValidatorImpl{
client: http.DefaultClient,
isPublicKey: isPublicKey,
apiBase: APIBase,
apiKey: apiKey,
}
}
// NewEmailValidatorFromEnv returns a new EmailValidator using environment variables
// If MG_PUBLIC_API_KEY is set, assume using the free validation subject to daily usage limits
// If only MG_API_KEY is set, assume using the /private validation routes with no daily usage limits
func NewEmailValidatorFromEnv() (*EmailValidatorImpl, error) {
apiKey := os.Getenv("MG_PUBLIC_API_KEY")
if apiKey == "" {
apiKey = os.Getenv("MG_API_KEY")
if apiKey == "" {
return nil, errors.New(
"environment variable MG_PUBLIC_API_KEY or MG_API_KEY required for email validation")
}
}
v := NewEmailValidator(apiKey)
url := os.Getenv("MG_URL")
if url != "" {
v.SetAPIBase(url)
}
return v, nil
}
// APIBase returns the API Base URL configured for this client.
func (m *EmailValidatorImpl) APIBase() string {
return m.apiBase
}
// SetAPIBase updates the API Base URL for this client.
func (m *EmailValidatorImpl) SetAPIBase(address string) {
m.apiBase = address
}
// SetClient updates the HTTP client for this client.
func (m *EmailValidatorImpl) SetClient(c *http.Client) {
m.client = c
}
// Client returns the HTTP client configured for this client.
func (m *EmailValidatorImpl) Client() *http.Client {
return m.client
}
// APIKey returns the API key used for validations
func (m *EmailValidatorImpl) APIKey() string {
return m.apiKey
}
func (m *EmailValidatorImpl) getAddressURL(endpoint string) string {
if m.isPublicKey {
return fmt.Sprintf("%s/address/%s", m.APIBase(), endpoint)
}
return fmt.Sprintf("%s/address/private/%s", m.APIBase(), endpoint)
}
// ValidateEmail performs various checks on the email address provided to ensure it's correctly formatted.
// It may also be used to break an email address into its sub-components. (See example.)
func (m *EmailValidatorImpl) ValidateEmail(ctx context.Context, email string, mailBoxVerify bool) (EmailVerification, error) {
r := newHTTPRequest(m.getAddressURL("validate"))
r.setClient(m.Client())
r.addParameter("address", email)
if mailBoxVerify {
r.addParameter("mailbox_verification", "true")
}
r.setBasicAuth(basicAuthUser, m.APIKey())
var response EmailVerification
err := getResponseFromJSON(ctx, r, &response)
if err != nil {
return EmailVerification{}, err
}
return response, nil
}
// ParseAddresses takes a list of addresses and sorts them into valid and invalid address categories.
// NOTE: Use of this function requires a proper public API key. The private API key will not work.
func (m *EmailValidatorImpl) ParseAddresses(ctx context.Context, addresses ...string) ([]string, []string, error) {
r := newHTTPRequest(m.getAddressURL("parse"))
r.setClient(m.Client())
r.addParameter("addresses", strings.Join(addresses, ","))
r.setBasicAuth(basicAuthUser, m.APIKey())
var response addressParseResult
err := getResponseFromJSON(ctx, r, &response)
if err != nil {
return nil, nil, err
}
return response.Parsed, response.Unparseable, nil
}

282
vendor/github.com/mailgun/mailgun-go/v3/events.go generated vendored Normal file
View File

@ -0,0 +1,282 @@
package mailgun
import (
"context"
"fmt"
"time"
"github.com/mailgun/mailgun-go/v3/events"
"github.com/mailru/easyjson"
)
// ListEventOptions{} modifies the behavior of ListEvents()
type ListEventOptions struct {
// Limits the results to a specific start and end time
Begin, End time.Time
// ForceAscending and ForceDescending are used to force Mailgun to use a given
// traversal order of the events. If both ForceAscending and ForceDescending are
// true, an error will result. If none, the default will be inferred from the Begin
// and End parameters.
ForceAscending, ForceDescending bool
// Compact, if true, compacts the returned JSON to minimize transmission bandwidth.
Compact bool
// Limit caps the number of results returned. If left unspecified, MailGun assumes 100.
Limit int
// Filter allows the caller to provide more specialized filters on the query.
// Consult the Mailgun documentation for more details.
Filter map[string]string
PollInterval time.Duration
}
// EventIterator maintains the state necessary for paging though small parcels of a larger set of events.
type EventIterator struct {
events.Response
mg Mailgun
err error
}
// Create an new iterator to fetch a page of events from the events api
func (mg *MailgunImpl) ListEvents(opts *ListEventOptions) *EventIterator {
req := newHTTPRequest(generateApiUrl(mg, eventsEndpoint))
if opts != nil {
if opts.Limit > 0 {
req.addParameter("limit", fmt.Sprintf("%d", opts.Limit))
}
if opts.Compact {
req.addParameter("pretty", "no")
}
if opts.ForceAscending {
req.addParameter("ascending", "yes")
} else if opts.ForceDescending {
req.addParameter("ascending", "no")
}
if !opts.Begin.IsZero() {
req.addParameter("begin", formatMailgunTime(opts.Begin))
}
if !opts.End.IsZero() {
req.addParameter("end", formatMailgunTime(opts.End))
}
if opts.Filter != nil {
for k, v := range opts.Filter {
req.addParameter(k, v)
}
}
}
url, err := req.generateUrlWithParameters()
return &EventIterator{
mg: mg,
Response: events.Response{Paging: events.Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (ei *EventIterator) Err() error {
return ei.err
}
// Next retrieves the next page of events from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (ei *EventIterator) Next(ctx context.Context, events *[]Event) bool {
if ei.err != nil {
return false
}
ei.err = ei.fetch(ctx, ei.Paging.Next)
if ei.err != nil {
return false
}
*events, ei.err = ParseEvents(ei.Items)
if ei.err != nil {
return false
}
if len(ei.Items) == 0 {
return false
}
return true
}
// First retrieves the first page of events from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (ei *EventIterator) First(ctx context.Context, events *[]Event) bool {
if ei.err != nil {
return false
}
ei.err = ei.fetch(ctx, ei.Paging.First)
if ei.err != nil {
return false
}
*events, ei.err = ParseEvents(ei.Items)
return true
}
// Last retrieves the last page of events from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (ei *EventIterator) Last(ctx context.Context, events *[]Event) bool {
if ei.err != nil {
return false
}
ei.err = ei.fetch(ctx, ei.Paging.Last)
if ei.err != nil {
return false
}
*events, ei.err = ParseEvents(ei.Items)
return true
}
// Previous retrieves the previous page of events from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (ei *EventIterator) Previous(ctx context.Context, events *[]Event) bool {
if ei.err != nil {
return false
}
if ei.Paging.Previous == "" {
return false
}
ei.err = ei.fetch(ctx, ei.Paging.Previous)
if ei.err != nil {
return false
}
*events, ei.err = ParseEvents(ei.Items)
if len(ei.Items) == 0 {
return false
}
return true
}
func (ei *EventIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(ei.mg.Client())
r.setBasicAuth(basicAuthUser, ei.mg.APIKey())
resp, err := makeRequest(ctx, r, "GET", nil)
if err != nil {
return err
}
if err := easyjson.Unmarshal(resp.Data, &ei.Response); err != nil {
return fmt.Errorf("failed to un-marshall event.Response: %s", err)
}
return nil
}
// EventPoller maintains the state necessary for polling events
type EventPoller struct {
it *EventIterator
opts ListEventOptions
thresholdTime time.Time
beginTime time.Time
sleepUntil time.Time
mg Mailgun
err error
}
// Poll the events api and return new events as they occur
// it = mg.PollEvents(&ListEventOptions{
// // Only events with a timestamp after this date/time will be returned
// Begin: time.Now().Add(time.Second * -3),
// // How often we poll the api for new events
// PollInterval: time.Second * 4
// })
//
// var events []Event
// ctx, cancel := context.WithCancel(context.Background())
//
// // Blocks until new events appear or context is cancelled
// for it.Poll(ctx, &events) {
// for _, event := range(events) {
// fmt.Printf("Event %+v\n", event)
// }
// }
// if it.Err() != nil {
// log.Fatal(it.Err())
// }
func (mg *MailgunImpl) PollEvents(opts *ListEventOptions) *EventPoller {
now := time.Now()
// ForceAscending must be set
opts.ForceAscending = true
// Default begin time is 30 minutes ago
if opts.Begin.IsZero() {
opts.Begin = now.Add(time.Minute * -30)
}
// Set a 15 second poll interval if none set
if opts.PollInterval.Nanoseconds() == 0 {
opts.PollInterval = time.Duration(time.Second * 15)
}
return &EventPoller{
it: mg.ListEvents(opts),
opts: *opts,
mg: mg,
}
}
// If an error occurred during polling `Err()` will return non nil
func (ep *EventPoller) Err() error {
return ep.err
}
func (ep *EventPoller) Poll(ctx context.Context, events *[]Event) bool {
var currentPage string
var results []Event
if ep.opts.Begin.IsZero() {
ep.beginTime = time.Now().UTC()
}
for {
// Remember our current page url
currentPage = ep.it.Paging.Next
// Attempt to get a page of events
var page []Event
if ep.it.Next(ctx, &page) == false {
if ep.it.Err() == nil && len(page) == 0 {
// No events, sleep for our poll interval
goto SLEEP
}
ep.err = ep.it.Err()
return false
}
for _, e := range page {
// If any events on the page are older than our being time
if e.GetTimestamp().After(ep.beginTime) {
results = append(results, e)
}
}
// If we have events to return
if len(results) != 0 {
*events = results
results = nil
return true
}
SLEEP:
// Since we didn't find an event older than our
// threshold, fetch this same page again
ep.it.Paging.Next = currentPage
// Sleep the rest of our duration
tick := time.NewTicker(ep.opts.PollInterval)
select {
case <-ctx.Done():
return false
case <-tick.C:
tick.Stop()
}
}
}
// Given time.Time{} return a float64 as given in mailgun event timestamps
func TimeToFloat(t time.Time) float64 {
return float64(t.Unix()) + (float64(t.Nanosecond()/int(time.Microsecond)) / float64(1000000))
}

View File

@ -0,0 +1,57 @@
package events
const (
EventAccepted = "accepted"
EventRejected = "rejected"
EventDelivered = "delivered"
EventFailed = "failed"
EventOpened = "opened"
EventClicked = "clicked"
EventUnsubscribed = "unsubscribed"
EventComplained = "complained"
EventStored = "stored"
EventDropped = "dropped"
EventListMemberUploaded = "list_member_uploaded"
EventListMemberUploadError = "list_member_upload_error"
EventListUploaded = "list_uploaded"
)
const (
TransportHTTP = "http"
TransportSMTP = "smtp"
DeviceUnknown = "unknown"
DeviceMobileBrowser = "desktop"
DeviceBrowser = "mobile"
DeviceEmail = "tablet"
DeviceOther = "other"
ClientUnknown = "unknown"
ClientMobileBrowser = "mobile browser"
ClientBrowser = "browser"
ClientEmail = "email client"
ClientLibrary = "library"
ClientRobot = "robot"
ClientOther = "other"
ReasonUnknown = "unknown"
ReasonGeneric = "generic"
ReasonBounce = "bounce"
ReasonESPBlock = "espblock"
ReasonGreylisted = "greylisted"
ReasonBlacklisted = "blacklisted"
ReasonSuppressBounce = "suppress-bounce"
ReasonSuppressComplaint = "suppress-complaint"
ReasonSuppressUnsubscribe = "suppress-unsubscribe"
ReasonOld = "old"
ReasonHardFail = "hardfail"
SeverityUnknown = "unknown"
SeverityTemporary = "temporary"
SeverityPermanent = "permanent"
SeverityInternal = "internal"
MethodUnknown = "unknown"
MethodSMTP = "smtp"
MethodHTTP = "http"
)

View File

@ -0,0 +1,262 @@
package events
import (
"strings"
"time"
)
// An EventName is a struct with the event name.
type EventName struct {
Name string `json:"event"`
}
// GetName returns the name of the event.
func (e *EventName) GetName() string {
return strings.ToLower(e.Name)
}
func (e *EventName) SetName(name string) {
e.Name = strings.ToLower(name)
}
type Generic struct {
EventName
Timestamp float64 `json:"timestamp"`
ID string `json:"id"`
}
func (g *Generic) GetTimestamp() time.Time {
return time.Unix(0, int64(g.Timestamp*float64(time.Second))).UTC()
}
func (g *Generic) SetTimestamp(t time.Time) {
// convert := fmt.Sprintf("%d.%06d", t.Unix(), t.Nanosecond()/int(time.Microsecond))
// ts, err := strconv.ParseFloat(convert, 64)
g.Timestamp = float64(t.Unix()) + (float64(t.Nanosecond()/int(time.Microsecond)) / float64(1000000))
}
func (g *Generic) GetID() string {
return g.ID
}
func (g *Generic) SetID(id string) {
g.ID = id
}
//
// Message Events
//
type Accepted struct {
Generic
Envelope Envelope `json:"envelope"`
Message Message `json:"message"`
Flags Flags `json:"flags"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Method string `json:"method"`
OriginatingIP string `json:"originating-ip"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
UserVariables map[string]interface{} `json:"user-variables"`
}
type Rejected struct {
Generic
Reject struct {
Reason string `json:"reason"`
Description string `json:"description"`
} `json:"reject"`
Message Message `json:"message"`
Storage Storage `json:"storage"`
Flags Flags `json:"flags"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
UserVariables map[string]interface{} `json:"user-variables"`
}
type Delivered struct {
Generic
Envelope Envelope `json:"envelope"`
Message Message `json:"message"`
Flags Flags `json:"flags"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Method string `json:"method"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
DeliveryStatus DeliveryStatus `json:"delivery-status"`
UserVariables map[string]interface{} `json:"user-variables"`
}
type Failed struct {
Generic
Envelope Envelope `json:"envelope"`
Message Message `json:"message"`
Flags Flags `json:"flags"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Method string `json:"method"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
DeliveryStatus DeliveryStatus `json:"delivery-status"`
Severity string `json:"severity"`
Reason string `json:"reason"`
UserVariables map[string]interface{} `json:"user-variables"`
}
type Stored struct {
Generic
Message Message `json:"message"`
Storage Storage `json:"storage"`
Flags Flags `json:"flags"`
Tags []string `json:"tags"`
Campaigns []Campaign `json:"campaigns"`
UserVariables map[string]interface{} `json:"user-variables"`
}
//
// Message Events (User)
//
type Opened struct {
Generic
Message Message `json:"message"`
Campaigns []Campaign `json:"campaigns"`
MailingList MailingList `json:"mailing-list"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Tags []string `json:"tags"`
IP string `json:"ip"`
ClientInfo ClientInfo `json:"client-info"`
GeoLocation GeoLocation `json:"geolocation"`
UserVariables map[string]interface{} `json:"user-variables"`
}
type Clicked struct {
Generic
Url string `json:"url"`
Message Message `json:"message"`
Campaigns []Campaign `json:"campaigns"`
MailingList MailingList `json:"mailing-list"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Tags []string `json:"tags"`
IP string `json:"ip"`
ClientInfo ClientInfo `json:"client-info"`
GeoLocation GeoLocation `json:"geolocation"`
UserVariables map[string]interface{} `json:"user-variables"`
}
type Unsubscribed struct {
Generic
Message Message `json:"message"`
Campaigns []Campaign `json:"campaigns"`
MailingList MailingList `json:"mailing-list"`
Recipient string `json:"recipient"`
RecipientDomain string `json:"recipient-domain"`
Tags []string `json:"tags"`
IP string `json:"ip"`
ClientInfo ClientInfo `json:"client-info"`
GeoLocation GeoLocation `json:"geolocation"`
UserVariables map[string]interface{} `json:"user-variables"`
}
type Complained struct {
Generic
Message Message `json:"message"`
Campaigns []Campaign `json:"campaigns"`
Recipient string `json:"recipient"`
Tags []string `json:"tags"`
UserVariables map[string]interface{} `json:"user-variables"`
}
//
// Mailing List Events
//
type MailingListMember struct {
Subscribed bool
Address string
Name string
Vars []string
}
type MailingListError struct {
Message string
}
type ListMemberUploaded struct {
Generic
MailingList MailingList `json:"mailing-list"`
Member MailingListMember `json:"member"`
TaskID string `json:"task-id"`
}
type ListMemberUploadError struct {
Generic
MailingList MailingList `json:"mailing-list"`
TaskID string `json:"task-id"`
Format string `json:"format"`
MemberDescription string `json:"member-description"`
Error MailingListError `json:"error"`
}
type ListUploaded struct {
Generic
MailingList MailingList `json:"mailing-list"`
IsUpsert bool `json:"is-upsert"`
Format string `json:"format"`
UpsertedCount int `json:"upserted-count"`
FailedCount int `json:"failed-count"`
Member MailingListMember `json:"member"`
Subscribed bool `json:"subscribed"`
TaskID string `json:"task-id"`
}
type Paging struct {
First string `json:"first,omitempty"`
Next string `json:"next,omitempty"`
Previous string `json:"previous,omitempty"`
Last string `json:"last,omitempty"`
}
type RawJSON []byte
func (v *RawJSON) UnmarshalJSON(data []byte) error {
*v = data
return nil
}
type Response struct {
Items []RawJSON `json:"items"`
Paging Paging `json:"paging"`
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
package events
type ClientInfo struct {
AcceptLanguage string `json:"accept-language"`
ClientName string `json:"client-name"`
ClientOS string `json:"client-os"`
ClientType string `json:"client-type"`
DeviceType string `json:"device-type"`
IP string `json:"ip"`
UserAgent string `json:"user-agent"`
}
type GeoLocation struct {
City string `json:"city"`
Country string `json:"country"`
Region string `json:"region"`
}
type MailingList struct {
Address string `json:"address"`
ListID string `json:"list-id"`
SID string `json:"sid"`
}
type Message struct {
Headers MessageHeaders `json:"headers"`
Attachments []Attachment `json:"attachments"`
Recipients []string `json:"recipients"`
Size int `json:"size"`
}
type Envelope struct {
MailFrom string `json:"mail-from"`
Sender string `json:"sender"`
Transport string `json:"transport"`
Targets string `json:"targets"`
SendingHost string `json:"sending-host"`
SendingIP string `json:"sending-ip"`
}
type Storage struct {
Key string `json:"key"`
URL string `json:"url"`
}
type Flags struct {
IsAuthenticated bool `json:"is-authenticated"`
IsBig bool `json:"is-big"`
IsSystemTest bool `json:"is-system-test"`
IsTestMode bool `json:"is-test-mode"`
IsDelayedBounce bool `json:"is-delayed-bounce"`
}
type Attachment struct {
FileName string `json:"filename"`
ContentType string `json:"content-type"`
Size int `json:"size"`
}
type MessageHeaders struct {
To string `json:"to"`
MessageID string `json:"message-id"`
From string `json:"from"`
Subject string `json:"subject"`
}
type Campaign struct {
ID string `json:"id"`
Name string `json:"name"`
}
type DeliveryStatus struct {
Code int `json:"code"`
AttemptNo int `json:"attempt-no"`
Description string `json:"description"`
Message string `json:"message"`
SessionSeconds float64 `json:"session-seconds"`
}

File diff suppressed because it is too large Load Diff

99
vendor/github.com/mailgun/mailgun-go/v3/exports.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
package mailgun
import (
"context"
"errors"
"fmt"
"net/http"
)
type ExportList struct {
Items []Export `json:"items"`
}
type Export struct {
ID string `json:"id"`
Status string `json:"status"`
URL string `json:"url"`
}
// Create an export based on the URL given
func (mg *MailgunImpl) CreateExport(ctx context.Context, url string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, exportsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("url", url)
_, err := makePostRequest(ctx, r, payload)
return err
}
// List all exports created within the past 24 hours
func (mg *MailgunImpl) ListExports(ctx context.Context, url string) ([]Export, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, exportsEndpoint))
r.setClient(mg.Client())
if url != "" {
r.addParameter("url", url)
}
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp ExportList
if err := getResponseFromJSON(ctx, r, &resp); err != nil {
return nil, err
}
var result []Export
for _, item := range resp.Items {
result = append(result, Export(item))
}
return result, nil
}
// GetExport gets an export by id
func (mg *MailgunImpl) GetExport(ctx context.Context, id string) (Export, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, exportsEndpoint) + "/" + id)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp Export
err := getResponseFromJSON(ctx, r, &resp)
return resp, err
}
// Download an export by ID. This will respond with a '302 Moved'
// with the Location header of temporary S3 URL if it is available.
func (mg *MailgunImpl) GetExportLink(ctx context.Context, id string) (string, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, exportsEndpoint) + "/" + id + "/download_url")
c := mg.Client()
// Ensure the client doesn't attempt to retry
c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return errors.New("redirect")
}
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addHeader("User-Agent", MailgunGoUserAgent)
req, err := r.NewRequest(ctx, "GET", nil)
if err != nil {
return "", err
}
if Debug {
fmt.Println(r.curlString(req, nil))
}
resp, err := r.Client.Do(req)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusFound {
url, err := resp.Location()
if err != nil {
return "", fmt.Errorf("while parsing 302 redirect url: %s", err)
}
return url.String(), nil
}
return "", err
}
return "", fmt.Errorf("expected a 302 response, API returned a '%d' instead", resp.StatusCode)
}

15
vendor/github.com/mailgun/mailgun-go/v3/go.mod generated vendored Normal file
View File

@ -0,0 +1,15 @@
module github.com/mailgun/mailgun-go/v3
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/go-chi/chi v4.0.0+incompatible
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329
github.com/pkg/errors v0.8.1
)
replace github.com/mailgun/mailgun-go/v3/events => ./events
go 1.13

14
vendor/github.com/mailgun/mailgun-go/v3/go.sum generated vendored Normal file
View File

@ -0,0 +1,14 @@
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/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuYrgaRcnW4=
github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

330
vendor/github.com/mailgun/mailgun-go/v3/httphelpers.go generated vendored Normal file
View File

@ -0,0 +1,330 @@
package mailgun
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"os"
"path"
"regexp"
"strings"
)
var validURL = regexp.MustCompile(`^/v[2-4].*`)
type httpRequest struct {
URL string
Parameters map[string][]string
Headers map[string]string
BasicAuthUser string
BasicAuthPassword string
Client *http.Client
}
type httpResponse struct {
Code int
Data []byte
}
type payload interface {
getPayloadBuffer() (*bytes.Buffer, error)
getContentType() string
getValues() []keyValuePair
}
type keyValuePair struct {
key string
value string
}
type keyNameRC struct {
key string
name string
value io.ReadCloser
}
type keyNameBuff struct {
key string
name string
value []byte
}
type formDataPayload struct {
contentType string
Values []keyValuePair
Files []keyValuePair
ReadClosers []keyNameRC
Buffers []keyNameBuff
}
type urlEncodedPayload struct {
Values []keyValuePair
}
func newHTTPRequest(url string) *httpRequest {
return &httpRequest{URL: url, Client: http.DefaultClient}
}
func (r *httpRequest) addParameter(name, value string) {
if r.Parameters == nil {
r.Parameters = make(map[string][]string)
}
r.Parameters[name] = append(r.Parameters[name], value)
}
func (r *httpRequest) setClient(c *http.Client) {
r.Client = c
}
func (r *httpRequest) setBasicAuth(user, password string) {
r.BasicAuthUser = user
r.BasicAuthPassword = password
}
func newUrlEncodedPayload() *urlEncodedPayload {
return &urlEncodedPayload{}
}
func (f *urlEncodedPayload) addValue(key, value string) {
f.Values = append(f.Values, keyValuePair{key: key, value: value})
}
func (f *urlEncodedPayload) getPayloadBuffer() (*bytes.Buffer, error) {
data := url.Values{}
for _, keyVal := range f.Values {
data.Add(keyVal.key, keyVal.value)
}
return bytes.NewBufferString(data.Encode()), nil
}
func (f *urlEncodedPayload) getContentType() string {
return "application/x-www-form-urlencoded"
}
func (f *urlEncodedPayload) getValues() []keyValuePair {
return f.Values
}
func (r *httpResponse) parseFromJSON(v interface{}) error {
return json.Unmarshal(r.Data, v)
}
func newFormDataPayload() *formDataPayload {
return &formDataPayload{}
}
func (f *formDataPayload) getValues() []keyValuePair {
return f.Values
}
func (f *formDataPayload) addValue(key, value string) {
f.Values = append(f.Values, keyValuePair{key: key, value: value})
}
func (f *formDataPayload) addFile(key, file string) {
f.Files = append(f.Files, keyValuePair{key: key, value: file})
}
func (f *formDataPayload) addBuffer(key, file string, buff []byte) {
f.Buffers = append(f.Buffers, keyNameBuff{key: key, name: file, value: buff})
}
func (f *formDataPayload) addReadCloser(key, name string, rc io.ReadCloser) {
f.ReadClosers = append(f.ReadClosers, keyNameRC{key: key, name: name, value: rc})
}
func (f *formDataPayload) getPayloadBuffer() (*bytes.Buffer, error) {
data := &bytes.Buffer{}
writer := multipart.NewWriter(data)
defer writer.Close()
for _, keyVal := range f.Values {
if tmp, err := writer.CreateFormField(keyVal.key); err == nil {
tmp.Write([]byte(keyVal.value))
} else {
return nil, err
}
}
for _, file := range f.Files {
if tmp, err := writer.CreateFormFile(file.key, path.Base(file.value)); err == nil {
if fp, err := os.Open(file.value); err == nil {
defer fp.Close()
io.Copy(tmp, fp)
} else {
return nil, err
}
} else {
return nil, err
}
}
for _, file := range f.ReadClosers {
if tmp, err := writer.CreateFormFile(file.key, file.name); err == nil {
defer file.value.Close()
io.Copy(tmp, file.value)
} else {
return nil, err
}
}
for _, buff := range f.Buffers {
if tmp, err := writer.CreateFormFile(buff.key, buff.name); err == nil {
r := bytes.NewReader(buff.value)
io.Copy(tmp, r)
} else {
return nil, err
}
}
f.contentType = writer.FormDataContentType()
return data, nil
}
func (f *formDataPayload) getContentType() string {
if f.contentType == "" {
f.getPayloadBuffer()
}
return f.contentType
}
func (r *httpRequest) addHeader(name, value string) {
if r.Headers == nil {
r.Headers = make(map[string]string)
}
r.Headers[name] = value
}
func (r *httpRequest) makeGetRequest(ctx context.Context) (*httpResponse, error) {
return r.makeRequest(ctx, "GET", nil)
}
func (r *httpRequest) makePostRequest(ctx context.Context, payload payload) (*httpResponse, error) {
return r.makeRequest(ctx, "POST", payload)
}
func (r *httpRequest) makePutRequest(ctx context.Context, payload payload) (*httpResponse, error) {
return r.makeRequest(ctx, "PUT", payload)
}
func (r *httpRequest) makeDeleteRequest(ctx context.Context) (*httpResponse, error) {
return r.makeRequest(ctx, "DELETE", nil)
}
func (r *httpRequest) NewRequest(ctx context.Context, method string, payload payload) (*http.Request, error) {
url, err := r.generateUrlWithParameters()
if err != nil {
return nil, err
}
var body io.Reader
if payload != nil {
if body, err = payload.getPayloadBuffer(); err != nil {
return nil, err
}
} else {
body = nil
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if payload != nil && payload.getContentType() != "" {
req.Header.Add("Content-Type", payload.getContentType())
}
if r.BasicAuthUser != "" && r.BasicAuthPassword != "" {
req.SetBasicAuth(r.BasicAuthUser, r.BasicAuthPassword)
}
for header, value := range r.Headers {
req.Header.Add(header, value)
}
return req, nil
}
func (r *httpRequest) makeRequest(ctx context.Context, method string, payload payload) (*httpResponse, error) {
req, err := r.NewRequest(ctx, method, payload)
if err != nil {
return nil, err
}
if Debug {
fmt.Println(r.curlString(req, payload))
}
response := httpResponse{}
resp, err := r.Client.Do(req)
if resp != nil {
response.Code = resp.StatusCode
}
if err != nil {
if urlErr, ok := err.(*url.Error); ok {
if urlErr.Err == io.EOF {
return nil, errors.Wrap(err, "remote server prematurely closed connection")
}
}
return nil, errors.Wrap(err, "while making http request")
}
defer resp.Body.Close()
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "while reading response body")
}
response.Data = responseBody
return &response, nil
}
func (r *httpRequest) generateUrlWithParameters() (string, error) {
url, err := url.Parse(r.URL)
if err != nil {
return "", err
}
if !validURL.MatchString(url.Path) {
return "", errors.New(`BaseAPI must end with a /v2, /v3 or /v4; setBaseAPI("https://host/v3")`)
}
q := url.Query()
if r.Parameters != nil && len(r.Parameters) > 0 {
for name, values := range r.Parameters {
for _, value := range values {
q.Add(name, value)
}
}
}
url.RawQuery = q.Encode()
return url.String(), nil
}
func (r *httpRequest) curlString(req *http.Request, p payload) string {
parts := []string{"curl", "-i", "-X", req.Method, req.URL.String()}
for key, value := range req.Header {
parts = append(parts, fmt.Sprintf("-H \"%s: %s\"", key, value[0]))
}
//parts = append(parts, fmt.Sprintf(" --user '%s:%s'", r.BasicAuthUser, r.BasicAuthPassword))
if p != nil {
for _, param := range p.getValues() {
parts = append(parts, fmt.Sprintf(" -F %s='%s'", param.key, param.value))
}
}
return strings.Join(parts, " ")
}

87
vendor/github.com/mailgun/mailgun-go/v3/ips.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
package mailgun
import "context"
type ipAddressListResponse struct {
TotalCount int `json:"total_count"`
Items []string `json:"items"`
}
type IPAddress struct {
IP string `json:"ip"`
RDNS string `json:"rdns"`
Dedicated bool `json:"dedicated"`
}
type okResp struct {
ID string `json:"id,omitempty"`
Message string `json:"message"`
}
// ListIPS returns a list of IPs assigned to your account
func (mg *MailgunImpl) ListIPS(ctx context.Context, dedicated bool) ([]IPAddress, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, ipsEndpoint))
r.setClient(mg.Client())
if dedicated {
r.addParameter("dedicated", "true")
}
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp ipAddressListResponse
if err := getResponseFromJSON(ctx, r, &resp); err != nil {
return nil, err
}
var result []IPAddress
for _, ip := range resp.Items {
result = append(result, IPAddress{IP: ip})
}
return result, nil
}
// GetIP returns information about the specified IP
func (mg *MailgunImpl) GetIP(ctx context.Context, ip string) (IPAddress, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, ipsEndpoint) + "/" + ip)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp IPAddress
err := getResponseFromJSON(ctx, r, &resp)
return resp, err
}
// ListDomainIPS returns a list of IPs currently assigned to the specified domain.
func (mg *MailgunImpl) ListDomainIPS(ctx context.Context) ([]IPAddress, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + mg.domain + "/ips")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp ipAddressListResponse
if err := getResponseFromJSON(ctx, r, &resp); err != nil {
return nil, err
}
var result []IPAddress
for _, ip := range resp.Items {
result = append(result, IPAddress{IP: ip})
}
return result, nil
}
// Assign a dedicated IP to the domain specified.
func (mg *MailgunImpl) AddDomainIP(ctx context.Context, ip string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + mg.domain + "/ips")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("ip", ip)
_, err := makePostRequest(ctx, r, payload)
return err
}
// Unassign an IP from the domain specified.
func (mg *MailgunImpl) DeleteDomainIP(ctx context.Context, ip string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + mg.domain + "/ips/" + ip)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}

18
vendor/github.com/mailgun/mailgun-go/v3/limits.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
package mailgun
import "context"
type TagLimits struct {
Limit int `json:"limit"`
Count int `json:"count"`
}
// GetTagLimits returns tracking settings for a domain
func (mg *MailgunImpl) GetTagLimits(ctx context.Context, domain string) (TagLimits, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint) + "/" + domain + "/limits/tag")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp TagLimits
err := getResponseFromJSON(ctx, r, &resp)
return resp, err
}

392
vendor/github.com/mailgun/mailgun-go/v3/mailgun.go generated vendored Normal file
View File

@ -0,0 +1,392 @@
// Package mailgun provides methods for interacting with the Mailgun API. It
// automates the HTTP request/response cycle, encodings, and other details
// needed by the API. This SDK lets you do everything the API lets you, in a
// more Go-friendly way.
//
// For further information please see the Mailgun documentation at
// http://documentation.mailgun.com/
//
// Original Author: Michael Banzon
// Contributions: Samuel A. Falvo II <sam.falvo %at% rackspace.com>
// Derrick J. Wippler <thrawn01 %at% gmail.com>
//
// Examples
//
// All functions and method have a corresponding test, so if you don't find an
// example for a function you'd like to know more about, please check for a
// corresponding test. Of course, contributions to the documentation are always
// welcome as well. Feel free to submit a pull request or open a Github issue
// if you cannot find an example to suit your needs.
//
// List iterators
//
// Most methods that begin with `List` return an iterator which simplfies
// paging through large result sets returned by the mailgun API. Most `List`
// methods allow you to specify a `Limit` parameter which as you'd expect,
// limits the number of items returned per page. Note that, at present,
// Mailgun imposes its own cap of 100 items per page, for all API endpoints.
//
// For example, the following iterates over all pages of events 100 items at a time
//
// mg := mailgun.NewMailgun("your-domain.com", "your-api-key")
// it := mg.ListEvents(&mailgun.ListEventOptions{Limit: 100})
//
// // The entire operation should not take longer than 30 seconds
// ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
// defer cancel()
//
// // For each page of 100 events
// var page []mailgun.Event
// for it.Next(ctx, &page) {
// for _, e := range page {
// // Do something with 'e'
// }
// }
//
//
// License
//
// Copyright (c) 2013-2019, Michael Banzon.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// * Neither the names of Mailgun, Michael Banzon, nor the names of their
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package mailgun
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"time"
)
// Set true to write the HTTP requests in curl for to stdout
var Debug = false
const (
// Base Url the library uses to contact mailgun. Use SetAPIBase() to override
APIBase = "https://api.mailgun.net/v3"
APIBaseUS = APIBase
APIBaseEU = "https://api.eu.mailgun.net/v3"
messagesEndpoint = "messages"
mimeMessagesEndpoint = "messages.mime"
bouncesEndpoint = "bounces"
statsTotalEndpoint = "stats/total"
domainsEndpoint = "domains"
tagsEndpoint = "tags"
eventsEndpoint = "events"
unsubscribesEndpoint = "unsubscribes"
routesEndpoint = "routes"
ipsEndpoint = "ips"
exportsEndpoint = "exports"
webhooksEndpoint = "webhooks"
listsEndpoint = "lists"
basicAuthUser = "api"
templatesEndpoint = "templates"
)
// Mailgun defines the supported subset of the Mailgun API.
// The Mailgun API may contain additional features which have been deprecated since writing this SDK.
// This SDK only covers currently supported interface endpoints.
//
// Note that Mailgun reserves the right to deprecate endpoints.
// Some endpoints listed in this interface may, at any time, become obsolete.
// Always double-check with the Mailgun API Documentation to
// determine the currently supported feature set.
type Mailgun interface {
APIBase() string
Domain() string
APIKey() string
Client() *http.Client
SetClient(client *http.Client)
SetAPIBase(url string)
Send(ctx context.Context, m *Message) (string, string, error)
ReSend(ctx context.Context, id string, recipients ...string) (string, string, error)
NewMessage(from, subject, text string, to ...string) *Message
NewMIMEMessage(body io.ReadCloser, to ...string) *Message
ListBounces(opts *ListOptions) *BouncesIterator
GetBounce(ctx context.Context, address string) (Bounce, error)
AddBounce(ctx context.Context, address, code, err string) error
DeleteBounce(ctx context.Context, address string) error
GetStats(ctx context.Context, events []string, opts *GetStatOptions) ([]Stats, error)
GetTag(ctx context.Context, tag string) (Tag, error)
DeleteTag(ctx context.Context, tag string) error
ListTags(*ListTagOptions) *TagIterator
ListDomains(opts *ListOptions) *DomainsIterator
GetDomain(ctx context.Context, domain string) (DomainResponse, error)
CreateDomain(ctx context.Context, name string, opts *CreateDomainOptions) (DomainResponse, error)
DeleteDomain(ctx context.Context, name string) error
VerifyDomain(ctx context.Context, name string) (string, error)
UpdateDomainConnection(ctx context.Context, domain string, dc DomainConnection) error
GetDomainConnection(ctx context.Context, domain string) (DomainConnection, error)
GetDomainTracking(ctx context.Context, domain string) (DomainTracking, error)
UpdateClickTracking(ctx context.Context, domain, active string) error
UpdateUnsubscribeTracking(ctx context.Context, domain, active, htmlFooter, textFooter string) error
UpdateOpenTracking(ctx context.Context, domain, active string) error
GetStoredMessage(ctx context.Context, url string) (StoredMessage, error)
GetStoredMessageRaw(ctx context.Context, id string) (StoredMessageRaw, error)
GetStoredAttachment(ctx context.Context, url string) ([]byte, error)
// Deprecated
GetStoredMessageForURL(ctx context.Context, url string) (StoredMessage, error)
// Deprecated
GetStoredMessageRawForURL(ctx context.Context, url string) (StoredMessageRaw, error)
ListCredentials(opts *ListOptions) *CredentialsIterator
CreateCredential(ctx context.Context, login, password string) error
ChangeCredentialPassword(ctx context.Context, login, password string) error
DeleteCredential(ctx context.Context, login string) error
ListUnsubscribes(opts *ListOptions) *UnsubscribesIterator
GetUnsubscribe(ctx context.Context, address string) (Unsubscribe, error)
CreateUnsubscribe(ctx context.Context, address, tag string) error
DeleteUnsubscribe(ctx context.Context, address string) error
DeleteUnsubscribeWithTag(ctx context.Context, a, t string) error
ListComplaints(opts *ListOptions) *ComplaintsIterator
GetComplaint(ctx context.Context, address string) (Complaint, error)
CreateComplaint(ctx context.Context, address string) error
DeleteComplaint(ctx context.Context, address string) error
ListRoutes(opts *ListOptions) *RoutesIterator
GetRoute(ctx context.Context, address string) (Route, error)
CreateRoute(ctx context.Context, address Route) (Route, error)
DeleteRoute(ctx context.Context, address string) error
UpdateRoute(ctx context.Context, address string, r Route) (Route, error)
ListWebhooks(ctx context.Context) (map[string][]string, error)
CreateWebhook(ctx context.Context, kind string, url []string) error
DeleteWebhook(ctx context.Context, kind string) error
GetWebhook(ctx context.Context, kind string) ([]string, error)
UpdateWebhook(ctx context.Context, kind string, url []string) error
VerifyWebhookRequest(req *http.Request) (verified bool, err error)
VerifyWebhookSignature(sig Signature) (verified bool, err error)
ListMailingLists(opts *ListOptions) *ListsIterator
CreateMailingList(ctx context.Context, address MailingList) (MailingList, error)
DeleteMailingList(ctx context.Context, address string) error
GetMailingList(ctx context.Context, address string) (MailingList, error)
UpdateMailingList(ctx context.Context, address string, ml MailingList) (MailingList, error)
ListMembers(address string, opts *ListOptions) *MemberListIterator
GetMember(ctx context.Context, MemberAddr, listAddr string) (Member, error)
CreateMember(ctx context.Context, merge bool, addr string, prototype Member) error
CreateMemberList(ctx context.Context, subscribed *bool, addr string, newMembers []interface{}) error
UpdateMember(ctx context.Context, Member, list string, prototype Member) (Member, error)
DeleteMember(ctx context.Context, Member, list string) error
ListEvents(*ListEventOptions) *EventIterator
PollEvents(*ListEventOptions) *EventPoller
ListIPS(ctx context.Context, dedicated bool) ([]IPAddress, error)
GetIP(ctx context.Context, ip string) (IPAddress, error)
ListDomainIPS(ctx context.Context) ([]IPAddress, error)
AddDomainIP(ctx context.Context, ip string) error
DeleteDomainIP(ctx context.Context, ip string) error
ListExports(ctx context.Context, url string) ([]Export, error)
GetExport(ctx context.Context, id string) (Export, error)
GetExportLink(ctx context.Context, id string) (string, error)
CreateExport(ctx context.Context, url string) error
GetTagLimits(ctx context.Context, domain string) (TagLimits, error)
CreateTemplate(ctx context.Context, template *Template) error
GetTemplate(ctx context.Context, name string) (Template, error)
UpdateTemplate(ctx context.Context, template *Template) error
DeleteTemplate(ctx context.Context, name string) error
ListTemplates(opts *ListTemplateOptions) *TemplatesIterator
AddTemplateVersion(ctx context.Context, templateName string, version *TemplateVersion) error
GetTemplateVersion(ctx context.Context, templateName, tag string) (TemplateVersion, error)
UpdateTemplateVersion(ctx context.Context, templateName string, version *TemplateVersion) error
DeleteTemplateVersion(ctx context.Context, templateName, tag string) error
ListTemplateVersions(templateName string, opts *ListOptions) *TemplateVersionsIterator
}
// MailgunImpl bundles data needed by a large number of methods in order to interact with the Mailgun API.
// Colloquially, we refer to instances of this structure as "clients."
type MailgunImpl struct {
apiBase string
domain string
apiKey string
client *http.Client
baseURL string
}
// NewMailGun creates a new client instance.
func NewMailgun(domain, apiKey string) *MailgunImpl {
return &MailgunImpl{
apiBase: APIBase,
domain: domain,
apiKey: apiKey,
client: http.DefaultClient,
}
}
// NewMailgunFromEnv returns a new Mailgun client using the environment variables
// MG_API_KEY, MG_DOMAIN, and MG_URL
func NewMailgunFromEnv() (*MailgunImpl, error) {
apiKey := os.Getenv("MG_API_KEY")
if apiKey == "" {
return nil, errors.New("required environment variable MG_API_KEY not defined")
}
domain := os.Getenv("MG_DOMAIN")
if domain == "" {
return nil, errors.New("required environment variable MG_DOMAIN not defined")
}
mg := NewMailgun(domain, apiKey)
url := os.Getenv("MG_URL")
if url != "" {
mg.SetAPIBase(url)
}
return mg, nil
}
// APIBase returns the API Base URL configured for this client.
func (mg *MailgunImpl) APIBase() string {
return mg.apiBase
}
// Domain returns the domain configured for this client.
func (mg *MailgunImpl) Domain() string {
return mg.domain
}
// ApiKey returns the API key configured for this client.
func (mg *MailgunImpl) APIKey() string {
return mg.apiKey
}
// Client returns the HTTP client configured for this client.
func (mg *MailgunImpl) Client() *http.Client {
return mg.client
}
// SetClient updates the HTTP client for this client.
func (mg *MailgunImpl) SetClient(c *http.Client) {
mg.client = c
}
// SetAPIBase updates the API Base URL for this client.
// // For EU Customers
// mg.SetAPIBase(mailgun.APIBaseEU)
//
// // For US Customers
// mg.SetAPIBase(mailgun.APIBaseUS)
//
// // Set a custom base API
// mg.SetAPIBase("https://localhost/v3")
func (mg *MailgunImpl) SetAPIBase(address string) {
mg.apiBase = address
}
// generateApiUrl renders a URL for an API endpoint using the domain and endpoint name.
func generateApiUrl(m Mailgun, endpoint string) string {
return fmt.Sprintf("%s/%s/%s", m.APIBase(), m.Domain(), endpoint)
}
// generateApiUrlWithDomain renders a URL for an API endpoint using a separate domain and endpoint name.
func generateApiUrlWithDomain(m Mailgun, endpoint, domain string) string {
return fmt.Sprintf("%s/%s/%s", m.APIBase(), domain, endpoint)
}
// generateMemberApiUrl renders a URL relevant for specifying mailing list members.
// The address parameter refers to the mailing list in question.
func generateMemberApiUrl(m Mailgun, endpoint, address string) string {
return fmt.Sprintf("%s/%s/%s/members", m.APIBase(), endpoint, address)
}
// generateApiUrlWithTarget works as generateApiUrl,
// but consumes an additional resource parameter called 'target'.
func generateApiUrlWithTarget(m Mailgun, endpoint, target string) string {
tail := ""
if target != "" {
tail = fmt.Sprintf("/%s", target)
}
return fmt.Sprintf("%s%s", generateApiUrl(m, endpoint), tail)
}
// generateDomainApiUrl renders a URL as generateApiUrl, but
// addresses a family of functions which have a non-standard URL structure.
// Most URLs consume a domain in the 2nd position, but some endpoints
// require the word "domains" to be there instead.
func generateDomainApiUrl(m Mailgun, endpoint string) string {
return fmt.Sprintf("%s/domains/%s/%s", m.APIBase(), m.Domain(), endpoint)
}
// generateCredentialsUrl renders a URL as generateDomainApiUrl,
// but focuses on the SMTP credentials family of API functions.
func generateCredentialsUrl(m Mailgun, login string) string {
tail := ""
if login != "" {
tail = fmt.Sprintf("/%s", login)
}
return generateDomainApiUrl(m, fmt.Sprintf("credentials%s", tail))
// return fmt.Sprintf("%s/domains/%s/credentials%s", apiBase, m.Domain(), tail)
}
// generateStoredMessageUrl generates the URL needed to acquire a copy of a stored message.
func generateStoredMessageUrl(m Mailgun, endpoint, id string) string {
return generateDomainApiUrl(m, fmt.Sprintf("%s/%s", endpoint, id))
// return fmt.Sprintf("%s/domains/%s/%s/%s", apiBase, m.Domain(), endpoint, id)
}
// generatePublicApiUrl works as generateApiUrl, except that generatePublicApiUrl has no need for the domain.
func generatePublicApiUrl(m Mailgun, endpoint string) string {
return fmt.Sprintf("%s/%s", m.APIBase(), endpoint)
}
// generateParameterizedUrl works as generateApiUrl, but supports query parameters.
func generateParameterizedUrl(m Mailgun, endpoint string, payload payload) (string, error) {
paramBuffer, err := payload.getPayloadBuffer()
if err != nil {
return "", err
}
params := string(paramBuffer.Bytes())
return fmt.Sprintf("%s?%s", generateApiUrl(m, eventsEndpoint), params), nil
}
// parseMailgunTime translates a timestamp as returned by Mailgun into a Go standard timestamp.
func parseMailgunTime(ts string) (t time.Time, err error) {
t, err = time.Parse("Mon, 2 Jan 2006 15:04:05 MST", ts)
return
}
// formatMailgunTime translates a timestamp into a human-readable form.
func formatMailgunTime(t time.Time) string {
return t.Format("Mon, 2 Jan 2006 15:04:05 -0700")
}

View File

@ -0,0 +1,249 @@
package mailgun
import (
"context"
"strconv"
)
// A mailing list may have one of three membership modes.
const (
// ReadOnly specifies that nobody, including Members, may send messages to
// the mailing list. Messages distributed on such lists come from list
// administrator accounts only.
AccessLevelReadOnly = "readonly"
// Members specifies that only those who subscribe to the mailing list may
// send messages.
AccessLevelMembers = "members"
// Everyone specifies that anyone and everyone may both read and submit
// messages to the mailing list, including non-subscribers.
AccessLevelEveryone = "everyone"
)
// Specify the access of a mailing list member
type AccessLevel string
// A List structure provides information for a mailing list.
//
// AccessLevel may be one of ReadOnly, Members, or Everyone.
type MailingList struct {
Address string `json:"address,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AccessLevel AccessLevel `json:"access_level,omitempty"`
CreatedAt RFC2822Time `json:"created_at,omitempty"`
MembersCount int `json:"members_count,omitempty"`
}
type listsResponse struct {
Items []MailingList `json:"items"`
Paging Paging `json:"paging"`
}
type mailingListResponse struct {
MailingList MailingList `json:"member"`
}
type ListsIterator struct {
listsResponse
mg Mailgun
err error
}
// ListMailingLists returns the specified set of mailing lists administered by your account.
func (mg *MailgunImpl) ListMailingLists(opts *ListOptions) *ListsIterator {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/pages")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
if opts != nil {
if opts.Limit != 0 {
r.addParameter("limit", strconv.Itoa(opts.Limit))
}
}
url, err := r.generateUrlWithParameters()
return &ListsIterator{
mg: mg,
listsResponse: listsResponse{Paging: Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (li *ListsIterator) Err() error {
return li.err
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (li *ListsIterator) Next(ctx context.Context, items *[]MailingList) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Next)
if li.err != nil {
return false
}
cpy := make([]MailingList, len(li.Items))
copy(cpy, li.Items)
*items = cpy
if len(li.Items) == 0 {
return false
}
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (li *ListsIterator) First(ctx context.Context, items *[]MailingList) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.First)
if li.err != nil {
return false
}
cpy := make([]MailingList, len(li.Items))
copy(cpy, li.Items)
*items = cpy
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (li *ListsIterator) Last(ctx context.Context, items *[]MailingList) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Last)
if li.err != nil {
return false
}
cpy := make([]MailingList, len(li.Items))
copy(cpy, li.Items)
*items = cpy
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (li *ListsIterator) Previous(ctx context.Context, items *[]MailingList) bool {
if li.err != nil {
return false
}
if li.Paging.Previous == "" {
return false
}
li.err = li.fetch(ctx, li.Paging.Previous)
if li.err != nil {
return false
}
cpy := make([]MailingList, len(li.Items))
copy(cpy, li.Items)
*items = cpy
if len(li.Items) == 0 {
return false
}
return true
}
func (li *ListsIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(li.mg.Client())
r.setBasicAuth(basicAuthUser, li.mg.APIKey())
return getResponseFromJSON(ctx, r, &li.listsResponse)
}
// CreateMailingList creates a new mailing list under your Mailgun account.
// You need specify only the Address and Name members of the prototype;
// Description, and AccessLevel are optional.
// If unspecified, Description remains blank,
// while AccessLevel defaults to Everyone.
func (mg *MailgunImpl) CreateMailingList(ctx context.Context, prototype MailingList) (MailingList, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if prototype.Address != "" {
p.addValue("address", prototype.Address)
}
if prototype.Name != "" {
p.addValue("name", prototype.Name)
}
if prototype.Description != "" {
p.addValue("description", prototype.Description)
}
if prototype.AccessLevel != "" {
p.addValue("access_level", string(prototype.AccessLevel))
}
response, err := makePostRequest(ctx, r, p)
if err != nil {
return MailingList{}, err
}
var l MailingList
err = response.parseFromJSON(&l)
return l, err
}
// DeleteMailingList removes all current members of the list, then removes the list itself.
// Attempts to send e-mail to the list will fail subsequent to this call.
func (mg *MailgunImpl) DeleteMailingList(ctx context.Context, addr string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetMailingList allows your application to recover the complete List structure
// representing a mailing list, so long as you have its e-mail address.
func (mg *MailgunImpl) GetMailingList(ctx context.Context, addr string) (MailingList, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
response, err := makeGetRequest(ctx, r)
if err != nil {
return MailingList{}, err
}
var resp mailingListResponse
err = response.parseFromJSON(&resp)
return resp.MailingList, err
}
// UpdateMailingList allows you to change various attributes of a list.
// Address, Name, Description, and AccessLevel are all optional;
// only those fields which are set in the prototype will change.
//
// Be careful! If changing the address of a mailing list,
// e-mail sent to the old address will not succeed.
// Make sure you account for the change accordingly.
func (mg *MailgunImpl) UpdateMailingList(ctx context.Context, addr string, prototype MailingList) (MailingList, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, listsEndpoint) + "/" + addr)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if prototype.Address != "" {
p.addValue("address", prototype.Address)
}
if prototype.Name != "" {
p.addValue("name", prototype.Name)
}
if prototype.Description != "" {
p.addValue("description", prototype.Description)
}
if prototype.AccessLevel != "" {
p.addValue("access_level", string(prototype.AccessLevel))
}
var l MailingList
response, err := makePutRequest(ctx, r, p)
if err != nil {
return l, err
}
err = response.parseFromJSON(&l)
return l, err
}

264
vendor/github.com/mailgun/mailgun-go/v3/members.go generated vendored Normal file
View File

@ -0,0 +1,264 @@
package mailgun
import (
"context"
"encoding/json"
"strconv"
)
// yes and no are variables which provide us the ability to take their addresses.
// Subscribed and Unsubscribed are pointers to these booleans.
//
// We use a pointer to boolean as a kind of trinary data type:
// if nil, the relevant data type remains unspecified.
// Otherwise, its value is either true or false.
var (
yes bool = true
no bool = false
)
// Mailing list members have an attribute that determines if they've subscribed to the mailing list or not.
// This attribute may be used to filter the results returned by GetSubscribers().
// All, Subscribed, and Unsubscribed provides a convenient and readable syntax for specifying the scope of the search.
var (
All *bool = nil
Subscribed *bool = &yes
Unsubscribed *bool = &no
)
// A Member structure represents a member of the mailing list.
// The Vars field can represent any JSON-encodable data.
type Member struct {
Address string `json:"address,omitempty"`
Name string `json:"name,omitempty"`
Subscribed *bool `json:"subscribed,omitempty"`
Vars map[string]interface{} `json:"vars,omitempty"`
}
type memberListResponse struct {
Lists []Member `json:"items"`
Paging Paging `json:"paging"`
}
type memberResponse struct {
Member Member `json:"member"`
}
type MemberListIterator struct {
memberListResponse
mg Mailgun
err error
}
// Used by List methods to specify what list parameters to send to the mailgun API
type ListOptions struct {
Limit int
}
func (mg *MailgunImpl) ListMembers(address string, opts *ListOptions) *MemberListIterator {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, address) + "/pages")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
if opts != nil {
if opts.Limit != 0 {
r.addParameter("limit", strconv.Itoa(opts.Limit))
}
}
url, err := r.generateUrlWithParameters()
return &MemberListIterator{
mg: mg,
memberListResponse: memberListResponse{Paging: Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (li *MemberListIterator) Err() error {
return li.err
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (li *MemberListIterator) Next(ctx context.Context, items *[]Member) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Next)
if li.err != nil {
return false
}
*items = li.Lists
if len(li.Lists) == 0 {
return false
}
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (li *MemberListIterator) First(ctx context.Context, items *[]Member) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.First)
if li.err != nil {
return false
}
*items = li.Lists
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (li *MemberListIterator) Last(ctx context.Context, items *[]Member) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Last)
if li.err != nil {
return false
}
*items = li.Lists
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (li *MemberListIterator) Previous(ctx context.Context, items *[]Member) bool {
if li.err != nil {
return false
}
if li.Paging.Previous == "" {
return false
}
li.err = li.fetch(ctx, li.Paging.Previous)
if li.err != nil {
return false
}
*items = li.Lists
if len(li.Lists) == 0 {
return false
}
return true
}
func (li *MemberListIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(li.mg.Client())
r.setBasicAuth(basicAuthUser, li.mg.APIKey())
return getResponseFromJSON(ctx, r, &li.memberListResponse)
}
// GetMember returns a complete Member structure for a member of a mailing list,
// given only their subscription e-mail address.
func (mg *MailgunImpl) GetMember(ctx context.Context, s, l string) (Member, error) {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, l) + "/" + s)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
response, err := makeGetRequest(ctx, r)
if err != nil {
return Member{}, err
}
var resp memberResponse
err = response.parseFromJSON(&resp)
return resp.Member, err
}
// CreateMember registers a new member of the indicated mailing list.
// If merge is set to true, then the registration may update an existing Member's settings.
// Otherwise, an error will occur if you attempt to add a member with a duplicate e-mail address.
func (mg *MailgunImpl) CreateMember(ctx context.Context, merge bool, addr string, prototype Member) error {
vs, err := json.Marshal(prototype.Vars)
if err != nil {
return err
}
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newFormDataPayload()
p.addValue("upsert", yesNo(merge))
p.addValue("address", prototype.Address)
p.addValue("name", prototype.Name)
p.addValue("vars", string(vs))
if prototype.Subscribed != nil {
p.addValue("subscribed", yesNo(*prototype.Subscribed))
}
_, err = makePostRequest(ctx, r, p)
return err
}
// UpdateMember lets you change certain details about the indicated mailing list member.
// Address, Name, Vars, and Subscribed fields may be changed.
func (mg *MailgunImpl) UpdateMember(ctx context.Context, s, l string, prototype Member) (Member, error) {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, l) + "/" + s)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newFormDataPayload()
if prototype.Address != "" {
p.addValue("address", prototype.Address)
}
if prototype.Name != "" {
p.addValue("name", prototype.Name)
}
if prototype.Vars != nil {
vs, err := json.Marshal(prototype.Vars)
if err != nil {
return Member{}, err
}
p.addValue("vars", string(vs))
}
if prototype.Subscribed != nil {
p.addValue("subscribed", yesNo(*prototype.Subscribed))
}
response, err := makePutRequest(ctx, r, p)
if err != nil {
return Member{}, err
}
var envelope struct {
Member Member `json:"member"`
}
err = response.parseFromJSON(&envelope)
return envelope.Member, err
}
// DeleteMember removes the member from the list.
func (mg *MailgunImpl) DeleteMember(ctx context.Context, member, addr string) error {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr) + "/" + member)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// CreateMemberList registers multiple Members and non-Member members to a single mailing list
// in a single round-trip.
// u indicates if the existing members should be updated or duplicates should be updated.
// Use All to elect not to provide a default.
// The newMembers list can take one of two JSON-encodable forms: an slice of strings, or
// a slice of Member structures.
// If a simple slice of strings is passed, each string refers to the member's e-mail address.
// Otherwise, each Member needs to have at least the Address field filled out.
// Other fields are optional, but may be set according to your needs.
func (mg *MailgunImpl) CreateMemberList(ctx context.Context, u *bool, addr string, newMembers []interface{}) error {
r := newHTTPRequest(generateMemberApiUrl(mg, listsEndpoint, addr) + ".json")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newFormDataPayload()
if u != nil {
p.addValue("upsert", yesNo(*u))
}
bs, err := json.Marshal(newMembers)
if err != nil {
return err
}
p.addValue("members", string(bs))
_, err = makePostRequest(ctx, r, p)
return err
}

802
vendor/github.com/mailgun/mailgun-go/v3/messages.go generated vendored Normal file
View File

@ -0,0 +1,802 @@
package mailgun
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"time"
)
// MaxNumberOfRecipients represents the largest batch of recipients that Mailgun can support in a single API call.
// This figure includes To:, Cc:, Bcc:, etc. recipients.
const MaxNumberOfRecipients = 1000
// MaxNumberOfTags represents the maximum number of tags that can be added for a message
const MaxNumberOfTags = 3
// Message structures contain both the message text and the envelop for an e-mail message.
type Message struct {
to []string
tags []string
campaigns []string
dkim bool
deliveryTime time.Time
attachments []string
readerAttachments []ReaderAttachment
inlines []string
readerInlines []ReaderAttachment
bufferAttachments []BufferAttachment
nativeSend bool
testMode bool
tracking bool
trackingClicks bool
trackingOpens bool
headers map[string]string
variables map[string]string
templateVariables map[string]interface{}
recipientVariables map[string]map[string]interface{}
domain string
dkimSet bool
trackingSet bool
trackingClicksSet bool
trackingOpensSet bool
requireTLS bool
skipVerification bool
specific features
mg Mailgun
}
type ReaderAttachment struct {
Filename string
ReadCloser io.ReadCloser
}
type BufferAttachment struct {
Filename string
Buffer []byte
}
// StoredMessage structures contain the (parsed) message content for an email
// sent to a Mailgun account.
//
// The MessageHeaders field is special, in that it's formatted as a slice of pairs.
// Each pair consists of a name [0] and value [1]. Array notation is used instead of a map
// because that's how it's sent over the wire, and it's how encoding/json expects this field
// to be.
type StoredMessage struct {
Recipients string `json:"recipients"`
Sender string `json:"sender"`
From string `json:"from"`
Subject string `json:"subject"`
BodyPlain string `json:"body-plain"`
StrippedText string `json:"stripped-text"`
StrippedSignature string `json:"stripped-signature"`
BodyHtml string `json:"body-html"`
StrippedHtml string `json:"stripped-html"`
Attachments []StoredAttachment `json:"attachments"`
MessageUrl string `json:"message-url"`
ContentIDMap map[string]struct {
Url string `json:"url"`
ContentType string `json:"content-type"`
Name string `json:"name"`
Size int64 `json:"size"`
} `json:"content-id-map"`
MessageHeaders [][]string `json:"message-headers"`
}
// StoredAttachment structures contain information on an attachment associated with a stored message.
type StoredAttachment struct {
Size int `json:"size"`
Url string `json:"url"`
Name string `json:"name"`
ContentType string `json:"content-type"`
}
type StoredMessageRaw struct {
Recipients string `json:"recipients"`
Sender string `json:"sender"`
From string `json:"from"`
Subject string `json:"subject"`
BodyMime string `json:"body-mime"`
}
// plainMessage contains fields relevant to plain API-synthesized messages.
// You're expected to use various setters to set most of these attributes,
// although from, subject, and text are set when the message is created with
// NewMessage.
type plainMessage struct {
from string
cc []string
bcc []string
subject string
text string
html string
template string
}
// mimeMessage contains fields relevant to pre-packaged MIME messages.
type mimeMessage struct {
body io.ReadCloser
}
type sendMessageResponse struct {
Message string `json:"message"`
Id string `json:"id"`
}
// features abstracts the common characteristics between regular and MIME messages.
// addCC, addBCC, recipientCount, and setHTML are invoked via the package-global AddCC, AddBCC,
// RecipientCount, and SetHtml calls, as these functions are ignored for MIME messages.
// Send() invokes addValues to add message-type-specific MIME headers for the API call
// to Mailgun. isValid yeilds true if and only if the message is valid enough for sending
// through the API. Finally, endpoint() tells Send() which endpoint to use to submit the API call.
type features interface {
addCC(string)
addBCC(string)
setHtml(string)
addValues(*formDataPayload)
isValid() bool
endpoint() string
recipientCount() int
setTemplate(string)
}
// NewMessage returns a new e-mail message with the simplest envelop needed to send.
//
// Unlike the global function,
// this method supports arbitrary-sized recipient lists by
// automatically sending mail in batches of up to MaxNumberOfRecipients.
//
// To support batch sending, you don't want to provide a fixed To: header at this point.
// Pass nil as the to parameter to skip adding the To: header at this stage.
// You can do this explicitly, or implicitly, as follows:
//
// // Note absence of To parameter(s)!
// m := mg.NewMessage("me@example.com", "Help save our planet", "Hello world!")
//
// Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method
// before sending, though.
func (mg *MailgunImpl) NewMessage(from, subject, text string, to ...string) *Message {
return &Message{
specific: &plainMessage{
from: from,
subject: subject,
text: text,
},
to: to,
mg: mg,
}
}
// NewMIMEMessage creates a new MIME message. These messages are largely canned;
// you do not need to invoke setters to set message-related headers.
// However, you do still need to call setters for Mailgun-specific settings.
//
// Unlike the global function,
// this method supports arbitrary-sized recipient lists by
// automatically sending mail in batches of up to MaxNumberOfRecipients.
//
// To support batch sending, you don't want to provide a fixed To: header at this point.
// Pass nil as the to parameter to skip adding the To: header at this stage.
// You can do this explicitly, or implicitly, as follows:
//
// // Note absence of To parameter(s)!
// m := mg.NewMessage("me@example.com", "Help save our planet", "Hello world!")
//
// Note that you'll need to invoke the AddRecipientAndVariables or AddRecipient method
// before sending, though.
func (mg *MailgunImpl) NewMIMEMessage(body io.ReadCloser, to ...string) *Message {
return &Message{
specific: &mimeMessage{
body: body,
},
to: to,
mg: mg,
}
}
// AddReaderAttachment arranges to send a file along with the e-mail message.
// File contents are read from a io.ReadCloser.
// The filename parameter is the resulting filename of the attachment.
// The readCloser parameter is the io.ReadCloser which reads the actual bytes to be used
// as the contents of the attached file.
func (m *Message) AddReaderAttachment(filename string, readCloser io.ReadCloser) {
ra := ReaderAttachment{Filename: filename, ReadCloser: readCloser}
m.readerAttachments = append(m.readerAttachments, ra)
}
// AddBufferAttachment arranges to send a file along with the e-mail message.
// File contents are read from the []byte array provided
// The filename parameter is the resulting filename of the attachment.
// The buffer parameter is the []byte array which contains the actual bytes to be used
// as the contents of the attached file.
func (m *Message) AddBufferAttachment(filename string, buffer []byte) {
ba := BufferAttachment{Filename: filename, Buffer: buffer}
m.bufferAttachments = append(m.bufferAttachments, ba)
}
// AddAttachment arranges to send a file from the filesystem along with the e-mail message.
// The attachment parameter is a filename, which must refer to a file which actually resides
// in the local filesystem.
func (m *Message) AddAttachment(attachment string) {
m.attachments = append(m.attachments, attachment)
}
// AddReaderInline arranges to send a file along with the e-mail message.
// File contents are read from a io.ReadCloser.
// The filename parameter is the resulting filename of the attachment.
// The readCloser parameter is the io.ReadCloser which reads the actual bytes to be used
// as the contents of the attached file.
func (m *Message) AddReaderInline(filename string, readCloser io.ReadCloser) {
ra := ReaderAttachment{Filename: filename, ReadCloser: readCloser}
m.readerInlines = append(m.readerInlines, ra)
}
// AddInline arranges to send a file along with the e-mail message, but does so
// in a way that its data remains "inline" with the rest of the message. This
// can be used to send image or font data along with an HTML-encoded message body.
// The attachment parameter is a filename, which must refer to a file which actually resides
// in the local filesystem.
func (m *Message) AddInline(inline string) {
m.inlines = append(m.inlines, inline)
}
// AddRecipient appends a receiver to the To: header of a message.
// It will return an error if the limit of recipients have been exceeded for this message
func (m *Message) AddRecipient(recipient string) error {
return m.AddRecipientAndVariables(recipient, nil)
}
// AddRecipientAndVariables appends a receiver to the To: header of a message,
// and as well attaches a set of variables relevant for this recipient.
// It will return an error if the limit of recipients have been exceeded for this message
func (m *Message) AddRecipientAndVariables(r string, vars map[string]interface{}) error {
if m.RecipientCount() >= MaxNumberOfRecipients {
return fmt.Errorf("recipient limit exceeded (max %d)", MaxNumberOfRecipients)
}
m.to = append(m.to, r)
if vars != nil {
if m.recipientVariables == nil {
m.recipientVariables = make(map[string]map[string]interface{})
}
m.recipientVariables[r] = vars
}
return nil
}
// RecipientCount returns the total number of recipients for the message.
// This includes To:, Cc:, and Bcc: fields.
//
// NOTE: At present, this method is reliable only for non-MIME messages, as the
// Bcc: and Cc: fields are easily accessible.
// For MIME messages, only the To: field is considered.
// A fix for this issue is planned for a future release.
// For now, MIME messages are always assumed to have 10 recipients between Cc: and Bcc: fields.
// If your MIME messages have more than 10 non-To: field recipients,
// you may find that some recipients will not receive your e-mail.
// It's perfectly OK, of course, for a MIME message to not have any Cc: or Bcc: recipients.
func (m *Message) RecipientCount() int {
return len(m.to) + m.specific.recipientCount()
}
func (pm *plainMessage) recipientCount() int {
return len(pm.bcc) + len(pm.cc)
}
func (mm *mimeMessage) recipientCount() int {
return 10
}
func (m *Message) send(ctx context.Context) (string, string, error) {
return m.mg.Send(ctx, m)
}
// SetReplyTo sets the receiver who should receive replies
func (m *Message) SetReplyTo(recipient string) {
m.AddHeader("Reply-To", recipient)
}
// AddCC appends a receiver to the carbon-copy header of a message.
func (m *Message) AddCC(recipient string) {
m.specific.addCC(recipient)
}
func (pm *plainMessage) addCC(r string) {
pm.cc = append(pm.cc, r)
}
func (mm *mimeMessage) addCC(_ string) {}
// AddBCC appends a receiver to the blind-carbon-copy header of a message.
func (m *Message) AddBCC(recipient string) {
m.specific.addBCC(recipient)
}
func (pm *plainMessage) addBCC(r string) {
pm.bcc = append(pm.bcc, r)
}
func (mm *mimeMessage) addBCC(_ string) {}
// SetHtml is a helper. If you're sending a message that isn't already MIME encoded, SetHtml() will arrange to bundle
// an HTML representation of your message in addition to your plain-text body.
func (m *Message) SetHtml(html string) {
m.specific.setHtml(html)
}
func (pm *plainMessage) setHtml(h string) {
pm.html = h
}
func (mm *mimeMessage) setHtml(_ string) {}
// AddTag attaches tags to the message. Tags are useful for metrics gathering and event tracking purposes.
// Refer to the Mailgun documentation for further details.
func (m *Message) AddTag(tag ...string) error {
if len(m.tags) >= MaxNumberOfTags {
return fmt.Errorf("cannot add any new tags. Message tag limit (%d) reached", MaxNumberOfTags)
}
m.tags = append(m.tags, tag...)
return nil
}
// SetTemplate sets the name of a template stored via the template API.
// See https://documentation.mailgun.com/en/latest/user_manual.html#templating
func (m *Message) SetTemplate(t string) {
m.specific.setTemplate(t)
}
func (pm *plainMessage) setTemplate(t string) {
pm.template = t
}
func (mm *mimeMessage) setTemplate(t string) {}
// AddCampaign is no longer supported and is deprecated for new software.
func (m *Message) AddCampaign(campaign string) {
m.campaigns = append(m.campaigns, campaign)
}
// SetDKIM arranges to send the o:dkim header with the message, and sets its value accordingly.
// Refer to the Mailgun documentation for more information.
func (m *Message) SetDKIM(dkim bool) {
m.dkim = dkim
m.dkimSet = true
}
// EnableNativeSend allows the return path to match the address in the Message.Headers.From:
// field when sending from Mailgun rather than the usual bounce+ address in the return path.
func (m *Message) EnableNativeSend() {
m.nativeSend = true
}
// EnableTestMode allows submittal of a message, such that it will be discarded by Mailgun.
// This facilitates testing client-side software without actually consuming e-mail resources.
func (m *Message) EnableTestMode() {
m.testMode = true
}
// SetDeliveryTime schedules the message for transmission at the indicated time.
// Pass nil to remove any installed schedule.
// Refer to the Mailgun documentation for more information.
func (m *Message) SetDeliveryTime(dt time.Time) {
m.deliveryTime = dt
}
// SetTracking sets the o:tracking message parameter to adjust, on a message-by-message basis,
// whether or not Mailgun will rewrite URLs to facilitate event tracking.
// Events tracked includes opens, clicks, unsubscribes, etc.
// Note: simply calling this method ensures that the o:tracking header is passed in with the message.
// Its yes/no setting is determined by the call's parameter.
// Note that this header is not passed on to the final recipient(s).
// Refer to the Mailgun documentation for more information.
func (m *Message) SetTracking(tracking bool) {
m.tracking = tracking
m.trackingSet = true
}
// SetTrackingClicks information is found in the Mailgun documentation.
func (m *Message) SetTrackingClicks(trackingClicks bool) {
m.trackingClicks = trackingClicks
m.trackingClicksSet = true
}
// SetRequireTLS information is found in the Mailgun documentation.
func (m *Message) SetRequireTLS(b bool) {
m.requireTLS = b
}
// SetSkipVerification information is found in the Mailgun documentation.
func (m *Message) SetSkipVerification(b bool) {
m.skipVerification = b
}
//SetTrackingOpens information is found in the Mailgun documentation.
func (m *Message) SetTrackingOpens(trackingOpens bool) {
m.trackingOpens = trackingOpens
m.trackingOpensSet = true
}
// AddHeader allows you to send custom MIME headers with the message.
func (m *Message) AddHeader(header, value string) {
if m.headers == nil {
m.headers = make(map[string]string)
}
m.headers[header] = value
}
// AddVariable lets you associate a set of variables with messages you send,
// which Mailgun can use to, in essence, complete form-mail.
// Refer to the Mailgun documentation for more information.
func (m *Message) AddVariable(variable string, value interface{}) error {
if m.variables == nil {
m.variables = make(map[string]string)
}
j, err := json.Marshal(value)
if err != nil {
return err
}
encoded := string(j)
v, err := strconv.Unquote(encoded)
if err != nil {
v = encoded
}
m.variables[variable] = v
return nil
}
// AddTemplateVariable adds a template variable to the map of template variables, replacing the variable if it is already there.
// This is used for server-side message templates and can nest arbitrary values. At send time, the resulting map will be converted into
// a JSON string and sent as a header in the X-Mailgun-Variables header.
func (m *Message) AddTemplateVariable(variable string, value interface{}) error {
if m.templateVariables == nil {
m.templateVariables = make(map[string]interface{})
}
m.templateVariables[variable] = value
return nil
}
// AddDomain allows you to use a separate domain for the type of messages you are sending.
func (m *Message) AddDomain(domain string) {
m.domain = domain
}
// GetHeaders retrieves the http headers associated with this message
func (m *Message) GetHeaders() map[string]string {
return m.headers
}
// ErrInvalidMessage is returned by `Send()` when the `mailgun.Message` struct is incomplete
var ErrInvalidMessage = errors.New("message not valid")
// Send attempts to queue a message (see Message, NewMessage, and its methods) for delivery.
// It returns the Mailgun server response, which consists of two components:
// a human-readable status message, and a message ID. The status and message ID are set only
// if no error occurred.
func (mg *MailgunImpl) Send(ctx context.Context, message *Message) (mes string, id string, err error) {
if mg.domain == "" {
err = errors.New("you must provide a valid domain before calling Send()")
return
}
if mg.apiKey == "" {
err = errors.New("you must provide a valid api-key before calling Send()")
return
}
if !isValid(message) {
err = ErrInvalidMessage
return
}
payload := newFormDataPayload()
message.specific.addValues(payload)
for _, to := range message.to {
payload.addValue("to", to)
}
for _, tag := range message.tags {
payload.addValue("o:tag", tag)
}
for _, campaign := range message.campaigns {
payload.addValue("o:campaign", campaign)
}
if message.dkimSet {
payload.addValue("o:dkim", yesNo(message.dkim))
}
if !message.deliveryTime.IsZero() {
payload.addValue("o:deliverytime", formatMailgunTime(message.deliveryTime))
}
if message.nativeSend {
payload.addValue("o:native-send", "yes")
}
if message.testMode {
payload.addValue("o:testmode", "yes")
}
if message.trackingSet {
payload.addValue("o:tracking", yesNo(message.tracking))
}
if message.trackingClicksSet {
payload.addValue("o:tracking-clicks", yesNo(message.trackingClicks))
}
if message.trackingOpensSet {
payload.addValue("o:tracking-opens", yesNo(message.trackingOpens))
}
if message.requireTLS {
payload.addValue("o:require-tls", trueFalse(message.requireTLS))
}
if message.skipVerification {
payload.addValue("o:skip-verification", trueFalse(message.skipVerification))
}
if message.headers != nil {
for header, value := range message.headers {
payload.addValue("h:"+header, value)
}
}
if message.variables != nil {
for variable, value := range message.variables {
payload.addValue("v:"+variable, value)
}
}
if message.templateVariables != nil {
variableString, err := json.Marshal(message.templateVariables)
if err == nil {
// the map was marshalled as json so add it
payload.addValue("h:X-Mailgun-Variables", string(variableString))
}
}
if message.recipientVariables != nil {
j, err := json.Marshal(message.recipientVariables)
if err != nil {
return "", "", err
}
payload.addValue("recipient-variables", string(j))
}
if message.attachments != nil {
for _, attachment := range message.attachments {
payload.addFile("attachment", attachment)
}
}
if message.readerAttachments != nil {
for _, readerAttachment := range message.readerAttachments {
payload.addReadCloser("attachment", readerAttachment.Filename, readerAttachment.ReadCloser)
}
}
if message.bufferAttachments != nil {
for _, bufferAttachment := range message.bufferAttachments {
payload.addBuffer("attachment", bufferAttachment.Filename, bufferAttachment.Buffer)
}
}
if message.inlines != nil {
for _, inline := range message.inlines {
payload.addFile("inline", inline)
}
}
if message.readerInlines != nil {
for _, readerAttachment := range message.readerInlines {
payload.addReadCloser("inline", readerAttachment.Filename, readerAttachment.ReadCloser)
}
}
if message.domain == "" {
message.domain = mg.Domain()
}
r := newHTTPRequest(generateApiUrlWithDomain(mg, message.specific.endpoint(), message.domain))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var response sendMessageResponse
err = postResponseFromJSON(ctx, r, payload, &response)
if err == nil {
mes = response.Message
id = response.Id
}
return
}
func (pm *plainMessage) addValues(p *formDataPayload) {
p.addValue("from", pm.from)
p.addValue("subject", pm.subject)
p.addValue("text", pm.text)
for _, cc := range pm.cc {
p.addValue("cc", cc)
}
for _, bcc := range pm.bcc {
p.addValue("bcc", bcc)
}
if pm.html != "" {
p.addValue("html", pm.html)
}
if pm.template != "" {
p.addValue("template", pm.template)
}
}
func (mm *mimeMessage) addValues(p *formDataPayload) {
p.addReadCloser("message", "message.mime", mm.body)
}
func (pm *plainMessage) endpoint() string {
return messagesEndpoint
}
func (mm *mimeMessage) endpoint() string {
return mimeMessagesEndpoint
}
// yesNo translates a true/false boolean value into a yes/no setting suitable for the Mailgun API.
func yesNo(b bool) string {
if b {
return "yes"
}
return "no"
}
func trueFalse(b bool) string {
if b {
return "true"
}
return "false"
}
// isValid returns true if, and only if,
// a Message instance is sufficiently initialized to send via the Mailgun interface.
func isValid(m *Message) bool {
if m == nil {
return false
}
if !m.specific.isValid() {
return false
}
if m.RecipientCount() == 0 {
return false
}
if !validateStringList(m.tags, false) {
return false
}
if !validateStringList(m.campaigns, false) || len(m.campaigns) > 3 {
return false
}
return true
}
func (pm *plainMessage) isValid() bool {
if pm.from == "" {
return false
}
if !validateStringList(pm.cc, false) {
return false
}
if !validateStringList(pm.bcc, false) {
return false
}
if pm.template != "" {
// pm.text or pm.html not needed if template is supplied
return true
}
if pm.text == "" && pm.html == "" {
return false
}
return true
}
func (mm *mimeMessage) isValid() bool {
return mm.body != nil
}
// validateStringList returns true if, and only if,
// a slice of strings exists AND all of its elements exist,
// OR if the slice doesn't exist AND it's not required to exist.
// The requireOne parameter indicates whether the list is required to exist.
func validateStringList(list []string, requireOne bool) bool {
hasOne := false
if list == nil {
return !requireOne
} else {
for _, a := range list {
if a == "" {
return false
} else {
hasOne = hasOne || true
}
}
}
return hasOne
}
// GetStoredMessage retrieves information about a received e-mail message.
// This provides visibility into, e.g., replies to a message sent to a mailing list.
func (mg *MailgunImpl) GetStoredMessage(ctx context.Context, url string) (StoredMessage, error) {
r := newHTTPRequest(url)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var response StoredMessage
err := getResponseFromJSON(ctx, r, &response)
return response, err
}
// Given a storage id resend the stored message to the specified recipients
func (mg *MailgunImpl) ReSend(ctx context.Context, url string, recipients ...string) (string, string, error) {
r := newHTTPRequest(url)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newFormDataPayload()
if len(recipients) == 0 {
return "", "", errors.New("must provide at least one recipient")
}
for _, to := range recipients {
payload.addValue("to", to)
}
var resp sendMessageResponse
err := postResponseFromJSON(ctx, r, payload, &resp)
if err != nil {
return "", "", err
}
return resp.Message, resp.Id, nil
}
// GetStoredMessageRaw retrieves the raw MIME body of a received e-mail message.
// Compared to GetStoredMessage, it gives access to the unparsed MIME body, and
// thus delegates to the caller the required parsing.
func (mg *MailgunImpl) GetStoredMessageRaw(ctx context.Context, url string) (StoredMessageRaw, error) {
r := newHTTPRequest(url)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addHeader("Accept", "message/rfc2822")
var response StoredMessageRaw
err := getResponseFromJSON(ctx, r, &response)
return response, err
}
// Deprecated: Use GetStoreMessage() instead
func (mg *MailgunImpl) GetStoredMessageForURL(ctx context.Context, url string) (StoredMessage, error) {
return mg.GetStoredMessage(ctx, url)
}
// Deprecated: Use GetStoreMessageRaw() instead
func (mg *MailgunImpl) GetStoredMessageRawForURL(ctx context.Context, url string) (StoredMessageRaw, error) {
return mg.GetStoredMessageRaw(ctx, url)
}
// GetStoredAttachment retrieves the raw MIME body of a received e-mail message attachment.
func (mg *MailgunImpl) GetStoredAttachment(ctx context.Context, url string) ([]byte, error) {
r := newHTTPRequest(url)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addHeader("Accept", "message/rfc2822")
response, err := makeGetRequest(ctx, r)
return response.Data, err
}

194
vendor/github.com/mailgun/mailgun-go/v3/mock.go generated vendored Normal file
View File

@ -0,0 +1,194 @@
package mailgun
import (
"crypto/rand"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/mail"
"net/url"
"strconv"
"strings"
"github.com/go-chi/chi"
)
// A mailgun api mock suitable for testing
type MockServer struct {
srv *httptest.Server
domainIPS []string
domainList []domainContainer
exportList []Export
mailingList []mailingListContainer
routeList []Route
events []Event
webhooks WebHooksListResponse
}
// Create a new instance of the mailgun API mock server
func NewMockServer() MockServer {
ms := MockServer{}
// Add all our handlers
r := chi.NewRouter()
r.Route("/v3", func(r chi.Router) {
ms.addIPRoutes(r)
ms.addExportRoutes(r)
ms.addDomainRoutes(r)
ms.addMailingListRoutes(r)
ms.addEventRoutes(r)
ms.addMessagesRoutes(r)
ms.addValidationRoutes(r)
ms.addRoutes(r)
ms.addWebhookRoutes(r)
})
// Start the server
ms.srv = httptest.NewServer(r)
return ms
}
// Stop the server
func (ms *MockServer) Stop() {
ms.srv.Close()
}
// URL returns the URL used to connect to the mock server
func (ms *MockServer) URL() string {
return ms.srv.URL + "/v3"
}
func toJSON(w http.ResponseWriter, obj interface{}) {
if err := json.NewEncoder(w).Encode(obj); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
}
func stringToBool(v string) bool {
lower := strings.ToLower(v)
if lower == "yes" || lower == "no" {
return lower == "yes"
}
if v == "" {
return false
}
result, err := strconv.ParseBool(v)
if err != nil {
panic(err)
}
return result
}
func stringToInt(v string) int {
if v == "" {
return 0
}
result, err := strconv.ParseInt(v, 10, 64)
if err != nil {
panic(err)
}
return int(result)
}
func stringToMap(v string) map[string]interface{} {
if v == "" {
return nil
}
result := make(map[string]interface{})
err := json.Unmarshal([]byte(v), &result)
if err != nil {
panic(err)
}
return result
}
func parseAddress(v string) string {
if v == "" {
return ""
}
e, err := mail.ParseAddress(v)
if err != nil {
panic(err)
}
return e.Address
}
// Given the page direction, pivot value and limit, calculate the offsets for the slice
func pageOffsets(pivotIdx []string, pivotDir, pivotVal string, limit int) (int, int) {
switch pivotDir {
case "first":
if limit < len(pivotIdx) {
return 0, limit
}
return 0, len(pivotIdx)
case "last":
if limit < len(pivotIdx) {
return len(pivotIdx) - limit, len(pivotIdx)
}
return 0, len(pivotIdx)
case "next":
for i, item := range pivotIdx {
if item == pivotVal {
offset := i + 1 + limit
if offset > len(pivotIdx) {
offset = len(pivotIdx)
}
return i + 1, offset
}
}
return 0, 0
case "prev":
for i, item := range pivotIdx {
if item == pivotVal {
if i == 0 {
return 0, 0
}
offset := i - limit
if offset < 0 {
offset = 0
}
return offset, i
}
}
return 0, 0
}
if limit > len(pivotIdx) {
return 0, len(pivotIdx)
}
return 0, limit
}
func getPageURL(r *http.Request, params url.Values) string {
if r.FormValue("limit") != "" {
params.Add("limit", r.FormValue("limit"))
}
return "http://" + r.Host + r.URL.EscapedPath() + "?" + params.Encode()
}
// randomString generates a string of given length, but random content.
// All content will be within the ASCII graphic character set.
// (Implementation from Even Shaw's contribution on
// http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go).
func randomString(n int, prefix string) string {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, n)
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
return prefix + string(bytes)
}
func randomEmail(prefix, domain string) string {
return strings.ToLower(fmt.Sprintf("%s@%s", randomString(20, prefix), domain))
}

283
vendor/github.com/mailgun/mailgun-go/v3/mock_domains.go generated vendored Normal file
View File

@ -0,0 +1,283 @@
package mailgun
import (
"net/http"
"time"
"github.com/go-chi/chi"
)
type domainContainer struct {
Domain Domain `json:"domain"`
ReceivingDNSRecords []DNSRecord `json:"receiving_dns_records"`
SendingDNSRecords []DNSRecord `json:"sending_dns_records"`
Connection *DomainConnection `json:"connection,omitempty"`
Tracking *DomainTracking `json:"tracking,omitempty"`
TagLimits *TagLimits `json:"limits,omitempty"`
}
func (ms *MockServer) addDomainRoutes(r chi.Router) {
ms.domainList = append(ms.domainList, domainContainer{
Domain: Domain{
CreatedAt: RFC2822Time(time.Now().UTC()),
Name: "mailgun.test",
SMTPLogin: "postmaster@mailgun.test",
SMTPPassword: "4rtqo4p6rrx9",
Wildcard: true,
SpamAction: SpamActionDisabled,
State: "active",
},
Connection: &DomainConnection{
RequireTLS: true,
SkipVerification: true,
},
TagLimits: &TagLimits{
Limit: 50000,
Count: 5000,
},
Tracking: &DomainTracking{
Click: TrackingStatus{Active: true},
Open: TrackingStatus{Active: true},
Unsubscribe: TrackingStatus{
Active: false,
HTMLFooter: "\n<br>\n<p><a href=\"%unsubscribe_url%\">unsubscribe</a></p>\n",
TextFooter: "\n\nTo unsubscribe click: <%unsubscribe_url%>\n\n",
},
},
ReceivingDNSRecords: []DNSRecord{
{
Priority: "10",
RecordType: "MX",
Valid: "valid",
Value: "mxa.mailgun.org",
},
{
Priority: "10",
RecordType: "MX",
Valid: "valid",
Value: "mxb.mailgun.org",
},
},
SendingDNSRecords: []DNSRecord{
{
RecordType: "TXT",
Valid: "valid",
Name: "domain.com",
Value: "v=spf1 include:mailgun.org ~all",
},
{
RecordType: "TXT",
Valid: "valid",
Name: "domain.com",
Value: "k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUA....",
},
{
RecordType: "CNAME",
Valid: "valid",
Name: "email.domain.com",
Value: "mailgun.org",
},
},
})
r.Get("/domains", ms.listDomains)
r.Post("/domains", ms.createDomain)
r.Get("/domains/{domain}", ms.getDomain)
r.Put("/domains/{domain}/verify", ms.getDomain)
r.Delete("/domains/{domain}", ms.deleteDomain)
//r.Get("/domains/{domain}/credentials", ms.getCredentials)
//r.Post("/domains/{domain}/credentials", ms.createCredentials)
//r.Put("/domains/{domain}/credentials/{login}", ms.updateCredentials)
//r.Delete("/domains/{domain}/credentials/{login}", ms.deleteCredentials)
r.Get("/domains/{domain}/connection", ms.getConnection)
r.Put("/domains/{domain}/connection", ms.updateConnection)
r.Get("/domains/{domain}/tracking", ms.getTracking)
r.Put("/domains/{domain}/tracking/click", ms.updateClickTracking)
r.Put("/domains/{domain}/tracking/open", ms.updateOpenTracking)
r.Put("/domains/{domain}/tracking/unsubscribe", ms.updateUnsubTracking)
r.Get("/domains/{domain}/limits/tag", ms.getTagLimits)
}
func (ms *MockServer) listDomains(w http.ResponseWriter, r *http.Request) {
var list []Domain
for _, domain := range ms.domainList {
list = append(list, domain.Domain)
}
skip := stringToInt(r.FormValue("skip"))
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
if skip > len(list) {
skip = len(list)
}
end := limit + skip
if end > len(list) {
end = len(list)
}
// If we are at the end of the list
if skip == end {
toJSON(w, domainsListResponse{
TotalCount: len(list),
Items: []Domain{},
})
return
}
toJSON(w, domainsListResponse{
TotalCount: len(list),
Items: list[skip:end],
})
}
func (ms *MockServer) getDomain(w http.ResponseWriter, r *http.Request) {
for _, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
d.Connection = nil
toJSON(w, d)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) createDomain(w http.ResponseWriter, r *http.Request) {
ms.domainList = append(ms.domainList, domainContainer{
Domain: Domain{
CreatedAt: RFC2822Time(time.Now()),
Name: r.FormValue("name"),
SMTPLogin: r.FormValue("smtp_login"),
SMTPPassword: r.FormValue("smtp_password"),
Wildcard: stringToBool(r.FormValue("wildcard")),
SpamAction: SpamAction(r.FormValue("spam_action")),
State: "active",
},
})
toJSON(w, okResp{Message: "Domain has been created"})
}
func (ms *MockServer) deleteDomain(w http.ResponseWriter, r *http.Request) {
result := ms.domainList[:0]
for _, domain := range ms.domainList {
if domain.Domain.Name == chi.URLParam(r, "domain") {
continue
}
result = append(result, domain)
}
if len(result) != len(ms.domainList) {
toJSON(w, okResp{Message: "success"})
ms.domainList = result
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) getConnection(w http.ResponseWriter, r *http.Request) {
for _, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
resp := domainConnectionResponse{
Connection: *d.Connection,
}
toJSON(w, resp)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) updateConnection(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
ms.domainList[i].Connection = &DomainConnection{
RequireTLS: stringToBool(r.FormValue("require_tls")),
SkipVerification: stringToBool(r.FormValue("skip_verification")),
}
toJSON(w, okResp{Message: "Domain connection settings have been updated, may take 10 minutes to fully propagate"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) getTracking(w http.ResponseWriter, r *http.Request) {
for _, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
resp := domainTrackingResponse{
Tracking: *d.Tracking,
}
toJSON(w, resp)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) updateClickTracking(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
ms.domainList[i].Tracking.Click.Active = stringToBool(r.FormValue("active"))
toJSON(w, okResp{Message: "Domain tracking settings have been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) updateOpenTracking(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
ms.domainList[i].Tracking.Open.Active = stringToBool(r.FormValue("active"))
toJSON(w, okResp{Message: "Domain tracking settings have been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) updateUnsubTracking(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
ms.domainList[i].Tracking.Unsubscribe.Active = stringToBool(r.FormValue("active"))
if len(r.FormValue("html_footer")) != 0 {
ms.domainList[i].Tracking.Unsubscribe.HTMLFooter = r.FormValue("html_footer")
}
if len(r.FormValue("text_footer")) != 0 {
ms.domainList[i].Tracking.Unsubscribe.TextFooter = r.FormValue("text_footer")
}
toJSON(w, okResp{Message: "Domain tracking settings have been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}
func (ms *MockServer) getTagLimits(w http.ResponseWriter, r *http.Request) {
for _, d := range ms.domainList {
if d.Domain.Name == chi.URLParam(r, "domain") {
if d.TagLimits == nil {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "no limits defined for domain"})
return
}
toJSON(w, d.TagLimits)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "domain not found"})
}

242
vendor/github.com/mailgun/mailgun-go/v3/mock_events.go generated vendored Normal file
View File

@ -0,0 +1,242 @@
package mailgun
import (
"net/http"
"net/url"
"time"
"github.com/go-chi/chi"
"github.com/mailgun/mailgun-go/v3/events"
)
func (ms *MockServer) addEventRoutes(r chi.Router) {
r.Get("/{domain}/events", ms.listEvents)
var (
tags = []string{"tag1", "tag2"}
recipients = []string{"one@mailgun.test", "two@mailgun.test"}
recipientDomain = "mailgun.test"
timeStamp = TimeToFloat(time.Now().UTC())
ipAddress = "192.168.1.1"
message = events.Message{Headers: events.MessageHeaders{MessageID: "1234"}}
clientInfo = events.ClientInfo{
AcceptLanguage: "EN",
ClientName: "Firefox",
ClientOS: "OS X",
ClientType: "browser",
DeviceType: "desktop",
IP: "8.8.8.8",
UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0",
}
geoLocation = events.GeoLocation{
City: "San Antonio",
Country: "US",
Region: "TX",
}
)
// AcceptedNoAuth
accepted := new(events.Accepted)
accepted.ID = randomString(16, "ID-")
accepted.Message.Headers.MessageID = accepted.ID
accepted.Name = events.EventAccepted
accepted.Tags = tags
accepted.Timestamp = timeStamp
accepted.Recipient = recipients[0]
accepted.RecipientDomain = recipientDomain
accepted.Flags = events.Flags{
IsAuthenticated: false,
}
ms.events = append(ms.events, accepted)
// AcceptedAuth
accepted = new(events.Accepted)
accepted.ID = randomString(16, "ID-")
accepted.Message.Headers.MessageID = accepted.ID
accepted.Name = events.EventAccepted
accepted.Tags = tags
accepted.Timestamp = timeStamp
accepted.Recipient = recipients[0]
accepted.RecipientDomain = recipientDomain
accepted.Campaigns = []events.Campaign{
{ID: "test-id", Name: "test"},
}
accepted.Flags = events.Flags{
IsAuthenticated: true,
}
ms.events = append(ms.events, accepted)
// DeliveredSMTP
delivered := new(events.Delivered)
delivered.ID = randomString(16, "ID-")
delivered.Message.Headers.MessageID = delivered.ID
delivered.Name = events.EventDelivered
delivered.Tags = tags
delivered.Timestamp = timeStamp
delivered.Recipient = recipients[0]
delivered.RecipientDomain = recipientDomain
delivered.DeliveryStatus.Message = "We sent an email Yo"
delivered.Envelope = events.Envelope{
Transport: "smtp",
SendingIP: ipAddress,
}
delivered.Flags = events.Flags{
IsAuthenticated: true,
}
ms.events = append(ms.events, delivered)
// DeliveredHTTP
delivered = new(events.Delivered)
delivered.ID = randomString(16, "ID-")
delivered.Message.Headers.MessageID = delivered.ID
delivered.Name = events.EventDelivered
delivered.Tags = tags
delivered.Timestamp = timeStamp
delivered.Recipient = recipients[0]
delivered.RecipientDomain = recipientDomain
delivered.DeliveryStatus.Message = "We sent an email Yo"
delivered.Envelope = events.Envelope{
Transport: "http",
SendingIP: ipAddress,
}
delivered.Flags = events.Flags{
IsAuthenticated: true,
}
ms.events = append(ms.events, delivered)
// Stored
stored := new(events.Stored)
stored.ID = randomString(16, "ID-")
stored.Name = events.EventStored
stored.Tags = tags
stored.Timestamp = timeStamp
stored.Storage.URL = "http://mailgun.text/some/url"
ms.events = append(ms.events, stored)
// Clicked
for _, recipient := range recipients {
clicked := new(events.Clicked)
clicked.ID = randomString(16, "ID-")
clicked.Name = events.EventClicked
clicked.Message = message
clicked.Tags = tags
clicked.Recipient = recipient
clicked.ClientInfo = clientInfo
clicked.GeoLocation = geoLocation
clicked.Timestamp = timeStamp
ms.events = append(ms.events, clicked)
}
clicked := new(events.Clicked)
clicked.ID = randomString(16, "ID-")
clicked.Name = events.EventClicked
clicked.Message = message
clicked.Tags = tags
clicked.Recipient = recipients[0]
clicked.ClientInfo = clientInfo
clicked.GeoLocation = geoLocation
clicked.Timestamp = timeStamp
ms.events = append(ms.events, clicked)
// Opened
for _, recipient := range recipients {
opened := new(events.Opened)
opened.ID = randomString(16, "ID-")
opened.Name = events.EventOpened
opened.Message = message
opened.Tags = tags
opened.Recipient = recipient
opened.ClientInfo = clientInfo
opened.GeoLocation = geoLocation
opened.Timestamp = timeStamp
ms.events = append(ms.events, opened)
}
opened := new(events.Opened)
opened.ID = randomString(16, "ID-")
opened.Name = events.EventOpened
opened.Message = message
opened.Tags = tags
opened.Recipient = recipients[0]
opened.ClientInfo = clientInfo
opened.GeoLocation = geoLocation
opened.Timestamp = timeStamp
ms.events = append(ms.events, opened)
// Unsubscribed
for _, recipient := range recipients {
unsub := new(events.Unsubscribed)
unsub.ID = randomString(16, "ID-")
unsub.Name = events.EventUnsubscribed
unsub.Tags = tags
unsub.Recipient = recipient
unsub.ClientInfo = clientInfo
unsub.GeoLocation = geoLocation
unsub.Timestamp = timeStamp
ms.events = append(ms.events, unsub)
}
// Complained
for _, recipient := range recipients {
complained := new(events.Complained)
complained.ID = randomString(16, "ID-")
complained.Name = events.EventComplained
complained.Tags = tags
complained.Recipient = recipient
complained.Timestamp = timeStamp
ms.events = append(ms.events, complained)
}
}
type eventsResponse struct {
Items []Event `json:"items"`
Paging Paging `json:"paging"`
}
func (ms *MockServer) listEvents(w http.ResponseWriter, r *http.Request) {
var idx []string
for _, e := range ms.events {
idx = append(idx, e.GetID())
}
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
start, end := pageOffsets(idx, r.FormValue("page"), r.FormValue("address"), limit)
var nextAddress, prevAddress string
var results []Event
if start != end {
results = ms.events[start:end]
nextAddress = results[len(results)-1].GetID()
prevAddress = results[0].GetID()
} else {
results = []Event{}
nextAddress = r.FormValue("address")
prevAddress = r.FormValue("address")
}
resp := eventsResponse{
Paging: Paging{
First: getPageURL(r, url.Values{
"page": []string{"first"},
}),
Last: getPageURL(r, url.Values{
"page": []string{"last"},
}),
Next: getPageURL(r, url.Values{
"page": []string{"next"},
"address": []string{nextAddress},
}),
Previous: getPageURL(r, url.Values{
"page": []string{"prev"},
"address": []string{prevAddress},
}),
},
Items: results,
}
toJSON(w, resp)
}

View File

@ -0,0 +1,48 @@
package mailgun
import (
"net/http"
"strconv"
"github.com/go-chi/chi"
)
func (ms *MockServer) addExportRoutes(r chi.Router) {
r.Post("/exports", ms.postExports)
r.Get("/exports", ms.listExports)
r.Get("/exports/{id}", ms.getExport)
r.Get("/exports/{id}/download_url", ms.getExportLink)
}
func (ms *MockServer) postExports(w http.ResponseWriter, r *http.Request) {
e := Export{
ID: strconv.Itoa(len(ms.exportList)),
URL: r.FormValue("url"),
Status: "complete",
}
ms.exportList = append(ms.exportList, e)
toJSON(w, okResp{Message: "success"})
}
func (ms *MockServer) listExports(w http.ResponseWriter, _ *http.Request) {
toJSON(w, ExportList{
Items: ms.exportList,
})
}
func (ms *MockServer) getExport(w http.ResponseWriter, r *http.Request) {
for _, export := range ms.exportList {
if export.ID == chi.URLParam(r, "id") {
toJSON(w, export)
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "export not found"})
}
func (ms *MockServer) getExportLink(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "/some/s3/url")
w.WriteHeader(http.StatusFound)
}

65
vendor/github.com/mailgun/mailgun-go/v3/mock_ips.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package mailgun
import (
"net/http"
"github.com/go-chi/chi"
)
func (ms *MockServer) addIPRoutes(r chi.Router) {
r.Get("/ips", ms.listIPS)
r.Get("/ips/{ip}", ms.getIPAddress)
r.Route("/domains/{domain}/ips", func(r chi.Router) {
r.Get("/", ms.listDomainIPS)
r.Get("/{ip}", ms.getIPAddress)
r.Post("/", ms.postDomainIPS)
r.Delete("/{ip}", ms.deleteDomainIPS)
})
}
func (ms *MockServer) listIPS(w http.ResponseWriter, _ *http.Request) {
toJSON(w, ipAddressListResponse{
TotalCount: 2,
Items: []string{"172.0.0.1", "192.168.1.1"},
})
}
func (ms *MockServer) getIPAddress(w http.ResponseWriter, r *http.Request) {
toJSON(w, IPAddress{
IP: chi.URLParam(r, "ip"),
RDNS: "luna.mailgun.net",
Dedicated: true,
})
}
func (ms *MockServer) listDomainIPS(w http.ResponseWriter, _ *http.Request) {
toJSON(w, ipAddressListResponse{
TotalCount: 2,
Items: ms.domainIPS,
})
}
func (ms *MockServer) postDomainIPS(w http.ResponseWriter, r *http.Request) {
ms.domainIPS = append(ms.domainIPS, r.FormValue("ip"))
toJSON(w, okResp{Message: "success"})
}
func (ms *MockServer) deleteDomainIPS(w http.ResponseWriter, r *http.Request) {
result := ms.domainIPS[:0]
for _, ip := range ms.domainIPS {
if ip == chi.URLParam(r, "ip") {
continue
}
result = append(result, ip)
}
if len(result) != len(ms.domainIPS) {
toJSON(w, okResp{Message: "success"})
ms.domainIPS = result
return
}
// Not the actual error returned by the mailgun API
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "ip not found"})
}

View File

@ -0,0 +1,389 @@
package mailgun
import (
"encoding/json"
"net/http"
"net/url"
"time"
"github.com/go-chi/chi"
)
type mailingListContainer struct {
MailingList MailingList
Members []Member
}
func (ms *MockServer) addMailingListRoutes(r chi.Router) {
r.Get("/lists/pages", ms.listMailingLists)
r.Get("/lists/{address}", ms.getMailingList)
r.Post("/lists", ms.createMailingList)
r.Put("/lists/{address}", ms.updateMailingList)
r.Delete("/lists/{address}", ms.deleteMailingList)
r.Get("/lists/{address}/members/pages", ms.listMembers)
r.Get("/lists/{address}/members/{member}", ms.getMember)
r.Post("/lists/{address}/members", ms.createMember)
r.Put("/lists/{address}/members/{member}", ms.updateMember)
r.Delete("/lists/{address}/members/{member}", ms.deleteMember)
r.Post("/lists/{address}/members.json", ms.bulkCreate)
ms.mailingList = append(ms.mailingList, mailingListContainer{
MailingList: MailingList{
AccessLevel: "everyone",
Address: "foo@mailgun.test",
CreatedAt: RFC2822Time(time.Now().UTC()),
Description: "Mailgun developers list",
MembersCount: 1,
Name: "",
},
Members: []Member{
{
Address: "dev@samples.mailgun.org",
Name: "Developer",
},
},
})
}
func (ms *MockServer) listMailingLists(w http.ResponseWriter, r *http.Request) {
var list []MailingList
var idx []string
for _, ml := range ms.mailingList {
list = append(list, ml.MailingList)
idx = append(idx, ml.MailingList.Address)
}
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
start, end := pageOffsets(idx, r.FormValue("page"), r.FormValue("address"), limit)
results := list[start:end]
if len(results) == 0 {
toJSON(w, listsResponse{})
return
}
resp := listsResponse{
Paging: Paging{
First: getPageURL(r, url.Values{
"page": []string{"first"},
}),
Last: getPageURL(r, url.Values{
"page": []string{"last"},
}),
Next: getPageURL(r, url.Values{
"page": []string{"next"},
"address": []string{results[len(results)-1].Address},
}),
Previous: getPageURL(r, url.Values{
"page": []string{"prev"},
"address": []string{results[0].Address},
}),
},
Items: results,
}
toJSON(w, resp)
}
func (ms *MockServer) getMailingList(w http.ResponseWriter, r *http.Request) {
for _, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
toJSON(w, mailingListResponse{MailingList: ml.MailingList})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
}
func (ms *MockServer) deleteMailingList(w http.ResponseWriter, r *http.Request) {
result := ms.mailingList[:0]
for _, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
continue
}
result = append(result, ml)
}
if len(result) != len(ms.mailingList) {
toJSON(w, okResp{Message: "success"})
ms.mailingList = result
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
}
func (ms *MockServer) updateMailingList(w http.ResponseWriter, r *http.Request) {
for i, d := range ms.mailingList {
if d.MailingList.Address == chi.URLParam(r, "address") {
if r.FormValue("address") != "" {
ms.mailingList[i].MailingList.Address = r.FormValue("address")
}
if r.FormValue("name") != "" {
ms.mailingList[i].MailingList.Name = r.FormValue("name")
}
if r.FormValue("description") != "" {
ms.mailingList[i].MailingList.Description = r.FormValue("description")
}
if r.FormValue("access_level") != "" {
ms.mailingList[i].MailingList.AccessLevel = AccessLevel(r.FormValue("access_level"))
}
toJSON(w, okResp{Message: "Mailing list member has been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
}
func (ms *MockServer) createMailingList(w http.ResponseWriter, r *http.Request) {
ms.mailingList = append(ms.mailingList, mailingListContainer{
MailingList: MailingList{
CreatedAt: RFC2822Time(time.Now().UTC()),
Name: r.FormValue("name"),
Address: r.FormValue("address"),
Description: r.FormValue("description"),
AccessLevel: AccessLevel(r.FormValue("access_level")),
},
})
toJSON(w, okResp{Message: "Mailing list has been created"})
}
func (ms *MockServer) listMembers(w http.ResponseWriter, r *http.Request) {
var list []Member
var idx []string
var found bool
for _, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
found = true
for _, member := range ml.Members {
list = append(list, member)
idx = append(idx, member.Address)
}
}
}
if !found {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
start, end := pageOffsets(idx, r.FormValue("page"), r.FormValue("address"), limit)
results := list[start:end]
if len(results) == 0 {
toJSON(w, memberListResponse{})
return
}
resp := memberListResponse{
Paging: Paging{
First: getPageURL(r, url.Values{
"page": []string{"first"},
}),
Last: getPageURL(r, url.Values{
"page": []string{"last"},
}),
Next: getPageURL(r, url.Values{
"page": []string{"next"},
"address": []string{results[len(results)-1].Address},
}),
Previous: getPageURL(r, url.Values{
"page": []string{"prev"},
"address": []string{results[0].Address},
}),
},
Lists: results,
}
toJSON(w, resp)
}
func (ms *MockServer) getMember(w http.ResponseWriter, r *http.Request) {
var found bool
for _, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
found = true
for _, member := range ml.Members {
if member.Address == chi.URLParam(r, "member") {
toJSON(w, memberResponse{Member: member})
return
}
}
}
}
if !found {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "member not found"})
}
func (ms *MockServer) deleteMember(w http.ResponseWriter, r *http.Request) {
idx := -1
for i, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
idx = i
}
}
if idx == -1 {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
result := ms.mailingList[idx].Members[:0]
for _, m := range ms.mailingList[idx].Members {
if m.Address == chi.URLParam(r, "member") {
continue
}
result = append(result, m)
}
if len(result) != len(ms.mailingList[idx].Members) {
toJSON(w, okResp{Message: "Mailing list member has been deleted"})
ms.mailingList[idx].Members = result
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "member not found"})
}
func (ms *MockServer) updateMember(w http.ResponseWriter, r *http.Request) {
idx := -1
for i, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
idx = i
}
}
if idx == -1 {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
for i, m := range ms.mailingList[idx].Members {
if m.Address == chi.URLParam(r, "member") {
if r.FormValue("address") != "" {
ms.mailingList[idx].Members[i].Address = parseAddress(r.FormValue("address"))
}
if r.FormValue("name") != "" {
ms.mailingList[idx].Members[i].Name = r.FormValue("name")
}
if r.FormValue("vars") != "" {
ms.mailingList[idx].Members[i].Vars = stringToMap(r.FormValue("vars"))
}
if r.FormValue("subscribed") != "" {
sub := stringToBool(r.FormValue("subscribed"))
ms.mailingList[idx].Members[i].Subscribed = &sub
}
toJSON(w, okResp{Message: "Mailing list member has been updated"})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "member not found"})
}
func (ms *MockServer) createMember(w http.ResponseWriter, r *http.Request) {
idx := -1
for i, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
idx = i
}
}
if idx == -1 {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
sub := stringToBool(r.FormValue("subscribed"))
if len(ms.mailingList[idx].Members) != 0 {
for i, m := range ms.mailingList[idx].Members {
if m.Address == r.FormValue("address") {
if !stringToBool(r.FormValue("upsert")) {
w.WriteHeader(http.StatusConflict)
toJSON(w, okResp{Message: "member already exists"})
return
}
ms.mailingList[idx].Members[i].Address = parseAddress(r.FormValue("address"))
ms.mailingList[idx].Members[i].Name = r.FormValue("name")
ms.mailingList[idx].Members[i].Vars = stringToMap(r.FormValue("vars"))
ms.mailingList[idx].Members[i].Subscribed = &sub
break
}
}
}
ms.mailingList[idx].Members = append(ms.mailingList[idx].Members, Member{
Name: r.FormValue("name"),
Address: parseAddress(r.FormValue("address")),
Vars: stringToMap(r.FormValue("vars")),
Subscribed: &sub,
})
toJSON(w, okResp{Message: "Mailing list member has been created"})
}
func (ms *MockServer) bulkCreate(w http.ResponseWriter, r *http.Request) {
idx := -1
for i, ml := range ms.mailingList {
if ml.MailingList.Address == chi.URLParam(r, "address") {
idx = i
}
}
if idx == -1 {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "mailing list not found"})
return
}
var bulkList []Member
if err := json.Unmarshal([]byte(r.FormValue("members")), &bulkList); err != nil {
w.WriteHeader(http.StatusInternalServerError)
toJSON(w, okResp{Message: "while un-marshalling 'members' param - " + err.Error()})
return
}
BULK:
for _, member := range bulkList {
member.Address = parseAddress(member.Address)
if len(ms.mailingList[idx].Members) != 0 {
for i, m := range ms.mailingList[idx].Members {
if m.Address == member.Address {
if !stringToBool(r.FormValue("upsert")) {
w.WriteHeader(http.StatusConflict)
toJSON(w, okResp{Message: "member already exists"})
return
}
ms.mailingList[idx].Members[i] = member
continue BULK
}
}
}
ms.mailingList[idx].Members = append(ms.mailingList[idx].Members, member)
}
toJSON(w, okResp{Message: "Mailing list has been updated"})
}

View File

@ -0,0 +1,123 @@
package mailgun
import (
"net/http"
"net/mail"
"strings"
"time"
"github.com/go-chi/chi"
"github.com/mailgun/mailgun-go/v3/events"
)
func (ms *MockServer) addMessagesRoutes(r chi.Router) {
r.Post("/{domain}/messages", ms.createMessages)
// This path is made up; it could be anything as the storage url could change over time
r.Get("/se.storage.url/messages/{id}", ms.getStoredMessages)
r.Post("/se.storage.url/messages/{id}", ms.sendStoredMessages)
}
// TODO: This implementation doesn't support multiple recipients
func (ms *MockServer) createMessages(w http.ResponseWriter, r *http.Request) {
to, err := mail.ParseAddress(r.FormValue("to"))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: "invalid 'to' address"})
return
}
id := randomString(16, "ID-")
switch to.Address {
case "stored@mailgun.test":
stored := new(events.Stored)
stored.Name = events.EventStored
stored.Timestamp = TimeToFloat(time.Now().UTC())
stored.ID = id
stored.Storage.URL = ms.URL() + "/se.storage.url/messages/" + id
stored.Storage.Key = id
stored.Message.Headers = events.MessageHeaders{
Subject: r.FormValue("subject"),
From: r.FormValue("from"),
To: to.Address,
MessageID: id,
}
stored.Message.Recipients = []string{
r.FormValue("to"),
}
stored.Message.Size = 10
stored.Flags = events.Flags{
IsTestMode: false,
}
ms.events = append(ms.events, stored)
default:
accepted := new(events.Accepted)
accepted.Name = events.EventAccepted
accepted.ID = id
accepted.Timestamp = TimeToFloat(time.Now().UTC())
accepted.Message.Headers.From = r.FormValue("from")
accepted.Message.Headers.To = r.FormValue("to")
accepted.Message.Headers.MessageID = accepted.ID
accepted.Message.Headers.Subject = r.FormValue("subject")
accepted.Recipient = r.FormValue("to")
accepted.RecipientDomain = strings.Split(to.Address, "@")[1]
accepted.Flags = events.Flags{
IsAuthenticated: true,
}
ms.events = append(ms.events, accepted)
}
toJSON(w, okResp{ID: "<" + id + ">", Message: "Queued. Thank you."})
}
func (ms *MockServer) getStoredMessages(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
// Find our stored event
var stored *events.Stored
for _, event := range ms.events {
if event.GetID() == id {
stored = event.(*events.Stored)
}
}
if stored == nil {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "not found"})
}
toJSON(w, StoredMessage{
Recipients: strings.Join(stored.Message.Recipients, ","),
Sender: stored.Message.Headers.From,
Subject: stored.Message.Headers.Subject,
From: stored.Message.Headers.From,
MessageHeaders: [][]string{
{"Sender", stored.Message.Headers.From},
{"To", stored.Message.Headers.To},
{"Subject", stored.Message.Headers.Subject},
{"Content-Type", "text/plain"},
},
})
}
func (ms *MockServer) sendStoredMessages(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
// Find our stored event
var stored *events.Stored
for _, event := range ms.events {
if event.GetID() == id {
stored = event.(*events.Stored)
}
}
if stored == nil {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "not found"})
}
// DO NOTHING
toJSON(w, okResp{ID: "<" + id + ">", Message: "Queued. Thank you."})
}

139
vendor/github.com/mailgun/mailgun-go/v3/mock_routes.go generated vendored Normal file
View File

@ -0,0 +1,139 @@
package mailgun
import (
"fmt"
"github.com/go-chi/chi"
"net/http"
"time"
)
type routeResponse struct {
Route Route `json:"route"`
}
func (ms *MockServer) addRoutes(r chi.Router) {
r.Post("/routes", ms.createRoute)
r.Get("/routes", ms.listRoutes)
r.Get("/routes/{id}", ms.getRoute)
r.Put("/routes/{id}", ms.updateRoute)
r.Delete("/routes/{id}", ms.deleteRoute)
for i := 0; i < 10; i++ {
ms.routeList = append(ms.routeList, Route{
Id: randomString(10, "ID-"),
Priority: 0,
Description: fmt.Sprintf("Sample Route %d", i),
Actions: []string{
`forward("http://myhost.com/messages/")`,
`stop()`,
},
Expression: `match_recipient(".*@samples.mailgun.org")`,
})
}
}
func (ms *MockServer) listRoutes(w http.ResponseWriter, r *http.Request) {
skip := stringToInt(r.FormValue("skip"))
limit := stringToInt(r.FormValue("limit"))
if limit == 0 {
limit = 100
}
if skip > len(ms.routeList) {
skip = len(ms.routeList)
}
end := limit + skip
if end > len(ms.routeList) {
end = len(ms.routeList)
}
// If we are at the end of the list
if skip == end {
toJSON(w, routesListResponse{
TotalCount: len(ms.routeList),
Items: []Route{},
})
return
}
toJSON(w, routesListResponse{
TotalCount: len(ms.routeList),
Items: ms.routeList[skip:end],
})
}
func (ms *MockServer) getRoute(w http.ResponseWriter, r *http.Request) {
for _, item := range ms.routeList {
if item.Id == chi.URLParam(r, "id") {
toJSON(w, routeResponse{Route: item})
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "route not found"})
}
func (ms *MockServer) createRoute(w http.ResponseWriter, r *http.Request) {
if r.FormValue("action") == "" {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: "'action' parameter is required"})
return
}
ms.routeList = append(ms.routeList, Route{
CreatedAt: RFC2822Time(time.Now().UTC()),
Id: randomString(10, "ID-"),
Priority: stringToInt(r.FormValue("priority")),
Description: r.FormValue("description"),
Expression: r.FormValue("expression"),
Actions: r.Form["action"],
})
toJSON(w, createRouteResp{
Message: "Route has been created",
Route: ms.routeList[len(ms.routeList)-1],
})
}
func (ms *MockServer) updateRoute(w http.ResponseWriter, r *http.Request) {
for i, item := range ms.routeList {
if item.Id == chi.URLParam(r, "id") {
if r.FormValue("action") != "" {
ms.routeList[i].Actions = r.Form["action"]
}
if r.FormValue("priority") != "" {
ms.routeList[i].Priority = stringToInt(r.FormValue("priority"))
}
if r.FormValue("description") != "" {
ms.routeList[i].Description = r.FormValue("description")
}
if r.FormValue("expression") != "" {
ms.routeList[i].Expression = r.FormValue("expression")
}
toJSON(w, ms.routeList[i])
return
}
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "route not found"})
}
func (ms *MockServer) deleteRoute(w http.ResponseWriter, r *http.Request) {
result := ms.routeList[:0]
for _, item := range ms.routeList {
if item.Id == chi.URLParam(r, "id") {
continue
}
result = append(result, item)
}
if len(result) != len(ms.domainList) {
toJSON(w, okResp{Message: "success"})
ms.routeList = result
return
}
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "route not found"})
}

View File

@ -0,0 +1,55 @@
package mailgun
import (
"net/http"
"net/mail"
"strings"
"github.com/go-chi/chi"
)
func (ms *MockServer) addValidationRoutes(r chi.Router) {
r.Get("/address/validate", ms.validateEmail)
r.Get("/address/parse", ms.parseEmail)
r.Get("/address/private/validate", ms.validateEmail)
r.Get("/address/private/parse", ms.parseEmail)
}
func (ms *MockServer) validateEmail(w http.ResponseWriter, r *http.Request) {
if r.FormValue("address") == "" {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: "'address' parameter is required"})
return
}
var results EmailVerification
parts, err := mail.ParseAddress(r.FormValue("address"))
if err == nil {
results.IsValid = true
results.Parts.Domain = strings.Split(parts.Address, "@")[1]
results.Parts.LocalPart = strings.Split(parts.Address, "@")[0]
results.Parts.DisplayName = parts.Name
}
toJSON(w, results)
}
func (ms *MockServer) parseEmail(w http.ResponseWriter, r *http.Request) {
if r.FormValue("addresses") == "" {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: "'addresses' parameter is required"})
return
}
addresses := strings.Split(r.FormValue("addresses"), ",")
var results addressParseResult
for _, address := range addresses {
_, err := mail.ParseAddress(address)
if err != nil {
results.Unparseable = append(results.Unparseable, address)
} else {
results.Parsed = append(results.Parsed, address)
}
}
toJSON(w, results)
}

View File

@ -0,0 +1,83 @@
package mailgun
import (
"net/http"
"github.com/go-chi/chi"
)
func (ms *MockServer) addWebhookRoutes(r chi.Router) {
r.Route("/domains/{domain}/webhooks", func(r chi.Router) {
r.Get("/", ms.listWebHooks)
r.Post("/", ms.postWebHook)
r.Get("/{webhook}", ms.getWebHook)
r.Put("/{webhook}", ms.putWebHook)
r.Delete("/{webhook}", ms.deleteWebHook)
})
ms.webhooks = WebHooksListResponse{
Webhooks: map[string]UrlOrUrls{
"new-webhook": {
Urls: []string{"http://example.com/new"},
},
"legacy-webhook": {
Url: "http://example.com/legacy",
},
},
}
}
func (ms *MockServer) listWebHooks(w http.ResponseWriter, _ *http.Request) {
toJSON(w, ms.webhooks)
}
func (ms *MockServer) getWebHook(w http.ResponseWriter, r *http.Request) {
resp := WebHookResponse{
Webhook: UrlOrUrls{
Urls: ms.webhooks.Webhooks[chi.URLParam(r, "webhook")].Urls,
},
}
toJSON(w, resp)
}
func (ms *MockServer) postWebHook(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: err.Error()})
return
}
var urls []string
for _, url := range r.Form["url"] {
urls = append(urls, url)
}
ms.webhooks.Webhooks[r.FormValue("id")] = UrlOrUrls{Urls: urls}
toJSON(w, okResp{Message: "success"})
}
func (ms *MockServer) putWebHook(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusBadRequest)
toJSON(w, okResp{Message: err.Error()})
return
}
var urls []string
for _, url := range r.Form["url"] {
urls = append(urls, url)
}
ms.webhooks.Webhooks[chi.URLParam(r, "webhook")] = UrlOrUrls{Urls: urls}
toJSON(w, okResp{Message: "success"})
}
func (ms *MockServer) deleteWebHook(w http.ResponseWriter, r *http.Request) {
_, ok := ms.webhooks.Webhooks[chi.URLParam(r, "webhook")]
if !ok {
w.WriteHeader(http.StatusNotFound)
toJSON(w, okResp{Message: "webhook not found"})
}
delete(ms.webhooks.Webhooks, chi.URLParam(r, "webhook"))
toJSON(w, okResp{Message: "success"})
}

99
vendor/github.com/mailgun/mailgun-go/v3/parse.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
package mailgun
import (
"fmt"
"reflect"
"time"
"github.com/mailgun/mailgun-go/v3/events"
"github.com/mailru/easyjson"
)
// All events returned by the EventIterator conform to this interface
type Event interface {
easyjson.Unmarshaler
easyjson.Marshaler
GetName() string
SetName(name string)
GetTimestamp() time.Time
SetTimestamp(time.Time)
GetID() string
SetID(id string)
}
// A list of all JSON event types returned by the /events API
var EventNames = map[string]func() Event{
"accepted": new_(events.Accepted{}),
"clicked": new_(events.Clicked{}),
"complained": new_(events.Complained{}),
"delivered": new_(events.Delivered{}),
"failed": new_(events.Failed{}),
"opened": new_(events.Opened{}),
"rejected": new_(events.Rejected{}),
"stored": new_(events.Stored{}),
"unsubscribed": new_(events.Unsubscribed{}),
"list_member_uploaded": new_(events.ListMemberUploaded{}),
"list_member_upload_error": new_(events.ListMemberUploadError{}),
"list_uploaded": new_(events.ListUploaded{}),
}
// new_ is a universal event "constructor".
func new_(e interface{}) func() Event {
typ := reflect.TypeOf(e)
return func() Event {
return reflect.New(typ).Interface().(Event)
}
}
func parseResponse(raw []byte) ([]Event, error) {
var resp events.Response
if err := easyjson.Unmarshal(raw, &resp); err != nil {
return nil, fmt.Errorf("failed to un-marshall event.Response: %s", err)
}
var result []Event
for _, value := range resp.Items {
event, err := ParseEvent(value)
if err != nil {
return nil, fmt.Errorf("while parsing event: %s", err)
}
result = append(result, event)
}
return result, nil
}
// Given a slice of events.RawJSON events return a slice of Event for each parsed event
func ParseEvents(raw []events.RawJSON) ([]Event, error) {
var result []Event
for _, value := range raw {
event, err := ParseEvent(value)
if err != nil {
return nil, fmt.Errorf("while parsing event: %s", err)
}
result = append(result, event)
}
return result, nil
}
// Parse converts raw bytes data into an event struct. Can accept events.RawJSON as input
func ParseEvent(raw []byte) (Event, error) {
// Try to recognize the event first.
var e events.EventName
if err := easyjson.Unmarshal(raw, &e); err != nil {
return nil, fmt.Errorf("failed to recognize event: %v", err)
}
// Get the event "constructor" from the map.
newEvent, ok := EventNames[e.Name]
if !ok {
return nil, fmt.Errorf("unsupported event: '%s'", e.Name)
}
event := newEvent()
// Parse the known event.
if err := easyjson.Unmarshal(raw, event); err != nil {
return nil, fmt.Errorf("failed to parse event '%s': %v", e.Name, err)
}
return event, nil
}

42
vendor/github.com/mailgun/mailgun-go/v3/recipients.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
package mailgun
import "fmt"
import "strings"
type Recipient struct {
Name string `json:"-"`
Email string `json:"-"`
}
func (r Recipient) String() string {
if r.Name != "" {
return fmt.Sprintf("%s <%s>", r.Name, r.Email)
}
return r.Email
}
// MarshalText satisfies TextMarshaler
func (r Recipient) MarshalText() ([]byte, error) {
return []byte(r.String()), nil
}
// UnmarshalText satisfies TextUnmarshaler
func (r *Recipient) UnmarshalText(text []byte) error {
s := string(text)
if s[len(s)-1:] != ">" {
*r = Recipient{Email: s}
return nil
}
i := strings.Index(s, "<")
// at least 1 char followed by a space
if i < 2 {
return fmt.Errorf("malformed recipient string '%s'", s)
}
*r = Recipient{
Name: strings.TrimSpace(s[:i]),
Email: s[i+1 : len(s)-1],
}
return nil
}

163
vendor/github.com/mailgun/mailgun-go/v3/rest_shim.go generated vendored Normal file
View File

@ -0,0 +1,163 @@
package mailgun
import (
"context"
"fmt"
)
// The MailgunGoUserAgent identifies the client to the server, for logging purposes.
// In the event of problems requiring a human administrator's assistance,
// this user agent allows them to identify the client from human-generated activity.
const MailgunGoUserAgent = "mailgun-go/" + Version
// This error will be returned whenever a Mailgun API returns an error response.
// Your application can check the Actual field to see the actual HTTP response code returned.
// URL contains the base URL accessed, sans any query parameters.
type UnexpectedResponseError struct {
Expected []int
Actual int
URL string
Data []byte
}
// String() converts the error into a human-readable, logfmt-compliant string.
// See http://godoc.org/github.com/kr/logfmt for details on logfmt formatting.
func (e *UnexpectedResponseError) String() string {
return fmt.Sprintf("UnexpectedResponseError URL=%s ExpectedOneOf=%#v Got=%d Error: %s", e.URL, e.Expected, e.Actual, string(e.Data))
}
// Error() performs as String().
func (e *UnexpectedResponseError) Error() string {
return e.String()
}
// newError creates a new error condition to be returned.
func newError(url string, expected []int, got *httpResponse) error {
return &UnexpectedResponseError{
URL: url,
Expected: expected,
Actual: got.Code,
Data: got.Data,
}
}
// notGood searches a list of response codes (the haystack) for a matching entry (the needle).
// If found, the response code is considered good, and thus false is returned.
// Otherwise true is returned.
func notGood(needle int, haystack []int) bool {
for _, i := range haystack {
if needle == i {
return false
}
}
return true
}
// expected denotes the expected list of known-good HTTP response codes possible from the Mailgun API.
var expected = []int{200, 202, 204}
// makeRequest shim performs a generic request, checking for a positive outcome.
// See simplehttp.MakeRequest for more details.
func makeRequest(ctx context.Context, r *httpRequest, method string, p payload) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makeRequest(ctx, method, p)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// getResponseFromJSON shim performs a GET request, checking for a positive outcome.
// See simplehttp.GetResponseFromJSON for more details.
func getResponseFromJSON(ctx context.Context, r *httpRequest, v interface{}) error {
r.addHeader("User-Agent", MailgunGoUserAgent)
response, err := r.makeGetRequest(ctx)
if err != nil {
return err
}
if notGood(response.Code, expected) {
return newError(r.URL, expected, response)
}
return response.parseFromJSON(v)
}
// postResponseFromJSON shim performs a POST request, checking for a positive outcome.
// See simplehttp.PostResponseFromJSON for more details.
func postResponseFromJSON(ctx context.Context, r *httpRequest, p payload, v interface{}) error {
r.addHeader("User-Agent", MailgunGoUserAgent)
response, err := r.makePostRequest(ctx, p)
if err != nil {
return err
}
if notGood(response.Code, expected) {
return newError(r.URL, expected, response)
}
return response.parseFromJSON(v)
}
// putResponseFromJSON shim performs a PUT request, checking for a positive outcome.
// See simplehttp.PutResponseFromJSON for more details.
func putResponseFromJSON(ctx context.Context, r *httpRequest, p payload, v interface{}) error {
r.addHeader("User-Agent", MailgunGoUserAgent)
response, err := r.makePutRequest(ctx, p)
if err != nil {
return err
}
if notGood(response.Code, expected) {
return newError(r.URL, expected, response)
}
return response.parseFromJSON(v)
}
// makeGetRequest shim performs a GET request, checking for a positive outcome.
// See simplehttp.MakeGetRequest for more details.
func makeGetRequest(ctx context.Context, r *httpRequest) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makeGetRequest(ctx)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// makePostRequest shim performs a POST request, checking for a positive outcome.
// See simplehttp.MakePostRequest for more details.
func makePostRequest(ctx context.Context, r *httpRequest, p payload) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makePostRequest(ctx, p)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// makePutRequest shim performs a PUT request, checking for a positive outcome.
// See simplehttp.MakePutRequest for more details.
func makePutRequest(ctx context.Context, r *httpRequest, p payload) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makePutRequest(ctx, p)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// makeDeleteRequest shim performs a DELETE request, checking for a positive outcome.
// See simplehttp.MakeDeleteRequest for more details.
func makeDeleteRequest(ctx context.Context, r *httpRequest) (*httpResponse, error) {
r.addHeader("User-Agent", MailgunGoUserAgent)
rsp, err := r.makeDeleteRequest(ctx)
if (err == nil) && notGood(rsp.Code, expected) {
return rsp, newError(r.URL, expected, rsp)
}
return rsp, err
}
// Extract the http status code from error object
func GetStatusFromErr(err error) int {
obj, ok := err.(*UnexpectedResponseError)
if !ok {
return -1
}
return obj.Actual
}

52
vendor/github.com/mailgun/mailgun-go/v3/rfc2822.go generated vendored Normal file
View File

@ -0,0 +1,52 @@
package mailgun
import (
"strconv"
"strings"
"time"
)
// Mailgun uses RFC2822 format for timestamps everywhere ('Thu, 13 Oct 2011 18:02:00 GMT'), but
// by default Go's JSON package uses another format when decoding/encoding timestamps.
type RFC2822Time time.Time
func NewRFC2822Time(str string) (RFC2822Time, error) {
t, err := time.Parse(time.RFC1123, str)
if err != nil {
return RFC2822Time{}, err
}
return RFC2822Time(t), nil
}
func (t RFC2822Time) Unix() int64 {
return time.Time(t).Unix()
}
func (t RFC2822Time) IsZero() bool {
return time.Time(t).IsZero()
}
func (t RFC2822Time) MarshalJSON() ([]byte, error) {
return []byte(strconv.Quote(time.Time(t).Format(time.RFC1123))), nil
}
func (t *RFC2822Time) UnmarshalJSON(s []byte) error {
q, err := strconv.Unquote(string(s))
if err != nil {
return err
}
if *(*time.Time)(t), err = time.Parse(time.RFC1123, q); err != nil {
if strings.Contains(err.Error(), "extra text") {
if *(*time.Time)(t), err = time.Parse(time.RFC1123Z, q); err != nil {
return err
}
return nil
}
return err
}
return nil
}
func (t RFC2822Time) String() string {
return time.Time(t).Format(time.RFC1123)
}

272
vendor/github.com/mailgun/mailgun-go/v3/routes.go generated vendored Normal file
View File

@ -0,0 +1,272 @@
package mailgun
import (
"context"
"strconv"
)
// A Route structure contains information on a configured or to-be-configured route.
// When creating a new route, the SDK only uses a subset of the fields of this structure.
// In particular, CreatedAt and ID are meaningless in this context, and will be ignored.
// Only Priority, Description, Expression, and Actions need be provided.
type Route struct {
// The Priority field indicates how soon the route works relative to other configured routes.
// Routes of equal priority are consulted in chronological order.
Priority int `json:"priority,omitempty"`
// The Description field provides a human-readable description for the route.
// Mailgun ignores this field except to provide the description when viewing the Mailgun web control panel.
Description string `json:"description,omitempty"`
// The Expression field lets you specify a pattern to match incoming messages against.
Expression string `json:"expression,omitempty"`
// The Actions field contains strings specifying what to do
// with any message which matches the provided expression.
Actions []string `json:"actions,omitempty"`
// The CreatedAt field provides a time-stamp for when the route came into existence.
CreatedAt RFC2822Time `json:"created_at,omitempty"`
// ID field provides a unique identifier for this route.
Id string `json:"id,omitempty"`
}
type routesListResponse struct {
// is -1 if Next() or First() have not been called
TotalCount int `json:"total_count"`
Items []Route `json:"items"`
}
type createRouteResp struct {
Message string `json:"message"`
Route `json:"route"`
}
// ListRoutes allows you to iterate through a list of routes returned by the API
func (mg *MailgunImpl) ListRoutes(opts *ListOptions) *RoutesIterator {
var limit int
if opts != nil {
limit = opts.Limit
}
if limit == 0 {
limit = 100
}
return &RoutesIterator{
mg: mg,
url: generatePublicApiUrl(mg, routesEndpoint),
routesListResponse: routesListResponse{TotalCount: -1},
limit: limit,
}
}
type RoutesIterator struct {
routesListResponse
limit int
mg Mailgun
offset int
url string
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ri *RoutesIterator) Err() error {
return ri.err
}
// Offset returns the current offset of the iterator
func (ri *RoutesIterator) Offset() int {
return ri.offset
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (ri *RoutesIterator) Next(ctx context.Context, items *[]Route) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Route, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
ri.offset = ri.offset + len(ri.Items)
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (ri *RoutesIterator) First(ctx context.Context, items *[]Route) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, 0, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Route, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
ri.offset = len(ri.Items)
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (ri *RoutesIterator) Last(ctx context.Context, items *[]Route) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.TotalCount - ri.limit
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Route, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (ri *RoutesIterator) Previous(ctx context.Context, items *[]Route) bool {
if ri.err != nil {
return false
}
if ri.TotalCount == -1 {
return false
}
ri.offset = ri.offset - (ri.limit * 2)
if ri.offset < 0 {
ri.offset = 0
}
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Route, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
return true
}
func (ri *RoutesIterator) fetch(ctx context.Context, skip, limit int) error {
r := newHTTPRequest(ri.url)
r.setBasicAuth(basicAuthUser, ri.mg.APIKey())
r.setClient(ri.mg.Client())
if skip != 0 {
r.addParameter("skip", strconv.Itoa(skip))
}
if limit != 0 {
r.addParameter("limit", strconv.Itoa(limit))
}
return getResponseFromJSON(ctx, r, &ri.routesListResponse)
}
// CreateRoute installs a new route for your domain.
// The route structure you provide serves as a template, and
// only a subset of the fields influence the operation.
// See the Route structure definition for more details.
func (mg *MailgunImpl) CreateRoute(ctx context.Context, prototype Route) (_ignored Route, err error) {
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("priority", strconv.Itoa(prototype.Priority))
p.addValue("description", prototype.Description)
p.addValue("expression", prototype.Expression)
for _, action := range prototype.Actions {
p.addValue("action", action)
}
var resp createRouteResp
if err = postResponseFromJSON(ctx, r, p, &resp); err != nil {
return _ignored, err
}
return resp.Route, err
}
// DeleteRoute removes the specified route from your domain's configuration.
// To avoid ambiguity, Mailgun identifies the route by unique ID.
// See the Route structure definition and the Mailgun API documentation for more details.
func (mg *MailgunImpl) DeleteRoute(ctx context.Context, id string) error {
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint) + "/" + id)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetRoute retrieves the complete route definition associated with the unique route ID.
func (mg *MailgunImpl) GetRoute(ctx context.Context, id string) (Route, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint) + "/" + id)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var envelope struct {
Message string `json:"message"`
*Route `json:"route"`
}
err := getResponseFromJSON(ctx, r, &envelope)
if err != nil {
return Route{}, err
}
return *envelope.Route, err
}
// UpdateRoute provides an "in-place" update of the specified route.
// Only those route fields which are non-zero or non-empty are updated.
// All other fields remain as-is.
func (mg *MailgunImpl) UpdateRoute(ctx context.Context, id string, route Route) (Route, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, routesEndpoint) + "/" + id)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if route.Priority != 0 {
p.addValue("priority", strconv.Itoa(route.Priority))
}
if route.Description != "" {
p.addValue("description", route.Description)
}
if route.Expression != "" {
p.addValue("expression", route.Expression)
}
if route.Actions != nil {
for _, action := range route.Actions {
p.addValue("action", action)
}
}
// For some reason, this API function just returns a bare Route on success.
// Unsure why this is the case; it seems like it ought to be a bug.
var envelope Route
err := putResponseFromJSON(ctx, r, p, &envelope)
return envelope, err
}

View File

@ -0,0 +1,174 @@
package mailgun
import (
"context"
"strconv"
)
const (
complaintsEndpoint = "complaints"
)
// Complaint structures track how many times one of your emails have been marked as spam.
// the recipient thought your messages were not solicited.
type Complaint struct {
Count int `json:"count"`
CreatedAt RFC2822Time `json:"created_at"`
Address string `json:"address"`
}
type complaintsResponse struct {
Paging Paging `json:"paging"`
Items []Complaint `json:"items"`
}
// ListComplaints returns a set of spam complaints registered against your domain.
// Recipients of your messages can click on a link which sends feedback to Mailgun
// indicating that the message they received is, to them, spam.
func (mg *MailgunImpl) ListComplaints(opts *ListOptions) *ComplaintsIterator {
r := newHTTPRequest(generateApiUrl(mg, complaintsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
if opts != nil {
if opts.Limit != 0 {
r.addParameter("limit", strconv.Itoa(opts.Limit))
}
}
url, err := r.generateUrlWithParameters()
return &ComplaintsIterator{
mg: mg,
complaintsResponse: complaintsResponse{Paging: Paging{Next: url, First: url}},
err: err,
}
}
type ComplaintsIterator struct {
complaintsResponse
mg Mailgun
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ci *ComplaintsIterator) Err() error {
return ci.err
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (ci *ComplaintsIterator) Next(ctx context.Context, items *[]Complaint) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Next)
if ci.err != nil {
return false
}
cpy := make([]Complaint, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
if len(ci.Items) == 0 {
return false
}
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (ci *ComplaintsIterator) First(ctx context.Context, items *[]Complaint) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.First)
if ci.err != nil {
return false
}
cpy := make([]Complaint, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (ci *ComplaintsIterator) Last(ctx context.Context, items *[]Complaint) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Last)
if ci.err != nil {
return false
}
cpy := make([]Complaint, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (ci *ComplaintsIterator) Previous(ctx context.Context, items *[]Complaint) bool {
if ci.err != nil {
return false
}
if ci.Paging.Previous == "" {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Previous)
if ci.err != nil {
return false
}
cpy := make([]Complaint, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
if len(ci.Items) == 0 {
return false
}
return true
}
func (ci *ComplaintsIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(ci.mg.Client())
r.setBasicAuth(basicAuthUser, ci.mg.APIKey())
return getResponseFromJSON(ctx, r, &ci.complaintsResponse)
}
// GetComplaint returns a single complaint record filed by a recipient at the email address provided.
// If no complaint exists, the Complaint instance returned will be empty.
func (mg *MailgunImpl) GetComplaint(ctx context.Context, address string) (Complaint, error) {
r := newHTTPRequest(generateApiUrl(mg, complaintsEndpoint) + "/" + address)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var c Complaint
err := getResponseFromJSON(ctx, r, &c)
return c, err
}
// CreateComplaint registers the specified address as a recipient who has complained of receiving spam
// from your domain.
func (mg *MailgunImpl) CreateComplaint(ctx context.Context, address string) error {
r := newHTTPRequest(generateApiUrl(mg, complaintsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("address", address)
_, err := makePostRequest(ctx, r, p)
return err
}
// DeleteComplaint removes a previously registered e-mail address from the list of people who complained
// of receiving spam from your domain.
func (mg *MailgunImpl) DeleteComplaint(ctx context.Context, address string) error {
r := newHTTPRequest(generateApiUrl(mg, complaintsEndpoint) + "/" + address)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}

120
vendor/github.com/mailgun/mailgun-go/v3/stats.go generated vendored Normal file
View File

@ -0,0 +1,120 @@
package mailgun
import (
"context"
"strconv"
"time"
)
// Stats on accepted messages
type Accepted struct {
Incoming int `json:"incoming"`
Outgoing int `json:"outgoing"`
Total int `json:"total"`
}
// Stats on delivered messages
type Delivered struct {
Smtp int `json:"smtp"`
Http int `json:"http"`
Total int `json:"total"`
}
// Stats on temporary failures
type Temporary struct {
Espblock int `json:"espblock"`
}
// Stats on permanent failures
type Permanent struct {
SuppressBounce int `json:"suppress-bounce"`
SuppressUnsubscribe int `json:"suppress-unsubscribe"`
SuppressComplaint int `json:"suppress-complaint"`
Bounce int `json:"bounce"`
DelayedBounce int `json:"delayed-bounce"`
Total int `json:"total"`
}
// Stats on failed messages
type Failed struct {
Temporary Temporary `json:"temporary"`
Permanent Permanent `json:"permanent"`
}
// Total stats for messages
type Total struct {
Total int `json:"total"`
}
// Stats as returned by `GetStats()`
type Stats struct {
Time string `json:"time"`
Accepted Accepted `json:"accepted"`
Delivered Delivered `json:"delivered"`
Failed Failed `json:"failed"`
Stored Total `json:"stored"`
Opened Total `json:"opened"`
Clicked Total `json:"clicked"`
Unsubscribed Total `json:"unsubscribed"`
Complained Total `json:"complained"`
}
type statsTotalResponse struct {
End string `json:"end"`
Resolution string `json:"resolution"`
Start string `json:"start"`
Stats []Stats `json:"stats"`
}
// Used by GetStats() to specify the resolution stats are for
type Resolution string
// Indicate which resolution a stat response for request is for
const (
ResolutionHour = Resolution("hour")
ResolutionDay = Resolution("day")
ResolutionMonth = Resolution("month")
)
// Options for GetStats()
type GetStatOptions struct {
Resolution Resolution
Duration string
Start time.Time
End time.Time
}
// GetStats returns total stats for a given domain for the specified time period
func (mg *MailgunImpl) GetStats(ctx context.Context, events []string, opts *GetStatOptions) ([]Stats, error) {
r := newHTTPRequest(generateApiUrl(mg, statsTotalEndpoint))
if opts != nil {
if !opts.Start.IsZero() {
r.addParameter("start", strconv.Itoa(int(opts.Start.Unix())))
}
if !opts.End.IsZero() {
r.addParameter("end", strconv.Itoa(int(opts.End.Unix())))
}
if opts.Resolution != "" {
r.addParameter("resolution", string(opts.Resolution))
}
if opts.Duration != "" {
r.addParameter("duration", opts.Duration)
}
}
for _, e := range events {
r.addParameter("event", e)
}
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var res statsTotalResponse
err := getResponseFromJSON(ctx, r, &res)
if err != nil {
return nil, err
} else {
return res.Stats, nil
}
}

183
vendor/github.com/mailgun/mailgun-go/v3/tags.go generated vendored Normal file
View File

@ -0,0 +1,183 @@
package mailgun
import (
"context"
"net/url"
"strconv"
"time"
)
type Tag struct {
Value string `json:"tag"`
Description string `json:"description"`
FirstSeen *time.Time `json:"first-seen,omitempty"`
LastSeen *time.Time `json:"last-seen,omitempty"`
}
type tagsResponse struct {
Items []Tag `json:"items"`
Paging Paging `json:"paging"`
}
type ListTagOptions struct {
// Restrict the page size to this limit
Limit int
// Return only the tags starting with the given prefix
Prefix string
}
// DeleteTag removes all counters for a particular tag, including the tag itself.
func (mg *MailgunImpl) DeleteTag(ctx context.Context, tag string) error {
r := newHTTPRequest(generateApiUrl(mg, tagsEndpoint) + "/" + tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetTag retrieves metadata about the tag from the api
func (mg *MailgunImpl) GetTag(ctx context.Context, tag string) (Tag, error) {
r := newHTTPRequest(generateApiUrl(mg, tagsEndpoint) + "/" + tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var tagItem Tag
return tagItem, getResponseFromJSON(ctx, r, &tagItem)
}
// ListTags returns a cursor used to iterate through a list of tags
// it := mg.ListTags(nil)
// var page []mailgun.Tag
// for it.Next(&page) {
// for _, tag := range(page) {
// // Do stuff with tags
// }
// }
// if it.Err() != nil {
// log.Fatal(it.Err())
// }
func (mg *MailgunImpl) ListTags(opts *ListTagOptions) *TagIterator {
req := newHTTPRequest(generateApiUrl(mg, tagsEndpoint))
if opts != nil {
if opts.Limit != 0 {
req.addParameter("limit", strconv.Itoa(opts.Limit))
}
if opts.Prefix != "" {
req.addParameter("prefix", opts.Prefix)
}
}
url, err := req.generateUrlWithParameters()
return &TagIterator{
tagsResponse: tagsResponse{Paging: Paging{Next: url, First: url}},
err: err,
mg: mg,
}
}
type TagIterator struct {
tagsResponse
mg Mailgun
err error
}
// Next returns the next page in the list of tags
func (ti *TagIterator) Next(ctx context.Context, items *[]Tag) bool {
if ti.err != nil {
return false
}
if !canFetchPage(ti.Paging.Next) {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Next)
if ti.err != nil {
return false
}
*items = ti.Items
if len(ti.Items) == 0 {
return false
}
return true
}
// Previous returns the previous page in the list of tags
func (ti *TagIterator) Previous(ctx context.Context, items *[]Tag) bool {
if ti.err != nil {
return false
}
if ti.Paging.Previous == "" {
return false
}
if !canFetchPage(ti.Paging.Previous) {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Previous)
if ti.err != nil {
return false
}
*items = ti.Items
if len(ti.Items) == 0 {
return false
}
return true
}
// First returns the first page in the list of tags
func (ti *TagIterator) First(ctx context.Context, items *[]Tag) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.First)
if ti.err != nil {
return false
}
*items = ti.Items
return true
}
// Last returns the last page in the list of tags
func (ti *TagIterator) Last(ctx context.Context, items *[]Tag) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Last)
if ti.err != nil {
return false
}
*items = ti.Items
return true
}
// Err returns any error if one occurred
func (ti *TagIterator) Err() error {
return ti.err
}
func (ti *TagIterator) fetch(ctx context.Context, url string) error {
req := newHTTPRequest(url)
req.setClient(ti.mg.Client())
req.setBasicAuth(basicAuthUser, ti.mg.APIKey())
return getResponseFromJSON(ctx, req, &ti.tagsResponse)
}
func canFetchPage(slug string) bool {
parts, err := url.Parse(slug)
if err != nil {
return false
}
params, _ := url.ParseQuery(parts.RawQuery)
if err != nil {
return false
}
value, ok := params["tag"]
// If tags doesn't exist, it's our first time fetching pages
if !ok {
return true
}
// If tags has no value, there are no more pages to fetch
return len(value) == 0
}

240
vendor/github.com/mailgun/mailgun-go/v3/template.go generated vendored Normal file
View File

@ -0,0 +1,240 @@
package mailgun
import (
"context"
"errors"
"strconv"
)
type TemplateEngine string
// Used by CreateTemplate() and AddTemplateVersion() to specify the template engine
const (
TemplateEngineMustache = TemplateEngine("mustache")
TemplateEngineHandlebars = TemplateEngine("handlebars")
TemplateEngineGo = TemplateEngine("go")
)
type Template struct {
Name string `json:"name"`
Description string `json:"description"`
CreatedAt RFC2822Time `json:"createdAt"`
Version TemplateVersion `json:"version,omitempty"`
}
type templateResp struct {
Item Template `json:"template"`
Message string `json:"message"`
}
type templateListResp struct {
Items []Template `json:"items"`
Paging Paging `json:"paging"`
}
// Create a new template which can be used to attach template versions to
func (mg *MailgunImpl) CreateTemplate(ctx context.Context, template *Template) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
if template.Name != "" {
payload.addValue("name", template.Name)
}
if template.Description != "" {
payload.addValue("description", template.Description)
}
if template.Version.Engine != "" {
payload.addValue("engine", string(template.Version.Engine))
}
if template.Version.Template != "" {
payload.addValue("template", template.Version.Template)
}
if template.Version.Comment != "" {
payload.addValue("comment", template.Version.Comment)
}
var resp templateResp
if err := postResponseFromJSON(ctx, r, payload, &resp); err != nil {
return err
}
*template = resp.Item
return nil
}
// GetTemplate gets a template given the template name
func (mg *MailgunImpl) GetTemplate(ctx context.Context, name string) (Template, error) {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + name)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addParameter("active", "yes")
var resp templateResp
err := getResponseFromJSON(ctx, r, &resp)
if err != nil {
return Template{}, err
}
return resp.Item, nil
}
// Update the name and description of a template
func (mg *MailgunImpl) UpdateTemplate(ctx context.Context, template *Template) error {
if template.Name == "" {
return errors.New("UpdateTemplate() Template.Name cannot be empty")
}
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + template.Name)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if template.Name != "" {
p.addValue("name", template.Name)
}
if template.Description != "" {
p.addValue("description", template.Description)
}
var resp templateResp
err := putResponseFromJSON(ctx, r, p, &resp)
if err != nil {
return err
}
*template = resp.Item
return nil
}
// Delete a template given a template name
func (mg *MailgunImpl) DeleteTemplate(ctx context.Context, name string) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + name)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
type TemplatesIterator struct {
templateListResp
mg Mailgun
err error
}
type ListTemplateOptions struct {
Limit int
Active bool
}
// List all available templates
func (mg *MailgunImpl) ListTemplates(opts *ListTemplateOptions) *TemplatesIterator {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
if opts != nil {
if opts.Limit != 0 {
r.addParameter("limit", strconv.Itoa(opts.Limit))
}
if opts.Active {
r.addParameter("active", "yes")
}
}
url, err := r.generateUrlWithParameters()
return &TemplatesIterator{
mg: mg,
templateListResp: templateListResp{Paging: Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (ti *TemplatesIterator) Err() error {
return ti.err
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (ti *TemplatesIterator) Next(ctx context.Context, items *[]Template) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Next)
if ti.err != nil {
return false
}
cpy := make([]Template, len(ti.Items))
copy(cpy, ti.Items)
*items = cpy
if len(ti.Items) == 0 {
return false
}
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (ti *TemplatesIterator) First(ctx context.Context, items *[]Template) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.First)
if ti.err != nil {
return false
}
cpy := make([]Template, len(ti.Items))
copy(cpy, ti.Items)
*items = cpy
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (ti *TemplatesIterator) Last(ctx context.Context, items *[]Template) bool {
if ti.err != nil {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Last)
if ti.err != nil {
return false
}
cpy := make([]Template, len(ti.Items))
copy(cpy, ti.Items)
*items = cpy
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (ti *TemplatesIterator) Previous(ctx context.Context, items *[]Template) bool {
if ti.err != nil {
return false
}
if ti.Paging.Previous == "" {
return false
}
ti.err = ti.fetch(ctx, ti.Paging.Previous)
if ti.err != nil {
return false
}
cpy := make([]Template, len(ti.Items))
copy(cpy, ti.Items)
*items = cpy
if len(ti.Items) == 0 {
return false
}
return true
}
func (ti *TemplatesIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(ti.mg.Client())
r.setBasicAuth(basicAuthUser, ti.mg.APIKey())
return getResponseFromJSON(ctx, r, &ti.templateListResp)
}

View File

@ -0,0 +1,214 @@
package mailgun
import (
"context"
"strconv"
)
type TemplateVersion struct {
Tag string `json:"tag"`
Template string `json:"template,omitempty"`
Engine TemplateEngine `json:"engine"`
CreatedAt RFC2822Time `json:"createdAt"`
Comment string `json:"comment"`
Active bool `json:"active"`
}
type templateVersionListResp struct {
Template struct {
Template
Versions []TemplateVersion `json:"versions,omitempty"`
} `json:"template"`
Paging Paging `json:"paging"`
}
// AddTemplateVersion adds a template version to a template
func (mg *MailgunImpl) AddTemplateVersion(ctx context.Context, templateName string, version *TemplateVersion) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
payload := newUrlEncodedPayload()
payload.addValue("template", version.Template)
if version.Tag != "" {
payload.addValue("tag", string(version.Tag))
}
if version.Engine != "" {
payload.addValue("engine", string(version.Engine))
}
if version.Comment != "" {
payload.addValue("comment", version.Comment)
}
if version.Active {
payload.addValue("active", boolToString(version.Active))
}
var resp templateResp
if err := postResponseFromJSON(ctx, r, payload, &resp); err != nil {
return err
}
*version = resp.Item.Version
return nil
}
// GetTemplateVersion gets a specific version of a template
func (mg *MailgunImpl) GetTemplateVersion(ctx context.Context, templateName, tag string) (TemplateVersion, error) {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions/" + tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var resp templateResp
err := getResponseFromJSON(ctx, r, &resp)
if err != nil {
return TemplateVersion{}, err
}
return resp.Item.Version, nil
}
// Update the comment and mark a version of a template active
func (mg *MailgunImpl) UpdateTemplateVersion(ctx context.Context, templateName string, version *TemplateVersion) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions/" + version.Tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
if version.Comment != "" {
p.addValue("comment", version.Comment)
}
if version.Active {
p.addValue("active", boolToString(version.Active))
}
var resp templateResp
err := putResponseFromJSON(ctx, r, p, &resp)
if err != nil {
return err
}
*version = resp.Item.Version
return nil
}
// Delete a specific version of a template
func (mg *MailgunImpl) DeleteTemplateVersion(ctx context.Context, templateName, tag string) error {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions/" + tag)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
type TemplateVersionsIterator struct {
templateVersionListResp
mg Mailgun
err error
}
// List all the versions of a specific template
func (mg *MailgunImpl) ListTemplateVersions(templateName string, opts *ListOptions) *TemplateVersionsIterator {
r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateName + "/versions")
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
if opts != nil {
if opts.Limit != 0 {
r.addParameter("limit", strconv.Itoa(opts.Limit))
}
}
url, err := r.generateUrlWithParameters()
return &TemplateVersionsIterator{
mg: mg,
templateVersionListResp: templateVersionListResp{Paging: Paging{Next: url, First: url}},
err: err,
}
}
// If an error occurred during iteration `Err()` will return non nil
func (li *TemplateVersionsIterator) Err() error {
return li.err
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (li *TemplateVersionsIterator) Next(ctx context.Context, items *[]TemplateVersion) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Next)
if li.err != nil {
return false
}
cpy := make([]TemplateVersion, len(li.Template.Versions))
copy(cpy, li.Template.Versions)
*items = cpy
if len(li.Template.Versions) == 0 {
return false
}
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (li *TemplateVersionsIterator) First(ctx context.Context, items *[]TemplateVersion) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.First)
if li.err != nil {
return false
}
cpy := make([]TemplateVersion, len(li.Template.Versions))
copy(cpy, li.Template.Versions)
*items = cpy
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (li *TemplateVersionsIterator) Last(ctx context.Context, items *[]TemplateVersion) bool {
if li.err != nil {
return false
}
li.err = li.fetch(ctx, li.Paging.Last)
if li.err != nil {
return false
}
cpy := make([]TemplateVersion, len(li.Template.Versions))
copy(cpy, li.Template.Versions)
*items = cpy
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (li *TemplateVersionsIterator) Previous(ctx context.Context, items *[]TemplateVersion) bool {
if li.err != nil {
return false
}
if li.Paging.Previous == "" {
return false
}
li.err = li.fetch(ctx, li.Paging.Previous)
if li.err != nil {
return false
}
cpy := make([]TemplateVersion, len(li.Template.Versions))
copy(cpy, li.Template.Versions)
*items = cpy
if len(li.Template.Versions) == 0 {
return false
}
return true
}
func (li *TemplateVersionsIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(li.mg.Client())
r.setBasicAuth(basicAuthUser, li.mg.APIKey())
return getResponseFromJSON(ctx, r, &li.templateVersionListResp)
}

180
vendor/github.com/mailgun/mailgun-go/v3/unsubscribes.go generated vendored Normal file
View File

@ -0,0 +1,180 @@
package mailgun
import (
"context"
"strconv"
)
type Unsubscribe struct {
CreatedAt RFC2822Time `json:"created_at"`
Tags []string `json:"tags"`
ID string `json:"id"`
Address string `json:"address"`
}
type unsubscribesResponse struct {
Paging Paging `json:"paging"`
Items []Unsubscribe `json:"items"`
}
// Fetches the list of unsubscribes
func (mg *MailgunImpl) ListUnsubscribes(opts *ListOptions) *UnsubscribesIterator {
r := newHTTPRequest(generateApiUrl(mg, unsubscribesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
if opts != nil {
if opts.Limit != 0 {
r.addParameter("limit", strconv.Itoa(opts.Limit))
}
}
url, err := r.generateUrlWithParameters()
return &UnsubscribesIterator{
mg: mg,
unsubscribesResponse: unsubscribesResponse{Paging: Paging{Next: url, First: url}},
err: err,
}
}
type UnsubscribesIterator struct {
unsubscribesResponse
mg Mailgun
err error
}
// If an error occurred during iteration `Err()` will return non nil
func (ci *UnsubscribesIterator) Err() error {
return ci.err
}
// Next retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (ci *UnsubscribesIterator) Next(ctx context.Context, items *[]Unsubscribe) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Next)
if ci.err != nil {
return false
}
cpy := make([]Unsubscribe, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
if len(ci.Items) == 0 {
return false
}
return true
}
// First retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (ci *UnsubscribesIterator) First(ctx context.Context, items *[]Unsubscribe) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.First)
if ci.err != nil {
return false
}
cpy := make([]Unsubscribe, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
return true
}
// Last retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (ci *UnsubscribesIterator) Last(ctx context.Context, items *[]Unsubscribe) bool {
if ci.err != nil {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Last)
if ci.err != nil {
return false
}
cpy := make([]Unsubscribe, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
return true
}
// Previous retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (ci *UnsubscribesIterator) Previous(ctx context.Context, items *[]Unsubscribe) bool {
if ci.err != nil {
return false
}
if ci.Paging.Previous == "" {
return false
}
ci.err = ci.fetch(ctx, ci.Paging.Previous)
if ci.err != nil {
return false
}
cpy := make([]Unsubscribe, len(ci.Items))
copy(cpy, ci.Items)
*items = cpy
if len(ci.Items) == 0 {
return false
}
return true
}
func (ci *UnsubscribesIterator) fetch(ctx context.Context, url string) error {
r := newHTTPRequest(url)
r.setClient(ci.mg.Client())
r.setBasicAuth(basicAuthUser, ci.mg.APIKey())
return getResponseFromJSON(ctx, r, &ci.unsubscribesResponse)
}
// Retreives a single unsubscribe record. Can be used to check if a given address is present in the list of unsubscribed users.
func (mg *MailgunImpl) GetUnsubscribe(ctx context.Context, address string) (Unsubscribe, error) {
r := newHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, address))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
envelope := Unsubscribe{}
err := getResponseFromJSON(ctx, r, &envelope)
return envelope, err
}
// Unsubscribe adds an e-mail address to the domain's unsubscription table.
func (mg *MailgunImpl) CreateUnsubscribe(ctx context.Context, address, tag string) error {
r := newHTTPRequest(generateApiUrl(mg, unsubscribesEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("address", address)
p.addValue("tag", tag)
_, err := makePostRequest(ctx, r, p)
return err
}
// DeleteUnsubscribe removes the e-mail address given from the domain's unsubscription table.
// If passing in an ID (discoverable from, e.g., ListUnsubscribes()), the e-mail address associated
// with the given ID will be removed.
func (mg *MailgunImpl) DeleteUnsubscribe(ctx context.Context, address string) error {
r := newHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, address))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// DeleteUnsubscribeWithTag removes the e-mail address given from the domain's unsubscription table with a matching tag.
// If passing in an ID (discoverable from, e.g., ListUnsubscribes()), the e-mail address associated
// with the given ID will be removed.
func (mg *MailgunImpl) DeleteUnsubscribeWithTag(ctx context.Context, a, t string) error {
r := newHTTPRequest(generateApiUrlWithTarget(mg, unsubscribesEndpoint, a))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
r.addParameter("tag", t)
_, err := makeDeleteRequest(ctx, r)
return err
}

4
vendor/github.com/mailgun/mailgun-go/v3/version.go generated vendored Normal file
View File

@ -0,0 +1,4 @@
package mailgun
// Version of current release
const Version = "3.6.1"

157
vendor/github.com/mailgun/mailgun-go/v3/webhooks.go generated vendored Normal file
View File

@ -0,0 +1,157 @@
package mailgun
import (
"context"
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"fmt"
"io"
"net/http"
"github.com/mailgun/mailgun-go/v3/events"
)
type UrlOrUrls struct {
Urls []string `json:"urls"`
Url string `json:"url"`
}
type WebHooksListResponse struct {
Webhooks map[string]UrlOrUrls `json:"webhooks"`
}
type WebHookResponse struct {
Webhook UrlOrUrls `json:"webhook"`
}
// ListWebhooks returns the complete set of webhooks configured for your domain.
// Note that a zero-length mapping is not an error.
func (mg *MailgunImpl) ListWebhooks(ctx context.Context) (map[string][]string, error) {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var body WebHooksListResponse
err := getResponseFromJSON(ctx, r, &body)
if err != nil {
return nil, err
}
hooks := make(map[string][]string, 0)
for k, v := range body.Webhooks {
if v.Url != "" {
hooks[k] = []string{v.Url}
}
if len(v.Urls) != 0 {
hooks[k] = append(hooks[k], v.Urls...)
}
}
return hooks, nil
}
// CreateWebhook installs a new webhook for your domain.
func (mg *MailgunImpl) CreateWebhook(ctx context.Context, kind string, urls []string) error {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
p.addValue("id", kind)
for _, url := range urls {
p.addValue("url", url)
}
_, err := makePostRequest(ctx, r, p)
return err
}
// DeleteWebhook removes the specified webhook from your domain's configuration.
func (mg *MailgunImpl) DeleteWebhook(ctx context.Context, kind string) error {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
_, err := makeDeleteRequest(ctx, r)
return err
}
// GetWebhook retrieves the currently assigned webhook URL associated with the provided type of webhook.
func (mg *MailgunImpl) GetWebhook(ctx context.Context, kind string) ([]string, error) {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
var body WebHookResponse
if err := getResponseFromJSON(ctx, r, &body); err != nil {
return nil, err
}
if body.Webhook.Url != "" {
return []string{body.Webhook.Url}, nil
}
if len(body.Webhook.Urls) != 0 {
return body.Webhook.Urls, nil
}
return nil, fmt.Errorf("webhook '%s' returned no urls", kind)
}
// UpdateWebhook replaces one webhook setting for another.
func (mg *MailgunImpl) UpdateWebhook(ctx context.Context, kind string, urls []string) error {
r := newHTTPRequest(generateDomainApiUrl(mg, webhooksEndpoint) + "/" + kind)
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
p := newUrlEncodedPayload()
for _, url := range urls {
p.addValue("url", url)
}
_, err := makePutRequest(ctx, r, p)
return err
}
// Represents the signature portion of the webhook POST body
type Signature struct {
TimeStamp string `json:"timestamp"`
Token string `json:"token"`
Signature string `json:"signature"`
}
// Represents the JSON payload provided when a Webhook is called by mailgun
type WebhookPayload struct {
Signature Signature `json:"signature"`
EventData events.RawJSON `json:"event-data"`
}
// Use this method to parse the webhook signature given as JSON in the webhook response
func (mg *MailgunImpl) VerifyWebhookSignature(sig Signature) (verified bool, err error) {
h := hmac.New(sha256.New, []byte(mg.APIKey()))
io.WriteString(h, sig.TimeStamp)
io.WriteString(h, sig.Token)
calculatedSignature := h.Sum(nil)
signature, err := hex.DecodeString(sig.Signature)
if err != nil {
return false, err
}
if len(calculatedSignature) != len(signature) {
return false, nil
}
return subtle.ConstantTimeCompare(signature, calculatedSignature) == 1, nil
}
// Deprecated: Please use the VerifyWebhookSignature() to parse the latest
// version of WebHooks from mailgun
func (mg *MailgunImpl) VerifyWebhookRequest(req *http.Request) (verified bool, err error) {
h := hmac.New(sha256.New, []byte(mg.APIKey()))
io.WriteString(h, req.FormValue("timestamp"))
io.WriteString(h, req.FormValue("token"))
calculatedSignature := h.Sum(nil)
signature, err := hex.DecodeString(req.FormValue("signature"))
if err != nil {
return false, err
}
if len(calculatedSignature) != len(signature) {
return false, nil
}
return subtle.ConstantTimeCompare(signature, calculatedSignature) == 1, nil
}

5
vendor/github.com/mailru/easyjson/.gitignore generated vendored Normal file
View File

@ -0,0 +1,5 @@
.root
*_easyjson.go
*.iml
.idea
*.swp

9
vendor/github.com/mailru/easyjson/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,9 @@
language: go
go:
- tip
install:
- go get github.com/ugorji/go/codec
- go get github.com/pquerna/ffjson/fflib/v1
- go get github.com/json-iterator/go
- go get github.com/golang/lint/golint

7
vendor/github.com/mailru/easyjson/LICENSE generated vendored Normal file
View File

@ -0,0 +1,7 @@
Copyright (c) 2016 Mail.Ru Group
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.

61
vendor/github.com/mailru/easyjson/Makefile generated vendored Normal file
View File

@ -0,0 +1,61 @@
PKG=github.com/mailru/easyjson
GOPATH:=$(PWD)/.root:$(GOPATH)
export GOPATH
all: test
.root/src/$(PKG):
mkdir -p $@
for i in $$PWD/* ; do ln -s $$i $@/`basename $$i` ; done
root: .root/src/$(PKG)
clean:
rm -rf .root
rm -rf tests/*_easyjson.go
build:
go build -i -o .root/bin/easyjson $(PKG)/easyjson
generate: root build
.root/bin/easyjson -stubs \
.root/src/$(PKG)/tests/snake.go \
.root/src/$(PKG)/tests/data.go \
.root/src/$(PKG)/tests/omitempty.go \
.root/src/$(PKG)/tests/nothing.go \
.root/src/$(PKG)/tests/named_type.go \
.root/src/$(PKG)/tests/custom_map_key_type.go \
.root/src/$(PKG)/tests/embedded_type.go
.root/bin/easyjson -all .root/src/$(PKG)/tests/data.go
.root/bin/easyjson -all .root/src/$(PKG)/tests/nothing.go
.root/bin/easyjson -all .root/src/$(PKG)/tests/errors.go
.root/bin/easyjson -snake_case .root/src/$(PKG)/tests/snake.go
.root/bin/easyjson -omit_empty .root/src/$(PKG)/tests/omitempty.go
.root/bin/easyjson -build_tags=use_easyjson .root/src/$(PKG)/benchmark/data.go
.root/bin/easyjson .root/src/$(PKG)/tests/nested_easy.go
.root/bin/easyjson .root/src/$(PKG)/tests/named_type.go
.root/bin/easyjson .root/src/$(PKG)/tests/custom_map_key_type.go
.root/bin/easyjson .root/src/$(PKG)/tests/embedded_type.go
.root/bin/easyjson -disallow_unknown_fields .root/src/$(PKG)/tests/disallow_unknown.go
test: generate root
go test \
$(PKG)/tests \
$(PKG)/jlexer \
$(PKG)/gen \
$(PKG)/buffer
go test -benchmem -tags use_easyjson -bench . $(PKG)/benchmark
golint -set_exit_status .root/src/$(PKG)/tests/*_easyjson.go
bench-other: generate root
@go test -benchmem -bench . $(PKG)/benchmark
@go test -benchmem -tags use_ffjson -bench . $(PKG)/benchmark
@go test -benchmem -tags use_jsoniter -bench . $(PKG)/benchmark
@go test -benchmem -tags use_codec -bench . $(PKG)/benchmark
bench-python:
benchmark/ujson.sh
.PHONY: root clean generate test build

333
vendor/github.com/mailru/easyjson/README.md generated vendored Normal file
View File

@ -0,0 +1,333 @@
# easyjson [![Build Status](https://travis-ci.org/mailru/easyjson.svg?branch=master)](https://travis-ci.org/mailru/easyjson) [![Go Report Card](https://goreportcard.com/badge/github.com/mailru/easyjson)](https://goreportcard.com/report/github.com/mailru/easyjson)
Package easyjson provides a fast and easy way to marshal/unmarshal Go structs
to/from JSON without the use of reflection. In performance tests, easyjson
outperforms the standard `encoding/json` package by a factor of 4-5x, and other
JSON encoding packages by a factor of 2-3x.
easyjson aims to keep generated Go code simple enough so that it can be easily
optimized or fixed. Another goal is to provide users with the ability to
customize the generated code by providing options not available with the
standard `encoding/json` package, such as generating "snake_case" names or
enabling `omitempty` behavior by default.
## Usage
```sh
# install
go get -u github.com/mailru/easyjson/...
# run
easyjson -all <file>.go
```
The above will generate `<file>_easyjson.go` containing the appropriate marshaler and
unmarshaler funcs for all structs contained in `<file>.go`.
Please note that easyjson requires a full Go build environment and the `GOPATH`
environment variable to be set. This is because easyjson code generation
invokes `go run` on a temporary file (an approach to code generation borrowed
from [ffjson](https://github.com/pquerna/ffjson)).
## Options
```txt
Usage of easyjson:
-all
generate marshaler/unmarshalers for all structs in a file
-build_tags string
build tags to add to generated file
-leave_temps
do not delete temporary files
-no_std_marshalers
don't generate MarshalJSON/UnmarshalJSON funcs
-noformat
do not run 'gofmt -w' on output file
-omit_empty
omit empty fields by default
-output_filename string
specify the filename of the output
-pkg
process the whole package instead of just the given file
-snake_case
use snake_case names instead of CamelCase by default
-lower_camel_case
use lowerCamelCase instead of CamelCase by default
-stubs
only generate stubs for marshaler/unmarshaler funcs
-disallow_unknown_fields
return error if some unknown field in json appeared
```
Using `-all` will generate marshalers/unmarshalers for all Go structs in the
file. If `-all` is not provided, then only those structs whose preceding
comment starts with `easyjson:json` will have marshalers/unmarshalers
generated. For example:
```go
//easyjson:json
type A struct {}
```
Additional option notes:
* `-snake_case` tells easyjson to generate snake\_case field names by default
(unless overridden by a field tag). The CamelCase to snake\_case conversion
algorithm should work in most cases (ie, HTTPVersion will be converted to
"http_version").
* `-build_tags` will add the specified build tags to generated Go sources.
## Generated Marshaler/Unmarshaler Funcs
For Go struct types, easyjson generates the funcs `MarshalEasyJSON` /
`UnmarshalEasyJSON` for marshaling/unmarshaling JSON. In turn, these satisify
the `easyjson.Marshaler` and `easyjson.Unmarshaler` interfaces and when used in
conjunction with `easyjson.Marshal` / `easyjson.Unmarshal` avoid unnecessary
reflection / type assertions during marshaling/unmarshaling to/from JSON for Go
structs.
easyjson also generates `MarshalJSON` and `UnmarshalJSON` funcs for Go struct
types compatible with the standard `json.Marshaler` and `json.Unmarshaler`
interfaces. Please be aware that using the standard `json.Marshal` /
`json.Unmarshal` for marshaling/unmarshaling will incur a significant
performance penalty when compared to using `easyjson.Marshal` /
`easyjson.Unmarshal`.
Additionally, easyjson exposes utility funcs that use the `MarshalEasyJSON` and
`UnmarshalEasyJSON` for marshaling/unmarshaling to and from standard readers
and writers. For example, easyjson provides `easyjson.MarshalToHTTPResponseWriter`
which marshals to the standard `http.ResponseWriter`. Please see the [GoDoc
listing](https://godoc.org/github.com/mailru/easyjson) for the full listing of
utility funcs that are available.
## Controlling easyjson Marshaling and Unmarshaling Behavior
Go types can provide their own `MarshalEasyJSON` and `UnmarshalEasyJSON` funcs
that satisify the `easyjson.Marshaler` / `easyjson.Unmarshaler` interfaces.
These will be used by `easyjson.Marshal` and `easyjson.Unmarshal` when defined
for a Go type.
Go types can also satisify the `easyjson.Optional` interface, which allows the
type to define its own `omitempty` logic.
## Type Wrappers
easyjson provides additional type wrappers defined in the `easyjson/opt`
package. These wrap the standard Go primitives and in turn satisify the
easyjson interfaces.
The `easyjson/opt` type wrappers are useful when needing to distinguish between
a missing value and/or when needing to specifying a default value. Type
wrappers allow easyjson to avoid additional pointers and heap allocations and
can significantly increase performance when used properly.
## Memory Pooling
easyjson uses a buffer pool that allocates data in increasing chunks from 128
to 32768 bytes. Chunks of 512 bytes and larger will be reused with the help of
`sync.Pool`. The maximum size of a chunk is bounded to reduce redundant memory
allocation and to allow larger reusable buffers.
easyjson's custom allocation buffer pool is defined in the `easyjson/buffer`
package, and the default behavior pool behavior can be modified (if necessary)
through a call to `buffer.Init()` prior to any marshaling or unmarshaling.
Please see the [GoDoc listing](https://godoc.org/github.com/mailru/easyjson/buffer)
for more information.
## Issues, Notes, and Limitations
* easyjson is still early in its development. As such, there are likely to be
bugs and missing features when compared to `encoding/json`. In the case of a
missing feature or bug, please create a GitHub issue. Pull requests are
welcome!
* Unlike `encoding/json`, object keys are case-sensitive. Case-insensitive
matching is not currently provided due to the significant performance hit
when doing case-insensitive key matching. In the future, case-insensitive
object key matching may be provided via an option to the generator.
* easyjson makes use of `unsafe`, which simplifies the code and
provides significant performance benefits by allowing no-copy
conversion from `[]byte` to `string`. That said, `unsafe` is used
only when unmarshaling and parsing JSON, and any `unsafe` operations
/ memory allocations done will be safely deallocated by
easyjson. Set the build tag `easyjson_nounsafe` to compile it
without `unsafe`.
* easyjson is compatible with Google App Engine. The `appengine` build
tag (set by App Engine's environment) will automatically disable the
use of `unsafe`, which is not allowed in App Engine's Standard
Environment. Note that the use with App Engine is still experimental.
* Floats are formatted using the default precision from Go's `strconv` package.
As such, easyjson will not correctly handle high precision floats when
marshaling/unmarshaling JSON. Note, however, that there are very few/limited
uses where this behavior is not sufficient for general use. That said, a
different package may be needed if precise marshaling/unmarshaling of high
precision floats to/from JSON is required.
* While unmarshaling, the JSON parser does the minimal amount of work needed to
skip over unmatching parens, and as such full validation is not done for the
entire JSON value being unmarshaled/parsed.
* Currently there is no true streaming support for encoding/decoding as
typically for many uses/protocols the final, marshaled length of the JSON
needs to be known prior to sending the data. Currently this is not possible
with easyjson's architecture.
## Benchmarks
Most benchmarks were done using the example
[13kB example JSON](https://dev.twitter.com/rest/reference/get/search/tweets)
(9k after eliminating whitespace). This example is similar to real-world data,
is well-structured, and contains a healthy variety of different types, making
it ideal for JSON serialization benchmarks.
Note:
* For small request benchmarks, an 80 byte portion of the above example was
used.
* For large request marshaling benchmarks, a struct containing 50 regular
samples was used, making a ~500kB output JSON.
* Benchmarks are showing the results of easyjson's default behaviour,
which makes use of `unsafe`.
Benchmarks are available in the repository and can be run by invoking `make`.
### easyjson vs. encoding/json
easyjson is roughly 5-6 times faster than the standard `encoding/json` for
unmarshaling, and 3-4 times faster for non-concurrent marshaling. Concurrent
marshaling is 6-7x faster if marshaling to a writer.
### easyjson vs. ffjson
easyjson uses the same approach for JSON marshaling as
[ffjson](https://github.com/pquerna/ffjson), but takes a significantly
different approach to lexing and parsing JSON during unmarshaling. This means
easyjson is roughly 2-3x faster for unmarshaling and 1.5-2x faster for
non-concurrent unmarshaling.
As of this writing, `ffjson` seems to have issues when used concurrently:
specifically, large request pooling hurts `ffjson`'s performance and causes
scalability issues. These issues with `ffjson` can likely be fixed, but as of
writing remain outstanding/known issues with `ffjson`.
easyjson and `ffjson` have similar performance for small requests, however
easyjson outperforms `ffjson` by roughly 2-5x times for large requests when
used with a writer.
### easyjson vs. go/codec
[go/codec](https://github.com/ugorji/go) provides
compile-time helpers for JSON generation. In this case, helpers do not work
like marshalers as they are encoding-independent.
easyjson is generally 2x faster than `go/codec` for non-concurrent benchmarks
and about 3x faster for concurrent encoding (without marshaling to a writer).
In an attempt to measure marshaling performance of `go/codec` (as opposed to
allocations/memcpy/writer interface invocations), a benchmark was done with
resetting length of a byte slice rather than resetting the whole slice to nil.
However, the optimization in this exact form may not be applicable in practice,
since the memory is not freed between marshaling operations.
### easyjson vs 'ujson' python module
[ujson](https://github.com/esnme/ultrajson) is using C code for parsing, so it
is interesting to see how plain golang compares to that. It is imporant to note
that the resulting object for python is slower to access, since the library
parses JSON object into dictionaries.
easyjson is slightly faster for unmarshaling and 2-3x faster than `ujson` for
marshaling.
### Benchmark Results
`ffjson` results are from February 4th, 2016, using the latest `ffjson` and go1.6.
`go/codec` results are from March 4th, 2016, using the latest `go/codec` and go1.6.
#### Unmarshaling
| lib | json size | MB/s | allocs/op | B/op |
|:---------|:----------|-----:|----------:|------:|
| standard | regular | 22 | 218 | 10229 |
| standard | small | 9.7 | 14 | 720 |
| | | | | |
| easyjson | regular | 125 | 128 | 9794 |
| easyjson | small | 67 | 3 | 128 |
| | | | | |
| ffjson | regular | 66 | 141 | 9985 |
| ffjson | small | 17.6 | 10 | 488 |
| | | | | |
| codec | regular | 55 | 434 | 19299 |
| codec | small | 29 | 7 | 336 |
| | | | | |
| ujson | regular | 103 | N/A | N/A |
#### Marshaling, one goroutine.
| lib | json size | MB/s | allocs/op | B/op |
|:----------|:----------|-----:|----------:|------:|
| standard | regular | 75 | 9 | 23256 |
| standard | small | 32 | 3 | 328 |
| standard | large | 80 | 17 | 1.2M |
| | | | | |
| easyjson | regular | 213 | 9 | 10260 |
| easyjson* | regular | 263 | 8 | 742 |
| easyjson | small | 125 | 1 | 128 |
| easyjson | large | 212 | 33 | 490k |
| easyjson* | large | 262 | 25 | 2879 |
| | | | | |
| ffjson | regular | 122 | 153 | 21340 |
| ffjson** | regular | 146 | 152 | 4897 |
| ffjson | small | 36 | 5 | 384 |
| ffjson** | small | 64 | 4 | 128 |
| ffjson | large | 134 | 7317 | 818k |
| ffjson** | large | 125 | 7320 | 827k |
| | | | | |
| codec | regular | 80 | 17 | 33601 |
| codec*** | regular | 108 | 9 | 1153 |
| codec | small | 42 | 3 | 304 |
| codec*** | small | 56 | 1 | 48 |
| codec | large | 73 | 483 | 2.5M |
| codec*** | large | 103 | 451 | 66007 |
| | | | | |
| ujson | regular | 92 | N/A | N/A |
\* marshaling to a writer,
\*\* using `ffjson.Pool()`,
\*\*\* reusing output slice instead of resetting it to nil
#### Marshaling, concurrent.
| lib | json size | MB/s | allocs/op | B/op |
|:----------|:----------|-----:|----------:|------:|
| standard | regular | 252 | 9 | 23257 |
| standard | small | 124 | 3 | 328 |
| standard | large | 289 | 17 | 1.2M |
| | | | | |
| easyjson | regular | 792 | 9 | 10597 |
| easyjson* | regular | 1748 | 8 | 779 |
| easyjson | small | 333 | 1 | 128 |
| easyjson | large | 718 | 36 | 548k |
| easyjson* | large | 2134 | 25 | 4957 |
| | | | | |
| ffjson | regular | 301 | 153 | 21629 |
| ffjson** | regular | 707 | 152 | 5148 |
| ffjson | small | 62 | 5 | 384 |
| ffjson** | small | 282 | 4 | 128 |
| ffjson | large | 438 | 7330 | 1.0M |
| ffjson** | large | 131 | 7319 | 820k |
| | | | | |
| codec | regular | 183 | 17 | 33603 |
| codec*** | regular | 671 | 9 | 1157 |
| codec | small | 147 | 3 | 304 |
| codec*** | small | 299 | 1 | 48 |
| codec | large | 190 | 483 | 2.5M |
| codec*** | large | 752 | 451 | 77574 |
\* marshaling to a writer,
\*\* using `ffjson.Pool()`,
\*\*\* reusing output slice instead of resetting it to nil

270
vendor/github.com/mailru/easyjson/buffer/pool.go generated vendored Normal file
View File

@ -0,0 +1,270 @@
// Package buffer implements a buffer for serialization, consisting of a chain of []byte-s to
// reduce copying and to allow reuse of individual chunks.
package buffer
import (
"io"
"sync"
)
// PoolConfig contains configuration for the allocation and reuse strategy.
type PoolConfig struct {
StartSize int // Minimum chunk size that is allocated.
PooledSize int // Minimum chunk size that is reused, reusing chunks too small will result in overhead.
MaxSize int // Maximum chunk size that will be allocated.
}
var config = PoolConfig{
StartSize: 128,
PooledSize: 512,
MaxSize: 32768,
}
// Reuse pool: chunk size -> pool.
var buffers = map[int]*sync.Pool{}
func initBuffers() {
for l := config.PooledSize; l <= config.MaxSize; l *= 2 {
buffers[l] = new(sync.Pool)
}
}
func init() {
initBuffers()
}
// Init sets up a non-default pooling and allocation strategy. Should be run before serialization is done.
func Init(cfg PoolConfig) {
config = cfg
initBuffers()
}
// putBuf puts a chunk to reuse pool if it can be reused.
func putBuf(buf []byte) {
size := cap(buf)
if size < config.PooledSize {
return
}
if c := buffers[size]; c != nil {
c.Put(buf[:0])
}
}
// getBuf gets a chunk from reuse pool or creates a new one if reuse failed.
func getBuf(size int) []byte {
if size < config.PooledSize {
return make([]byte, 0, size)
}
if c := buffers[size]; c != nil {
v := c.Get()
if v != nil {
return v.([]byte)
}
}
return make([]byte, 0, size)
}
// Buffer is a buffer optimized for serialization without extra copying.
type Buffer struct {
// Buf is the current chunk that can be used for serialization.
Buf []byte
toPool []byte
bufs [][]byte
}
// EnsureSpace makes sure that the current chunk contains at least s free bytes,
// possibly creating a new chunk.
func (b *Buffer) EnsureSpace(s int) {
if cap(b.Buf)-len(b.Buf) >= s {
return
}
l := len(b.Buf)
if l > 0 {
if cap(b.toPool) != cap(b.Buf) {
// Chunk was reallocated, toPool can be pooled.
putBuf(b.toPool)
}
if cap(b.bufs) == 0 {
b.bufs = make([][]byte, 0, 8)
}
b.bufs = append(b.bufs, b.Buf)
l = cap(b.toPool) * 2
} else {
l = config.StartSize
}
if l > config.MaxSize {
l = config.MaxSize
}
b.Buf = getBuf(l)
b.toPool = b.Buf
}
// AppendByte appends a single byte to buffer.
func (b *Buffer) AppendByte(data byte) {
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
b.EnsureSpace(1)
}
b.Buf = append(b.Buf, data)
}
// AppendBytes appends a byte slice to buffer.
func (b *Buffer) AppendBytes(data []byte) {
for len(data) > 0 {
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
b.EnsureSpace(1)
}
sz := cap(b.Buf) - len(b.Buf)
if sz > len(data) {
sz = len(data)
}
b.Buf = append(b.Buf, data[:sz]...)
data = data[sz:]
}
}
// AppendBytes appends a string to buffer.
func (b *Buffer) AppendString(data string) {
for len(data) > 0 {
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
b.EnsureSpace(1)
}
sz := cap(b.Buf) - len(b.Buf)
if sz > len(data) {
sz = len(data)
}
b.Buf = append(b.Buf, data[:sz]...)
data = data[sz:]
}
}
// Size computes the size of a buffer by adding sizes of every chunk.
func (b *Buffer) Size() int {
size := len(b.Buf)
for _, buf := range b.bufs {
size += len(buf)
}
return size
}
// DumpTo outputs the contents of a buffer to a writer and resets the buffer.
func (b *Buffer) DumpTo(w io.Writer) (written int, err error) {
var n int
for _, buf := range b.bufs {
if err == nil {
n, err = w.Write(buf)
written += n
}
putBuf(buf)
}
if err == nil {
n, err = w.Write(b.Buf)
written += n
}
putBuf(b.toPool)
b.bufs = nil
b.Buf = nil
b.toPool = nil
return
}
// BuildBytes creates a single byte slice with all the contents of the buffer. Data is
// copied if it does not fit in a single chunk. You can optionally provide one byte
// slice as argument that it will try to reuse.
func (b *Buffer) BuildBytes(reuse ...[]byte) []byte {
if len(b.bufs) == 0 {
ret := b.Buf
b.toPool = nil
b.Buf = nil
return ret
}
var ret []byte
size := b.Size()
// If we got a buffer as argument and it is big enought, reuse it.
if len(reuse) == 1 && cap(reuse[0]) >= size {
ret = reuse[0][:0]
} else {
ret = make([]byte, 0, size)
}
for _, buf := range b.bufs {
ret = append(ret, buf...)
putBuf(buf)
}
ret = append(ret, b.Buf...)
putBuf(b.toPool)
b.bufs = nil
b.toPool = nil
b.Buf = nil
return ret
}
type readCloser struct {
offset int
bufs [][]byte
}
func (r *readCloser) Read(p []byte) (n int, err error) {
for _, buf := range r.bufs {
// Copy as much as we can.
x := copy(p[n:], buf[r.offset:])
n += x // Increment how much we filled.
// Did we empty the whole buffer?
if r.offset+x == len(buf) {
// On to the next buffer.
r.offset = 0
r.bufs = r.bufs[1:]
// We can release this buffer.
putBuf(buf)
} else {
r.offset += x
}
if n == len(p) {
break
}
}
// No buffers left or nothing read?
if len(r.bufs) == 0 {
err = io.EOF
}
return
}
func (r *readCloser) Close() error {
// Release all remaining buffers.
for _, buf := range r.bufs {
putBuf(buf)
}
// In case Close gets called multiple times.
r.bufs = nil
return nil
}
// ReadCloser creates an io.ReadCloser with all the contents of the buffer.
func (b *Buffer) ReadCloser() io.ReadCloser {
ret := &readCloser{0, append(b.bufs, b.Buf)}
b.bufs = nil
b.toPool = nil
b.Buf = nil
return ret
}

78
vendor/github.com/mailru/easyjson/helpers.go generated vendored Normal file
View File

@ -0,0 +1,78 @@
// Package easyjson contains marshaler/unmarshaler interfaces and helper functions.
package easyjson
import (
"io"
"io/ioutil"
"net/http"
"strconv"
"github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
)
// Marshaler is an easyjson-compatible marshaler interface.
type Marshaler interface {
MarshalEasyJSON(w *jwriter.Writer)
}
// Marshaler is an easyjson-compatible unmarshaler interface.
type Unmarshaler interface {
UnmarshalEasyJSON(w *jlexer.Lexer)
}
// Optional defines an undefined-test method for a type to integrate with 'omitempty' logic.
type Optional interface {
IsDefined() bool
}
// Marshal returns data as a single byte slice. Method is suboptimal as the data is likely to be copied
// from a chain of smaller chunks.
func Marshal(v Marshaler) ([]byte, error) {
w := jwriter.Writer{}
v.MarshalEasyJSON(&w)
return w.BuildBytes()
}
// MarshalToWriter marshals the data to an io.Writer.
func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) {
jw := jwriter.Writer{}
v.MarshalEasyJSON(&jw)
return jw.DumpTo(w)
}
// MarshalToHTTPResponseWriter sets Content-Length and Content-Type headers for the
// http.ResponseWriter, and send the data to the writer. started will be equal to
// false if an error occurred before any http.ResponseWriter methods were actually
// invoked (in this case a 500 reply is possible).
func MarshalToHTTPResponseWriter(v Marshaler, w http.ResponseWriter) (started bool, written int, err error) {
jw := jwriter.Writer{}
v.MarshalEasyJSON(&jw)
if jw.Error != nil {
return false, 0, jw.Error
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(jw.Size()))
started = true
written, err = jw.DumpTo(w)
return
}
// Unmarshal decodes the JSON in data into the object.
func Unmarshal(data []byte, v Unmarshaler) error {
l := jlexer.Lexer{Data: data}
v.UnmarshalEasyJSON(&l)
return l.Error()
}
// UnmarshalFromReader reads all the data in the reader and decodes as JSON into the object.
func UnmarshalFromReader(r io.Reader, v Unmarshaler) error {
data, err := ioutil.ReadAll(r)
if err != nil {
return err
}
l := jlexer.Lexer{Data: data}
v.UnmarshalEasyJSON(&l)
return l.Error()
}

24
vendor/github.com/mailru/easyjson/jlexer/bytestostr.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
// This file will only be included to the build if neither
// easyjson_nounsafe nor appengine build tag is set. See README notes
// for more details.
//+build !easyjson_nounsafe
//+build !appengine
package jlexer
import (
"reflect"
"unsafe"
)
// bytesToStr creates a string pointing at the slice to avoid copying.
//
// Warning: the string returned by the function should be used with care, as the whole input data
// chunk may be either blocked from being freed by GC because of a single string or the buffer.Data
// may be garbage-collected even when the string exists.
func bytesToStr(data []byte) string {
h := (*reflect.SliceHeader)(unsafe.Pointer(&data))
shdr := reflect.StringHeader{Data: h.Data, Len: h.Len}
return *(*string)(unsafe.Pointer(&shdr))
}

View File

@ -0,0 +1,13 @@
// This file is included to the build if any of the buildtags below
// are defined. Refer to README notes for more details.
//+build easyjson_nounsafe appengine
package jlexer
// bytesToStr creates a string normally from []byte
//
// Note that this method is roughly 1.5x slower than using the 'unsafe' method.
func bytesToStr(data []byte) string {
return string(data)
}

15
vendor/github.com/mailru/easyjson/jlexer/error.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
package jlexer
import "fmt"
// LexerError implements the error interface and represents all possible errors that can be
// generated during parsing the JSON data.
type LexerError struct {
Reason string
Offset int
Data string
}
func (l *LexerError) Error() string {
return fmt.Sprintf("parse error: %s near offset %d of '%s'", l.Reason, l.Offset, l.Data)
}

1181
vendor/github.com/mailru/easyjson/jlexer/lexer.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

390
vendor/github.com/mailru/easyjson/jwriter/writer.go generated vendored Normal file
View File

@ -0,0 +1,390 @@
// Package jwriter contains a JSON writer.
package jwriter
import (
"io"
"strconv"
"unicode/utf8"
"github.com/mailru/easyjson/buffer"
)
// Flags describe various encoding options. The behavior may be actually implemented in the encoder, but
// Flags field in Writer is used to set and pass them around.
type Flags int
const (
NilMapAsEmpty Flags = 1 << iota // Encode nil map as '{}' rather than 'null'.
NilSliceAsEmpty // Encode nil slice as '[]' rather than 'null'.
)
// Writer is a JSON writer.
type Writer struct {
Flags Flags
Error error
Buffer buffer.Buffer
NoEscapeHTML bool
}
// Size returns the size of the data that was written out.
func (w *Writer) Size() int {
return w.Buffer.Size()
}
// DumpTo outputs the data to given io.Writer, resetting the buffer.
func (w *Writer) DumpTo(out io.Writer) (written int, err error) {
return w.Buffer.DumpTo(out)
}
// BuildBytes returns writer data as a single byte slice. You can optionally provide one byte slice
// as argument that it will try to reuse.
func (w *Writer) BuildBytes(reuse ...[]byte) ([]byte, error) {
if w.Error != nil {
return nil, w.Error
}
return w.Buffer.BuildBytes(reuse...), nil
}
// ReadCloser returns an io.ReadCloser that can be used to read the data.
// ReadCloser also resets the buffer.
func (w *Writer) ReadCloser() (io.ReadCloser, error) {
if w.Error != nil {
return nil, w.Error
}
return w.Buffer.ReadCloser(), nil
}
// RawByte appends raw binary data to the buffer.
func (w *Writer) RawByte(c byte) {
w.Buffer.AppendByte(c)
}
// RawByte appends raw binary data to the buffer.
func (w *Writer) RawString(s string) {
w.Buffer.AppendString(s)
}
// Raw appends raw binary data to the buffer or sets the error if it is given. Useful for
// calling with results of MarshalJSON-like functions.
func (w *Writer) Raw(data []byte, err error) {
switch {
case w.Error != nil:
return
case err != nil:
w.Error = err
case len(data) > 0:
w.Buffer.AppendBytes(data)
default:
w.RawString("null")
}
}
// RawText encloses raw binary data in quotes and appends in to the buffer.
// Useful for calling with results of MarshalText-like functions.
func (w *Writer) RawText(data []byte, err error) {
switch {
case w.Error != nil:
return
case err != nil:
w.Error = err
case len(data) > 0:
w.String(string(data))
default:
w.RawString("null")
}
}
// Base64Bytes appends data to the buffer after base64 encoding it
func (w *Writer) Base64Bytes(data []byte) {
if data == nil {
w.Buffer.AppendString("null")
return
}
w.Buffer.AppendByte('"')
w.base64(data)
w.Buffer.AppendByte('"')
}
func (w *Writer) Uint8(n uint8) {
w.Buffer.EnsureSpace(3)
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
}
func (w *Writer) Uint16(n uint16) {
w.Buffer.EnsureSpace(5)
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
}
func (w *Writer) Uint32(n uint32) {
w.Buffer.EnsureSpace(10)
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
}
func (w *Writer) Uint(n uint) {
w.Buffer.EnsureSpace(20)
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
}
func (w *Writer) Uint64(n uint64) {
w.Buffer.EnsureSpace(20)
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, n, 10)
}
func (w *Writer) Int8(n int8) {
w.Buffer.EnsureSpace(4)
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10)
}
func (w *Writer) Int16(n int16) {
w.Buffer.EnsureSpace(6)
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10)
}
func (w *Writer) Int32(n int32) {
w.Buffer.EnsureSpace(11)
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10)
}
func (w *Writer) Int(n int) {
w.Buffer.EnsureSpace(21)
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10)
}
func (w *Writer) Int64(n int64) {
w.Buffer.EnsureSpace(21)
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, n, 10)
}
func (w *Writer) Uint8Str(n uint8) {
w.Buffer.EnsureSpace(3)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Uint16Str(n uint16) {
w.Buffer.EnsureSpace(5)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Uint32Str(n uint32) {
w.Buffer.EnsureSpace(10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) UintStr(n uint) {
w.Buffer.EnsureSpace(20)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Uint64Str(n uint64) {
w.Buffer.EnsureSpace(20)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, n, 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) UintptrStr(n uintptr) {
w.Buffer.EnsureSpace(20)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendUint(w.Buffer.Buf, uint64(n), 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Int8Str(n int8) {
w.Buffer.EnsureSpace(4)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Int16Str(n int16) {
w.Buffer.EnsureSpace(6)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Int32Str(n int32) {
w.Buffer.EnsureSpace(11)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) IntStr(n int) {
w.Buffer.EnsureSpace(21)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, int64(n), 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Int64Str(n int64) {
w.Buffer.EnsureSpace(21)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendInt(w.Buffer.Buf, n, 10)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Float32(n float32) {
w.Buffer.EnsureSpace(20)
w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 32)
}
func (w *Writer) Float32Str(n float32) {
w.Buffer.EnsureSpace(20)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 32)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Float64(n float64) {
w.Buffer.EnsureSpace(20)
w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, n, 'g', -1, 64)
}
func (w *Writer) Float64Str(n float64) {
w.Buffer.EnsureSpace(20)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
w.Buffer.Buf = strconv.AppendFloat(w.Buffer.Buf, float64(n), 'g', -1, 64)
w.Buffer.Buf = append(w.Buffer.Buf, '"')
}
func (w *Writer) Bool(v bool) {
w.Buffer.EnsureSpace(5)
if v {
w.Buffer.Buf = append(w.Buffer.Buf, "true"...)
} else {
w.Buffer.Buf = append(w.Buffer.Buf, "false"...)
}
}
const chars = "0123456789abcdef"
func isNotEscapedSingleChar(c byte, escapeHTML bool) bool {
// Note: might make sense to use a table if there are more chars to escape. With 4 chars
// it benchmarks the same.
if escapeHTML {
return c != '<' && c != '>' && c != '&' && c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf
} else {
return c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf
}
}
func (w *Writer) String(s string) {
w.Buffer.AppendByte('"')
// Portions of the string that contain no escapes are appended as
// byte slices.
p := 0 // last non-escape symbol
for i := 0; i < len(s); {
c := s[i]
if isNotEscapedSingleChar(c, !w.NoEscapeHTML) {
// single-width character, no escaping is required
i++
continue
} else if c < utf8.RuneSelf {
// single-with character, need to escape
w.Buffer.AppendString(s[p:i])
switch c {
case '\t':
w.Buffer.AppendString(`\t`)
case '\r':
w.Buffer.AppendString(`\r`)
case '\n':
w.Buffer.AppendString(`\n`)
case '\\':
w.Buffer.AppendString(`\\`)
case '"':
w.Buffer.AppendString(`\"`)
default:
w.Buffer.AppendString(`\u00`)
w.Buffer.AppendByte(chars[c>>4])
w.Buffer.AppendByte(chars[c&0xf])
}
i++
p = i
continue
}
// broken utf
runeValue, runeWidth := utf8.DecodeRuneInString(s[i:])
if runeValue == utf8.RuneError && runeWidth == 1 {
w.Buffer.AppendString(s[p:i])
w.Buffer.AppendString(`\ufffd`)
i++
p = i
continue
}
// jsonp stuff - tab separator and line separator
if runeValue == '\u2028' || runeValue == '\u2029' {
w.Buffer.AppendString(s[p:i])
w.Buffer.AppendString(`\u202`)
w.Buffer.AppendByte(chars[runeValue&0xf])
i += runeWidth
p = i
continue
}
i += runeWidth
}
w.Buffer.AppendString(s[p:])
w.Buffer.AppendByte('"')
}
const encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
const padChar = '='
func (w *Writer) base64(in []byte) {
if len(in) == 0 {
return
}
w.Buffer.EnsureSpace(((len(in)-1)/3 + 1) * 4)
si := 0
n := (len(in) / 3) * 3
for si < n {
// Convert 3x 8bit source bytes into 4 bytes
val := uint(in[si+0])<<16 | uint(in[si+1])<<8 | uint(in[si+2])
w.Buffer.Buf = append(w.Buffer.Buf, encode[val>>18&0x3F], encode[val>>12&0x3F], encode[val>>6&0x3F], encode[val&0x3F])
si += 3
}
remain := len(in) - si
if remain == 0 {
return
}
// Add the remaining small block
val := uint(in[si+0]) << 16
if remain == 2 {
val |= uint(in[si+1]) << 8
}
w.Buffer.Buf = append(w.Buffer.Buf, encode[val>>18&0x3F], encode[val>>12&0x3F])
switch remain {
case 2:
w.Buffer.Buf = append(w.Buffer.Buf, encode[val>>6&0x3F], byte(padChar))
case 1:
w.Buffer.Buf = append(w.Buffer.Buf, byte(padChar), byte(padChar))
}
}

45
vendor/github.com/mailru/easyjson/raw.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
package easyjson
import (
"github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
)
// RawMessage is a raw piece of JSON (number, string, bool, object, array or
// null) that is extracted without parsing and output as is during marshaling.
type RawMessage []byte
// MarshalEasyJSON does JSON marshaling using easyjson interface.
func (v *RawMessage) MarshalEasyJSON(w *jwriter.Writer) {
if len(*v) == 0 {
w.RawString("null")
} else {
w.Raw(*v, nil)
}
}
// UnmarshalEasyJSON does JSON unmarshaling using easyjson interface.
func (v *RawMessage) UnmarshalEasyJSON(l *jlexer.Lexer) {
*v = RawMessage(l.Raw())
}
// UnmarshalJSON implements encoding/json.Unmarshaler interface.
func (v *RawMessage) UnmarshalJSON(data []byte) error {
*v = data
return nil
}
var nullBytes = []byte("null")
// MarshalJSON implements encoding/json.Marshaler interface.
func (v RawMessage) MarshalJSON() ([]byte, error) {
if len(v) == 0 {
return nullBytes, nil
}
return v, nil
}
// IsDefined is required for integration with omitempty easyjson logic.
func (v *RawMessage) IsDefined() bool {
return len(*v) > 0
}

24
vendor/github.com/pkg/errors/.gitignore generated vendored Normal file
View File

@ -0,0 +1,24 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof

15
vendor/github.com/pkg/errors/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,15 @@
language: go
go_import_path: github.com/pkg/errors
go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
- tip
script:
- go test -v ./...

23
vendor/github.com/pkg/errors/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

52
vendor/github.com/pkg/errors/README.md generated vendored Normal file
View File

@ -0,0 +1,52 @@
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
Package errors provides simple error handling primitives.
`go get github.com/pkg/errors`
The traditional error handling idiom in Go is roughly akin to
```go
if err != nil {
return err
}
```
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
## Adding context to an error
The errors.Wrap function returns a new error that adds context to the original error. For example
```go
_, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "read failed")
}
```
## Retrieving the cause of an error
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
```go
type causer interface {
Cause() error
}
```
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
```go
switch err := errors.Cause(err).(type) {
case *MyError:
// handle specifically
default:
// unknown error
}
```
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
## Contributing
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
Before proposing a change, please discuss your change by raising an issue.
## License
BSD-2-Clause

32
vendor/github.com/pkg/errors/appveyor.yml generated vendored Normal file
View File

@ -0,0 +1,32 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\pkg\errors
shallow_clone: true # for startup speed
environment:
GOPATH: C:\gopath
platform:
- x64
# http://www.appveyor.com/docs/installed-software
install:
# some helpful output for debugging builds
- go version
- go env
# pre-installed MinGW at C:\MinGW is 32bit only
# but MSYS2 at C:\msys64 has mingw64
- set PATH=C:\msys64\mingw64\bin;%PATH%
- gcc --version
- g++ --version
build_script:
- go install -v ./...
test_script:
- set PATH=C:\gopath\bin;%PATH%
- go test -v ./...
#artifacts:
# - path: '%GOPATH%\bin\*.exe'
deploy: off

282
vendor/github.com/pkg/errors/errors.go generated vendored Normal file
View File

@ -0,0 +1,282 @@
// Package errors provides simple error handling primitives.
//
// The traditional error handling idiom in Go is roughly akin to
//
// if err != nil {
// return err
// }
//
// which when applied recursively up the call stack results in error reports
// without context or debugging information. The errors package allows
// programmers to add context to the failure path in their code in a way
// that does not destroy the original value of the error.
//
// Adding context to an error
//
// The errors.Wrap function returns a new error that adds context to the
// original error by recording a stack trace at the point Wrap is called,
// together with the supplied message. For example
//
// _, err := ioutil.ReadAll(r)
// if err != nil {
// return errors.Wrap(err, "read failed")
// }
//
// If additional control is required, the errors.WithStack and
// errors.WithMessage functions destructure errors.Wrap into its component
// operations: annotating an error with a stack trace and with a message,
// respectively.
//
// Retrieving the cause of an error
//
// Using errors.Wrap constructs a stack of errors, adding context to the
// preceding error. Depending on the nature of the error it may be necessary
// to reverse the operation of errors.Wrap to retrieve the original error
// for inspection. Any error value which implements this interface
//
// type causer interface {
// Cause() error
// }
//
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
// the topmost error that does not implement causer, which is assumed to be
// the original cause. For example:
//
// switch err := errors.Cause(err).(type) {
// case *MyError:
// // handle specifically
// default:
// // unknown error
// }
//
// Although the causer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// Formatted printing of errors
//
// All error values returned from this package implement fmt.Formatter and can
// be formatted by the fmt package. The following verbs are supported:
//
// %s print the error. If the error has a Cause it will be
// printed recursively.
// %v see %s
// %+v extended format. Each Frame of the error's StackTrace will
// be printed in detail.
//
// Retrieving the stack trace of an error or wrapper
//
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface:
//
// type stackTracer interface {
// StackTrace() errors.StackTrace
// }
//
// The returned errors.StackTrace type is defined as
//
// type StackTrace []Frame
//
// The Frame type represents a call site in the stack trace. Frame supports
// the fmt.Formatter interface that can be used for printing information about
// the stack trace of this error. For example:
//
// if err, ok := err.(stackTracer); ok {
// for _, f := range err.StackTrace() {
// fmt.Printf("%+s:%d", f)
// }
// }
//
// Although the stackTracer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// See the documentation for Frame.Format for more details.
package errors
import (
"fmt"
"io"
)
// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {
return &fundamental{
msg: message,
stack: callers(),
}
}
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
// Errorf also records the stack trace at the point it was called.
func Errorf(format string, args ...interface{}) error {
return &fundamental{
msg: fmt.Sprintf(format, args...),
stack: callers(),
}
}
// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*stack
}
func (f *fundamental) Error() string { return f.msg }
func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, f.msg)
f.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, f.msg)
case 'q':
fmt.Fprintf(s, "%q", f.msg)
}
}
// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {
if err == nil {
return nil
}
return &withStack{
err,
callers(),
}
}
type withStack struct {
error
*stack
}
func (w *withStack) Cause() error { return w.error }
func (w *withStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v", w.Cause())
w.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
}
}
// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: message,
}
return &withStack{
err,
callers(),
}
}
// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is called, and the format specifier.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
return &withStack{
err,
callers(),
}
}
// WithMessage annotates err with a new message.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: message,
}
}
// WithMessagef annotates err with the format specifier.
// If err is nil, WithMessagef returns nil.
func WithMessagef(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
}
type withMessage struct {
cause error
msg string
}
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause }
func (w *withMessage) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v\n", w.Cause())
io.WriteString(s, w.msg)
return
}
fallthrough
case 's', 'q':
io.WriteString(s, w.Error())
}
}
// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
// type causer interface {
// Cause() error
// }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
type causer interface {
Cause() error
}
for err != nil {
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
return err
}

147
vendor/github.com/pkg/errors/stack.go generated vendored Normal file
View File

@ -0,0 +1,147 @@
package errors
import (
"fmt"
"io"
"path"
"runtime"
"strings"
)
// Frame represents a program counter inside a stack frame.
type Frame uintptr
// pc returns the program counter for this frame;
// multiple frames may have the same PC value.
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
// file returns the full path to the file that contains the
// function for this Frame's pc.
func (f Frame) file() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
file, _ := fn.FileLine(f.pc())
return file
}
// line returns the line number of source code of the
// function for this Frame's pc.
func (f Frame) line() int {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return 0
}
_, line := fn.FileLine(f.pc())
return line
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s function name and path of source file relative to the compile time
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
pc := f.pc()
fn := runtime.FuncForPC(pc)
if fn == nil {
io.WriteString(s, "unknown")
} else {
file, _ := fn.FileLine(pc)
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
}
default:
io.WriteString(s, path.Base(f.file()))
}
case 'd':
fmt.Fprintf(s, "%d", f.line())
case 'n':
name := runtime.FuncForPC(f.pc()).Name()
io.WriteString(s, funcname(name))
case 'v':
f.Format(s, 's')
io.WriteString(s, ":")
f.Format(s, 'd')
}
}
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame
// Format formats the stack of Frames according to the fmt.Formatter interface.
//
// %s lists source files for each Frame in the stack
// %v lists the source file and line number for each Frame in the stack
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+v Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range st {
fmt.Fprintf(s, "\n%+v", f)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", []Frame(st))
default:
fmt.Fprintf(s, "%v", []Frame(st))
}
case 's':
fmt.Fprintf(s, "%s", []Frame(st))
}
}
// stack represents a stack of program counters.
type stack []uintptr
func (s *stack) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := Frame(pc)
fmt.Fprintf(st, "\n%+v", f)
}
}
}
}
func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ {
f[i] = Frame((*s)[i])
}
return f
}
func callers() *stack {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(3, pcs[:])
var st stack = pcs[0:n]
return &st
}
// funcname removes the path prefix component of a function's name reported by func.Name().
func funcname(name string) string {
i := strings.LastIndex(name, "/")
name = name[i+1:]
i = strings.Index(name, ".")
return name[i+1:]
}

12
vendor/modules.txt vendored
View File

@ -2,6 +2,18 @@
git.rootprojects.org/root/keypairs
git.rootprojects.org/root/keypairs/keyfetch
git.rootprojects.org/root/keypairs/keyfetch/uncached
# github.com/go-chi/chi v4.0.0+incompatible
github.com/go-chi/chi
# github.com/joho/godotenv v1.3.0
github.com/joho/godotenv
github.com/joho/godotenv/autoload
# github.com/mailgun/mailgun-go/v3 v3.6.4
github.com/mailgun/mailgun-go/v3
github.com/mailgun/mailgun-go/v3/events
# github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329
github.com/mailru/easyjson
github.com/mailru/easyjson/buffer
github.com/mailru/easyjson/jlexer
github.com/mailru/easyjson/jwriter
# github.com/pkg/errors v0.8.1
github.com/pkg/errors