Commit f8d8fb6f authored by Craig Jackson's avatar Craig Jackson

Initial commit with chi, graphql, errorx used together

parents
settings:
legacy:
force: false
interval: 0s
schema:
- name: gqlerrorx
path: .
commands:
run:
status: true
watcher:
extensions:
- go
paths:
- /
ignored_paths:
- .git
- .realize
- vendor
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:a9fe0f8ff72c388d0128e88ce5f3c27d37dcd0950acd7cdb8323555f12463396"
name = "github.com/go-chi/chi"
packages = [
".",
"middleware",
]
pruneopts = "UT"
revision = "b5294d10673813fac8558e7f47242bc9e61b4c25"
version = "v3.3.3"
[[projects]]
digest = "1:6b588a3e17f529ec6239042690ce79b07d752624779f953d46e49335108f4f02"
name = "github.com/graphql-go/graphql"
packages = [
".",
"gqlerrors",
"language/ast",
"language/kinds",
"language/lexer",
"language/location",
"language/parser",
"language/printer",
"language/source",
"language/typeInfo",
"language/visitor",
]
pruneopts = "UT"
revision = "5c1be0872a56fba73b279434ea95dfda735abedd"
version = "v0.7.6"
[[projects]]
digest = "1:eeac0b32d8af13a48405ae34589bb2fc24ca0c39ee96fd94ca23f0cd1929fa36"
name = "github.com/graphql-go/handler"
packages = ["."]
pruneopts = "UT"
revision = "f0393b2c10daeebea900292b8e092e73475399d9"
version = "v0.2.1"
[[projects]]
digest = "1:cec0e8c3827033d7f24f23809dfc54d3080b2236a3e9bde2c5229d0ddb0c85ec"
name = "github.com/joomcode/errorx"
packages = ["."]
pruneopts = "UT"
revision = "090d06348063dc34b4d6eda8cc5d10c141786010"
version = "v0.1.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/go-chi/chi",
"github.com/go-chi/chi/middleware",
"github.com/graphql-go/graphql",
"github.com/graphql-go/handler",
"github.com/joomcode/errorx",
]
solver-name = "gps-cdcl"
solver-version = 1
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[prune]
go-tests = true
unused-packages = true
[[constraint]]
name = "github.com/go-chi/chi"
version = "3.3.3"
[[constraint]]
name = "github.com/graphql-go/graphql"
version = "0.7.6"
[[constraint]]
name = "github.com/graphql-go/handler"
version = "0.2.1"
[[constraint]]
name = "github.com/joomcode/errorx"
version = "0.1.0"
package main
import (
"database/sql"
"errors"
"fmt"
"log"
"net/http"
"sync/atomic"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
"github.com/joomcode/errorx"
)
const (
// NotFoundErrorCode is the standard error code in graphql responses when not found
NotFoundErrorCode = "NF"
// ServerErrorCode is the standard error code in graphql responses when there is a server-side error
ServerErrorCode = "SE"
// InvalidArgsErrorCode is the standard error code in graphql responses when there are invalid args
InvalidArgsErrorCode = "INV"
)
var (
gqlErrorCodes = map[errorx.Trait]string{
errorx.NotFound(): NotFoundErrorCode,
ServerErrorTrait: ServerErrorCode,
InvalidArgsTrait: InvalidArgsErrorCode,
}
)
var (
errorxNamespace = errorx.NewNamespace("R")
// LogStackTrait is a trait to determine if the error should log the stack trace
LogStackTrait = errorx.RegisterTrait("log_stack")
// ServerErrorTrait is a trait for a generic server error
ServerErrorTrait = errorx.RegisterTrait("server_error")
// InvalidArgsTrait is a trait for invalid args given
InvalidArgsTrait = errorx.RegisterTrait("invalid_args")
// InvalidArgsPropertyKey is the errorx property key for invalid args
InvalidArgsPropertyKey = errorx.RegisterProperty("invalid_args")
// ErrorxNotFound is a not found errorx type with NotFound trait
ErrorxNotFound = errorxNamespace.NewType(NotFoundErrorCode, errorx.NotFound())
// ErrorxServerError is a server error errorx type with ServerErrorTrait
ErrorxServerError = errorxNamespace.NewType(ServerErrorCode, ServerErrorTrait, LogStackTrait)
// ErrorxInvalidArgs is an invalid args errorx type with InvalidArgsTrait
ErrorxInvalidArgs = errorxNamespace.NewType(InvalidArgsErrorCode, InvalidArgsTrait)
)
type validationErrors map[string]string
type gqlError struct {
error
}
type messager interface {
Message() string
}
// Error will give the last decorated message if from errorx, else just gives standard Error()
func (err gqlError) Error() string {
if cause, ok := err.error.(messager); ok {
return cause.Message()
}
return err.error.Error()
}
type typer interface {
Type() *errorx.Type
}
// Extensions returns all standard error codes in the graphql extensions based on errorx Traits on the error
func (err gqlError) Extensions() map[string]interface{} {
result := map[string]interface{}{
"errorType": errorx.GetTypeName(err.error),
"errorCodes": []string{},
}
errorCodes := []string{}
for trait, code := range gqlErrorCodes {
if errorx.HasTrait(err.error, trait) {
errorCodes = append(errorCodes, code)
}
}
result["errorCodes"] = errorCodes
if valErrs, ok := errorx.ExtractProperty(err.error, InvalidArgsPropertyKey); ok {
result["invalidArgs"] = valErrs
}
return result
}
func wrapResolver(resolver graphql.FieldResolveFn) graphql.FieldResolveFn {
return func(p graphql.ResolveParams) (resp interface{}, err error) {
defer func() {
if r := recover(); r != nil {
switch v := r.(type) {
case string:
err = ErrorxServerError.New(v)
case error:
err = v
default:
err = ErrorxServerError.New("Unknown panic")
}
err = ErrorxServerError.Wrap(err, "internal server error")
if errorx.HasTrait(err, LogStackTrait) {
log.Printf("%+v\n", err)
} else {
log.Printf("%v\n", err)
}
err = &gqlError{err}
}
}()
resp, err = resolver(p)
if err != nil {
if errorx.HasTrait(err, LogStackTrait) {
log.Printf("%+v\n", err)
} else {
log.Printf("%v\n", err)
}
return resp, &gqlError{err}
}
return
}
}
func gqlSchema() (graphql.Schema, error) {
return graphql.NewSchema(
graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "RootQuery",
Fields: graphql.Fields{
"message": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument(map[string]*graphql.ArgumentConfig{
"id": &graphql.ArgumentConfig{
Type: graphql.Int,
Description: "id of the message",
},
}),
Resolve: wrapResolver(func(p graphql.ResolveParams) (interface{}, error) {
id, ok := p.Args["id"].(int)
if !ok {
return nil, fmt.Errorf("invalid id (%s)", p.Args["id"])
}
msg, err := findMessage(id)
if err != nil {
if errorx.HasTrait(err, errorx.NotFound()) {
return nil, errorx.Decorate(err, "message not found")
}
return nil, errorx.Decorate(err, "error finding message")
}
return msg, nil
}),
},
"error": &graphql.Field{
Type: graphql.String,
Resolve: wrapResolver(func(p graphql.ResolveParams) (interface{}, error) {
return nil, ErrorxServerError.New("ERROR HERE")
}),
},
"panic": &graphql.Field{
Type: graphql.String,
Resolve: wrapResolver(func(p graphql.ResolveParams) (interface{}, error) {
panic(errors.New("PANIC HERE"))
}),
},
},
}),
Mutation: graphql.NewObject(graphql.ObjectConfig{
Name: "MutationQuery",
Fields: graphql.Fields{
"message_create": &graphql.Field{
Type: graphql.String,
Args: graphql.FieldConfigArgument(map[string]*graphql.ArgumentConfig{
"body": &graphql.ArgumentConfig{
Type: graphql.String,
Description: "body of the message",
},
}),
Resolve: wrapResolver(func(p graphql.ResolveParams) (interface{}, error) {
valErrs := validationErrors{}
body, ok := p.Args["body"].(string)
if !ok {
return nil, fmt.Errorf("invalid body (%s)", p.Args["body"])
}
if body == "" {
valErrs["body"] = "body is required"
}
if len(valErrs) > 0 {
return nil, ErrorxInvalidArgs.New("invalid arguments given").WithProperty(InvalidArgsPropertyKey, valErrs)
}
id := createMessage(body)
return id, nil
}),
},
"error": &graphql.Field{
Type: graphql.String,
Resolve: wrapResolver(func(p graphql.ResolveParams) (interface{}, error) {
return nil, ErrorxServerError.New("ERROR HERE")
}),
},
"panic": &graphql.Field{
Type: graphql.String,
Resolve: wrapResolver(func(p graphql.ResolveParams) (interface{}, error) {
panic(errors.New("PANIC HERE"))
}),
},
},
}),
},
)
}
var (
messageID uint64
messages = map[int]string{}
)
func findMessage(id int) (string, error) {
msg, ok := messages[id]
if !ok {
return "", ErrorxNotFound.Wrap(sql.ErrNoRows, "message does not exist")
}
return msg, nil
}
func createMessage(body string) int {
id := int(atomic.AddUint64(&messageID, 1))
messages[id] = body
return id
}
func gqlHandler() (http.Handler, error) {
schema, err := gqlSchema()
if err != nil {
return nil, err
}
return handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: true,
}), nil
}
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
handler, err := gqlHandler()
if err != nil {
panic(err)
}
r.Handle("/graphql", handler)
log.Print("Listening...")
http.ListenAndServe(":3000", r)
}
language: go
go:
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
install:
- go get -u golang.org/x/tools/cmd/goimports
- go get -u github.com/golang/lint/golint
script:
- go get -d -t ./...
- go vet ./...
- golint ./...
- go test ./...
- >
go_version=$(go version);
if [ ${go_version:13:4} = "1.11" ]; then
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :;
fi
# Changelog
## 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")`
# 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
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.
This diff is collapsed.
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
}
//
// 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