Merge pull request #305 from MichaelMure/gomod

migrate to go modules, update gqlgen
This commit is contained in:
Michael Muré 2020-02-07 01:00:32 +01:00 committed by GitHub
commit 1a3cbdc418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1015 changed files with 2530 additions and 329065 deletions

View File

@ -1,13 +1,11 @@
matrix:
include:
- language: go
go: "1.10"
go: 1.11.x
- language: go
go: "1.11"
go: 1.12.x
- language: go
go: "1.12"
- language: go
go: "1.13"
go: 1.13.x
- language: node_js
node_js: 8
before_install:
@ -28,6 +26,19 @@ matrix:
before_install:
- cd webui
after_success: []
- language: node_js
node_js: 12
before_install:
- cd webui
after_success: []
- language: node_js
node_js: 13
before_install:
- cd webui
after_success: []
env:
GO111MODULE=on
install:
- make install

524
Gopkg.lock generated
View File

@ -1,524 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:25c825a120e8ae421b807f06eaa30f4d30ad3700fc103b7aaa24af8358498d31"
name = "github.com/99designs/gqlgen"
packages = [
"api",
"codegen",
"codegen/config",
"codegen/templates",
"complexity",
"graphql",
"graphql/introspection",
"handler",
"internal/code",
"internal/imports",
"plugin",
"plugin/modelgen",
"plugin/resolvergen",
"plugin/schemaconfig",
]
pruneopts = "UT"
revision = "4eeacc6e4cb7bedc7c5312b6a3947697ad5cfb55"
version = "v0.9.2"
[[projects]]
digest = "1:364c89f564ef045872f82ff2f156fd3cb09274e4a3bdbf89aef2c2370c955f95"
name = "github.com/MichaelMure/go-term-text"
packages = ["."]
pruneopts = "UT"
revision = "6d868251433124ef4ed19c9f8e973ac610d42ef5"
version = "v0.2.4"
[[projects]]
digest = "1:897d91c431ce469d35a5e6030e60e617dccd9a0e95bdffa6a80594f5c5800d29"
name = "github.com/agnivade/levenshtein"
packages = ["."]
pruneopts = "UT"
revision = "3d21ba515fe27b856f230847e856431ae1724adc"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:fdfcd99fd77a0d3e3228d4d0b5061d9a611d40cd369ea44e333401b06936fdc4"
name = "github.com/araddon/dateparse"
packages = ["."]
pruneopts = "UT"
revision = "0fb0a474d195a3449cf412ae0176faa193f0ef0b"
[[projects]]
branch = "master"
digest = "1:1bd56b71a75f4df8aae1d65feb3aee6742ed1a2ff00fa9aaf74b40d9fadc4440"
name = "github.com/awesome-gocui/gocui"
packages = ["."]
pruneopts = "UT"
revision = "a34ffb055986a3f9461735162c9a2d235b95b8cb"
[[projects]]
branch = "master"
digest = "1:237f3e0692e330851b8b5a53117cfcea1beba09aec7668d34db91b809879c296"
name = "github.com/awesome-gocui/termbox-go"
packages = ["."]
pruneopts = "UT"
revision = "c0aef3d18bcc218a92e318293310ca0191f29654"
[[projects]]
digest = "1:b6d886569181ec96ca83d529f4d6ba0cbf92ace7bb6f633f90c5f34d9bba7aab"
name = "github.com/blang/semver"
packages = ["."]
pruneopts = "UT"
revision = "ba2c2ddd89069b46a7011d4106f6868f17ee1705"
version = "v3.6.1"
[[projects]]
branch = "master"
digest = "1:f438d91be142877c3ad83157992c91de787ddfbddcc2a7da1ef6ef61606cadc4"
name = "github.com/cheekybits/genny"
packages = ["generic"]
pruneopts = "UT"
revision = "9127e812e1e9e501ce899a18121d316ecb52e4ba"
[[projects]]
branch = "master"
digest = "1:cc439e1d9d8cff3d575642f5401033b00f2b8d0cd9f859db45604701c990879a"
name = "github.com/corpix/uarand"
packages = ["."]
pruneopts = "UT"
revision = "2b8494104d86337cdd41d0a49cbed8e4583c0ab4"
[[projects]]
digest = "1:7cb4fdca4c251b3ef8027c90ea35f70c7b661a593b9eeae34753c65499098bb1"
name = "github.com/cpuguy83/go-md2man"
packages = ["md2man"]
pruneopts = "UT"
revision = "20f5889cbdc3c73dbd2862796665e7c465ade7d1"
version = "v1.0.8"
[[projects]]
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
branch = "master"
digest = "1:6f9339c912bbdda81302633ad7e99a28dfa5a639c864061f1929510a9a64aa74"
name = "github.com/dustin/go-humanize"
packages = ["."]
pruneopts = "UT"
revision = "9f541cc9db5d55bce703bd99987c9d5cb8eea45e"
[[projects]]
digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a"
name = "github.com/fatih/color"
packages = ["."]
pruneopts = "UT"
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"
[[projects]]
digest = "1:aacef5f5e45685f2aeda5534d0a750dee6859de7e9088cdd06192787bb01ae6d"
name = "github.com/go-errors/errors"
packages = ["."]
pruneopts = "UT"
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
version = "v1.0.1"
[[projects]]
digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d"
name = "github.com/golang/protobuf"
packages = ["proto"]
pruneopts = "UT"
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.2.0"
[[projects]]
digest = "1:a63cff6b5d8b95638bfe300385d93b2a6d9d687734b863da8e09dc834510a690"
name = "github.com/google/go-querystring"
packages = ["query"]
pruneopts = "UT"
revision = "44c6ddd0a2342c386950e880b658017258da92fc"
version = "v1.0.0"
[[projects]]
digest = "1:cbec35fe4d5a4fba369a656a8cd65e244ea2c743007d8f6c1ccb132acf9d1296"
name = "github.com/gorilla/mux"
packages = ["."]
pruneopts = "UT"
revision = "00bdffe0f3c77e27d2cf6f5c70232a2d3e4d9c15"
version = "v1.7.3"
[[projects]]
digest = "1:43dd08a10854b2056e615d1b1d22ac94559d822e1f8b6fcc92c1a1057e85188e"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "UT"
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
digest = "1:8ec8d88c248041a6df5f6574b87bc00e7e0b493881dad2e7ef47b11dc69093b5"
name = "github.com/hashicorp/golang-lru"
packages = [
".",
"simplelru",
]
pruneopts = "UT"
revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768"
version = "v0.5.0"
[[projects]]
branch = "master"
digest = "1:22725c01ecd8ed0c0f0078944305a57053340d92878b02db925c660cc4accf64"
name = "github.com/icrowley/fake"
packages = ["."]
pruneopts = "UT"
revision = "4178557ae428460c3780a381c824a1f3aceb6325"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = "UT"
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = "UT"
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "UT"
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
digest = "1:e2d1d410fb367567c2b53ed9e2d719d3c1f0891397bb2fa49afd747cfbf1e8e4"
name = "github.com/mattn/go-runewidth"
packages = ["."]
pruneopts = "UT"
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
version = "v0.0.2"
[[projects]]
branch = "master"
digest = "1:645110e089152bd0f4a011a2648fbb0e4df5977be73ca605781157ac297f50c4"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
pruneopts = "UT"
revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8"
[[projects]]
digest = "1:7413525ee648f20b4181be7fe8103d0cb98be9e141926a03ee082dc207061e4e"
name = "github.com/phayes/freeport"
packages = ["."]
pruneopts = "UT"
revision = "b8543db493a5ed890c5499e935e2cad7504f3a04"
version = "1.0.2"
[[projects]]
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = "UT"
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:8bc629776d035c003c7814d4369521afe67fdb8efc4b5f66540d29343b98cf23"
name = "github.com/russross/blackfriday"
packages = ["."]
pruneopts = "UT"
revision = "55d61fa8aa702f59229e6cff85793c22e580eaf5"
version = "v1.5.1"
[[projects]]
branch = "master"
digest = "1:2befa342040f385b214cfd400887b584d5eba4e4b25a0ebaea839ddb0d59c586"
name = "github.com/shurcooL/githubv4"
packages = ["."]
pruneopts = "UT"
revision = "068505affed7d8555196a48eb3e0ed43410aa8e8"
[[projects]]
branch = "master"
digest = "1:ddaa79b37808fbd09a1c3f484bba6320c884664dfc25ccc5f2d7d5a82222177c"
name = "github.com/shurcooL/go"
packages = ["ctxhttp"]
pruneopts = "UT"
revision = "9e1955d9fb6e1ee2345ba1f5e71669263e719e27"
[[projects]]
branch = "master"
digest = "1:b69bc7c0c0489f6f3467b737e243620b1a545dbeaf7623a9432b658f7b7ecc58"
name = "github.com/shurcooL/graphql"
packages = [
".",
"ident",
"internal/jsonutil",
]
pruneopts = "UT"
revision = "365899397c9ad12805631fe4c9b2a64be9d74818"
[[projects]]
branch = "master"
digest = "1:9d29b003dc5f98647a5dd6754d65c07171fcd35761102ea56ecd3d6993adee7f"
name = "github.com/shurcooL/httpfs"
packages = [
"filter",
"vfsutil",
]
pruneopts = "UT"
revision = "809beceb23714880abc4a382a00c05f89d13b1cc"
[[projects]]
branch = "master"
digest = "1:98450c86949b8cdc4637b80c1c686ca955e503d3fbae9296d1f49c532895d281"
name = "github.com/shurcooL/vfsgen"
packages = ["."]
pruneopts = "UT"
revision = "6a9ea43bcacdf716a5c1b38efff722c07adf0069"
[[projects]]
branch = "master"
digest = "1:519621a373ae7d96a16415a6356d7b97722ed3a292f73409b361b7c0322971e3"
name = "github.com/skratchdot/open-golang"
packages = ["open"]
pruneopts = "UT"
revision = "79abb63cd66e41cb1473e26d11ebdcd68b04c8e5"
[[projects]]
digest = "1:abe9f3f23399646a6263682cacc9e86969f6c7e768f0ef036449926aa24cbbef"
name = "github.com/spf13/cobra"
packages = [
".",
"doc",
]
pruneopts = "UT"
revision = "f2b07da1e2c38d5f12845a4f607e2e1018cbb1f5"
version = "v0.0.5"
[[projects]]
digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:c40d65817cdd41fac9aa7af8bed56927bb2d6d47e4fea566a74880f5c2b1c41e"
name = "github.com/stretchr/testify"
packages = [
"assert",
"require",
]
pruneopts = "UT"
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
digest = "1:8d784da270f610f505e2c9785faec4c81d4a6a6a1034412685a4bf2cffe24cef"
name = "github.com/theckman/goconstraint"
packages = ["go1.10/gte"]
pruneopts = "UT"
revision = "93babf24513d0e8277635da8169fcc5a46ae3f6a"
version = "v1.11.0"
[[projects]]
digest = "1:895fa0f564003de2498fbaf00be596fa814655ab445ce7cc215f7724bb6831ae"
name = "github.com/vektah/gqlgen"
packages = ["client"]
pruneopts = "UT"
revision = "636435b68700211441303f1a5ed92f3768ba5774"
version = "v0.5.1"
[[projects]]
digest = "1:ed6a41de3eedd8d4868eea057837860453ebbe08c5e385fd50d4c24e5642ec18"
name = "github.com/vektah/gqlparser"
packages = [
".",
"ast",
"gqlerror",
"lexer",
"parser",
"validator",
"validator/rules",
]
pruneopts = "UT"
revision = "e805d08bb209b1accdea76bd2327811858d81985"
version = "v1.0.0"
[[projects]]
digest = "1:3254b94c092d3b29b828cc4c457723ac875f4318bf801745b71ef437459a90e9"
name = "github.com/xanzy/go-gitlab"
packages = ["."]
pruneopts = "UT"
revision = "d8e9de1d4b4477fe420696e10cd97491d518d90f"
version = "v0.22.1"
[[projects]]
branch = "master"
digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
pruneopts = "UT"
revision = "0e37d006457bf46f9e6692014ba72ef82c33022c"
[[projects]]
branch = "master"
digest = "1:d6b719875cf8091fbab38527d81d34e71f4521b9ee9ccfbd4a32cff2ac5af96e"
name = "golang.org/x/net"
packages = [
"context",
"context/ctxhttp",
]
pruneopts = "UT"
revision = "2f5d2388922f370f4355f327fcf4cfe9f5583908"
[[projects]]
branch = "master"
digest = "1:363b547c971a2b07474c598b6e9ebcb238d556d8a27f37b3895ad20cd50e7281"
name = "golang.org/x/oauth2"
packages = [
".",
"internal",
]
pruneopts = "UT"
revision = "d2e6202438beef2727060aa7cabdd924d92ebfd9"
[[projects]]
branch = "master"
digest = "1:b521f10a2d8fa85c04a8ef4e62f2d1e14d303599a55d64dabf9f5a02f84d35eb"
name = "golang.org/x/sync"
packages = ["errgroup"]
pruneopts = "UT"
revision = "cd5d95a43a6e21273425c7ae415d3df9ea832eeb"
[[projects]]
branch = "master"
digest = "1:3364d01296ce7eeca363e3d530ae63a2092d6f8efb85fb3d101e8f6d7de83452"
name = "golang.org/x/sys"
packages = [
"unix",
"windows",
]
pruneopts = "UT"
revision = "ac767d655b305d4e9612f5f6e33120b9176c4ad4"
[[projects]]
digest = "1:86cb348528a842f96e651ca3f8197070e9ebc315f8c73e71d0df7a60e92a6db1"
name = "golang.org/x/text"
packages = [
"runes",
"transform",
]
pruneopts = "UT"
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
version = "v0.3.2"
[[projects]]
digest = "1:71850ac10bbeb4d8dd06ce0743fe57654daf28510b0f6cbd9692aaf0d269360e"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/gcexportdata",
"go/internal/gcimporter",
"go/internal/packagesdriver",
"go/packages",
"go/types/typeutil",
"imports",
"internal/fastwalk",
"internal/gopathwalk",
"internal/module",
"internal/semver",
]
pruneopts = "UT"
revision = "7e5bf9270d7061560865b8847c378236480f47e3"
[[projects]]
digest = "1:6247f76e55a1e1a5c19a81e2d4b4dff6730461eeb5bbb0a16dd4a8ec8637ee93"
name = "google.golang.org/appengine"
packages = [
"internal",
"internal/base",
"internal/datastore",
"internal/log",
"internal/remote_api",
"internal/urlfetch",
"urlfetch",
]
pruneopts = "UT"
revision = "ae0ab99deb4dc413a2b4bd6c8bdd0eb67f1e4d06"
version = "v1.2.0"
[[projects]]
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/99designs/gqlgen/api",
"github.com/99designs/gqlgen/codegen/config",
"github.com/99designs/gqlgen/graphql",
"github.com/99designs/gqlgen/graphql/introspection",
"github.com/99designs/gqlgen/handler",
"github.com/MichaelMure/go-term-text",
"github.com/araddon/dateparse",
"github.com/awesome-gocui/gocui",
"github.com/blang/semver",
"github.com/cheekybits/genny/generic",
"github.com/dustin/go-humanize",
"github.com/fatih/color",
"github.com/go-errors/errors",
"github.com/gorilla/mux",
"github.com/icrowley/fake",
"github.com/mattn/go-isatty",
"github.com/phayes/freeport",
"github.com/pkg/errors",
"github.com/shurcooL/githubv4",
"github.com/shurcooL/httpfs/filter",
"github.com/shurcooL/vfsgen",
"github.com/skratchdot/open-golang/open",
"github.com/spf13/cobra",
"github.com/spf13/cobra/doc",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"github.com/theckman/goconstraint/go1.10/gte",
"github.com/vektah/gqlgen/client",
"github.com/vektah/gqlparser",
"github.com/vektah/gqlparser/ast",
"github.com/xanzy/go-gitlab",
"golang.org/x/crypto/ssh/terminal",
"golang.org/x/oauth2",
"golang.org/x/sync/errgroup",
"golang.org/x/text/runes",
"golang.org/x/text/transform",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,89 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# 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/gorilla/mux"
version = "1.7.3"
[[constraint]]
name = "github.com/phayes/freeport"
version = "1.0.2"
[[constraint]]
name = "github.com/shurcooL/vfsgen"
branch = "master"
[[constraint]]
name = "github.com/skratchdot/open-golang"
branch = "master"
[[constraint]]
name = "github.com/fatih/color"
version = "1.7.0"
[[constraint]]
name = "github.com/spf13/cobra"
version = "v0.0.5"
[[constraint]]
name = "github.com/dustin/go-humanize"
branch = "master"
[[constraint]]
name = "github.com/99designs/gqlgen"
version = "0.9.2"
[[constraint]]
name = "github.com/awesome-gocui/gocui"
branch = "master"
[[override]]
name = "golang.org/x/tools"
revision = "7e5bf9270d7061560865b8847c378236480f47e3"
[[constraint]]
name = "github.com/blang/semver"
version = "3.6.1"
[[constraint]]
name = "github.com/xanzy/go-gitlab"
version = "0.22.1"
[[constraint]]
branch = "master"
name = "golang.org/x/sync"
[[constraint]]
name = "github.com/MichaelMure/go-term-text"
version = "0.2.4"
[[constraint]]
branch = "master"
name = "github.com/araddon/dateparse"

View File

@ -12,7 +12,7 @@ import (
"os/signal"
"time"
"github.com/99designs/gqlgen/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/gorilla/mux"
"github.com/phayes/freeport"
"github.com/skratchdot/open-golang/open"
@ -57,7 +57,7 @@ func runWebUI(cmd *cobra.Command, args []string) error {
}
// Routes
router.Path("/playground").Handler(handler.Playground("git-bug", "/graphql"))
router.Path("/playground").Handler(playground.Handler("git-bug", "/graphql"))
router.Path("/graphql").Handler(graphqlHandler)
router.Path("/gitfile/{hash}").Handler(newGitFileHandler(repo))
router.Path("/upload").Methods("POST").Handler(newGitUploadFileHandler(repo))

View File

@ -9,8 +9,8 @@ package main
import (
"github.com/MichaelMure/git-bug/commands"
// minimal go version is 1.10
_ "github.com/theckman/goconstraint/go1.10/gte"
// minimal go version is 1.11
_ "github.com/theckman/goconstraint/go1.11/gte"
)
func main() {

35
go.mod Normal file
View File

@ -0,0 +1,35 @@
module github.com/MichaelMure/git-bug
go 1.11
require (
github.com/99designs/gqlgen v0.10.3-0.20200205113530-b941b970f0b6
github.com/MichaelMure/go-term-text v0.2.4
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986
github.com/blang/semver v3.5.1+incompatible
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9
github.com/corpix/uarand v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.7.0
github.com/go-errors/errors v1.0.1
github.com/gorilla/mux v1.7.3
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.8
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5
github.com/pkg/errors v0.8.1
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e
github.com/spf13/cobra v0.0.5
github.com/stretchr/testify v1.4.0
github.com/theckman/goconstraint v1.11.0
github.com/vektah/gqlparser v1.2.1
github.com/xanzy/go-gitlab v0.22.1
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/text v0.3.0
)

168
go.sum Normal file
View File

@ -0,0 +1,168 @@
github.com/99designs/gqlgen v0.10.3-0.20200205113530-b941b970f0b6 h1:WU+9Z7AoCyl+HlB2kE0O+4/3BaVqLQfnX5dHigV5p8A=
github.com/99designs/gqlgen v0.10.3-0.20200205113530-b941b970f0b6/go.mod h1:28v/ATDVwPUriwNtAIrQEhRHXJjdi5dVGqxSqPna1I8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
github.com/MichaelMure/go-term-text v0.2.4 h1:h+lAsjG5o3oNvaJeh7OzE8zdSiB/VyJo9JzSnncrZv4=
github.com/MichaelMure/go-term-text v0.2.4/go.mod h1:o2Z5T3b28F4kwAojGvvNdbzjHf9t18vbQ7E2pmTe2Ww=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 h1:c4mLfegoDw6OhSJXTd2jUEQgZUQuJWtocudb97Qn9EM=
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986 h1:QvIfX96O11qjX1Zr3hKkG0dI12JBRBGABWffyZ1GI60=
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA=
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0=
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM6zG1u72DWJwZG3ayttYLfmLbxVETk=
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U=
github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 h1:Mo9W14pwbO9VfRe+ygqZ8dFbPpoIK1HFrG/zjTuQ+nc=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5 h1:rZQtoozkfsiNs36c7Tdv/gyGNzD1X1XWKO8rptVNZuM=
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7 h1:Vk3RiBQpF0Ja+OqbFG7lYTk79+l8Cm2QESLXB0x6u6U=
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk=
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e h1:VAzdS5Nw68fbf5RZ8RDVlUvPXNU6Z3jtPCK/qvm4FoQ=
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/theckman/goconstraint v1.11.0 h1:oBUwN5wpE4dwyPhRGraEgJsFTr+JtLWiDnaJZJeeXI0=
github.com/theckman/goconstraint v1.11.0/go.mod h1:zkCR/f2kOULTk/h1ujgyB9BlCNLaqlQ6GN2Zl4mg81g=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser v1.2.1 h1:C+L7Go/eUbN0w6Y0kaiq2W6p2wN5j8wU82EdDXxDivc=
github.com/vektah/gqlparser v1.2.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/xanzy/go-gitlab v0.22.1 h1:TVxgHmoa35jQL+9FCkG0nwPDxU9dQZXknBTDtGaSFno=
github.com/xanzy/go-gitlab v0.22.1/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,9 @@
package graphql
import (
"net/http/httptest"
"testing"
"github.com/vektah/gqlgen/client"
"github.com/99designs/gqlgen/client"
"github.com/MichaelMure/git-bug/graphql/models"
"github.com/MichaelMure/git-bug/misc/random_bugs"
@ -22,8 +21,7 @@ func TestQueries(t *testing.T) {
t.Fatal(err)
}
srv := httptest.NewServer(handler)
c := client.New(srv.URL)
c := client.New(handler)
query := `
query {

View File

@ -6,7 +6,8 @@ package graphql
import (
"net/http"
"github.com/99designs/gqlgen/handler"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/MichaelMure/git-bug/graphql/graph"
"github.com/MichaelMure/git-bug/graphql/resolvers"
"github.com/MichaelMure/git-bug/repository"
@ -14,7 +15,7 @@ import (
// Handler is the root GraphQL http handler
type Handler struct {
http.HandlerFunc
http.Handler
*resolvers.RootResolver
}
@ -32,7 +33,7 @@ func NewHandler(repo repository.ClockedRepo) (Handler, error) {
Resolvers: h.RootResolver,
}
h.HandlerFunc = handler.GraphQL(graph.NewExecutableSchema(config))
h.Handler = handler.NewDefaultServer(graph.NewExecutableSchema(config))
return h, nil
}

View File

@ -11,41 +11,10 @@ var _ graph.IdentityResolver = &identityResolver{}
type identityResolver struct{}
func (identityResolver) ID(ctx context.Context, obj *identity.Interface) (string, error) {
return (*obj).Id().String(), nil
func (identityResolver) ID(ctx context.Context, obj identity.Interface) (string, error) {
return obj.Id().String(), nil
}
func (identityResolver) HumanID(ctx context.Context, obj *identity.Interface) (string, error) {
return (*obj).Id().Human(), nil
}
func (identityResolver) Name(ctx context.Context, obj *identity.Interface) (*string, error) {
return nilIfEmpty((*obj).Name())
}
func (identityResolver) Email(ctx context.Context, obj *identity.Interface) (*string, error) {
return nilIfEmpty((*obj).Email())
}
func (identityResolver) Login(ctx context.Context, obj *identity.Interface) (*string, error) {
return nilIfEmpty((*obj).Login())
}
func (identityResolver) DisplayName(ctx context.Context, obj *identity.Interface) (string, error) {
return (*obj).DisplayName(), nil
}
func (identityResolver) AvatarURL(ctx context.Context, obj *identity.Interface) (*string, error) {
return nilIfEmpty((*obj).AvatarUrl())
}
func (identityResolver) IsProtected(ctx context.Context, obj *identity.Interface) (bool, error) {
return (*obj).IsProtected(), nil
}
func nilIfEmpty(s string) (*string, error) {
if s == "" {
return nil, nil
}
return &s, nil
func (identityResolver) HumanID(ctx context.Context, obj identity.Interface) (string, error) {
return obj.Id().Human(), nil
}

View File

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

View File

@ -1,78 +0,0 @@
package api
import (
"syscall"
"github.com/99designs/gqlgen/codegen"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin"
"github.com/99designs/gqlgen/plugin/modelgen"
"github.com/99designs/gqlgen/plugin/resolvergen"
"github.com/99designs/gqlgen/plugin/schemaconfig"
"github.com/pkg/errors"
"golang.org/x/tools/go/packages"
)
func Generate(cfg *config.Config, option ...Option) error {
_ = syscall.Unlink(cfg.Exec.Filename)
_ = syscall.Unlink(cfg.Model.Filename)
plugins := []plugin.Plugin{
schemaconfig.New(),
modelgen.New(),
resolvergen.New(),
}
for _, o := range option {
o(cfg, &plugins)
}
for _, p := range plugins {
if mut, ok := p.(plugin.ConfigMutator); ok {
err := mut.MutateConfig(cfg)
if err != nil {
return errors.Wrap(err, p.Name())
}
}
}
// Merge again now that the generated models have been injected into the typemap
data, err := codegen.BuildData(cfg)
if err != nil {
return errors.Wrap(err, "merging failed")
}
if err = codegen.GenerateCode(data); err != nil {
return errors.Wrap(err, "generating core failed")
}
for _, p := range plugins {
if mut, ok := p.(plugin.CodeGenerator); ok {
err := mut.GenerateCode(data)
if err != nil {
return errors.Wrap(err, p.Name())
}
}
}
if err := validate(cfg); err != nil {
return errors.Wrap(err, "validation failed")
}
return nil
}
func validate(cfg *config.Config) error {
roots := []string{cfg.Exec.ImportPath()}
if cfg.Model.IsDefined() {
roots = append(roots, cfg.Model.ImportPath())
}
if cfg.Resolver.IsDefined() {
roots = append(roots, cfg.Resolver.ImportPath())
}
_, err := packages.Load(&packages.Config{Mode: packages.LoadTypes | packages.LoadSyntax}, roots...)
if err != nil {
return errors.Wrap(err, "validation failed")
}
return nil
}

View File

@ -1,20 +0,0 @@
package api
import (
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin"
)
type Option func(cfg *config.Config, plugins *[]plugin.Plugin)
func NoPlugins() Option {
return func(cfg *config.Config, plugins *[]plugin.Plugin) {
*plugins = nil
}
}
func AddPlugin(p plugin.Plugin) Option {
return func(cfg *config.Config, plugins *[]plugin.Plugin) {
*plugins = append(*plugins, p)
}
}

View File

@ -1,120 +0,0 @@
package codegen
import (
"fmt"
"go/types"
"strings"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
)
type ArgSet struct {
Args []*FieldArgument
FuncDecl string
}
type FieldArgument struct {
*ast.ArgumentDefinition
TypeReference *config.TypeReference
VarName string // The name of the var in go
Object *Object // A link back to the parent object
Default interface{} // The default value
Directives []*Directive
Value interface{} // value set in Data
}
//ImplDirectives get not Builtin and location ARGUMENT_DEFINITION directive
func (f *FieldArgument) ImplDirectives() []*Directive {
d := make([]*Directive, 0)
for i := range f.Directives {
if !f.Directives[i].Builtin && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) {
d = append(d, f.Directives[i])
}
}
return d
}
func (f *FieldArgument) DirectiveObjName() string {
return "rawArgs"
}
func (f *FieldArgument) Stream() bool {
return f.Object != nil && f.Object.Stream
}
func (b *builder) buildArg(obj *Object, arg *ast.ArgumentDefinition) (*FieldArgument, error) {
tr, err := b.Binder.TypeReference(arg.Type, nil)
if err != nil {
return nil, err
}
argDirs, err := b.getDirectives(arg.Directives)
if err != nil {
return nil, err
}
newArg := FieldArgument{
ArgumentDefinition: arg,
TypeReference: tr,
Object: obj,
VarName: templates.ToGoPrivate(arg.Name),
Directives: argDirs,
}
if arg.DefaultValue != nil {
newArg.Default, err = arg.DefaultValue.Value(nil)
if err != nil {
return nil, errors.Errorf("default value is not valid: %s", err.Error())
}
}
return &newArg, nil
}
func (b *builder) bindArgs(field *Field, params *types.Tuple) error {
var newArgs []*FieldArgument
nextArg:
for j := 0; j < params.Len(); j++ {
param := params.At(j)
for _, oldArg := range field.Args {
if strings.EqualFold(oldArg.Name, param.Name()) {
tr, err := b.Binder.TypeReference(oldArg.Type, param.Type())
if err != nil {
return err
}
oldArg.TypeReference = tr
newArgs = append(newArgs, oldArg)
continue nextArg
}
}
// no matching arg found, abort
return fmt.Errorf("arg %s not in schema", param.Name())
}
field.Args = newArgs
return nil
}
func (a *Data) Args() map[string][]*FieldArgument {
ret := map[string][]*FieldArgument{}
for _, o := range a.Objects {
for _, f := range o.Fields {
if len(f.Args) > 0 {
ret[f.ArgsFunc()] = f.Args
}
}
}
for _, d := range a.Directives {
if len(d.Args) > 0 {
ret[d.ArgsFunc()] = d.Args
}
}
return ret
}

View File

@ -1,35 +0,0 @@
{{ range $name, $args := .Args }}
func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
{{- range $i, $arg := . }}
var arg{{$i}} {{ $arg.TypeReference.GO | ref}}
if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok {
{{- if $arg.ImplDirectives }}
directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) }
{{ template "implDirectives" $arg }}
tmp, err = directive{{$arg.ImplDirectives|len}}(ctx)
if err != nil {
return nil, err
}
if data, ok := tmp.({{ $arg.TypeReference.GO | ref }}) ; ok {
arg{{$i}} = data
{{- if $arg.TypeReference.IsNilable }}
} else if tmp == nil {
arg{{$i}} = nil
{{- end }}
} else {
return nil, fmt.Errorf(`unexpected type %T from directive, should be {{ $arg.TypeReference.GO }}`, tmp)
}
{{- else }}
arg{{$i}}, err = ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp)
if err != nil {
return nil, err
}
{{- end }}
}
args[{{$arg.Name|quote}}] = arg{{$i}}
{{- end }}
return args, nil
}
{{ end }}

View File

@ -1,11 +0,0 @@
package codegen
func (o *Object) UniqueFields() map[string][]*Field {
m := map[string][]*Field{}
for _, f := range o.Fields {
m[f.GoFieldName] = append(m[f.GoFieldName], f)
}
return m
}

View File

@ -1,448 +0,0 @@
package config
import (
"fmt"
"go/token"
"go/types"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/internal/code"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
"golang.org/x/tools/go/packages"
)
// Binder connects graphql types to golang types using static analysis
type Binder struct {
pkgs map[string]*packages.Package
schema *ast.Schema
cfg *Config
References []*TypeReference
}
func (c *Config) NewBinder(s *ast.Schema) (*Binder, error) {
pkgs, err := packages.Load(&packages.Config{Mode: packages.LoadTypes | packages.LoadSyntax}, c.Models.ReferencedPackages()...)
if err != nil {
return nil, err
}
mp := map[string]*packages.Package{}
for _, p := range pkgs {
populatePkg(mp, p)
for _, e := range p.Errors {
if e.Kind == packages.ListError {
return nil, p.Errors[0]
}
}
}
return &Binder{
pkgs: mp,
schema: s,
cfg: c,
}, nil
}
func populatePkg(mp map[string]*packages.Package, p *packages.Package) {
imp := code.NormalizeVendor(p.PkgPath)
if _, ok := mp[imp]; ok {
return
}
mp[imp] = p
for _, p := range p.Imports {
populatePkg(mp, p)
}
}
func (b *Binder) TypePosition(typ types.Type) token.Position {
named, isNamed := typ.(*types.Named)
if !isNamed {
return token.Position{
Filename: "unknown",
}
}
return b.ObjectPosition(named.Obj())
}
func (b *Binder) ObjectPosition(typ types.Object) token.Position {
if typ == nil {
return token.Position{
Filename: "unknown",
}
}
pkg := b.getPkg(typ.Pkg().Path())
return pkg.Fset.Position(typ.Pos())
}
func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) {
obj, err := b.FindObject(pkgName, typeName)
if err != nil {
return nil, err
}
if fun, isFunc := obj.(*types.Func); isFunc {
return fun.Type().(*types.Signature).Params().At(0).Type(), nil
}
return obj.Type(), nil
}
func (b *Binder) getPkg(find string) *packages.Package {
imp := code.NormalizeVendor(find)
if p, ok := b.pkgs[imp]; ok {
return p
}
return nil
}
var MapType = types.NewMap(types.Typ[types.String], types.NewInterfaceType(nil, nil).Complete())
var InterfaceType = types.NewInterfaceType(nil, nil)
func (b *Binder) DefaultUserObject(name string) (types.Type, error) {
models := b.cfg.Models[name].Model
if len(models) == 0 {
return nil, fmt.Errorf(name + " not found in typemap")
}
if models[0] == "map[string]interface{}" {
return MapType, nil
}
if models[0] == "interface{}" {
return InterfaceType, nil
}
pkgName, typeName := code.PkgAndType(models[0])
if pkgName == "" {
return nil, fmt.Errorf("missing package name for %s", name)
}
obj, err := b.FindObject(pkgName, typeName)
if err != nil {
return nil, err
}
return obj.Type(), nil
}
func (b *Binder) FindObject(pkgName string, typeName string) (types.Object, error) {
if pkgName == "" {
return nil, fmt.Errorf("package cannot be nil")
}
fullName := typeName
if pkgName != "" {
fullName = pkgName + "." + typeName
}
pkg := b.getPkg(pkgName)
if pkg == nil {
return nil, errors.Errorf("required package was not loaded: %s", fullName)
}
// function based marshalers take precedence
for astNode, def := range pkg.TypesInfo.Defs {
// only look at defs in the top scope
if def == nil || def.Parent() == nil || def.Parent() != pkg.Types.Scope() {
continue
}
if astNode.Name == "Marshal"+typeName {
return def, nil
}
}
// then look for types directly
for astNode, def := range pkg.TypesInfo.Defs {
// only look at defs in the top scope
if def == nil || def.Parent() == nil || def.Parent() != pkg.Types.Scope() {
continue
}
if astNode.Name == typeName {
return def, nil
}
}
return nil, errors.Errorf("unable to find type %s\n", fullName)
}
func (b *Binder) PointerTo(ref *TypeReference) *TypeReference {
newRef := &TypeReference{
GO: types.NewPointer(ref.GO),
GQL: ref.GQL,
CastType: ref.CastType,
Definition: ref.Definition,
Unmarshaler: ref.Unmarshaler,
Marshaler: ref.Marshaler,
IsMarshaler: ref.IsMarshaler,
}
b.References = append(b.References, newRef)
return newRef
}
// TypeReference is used by args and field types. The Definition can refer to both input and output types.
type TypeReference struct {
Definition *ast.Definition
GQL *ast.Type
GO types.Type
CastType types.Type // Before calling marshalling functions cast from/to this base type
Marshaler *types.Func // When using external marshalling functions this will point to the Marshal function
Unmarshaler *types.Func // When using external marshalling functions this will point to the Unmarshal function
IsMarshaler bool // Does the type implement graphql.Marshaler and graphql.Unmarshaler
}
func (ref *TypeReference) Elem() *TypeReference {
if p, isPtr := ref.GO.(*types.Pointer); isPtr {
return &TypeReference{
GO: p.Elem(),
GQL: ref.GQL,
CastType: ref.CastType,
Definition: ref.Definition,
Unmarshaler: ref.Unmarshaler,
Marshaler: ref.Marshaler,
IsMarshaler: ref.IsMarshaler,
}
}
if ref.IsSlice() {
return &TypeReference{
GO: ref.GO.(*types.Slice).Elem(),
GQL: ref.GQL.Elem,
CastType: ref.CastType,
Definition: ref.Definition,
Unmarshaler: ref.Unmarshaler,
Marshaler: ref.Marshaler,
IsMarshaler: ref.IsMarshaler,
}
}
return nil
}
func (t *TypeReference) IsPtr() bool {
_, isPtr := t.GO.(*types.Pointer)
return isPtr
}
func (t *TypeReference) IsNilable() bool {
_, isPtr := t.GO.(*types.Pointer)
_, isMap := t.GO.(*types.Map)
_, isInterface := t.GO.(*types.Interface)
return isPtr || isMap || isInterface
}
func (t *TypeReference) IsSlice() bool {
_, isSlice := t.GO.(*types.Slice)
return t.GQL.Elem != nil && isSlice
}
func (t *TypeReference) IsNamed() bool {
_, isSlice := t.GO.(*types.Named)
return isSlice
}
func (t *TypeReference) IsStruct() bool {
_, isStruct := t.GO.Underlying().(*types.Struct)
return isStruct
}
func (t *TypeReference) IsScalar() bool {
return t.Definition.Kind == ast.Scalar
}
func (t *TypeReference) UniquenessKey() string {
var nullability = "O"
if t.GQL.NonNull {
nullability = "N"
}
return nullability + t.Definition.Name + "2" + templates.TypeIdentifier(t.GO)
}
func (t *TypeReference) MarshalFunc() string {
if t.Definition == nil {
panic(errors.New("Definition missing for " + t.GQL.Name()))
}
if t.Definition.Kind == ast.InputObject {
return ""
}
return "marshal" + t.UniquenessKey()
}
func (t *TypeReference) UnmarshalFunc() string {
if t.Definition == nil {
panic(errors.New("Definition missing for " + t.GQL.Name()))
}
if !t.Definition.IsInputType() {
return ""
}
return "unmarshal" + t.UniquenessKey()
}
func (b *Binder) PushRef(ret *TypeReference) {
b.References = append(b.References, ret)
}
func isMap(t types.Type) bool {
if t == nil {
return true
}
_, ok := t.(*types.Map)
return ok
}
func isIntf(t types.Type) bool {
if t == nil {
return true
}
_, ok := t.(*types.Interface)
return ok
}
func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret *TypeReference, err error) {
var pkgName, typeName string
def := b.schema.Types[schemaType.Name()]
defer func() {
if err == nil && ret != nil {
b.PushRef(ret)
}
}()
if len(b.cfg.Models[schemaType.Name()].Model) == 0 {
return nil, fmt.Errorf("%s was not found", schemaType.Name())
}
for _, model := range b.cfg.Models[schemaType.Name()].Model {
if model == "map[string]interface{}" {
if !isMap(bindTarget) {
continue
}
return &TypeReference{
Definition: def,
GQL: schemaType,
GO: MapType,
}, nil
}
if model == "interface{}" {
if !isIntf(bindTarget) {
continue
}
return &TypeReference{
Definition: def,
GQL: schemaType,
GO: InterfaceType,
}, nil
}
pkgName, typeName = code.PkgAndType(model)
if pkgName == "" {
return nil, fmt.Errorf("missing package name for %s", schemaType.Name())
}
ref := &TypeReference{
Definition: def,
GQL: schemaType,
}
obj, err := b.FindObject(pkgName, typeName)
if err != nil {
return nil, err
}
if fun, isFunc := obj.(*types.Func); isFunc {
ref.GO = fun.Type().(*types.Signature).Params().At(0).Type()
ref.Marshaler = fun
ref.Unmarshaler = types.NewFunc(0, fun.Pkg(), "Unmarshal"+typeName, nil)
} else if hasMethod(obj.Type(), "MarshalGQL") && hasMethod(obj.Type(), "UnmarshalGQL") {
ref.GO = obj.Type()
ref.IsMarshaler = true
} else if underlying := basicUnderlying(obj.Type()); def.IsLeafType() && underlying != nil && underlying.Kind() == types.String {
// Special case for named types wrapping strings. Used by default enum implementations.
ref.GO = obj.Type()
ref.CastType = underlying
underlyingRef, err := b.TypeReference(&ast.Type{NamedType: "String"}, nil)
if err != nil {
return nil, err
}
ref.Marshaler = underlyingRef.Marshaler
ref.Unmarshaler = underlyingRef.Unmarshaler
} else {
ref.GO = obj.Type()
}
ref.GO = b.CopyModifiersFromAst(schemaType, ref.GO)
if bindTarget != nil {
if err = code.CompatibleTypes(ref.GO, bindTarget); err != nil {
continue
}
ref.GO = bindTarget
}
return ref, nil
}
return nil, fmt.Errorf("%s has type compatible with %s", schemaType.Name(), bindTarget.String())
}
func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type {
if t.Elem != nil {
child := b.CopyModifiersFromAst(t.Elem, base)
if _, isStruct := child.Underlying().(*types.Struct); isStruct {
child = types.NewPointer(child)
}
return types.NewSlice(child)
}
var isInterface bool
if named, ok := base.(*types.Named); ok {
_, isInterface = named.Underlying().(*types.Interface)
}
if !isInterface && !t.NonNull {
return types.NewPointer(base)
}
return base
}
func hasMethod(it types.Type, name string) bool {
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return false
}
for i := 0; i < namedType.NumMethods(); i++ {
if namedType.Method(i).Name() == name {
return true
}
}
return false
}
func basicUnderlying(it types.Type) *types.Basic {
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
}
namedType, ok := it.(*types.Named)
if !ok {
return nil
}
if basic, ok := namedType.Underlying().(*types.Basic); ok {
return basic
}
return nil
}

View File

@ -1,490 +0,0 @@
package config
import (
"fmt"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"golang.org/x/tools/go/packages"
"github.com/99designs/gqlgen/internal/code"
"github.com/pkg/errors"
"github.com/vektah/gqlparser"
"github.com/vektah/gqlparser/ast"
"gopkg.in/yaml.v2"
)
type Config struct {
SchemaFilename StringList `yaml:"schema,omitempty"`
Exec PackageConfig `yaml:"exec"`
Model PackageConfig `yaml:"model"`
Resolver PackageConfig `yaml:"resolver,omitempty"`
AutoBind []string `yaml:"autobind"`
Models TypeMap `yaml:"models,omitempty"`
StructTag string `yaml:"struct_tag,omitempty"`
Directives map[string]DirectiveConfig `yaml:"directives,omitempty"`
}
var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"}
// DefaultConfig creates a copy of the default config
func DefaultConfig() *Config {
return &Config{
SchemaFilename: StringList{"schema.graphql"},
Model: PackageConfig{Filename: "models_gen.go"},
Exec: PackageConfig{Filename: "generated.go"},
Directives: map[string]DirectiveConfig{},
}
}
// LoadConfigFromDefaultLocations looks for a config file in the current directory, and all parent directories
// walking up the tree. The closest config file will be returned.
func LoadConfigFromDefaultLocations() (*Config, error) {
cfgFile, err := findCfg()
if err != nil {
return nil, err
}
err = os.Chdir(filepath.Dir(cfgFile))
if err != nil {
return nil, errors.Wrap(err, "unable to enter config dir")
}
return LoadConfig(cfgFile)
}
var path2regex = strings.NewReplacer(
`.`, `\.`,
`*`, `.+`,
`\`, `[\\/]`,
`/`, `[\\/]`,
)
// LoadConfig reads the gqlgen.yml config file
func LoadConfig(filename string) (*Config, error) {
config := DefaultConfig()
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, errors.Wrap(err, "unable to read config")
}
if err := yaml.UnmarshalStrict(b, config); err != nil {
return nil, errors.Wrap(err, "unable to parse config")
}
defaultDirectives := map[string]DirectiveConfig{
"skip": {SkipRuntime: true},
"include": {SkipRuntime: true},
"deprecated": {SkipRuntime: true},
}
for key, value := range defaultDirectives {
if _, defined := config.Directives[key]; !defined {
config.Directives[key] = value
}
}
preGlobbing := config.SchemaFilename
config.SchemaFilename = StringList{}
for _, f := range preGlobbing {
var matches []string
// for ** we want to override default globbing patterns and walk all
// subdirectories to match schema files.
if strings.Contains(f, "**") {
pathParts := strings.SplitN(f, "**", 2)
rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`)
// turn the rest of the glob into a regex, anchored only at the end because ** allows
// for any number of dirs in between and walk will let us match against the full path name
globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`)
if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) {
matches = append(matches, path)
}
return nil
}); err != nil {
return nil, errors.Wrapf(err, "failed to walk schema at root %s", pathParts[0])
}
} else {
matches, err = filepath.Glob(f)
if err != nil {
return nil, errors.Wrapf(err, "failed to glob schema filename %s", f)
}
}
for _, m := range matches {
if config.SchemaFilename.Has(m) {
continue
}
config.SchemaFilename = append(config.SchemaFilename, m)
}
}
return config, nil
}
type PackageConfig struct {
Filename string `yaml:"filename,omitempty"`
Package string `yaml:"package,omitempty"`
Type string `yaml:"type,omitempty"`
}
type TypeMapEntry struct {
Model StringList `yaml:"model"`
Fields map[string]TypeMapField `yaml:"fields,omitempty"`
}
type TypeMapField struct {
Resolver bool `yaml:"resolver"`
FieldName string `yaml:"fieldName"`
}
type StringList []string
func (a *StringList) UnmarshalYAML(unmarshal func(interface{}) error) error {
var single string
err := unmarshal(&single)
if err == nil {
*a = []string{single}
return nil
}
var multi []string
err = unmarshal(&multi)
if err != nil {
return err
}
*a = multi
return nil
}
func (a StringList) Has(file string) bool {
for _, existing := range a {
if existing == file {
return true
}
}
return false
}
func (c *PackageConfig) normalize() error {
if c.Filename == "" {
return errors.New("Filename is required")
}
c.Filename = abs(c.Filename)
// If Package is not set, first attempt to load the package at the output dir. If that fails
// fallback to just the base dir name of the output filename.
if c.Package == "" {
c.Package = code.NameForDir(c.Dir())
}
return nil
}
func (c *PackageConfig) ImportPath() string {
return code.ImportPathForDir(c.Dir())
}
func (c *PackageConfig) Dir() string {
return filepath.Dir(c.Filename)
}
func (c *PackageConfig) Check() error {
if strings.ContainsAny(c.Package, "./\\") {
return fmt.Errorf("package should be the output package name only, do not include the output filename")
}
if c.Filename != "" && !strings.HasSuffix(c.Filename, ".go") {
return fmt.Errorf("filename should be path to a go source file")
}
return c.normalize()
}
func (c *PackageConfig) Pkg() *types.Package {
return types.NewPackage(c.ImportPath(), c.Dir())
}
func (c *PackageConfig) IsDefined() bool {
return c.Filename != ""
}
func (c *Config) Check() error {
if err := c.Models.Check(); err != nil {
return errors.Wrap(err, "config.models")
}
if err := c.Exec.Check(); err != nil {
return errors.Wrap(err, "config.exec")
}
if err := c.Model.Check(); err != nil {
return errors.Wrap(err, "config.model")
}
if c.Resolver.IsDefined() {
if err := c.Resolver.Check(); err != nil {
return errors.Wrap(err, "config.resolver")
}
}
// check packages names against conflict, if present in the same dir
// and check filenames for uniqueness
packageConfigList := []PackageConfig{
c.Model,
c.Exec,
c.Resolver,
}
filesMap := make(map[string]bool)
pkgConfigsByDir := make(map[string]PackageConfig)
for _, current := range packageConfigList {
_, fileFound := filesMap[current.Filename]
if fileFound {
return fmt.Errorf("filename %s defined more than once", current.Filename)
}
filesMap[current.Filename] = true
previous, inSameDir := pkgConfigsByDir[current.Dir()]
if inSameDir && current.Package != previous.Package {
return fmt.Errorf("filenames %s and %s are in the same directory but have different package definitions", stripPath(current.Filename), stripPath(previous.Filename))
}
pkgConfigsByDir[current.Dir()] = current
}
return c.normalize()
}
func stripPath(path string) string {
return filepath.Base(path)
}
type TypeMap map[string]TypeMapEntry
func (tm TypeMap) Exists(typeName string) bool {
_, ok := tm[typeName]
return ok
}
func (tm TypeMap) UserDefined(typeName string) bool {
m, ok := tm[typeName]
return ok && len(m.Model) > 0
}
func (tm TypeMap) Check() error {
for typeName, entry := range tm {
for _, model := range entry.Model {
if strings.LastIndex(model, ".") < strings.LastIndex(model, "/") {
return fmt.Errorf("model %s: invalid type specifier \"%s\" - you need to specify a struct to map to", typeName, entry.Model)
}
}
}
return nil
}
func (tm TypeMap) ReferencedPackages() []string {
var pkgs []string
for _, typ := range tm {
for _, model := range typ.Model {
if model == "map[string]interface{}" || model == "interface{}" {
continue
}
pkg, _ := code.PkgAndType(model)
if pkg == "" || inStrSlice(pkgs, pkg) {
continue
}
pkgs = append(pkgs, code.QualifyPackagePath(pkg))
}
}
sort.Slice(pkgs, func(i, j int) bool {
return pkgs[i] > pkgs[j]
})
return pkgs
}
func (tm TypeMap) Add(name string, goType string) {
modelCfg := tm[name]
modelCfg.Model = append(modelCfg.Model, goType)
tm[name] = modelCfg
}
type DirectiveConfig struct {
SkipRuntime bool `yaml:"skip_runtime"`
}
func inStrSlice(haystack []string, needle string) bool {
for _, v := range haystack {
if needle == v {
return true
}
}
return false
}
// findCfg searches for the config file in this directory and all parents up the tree
// looking for the closest match
func findCfg() (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", errors.Wrap(err, "unable to get working dir to findCfg")
}
cfg := findCfgInDir(dir)
for cfg == "" && dir != filepath.Dir(dir) {
dir = filepath.Dir(dir)
cfg = findCfgInDir(dir)
}
if cfg == "" {
return "", os.ErrNotExist
}
return cfg, nil
}
func findCfgInDir(dir string) string {
for _, cfgName := range cfgFilenames {
path := filepath.Join(dir, cfgName)
if _, err := os.Stat(path); err == nil {
return path
}
}
return ""
}
func (c *Config) normalize() error {
if err := c.Model.normalize(); err != nil {
return errors.Wrap(err, "model")
}
if err := c.Exec.normalize(); err != nil {
return errors.Wrap(err, "exec")
}
if c.Resolver.IsDefined() {
if err := c.Resolver.normalize(); err != nil {
return errors.Wrap(err, "resolver")
}
}
if c.Models == nil {
c.Models = TypeMap{}
}
return nil
}
func (c *Config) Autobind(s *ast.Schema) error {
if len(c.AutoBind) == 0 {
return nil
}
ps, err := packages.Load(&packages.Config{Mode: packages.LoadTypes}, c.AutoBind...)
if err != nil {
return err
}
for _, t := range s.Types {
if c.Models.UserDefined(t.Name) {
continue
}
for _, p := range ps {
if t := p.Types.Scope().Lookup(t.Name); t != nil {
c.Models.Add(t.Name(), t.Pkg().Path()+"."+t.Name())
break
}
}
}
return nil
}
func (c *Config) InjectBuiltins(s *ast.Schema) {
builtins := TypeMap{
"__Directive": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Directive"}},
"__DirectiveLocation": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}},
"__Type": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Type"}},
"__TypeKind": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}},
"__Field": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Field"}},
"__EnumValue": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.EnumValue"}},
"__InputValue": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.InputValue"}},
"__Schema": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Schema"}},
"Float": {Model: StringList{"github.com/99designs/gqlgen/graphql.Float"}},
"String": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}},
"Boolean": {Model: StringList{"github.com/99designs/gqlgen/graphql.Boolean"}},
"Int": {Model: StringList{
"github.com/99designs/gqlgen/graphql.Int",
"github.com/99designs/gqlgen/graphql.Int32",
"github.com/99designs/gqlgen/graphql.Int64",
}},
"ID": {
Model: StringList{
"github.com/99designs/gqlgen/graphql.ID",
"github.com/99designs/gqlgen/graphql.IntID",
},
},
}
for typeName, entry := range builtins {
if !c.Models.Exists(typeName) {
c.Models[typeName] = entry
}
}
// These are additional types that are injected if defined in the schema as scalars.
extraBuiltins := TypeMap{
"Time": {Model: StringList{"github.com/99designs/gqlgen/graphql.Time"}},
"Map": {Model: StringList{"github.com/99designs/gqlgen/graphql.Map"}},
"Upload": {Model: StringList{"github.com/99designs/gqlgen/graphql.Upload"}},
"Any": {Model: StringList{"github.com/99designs/gqlgen/graphql.Any"}},
}
for typeName, entry := range extraBuiltins {
if t, ok := s.Types[typeName]; !c.Models.Exists(typeName) && ok && t.Kind == ast.Scalar {
c.Models[typeName] = entry
}
}
}
func (c *Config) LoadSchema() (*ast.Schema, map[string]string, error) {
schemaStrings := map[string]string{}
sources := make([]*ast.Source, len(c.SchemaFilename))
for i, filename := range c.SchemaFilename {
filename = filepath.ToSlash(filename)
var err error
var schemaRaw []byte
schemaRaw, err = ioutil.ReadFile(filename)
if err != nil {
fmt.Fprintln(os.Stderr, "unable to open schema: "+err.Error())
os.Exit(1)
}
schemaStrings[filename] = string(schemaRaw)
sources[i] = &ast.Source{Name: filename, Input: schemaStrings[filename]}
}
schema, err := gqlparser.LoadSchema(sources...)
if err != nil {
return nil, nil, err
}
return schema, schemaStrings, nil
}
func abs(path string) string {
absPath, err := filepath.Abs(path)
if err != nil {
panic(err)
}
return filepath.ToSlash(absPath)
}

View File

@ -1,170 +0,0 @@
package codegen
import (
"fmt"
"sort"
"github.com/99designs/gqlgen/codegen/config"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
)
// Data is a unified model of the code to be generated. Plugins may modify this structure to do things like implement
// resolvers or directives automatically (eg grpc, validation)
type Data struct {
Config *config.Config
Schema *ast.Schema
SchemaStr map[string]string
Directives DirectiveList
Objects Objects
Inputs Objects
Interfaces map[string]*Interface
ReferencedTypes map[string]*config.TypeReference
ComplexityRoots map[string]*Object
QueryRoot *Object
MutationRoot *Object
SubscriptionRoot *Object
}
type builder struct {
Config *config.Config
Schema *ast.Schema
SchemaStr map[string]string
Binder *config.Binder
Directives map[string]*Directive
}
func BuildData(cfg *config.Config) (*Data, error) {
b := builder{
Config: cfg,
}
var err error
b.Schema, b.SchemaStr, err = cfg.LoadSchema()
if err != nil {
return nil, err
}
err = cfg.Check()
if err != nil {
return nil, err
}
err = cfg.Autobind(b.Schema)
if err != nil {
return nil, err
}
cfg.InjectBuiltins(b.Schema)
b.Binder, err = b.Config.NewBinder(b.Schema)
if err != nil {
return nil, err
}
b.Directives, err = b.buildDirectives()
if err != nil {
return nil, err
}
dataDirectives := make(map[string]*Directive)
for name, d := range b.Directives {
if !d.Builtin {
dataDirectives[name] = d
}
}
s := Data{
Config: cfg,
Directives: dataDirectives,
Schema: b.Schema,
SchemaStr: b.SchemaStr,
Interfaces: map[string]*Interface{},
}
for _, schemaType := range b.Schema.Types {
switch schemaType.Kind {
case ast.Object:
obj, err := b.buildObject(schemaType)
if err != nil {
return nil, errors.Wrap(err, "unable to build object definition")
}
s.Objects = append(s.Objects, obj)
case ast.InputObject:
input, err := b.buildObject(schemaType)
if err != nil {
return nil, errors.Wrap(err, "unable to build input definition")
}
s.Inputs = append(s.Inputs, input)
case ast.Union, ast.Interface:
s.Interfaces[schemaType.Name] = b.buildInterface(schemaType)
}
}
if s.Schema.Query != nil {
s.QueryRoot = s.Objects.ByName(s.Schema.Query.Name)
} else {
return nil, fmt.Errorf("query entry point missing")
}
if s.Schema.Mutation != nil {
s.MutationRoot = s.Objects.ByName(s.Schema.Mutation.Name)
}
if s.Schema.Subscription != nil {
s.SubscriptionRoot = s.Objects.ByName(s.Schema.Subscription.Name)
}
if err := b.injectIntrospectionRoots(&s); err != nil {
return nil, err
}
s.ReferencedTypes = b.buildTypes()
sort.Slice(s.Objects, func(i, j int) bool {
return s.Objects[i].Definition.Name < s.Objects[j].Definition.Name
})
sort.Slice(s.Inputs, func(i, j int) bool {
return s.Inputs[i].Definition.Name < s.Inputs[j].Definition.Name
})
return &s, nil
}
func (b *builder) injectIntrospectionRoots(s *Data) error {
obj := s.Objects.ByName(b.Schema.Query.Name)
if obj == nil {
return fmt.Errorf("root query type must be defined")
}
__type, err := b.buildField(obj, &ast.FieldDefinition{
Name: "__type",
Type: ast.NamedType("__Type", nil),
Arguments: []*ast.ArgumentDefinition{
{
Name: "name",
Type: ast.NonNullNamedType("String", nil),
},
},
})
if err != nil {
return err
}
__schema, err := b.buildField(obj, &ast.FieldDefinition{
Name: "__schema",
Type: ast.NamedType("__Schema", nil),
})
if err != nil {
return err
}
obj.Fields = append(obj.Fields, __type, __schema)
return nil
}

View File

@ -1,175 +0,0 @@
package codegen
import (
"fmt"
"strconv"
"strings"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
)
type DirectiveList map[string]*Directive
//LocationDirectives filter directives by location
func (dl DirectiveList) LocationDirectives(location string) DirectiveList {
return locationDirectives(dl, ast.DirectiveLocation(location))
}
type Directive struct {
*ast.DirectiveDefinition
Name string
Args []*FieldArgument
Builtin bool
}
//IsLocation check location directive
func (d *Directive) IsLocation(location ...ast.DirectiveLocation) bool {
for _, l := range d.Locations {
for _, a := range location {
if l == a {
return true
}
}
}
return false
}
func locationDirectives(directives DirectiveList, location ...ast.DirectiveLocation) map[string]*Directive {
mDirectives := make(map[string]*Directive)
for name, d := range directives {
if d.IsLocation(location...) {
mDirectives[name] = d
}
}
return mDirectives
}
func (b *builder) buildDirectives() (map[string]*Directive, error) {
directives := make(map[string]*Directive, len(b.Schema.Directives))
for name, dir := range b.Schema.Directives {
if _, ok := directives[name]; ok {
return nil, errors.Errorf("directive with name %s already exists", name)
}
var args []*FieldArgument
for _, arg := range dir.Arguments {
tr, err := b.Binder.TypeReference(arg.Type, nil)
if err != nil {
return nil, err
}
newArg := &FieldArgument{
ArgumentDefinition: arg,
TypeReference: tr,
VarName: templates.ToGoPrivate(arg.Name),
}
if arg.DefaultValue != nil {
var err error
newArg.Default, err = arg.DefaultValue.Value(nil)
if err != nil {
return nil, errors.Errorf("default value for directive argument %s(%s) is not valid: %s", dir.Name, arg.Name, err.Error())
}
}
args = append(args, newArg)
}
directives[name] = &Directive{
DirectiveDefinition: dir,
Name: name,
Args: args,
Builtin: b.Config.Directives[name].SkipRuntime,
}
}
return directives, nil
}
func (b *builder) getDirectives(list ast.DirectiveList) ([]*Directive, error) {
dirs := make([]*Directive, len(list))
for i, d := range list {
argValues := make(map[string]interface{}, len(d.Arguments))
for _, da := range d.Arguments {
val, err := da.Value.Value(nil)
if err != nil {
return nil, err
}
argValues[da.Name] = val
}
def, ok := b.Directives[d.Name]
if !ok {
return nil, fmt.Errorf("directive %s not found", d.Name)
}
var args []*FieldArgument
for _, a := range def.Args {
value := a.Default
if argValue, ok := argValues[a.Name]; ok {
value = argValue
}
args = append(args, &FieldArgument{
ArgumentDefinition: a.ArgumentDefinition,
Value: value,
VarName: a.VarName,
TypeReference: a.TypeReference,
})
}
dirs[i] = &Directive{
Name: d.Name,
Args: args,
DirectiveDefinition: list[i].Definition,
Builtin: b.Config.Directives[d.Name].SkipRuntime,
}
}
return dirs, nil
}
func (d *Directive) ArgsFunc() string {
if len(d.Args) == 0 {
return ""
}
return "dir_" + d.Name + "_args"
}
func (d *Directive) CallArgs() string {
args := []string{"ctx", "obj", "n"}
for _, arg := range d.Args {
args = append(args, "args["+strconv.Quote(arg.Name)+"].("+templates.CurrentImports.LookupType(arg.TypeReference.GO)+")")
}
return strings.Join(args, ", ")
}
func (d *Directive) ResolveArgs(obj string, next int) string {
args := []string{"ctx", obj, fmt.Sprintf("directive%d", next)}
for _, arg := range d.Args {
dArg := arg.VarName
if arg.Value == nil && arg.Default == nil {
dArg = "nil"
}
args = append(args, dArg)
}
return strings.Join(args, ", ")
}
func (d *Directive) Declaration() string {
res := ucFirst(d.Name) + " func(ctx context.Context, obj interface{}, next graphql.Resolver"
for _, arg := range d.Args {
res += fmt.Sprintf(", %s %s", arg.Name, templates.CurrentImports.LookupType(arg.TypeReference.GO))
}
res += ") (res interface{}, err error)"
return res
}

View File

@ -1,137 +0,0 @@
{{ define "implDirectives" }}{{ $in := .DirectiveObjName }}
{{- range $i, $directive := .ImplDirectives -}}
directive{{add $i 1}} := func(ctx context.Context) (interface{}, error) {
{{- range $arg := $directive.Args }}
{{- if notNil "Value" $arg }}
{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Value | dump }})
if err != nil{
return nil, err
}
{{- else if notNil "Default" $arg }}
{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Default | dump }})
if err != nil{
return nil, err
}
{{- end }}
{{- end }}
return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs $in $i }})
}
{{- end -}}
{{ end }}
{{define "queryDirectives"}}
for _, d := range obj.Directives {
switch d.Name {
{{- range $directive := . }}
case "{{$directive.Name}}":
{{- if $directive.Args }}
rawArgs := d.ArgumentMap(ec.Variables)
args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
{{- end }}
n := next
next = func(ctx context.Context) (interface{}, error) {
return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
}
{{- end }}
}
}
tmp, err := next(ctx)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if data, ok := tmp.(graphql.Marshaler); ok {
return data
}
ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp)
return graphql.Null
{{end}}
{{ if .Directives.LocationDirectives "QUERY" }}
func (ec *executionContext) _queryMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler {
{{ template "queryDirectives" .Directives.LocationDirectives "QUERY" }}
}
{{ end }}
{{ if .Directives.LocationDirectives "MUTATION" }}
func (ec *executionContext) _mutationMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler {
{{ template "queryDirectives" .Directives.LocationDirectives "MUTATION" }}
}
{{ end }}
{{ if .Directives.LocationDirectives "SUBSCRIPTION" }}
func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) func() graphql.Marshaler {
for _, d := range obj.Directives {
switch d.Name {
{{- range $directive := .Directives.LocationDirectives "SUBSCRIPTION" }}
case "{{$directive.Name}}":
{{- if $directive.Args }}
rawArgs := d.ArgumentMap(ec.Variables)
args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
if err != nil {
ec.Error(ctx, err)
return func() graphql.Marshaler {
return graphql.Null
}
}
{{- end }}
n := next
next = func(ctx context.Context) (interface{}, error) {
return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
}
{{- end }}
}
}
tmp, err := next(ctx)
if err != nil {
ec.Error(ctx, err)
return func() graphql.Marshaler {
return graphql.Null
}
}
if data, ok := tmp.(func() graphql.Marshaler); ok {
return data
}
ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp)
return func() graphql.Marshaler {
return graphql.Null
}
}
{{ end }}
{{ if .Directives.LocationDirectives "FIELD" }}
func (ec *executionContext) _fieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) interface{} {
{{- if .Directives.LocationDirectives "FIELD" }}
rctx := graphql.GetResolverContext(ctx)
for _, d := range rctx.Field.Directives {
switch d.Name {
{{- range $directive := .Directives.LocationDirectives "FIELD" }}
case "{{$directive.Name}}":
{{- if $directive.Args }}
rawArgs := d.ArgumentMap(ec.Variables)
args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
if err != nil {
ec.Error(ctx, err)
return nil
}
{{- end }}
n := next
next = func(ctx context.Context) (interface{}, error) {
return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
}
{{- end }}
}
}
{{- end }}
res, err := ec.ResolverMiddleware(ctx, next)
if err != nil {
ec.Error(ctx, err)
return nil
}
return res
}
{{ end }}

View File

@ -1,413 +0,0 @@
package codegen
import (
"fmt"
"go/types"
"log"
"reflect"
"strconv"
"strings"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
)
type Field struct {
*ast.FieldDefinition
TypeReference *config.TypeReference
GoFieldType GoFieldType // The field type in go, if any
GoReceiverName string // The name of method & var receiver in go, if any
GoFieldName string // The name of the method or var in go, if any
IsResolver bool // Does this field need a resolver
Args []*FieldArgument // A list of arguments to be passed to this field
MethodHasContext bool // If this is bound to a go method, does the method also take a context
NoErr bool // If this is bound to a go method, does that method have an error as the second argument
Object *Object // A link back to the parent object
Default interface{} // The default value
Directives []*Directive
}
func (b *builder) buildField(obj *Object, field *ast.FieldDefinition) (*Field, error) {
dirs, err := b.getDirectives(field.Directives)
if err != nil {
return nil, err
}
f := Field{
FieldDefinition: field,
Object: obj,
Directives: dirs,
GoFieldName: templates.ToGo(field.Name),
GoFieldType: GoFieldVariable,
GoReceiverName: "obj",
}
if field.DefaultValue != nil {
var err error
f.Default, err = field.DefaultValue.Value(nil)
if err != nil {
return nil, errors.Errorf("default value %s is not valid: %s", field.Name, err.Error())
}
}
for _, arg := range field.Arguments {
newArg, err := b.buildArg(obj, arg)
if err != nil {
return nil, err
}
f.Args = append(f.Args, newArg)
}
if err = b.bindField(obj, &f); err != nil {
f.IsResolver = true
log.Println(err.Error())
}
if f.IsResolver && !f.TypeReference.IsPtr() && f.TypeReference.IsStruct() {
f.TypeReference = b.Binder.PointerTo(f.TypeReference)
}
return &f, nil
}
func (b *builder) bindField(obj *Object, f *Field) error {
defer func() {
if f.TypeReference == nil {
tr, err := b.Binder.TypeReference(f.Type, nil)
if err != nil {
panic(err)
}
f.TypeReference = tr
}
}()
switch {
case f.Name == "__schema":
f.GoFieldType = GoFieldMethod
f.GoReceiverName = "ec"
f.GoFieldName = "introspectSchema"
return nil
case f.Name == "__type":
f.GoFieldType = GoFieldMethod
f.GoReceiverName = "ec"
f.GoFieldName = "introspectType"
return nil
case obj.Root:
f.IsResolver = true
return nil
case b.Config.Models[obj.Name].Fields[f.Name].Resolver:
f.IsResolver = true
return nil
case obj.Type == config.MapType:
f.GoFieldType = GoFieldMap
return nil
case b.Config.Models[obj.Name].Fields[f.Name].FieldName != "":
f.GoFieldName = b.Config.Models[obj.Name].Fields[f.Name].FieldName
}
target, err := b.findBindTarget(obj.Type.(*types.Named), f.GoFieldName)
if err != nil {
return err
}
pos := b.Binder.ObjectPosition(target)
switch target := target.(type) {
case nil:
objPos := b.Binder.TypePosition(obj.Type)
return fmt.Errorf(
"%s:%d adding resolver method for %s.%s, nothing matched",
objPos.Filename,
objPos.Line,
obj.Name,
f.Name,
)
case *types.Func:
sig := target.Type().(*types.Signature)
if sig.Results().Len() == 1 {
f.NoErr = true
} else if sig.Results().Len() != 2 {
return fmt.Errorf("method has wrong number of args")
}
params := sig.Params()
// If the first argument is the context, remove it from the comparison and set
// the MethodHasContext flag so that the context will be passed to this model's method
if params.Len() > 0 && params.At(0).Type().String() == "context.Context" {
f.MethodHasContext = true
vars := make([]*types.Var, params.Len()-1)
for i := 1; i < params.Len(); i++ {
vars[i-1] = params.At(i)
}
params = types.NewTuple(vars...)
}
if err = b.bindArgs(f, params); err != nil {
return errors.Wrapf(err, "%s:%d", pos.Filename, pos.Line)
}
result := sig.Results().At(0)
tr, err := b.Binder.TypeReference(f.Type, result.Type())
if err != nil {
return err
}
// success, args and return type match. Bind to method
f.GoFieldType = GoFieldMethod
f.GoReceiverName = "obj"
f.GoFieldName = target.Name()
f.TypeReference = tr
return nil
case *types.Var:
tr, err := b.Binder.TypeReference(f.Type, target.Type())
if err != nil {
return err
}
// success, bind to var
f.GoFieldType = GoFieldVariable
f.GoReceiverName = "obj"
f.GoFieldName = target.Name()
f.TypeReference = tr
return nil
default:
panic(fmt.Errorf("unknown bind target %T for %s", target, f.Name))
}
}
// findField attempts to match the name to a struct field with the following
// priorites:
// 1. Any method with a matching name
// 2. Any Fields with a struct tag (see config.StructTag)
// 3. Any fields with a matching name
// 4. Same logic again for embedded fields
func (b *builder) findBindTarget(named *types.Named, name string) (types.Object, error) {
for i := 0; i < named.NumMethods(); i++ {
method := named.Method(i)
if !method.Exported() {
continue
}
if !strings.EqualFold(method.Name(), name) {
continue
}
return method, nil
}
strukt, ok := named.Underlying().(*types.Struct)
if !ok {
return nil, fmt.Errorf("not a struct")
}
return b.findBindStructTarget(strukt, name)
}
func (b *builder) findBindStructTarget(strukt *types.Struct, name string) (types.Object, error) {
// struct tags have the highest priority
if b.Config.StructTag != "" {
var foundField *types.Var
for i := 0; i < strukt.NumFields(); i++ {
field := strukt.Field(i)
if !field.Exported() {
continue
}
tags := reflect.StructTag(strukt.Tag(i))
if val, ok := tags.Lookup(b.Config.StructTag); ok && equalFieldName(val, name) {
if foundField != nil {
return nil, errors.Errorf("tag %s is ambigious; multiple fields have the same tag value of %s", b.Config.StructTag, val)
}
foundField = field
}
}
if foundField != nil {
return foundField, nil
}
}
// Then matching field names
for i := 0; i < strukt.NumFields(); i++ {
field := strukt.Field(i)
if !field.Exported() {
continue
}
if equalFieldName(field.Name(), name) { // aqui!
return field, nil
}
}
// Then look in embedded structs
for i := 0; i < strukt.NumFields(); i++ {
field := strukt.Field(i)
if !field.Exported() {
continue
}
if !field.Anonymous() {
continue
}
fieldType := field.Type()
if ptr, ok := fieldType.(*types.Pointer); ok {
fieldType = ptr.Elem()
}
switch fieldType := fieldType.(type) {
case *types.Named:
f, err := b.findBindTarget(fieldType, name)
if err != nil {
return nil, err
}
if f != nil {
return f, nil
}
case *types.Struct:
f, err := b.findBindStructTarget(fieldType, name)
if err != nil {
return nil, err
}
if f != nil {
return f, nil
}
default:
panic(fmt.Errorf("unknown embedded field type %T", field.Type()))
}
}
return nil, nil
}
func (f *Field) HasDirectives() bool {
return len(f.ImplDirectives()) > 0
}
func (f *Field) DirectiveObjName() string {
if f.Object.Root {
return "nil"
}
return f.GoReceiverName
}
func (f *Field) ImplDirectives() []*Directive {
var d []*Directive
loc := ast.LocationFieldDefinition
if f.Object.IsInputType() {
loc = ast.LocationInputFieldDefinition
}
for i := range f.Directives {
if !f.Directives[i].Builtin && f.Directives[i].IsLocation(loc) {
d = append(d, f.Directives[i])
}
}
return d
}
func (f *Field) IsReserved() bool {
return strings.HasPrefix(f.Name, "__")
}
func (f *Field) IsMethod() bool {
return f.GoFieldType == GoFieldMethod
}
func (f *Field) IsVariable() bool {
return f.GoFieldType == GoFieldVariable
}
func (f *Field) IsMap() bool {
return f.GoFieldType == GoFieldMap
}
func (f *Field) IsConcurrent() bool {
if f.Object.DisableConcurrency {
return false
}
return f.MethodHasContext || f.IsResolver
}
func (f *Field) GoNameUnexported() string {
return templates.ToGoPrivate(f.Name)
}
func (f *Field) ShortInvocation() string {
return fmt.Sprintf("%s().%s(%s)", f.Object.Definition.Name, f.GoFieldName, f.CallArgs())
}
func (f *Field) ArgsFunc() string {
if len(f.Args) == 0 {
return ""
}
return "field_" + f.Object.Definition.Name + "_" + f.Name + "_args"
}
func (f *Field) ResolverType() string {
if !f.IsResolver {
return ""
}
return fmt.Sprintf("%s().%s(%s)", f.Object.Definition.Name, f.GoFieldName, f.CallArgs())
}
func (f *Field) ShortResolverDeclaration() string {
res := "(ctx context.Context"
if !f.Object.Root {
res += fmt.Sprintf(", obj *%s", templates.CurrentImports.LookupType(f.Object.Type))
}
for _, arg := range f.Args {
res += fmt.Sprintf(", %s %s", arg.VarName, templates.CurrentImports.LookupType(arg.TypeReference.GO))
}
result := templates.CurrentImports.LookupType(f.TypeReference.GO)
if f.Object.Stream {
result = "<-chan " + result
}
res += fmt.Sprintf(") (%s, error)", result)
return res
}
func (f *Field) ComplexitySignature() string {
res := fmt.Sprintf("func(childComplexity int")
for _, arg := range f.Args {
res += fmt.Sprintf(", %s %s", arg.VarName, templates.CurrentImports.LookupType(arg.TypeReference.GO))
}
res += ") int"
return res
}
func (f *Field) ComplexityArgs() string {
args := make([]string, len(f.Args))
for i, arg := range f.Args {
args[i] = "args[" + strconv.Quote(arg.Name) + "].(" + templates.CurrentImports.LookupType(arg.TypeReference.GO) + ")"
}
return strings.Join(args, ", ")
}
func (f *Field) CallArgs() string {
args := make([]string, 0, len(f.Args)+2)
if f.IsResolver {
args = append(args, "rctx")
if !f.Object.Root {
args = append(args, "obj")
}
} else if f.MethodHasContext {
args = append(args, "ctx")
}
for _, arg := range f.Args {
args = append(args, "args["+strconv.Quote(arg.Name)+"].("+templates.CurrentImports.LookupType(arg.TypeReference.GO)+")")
}
return strings.Join(args, ", ")
}

View File

@ -1,145 +0,0 @@
{{- range $object := .Objects }}{{- range $field := $object.Fields }}
{{- if $object.Stream }}
func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField) func() graphql.Marshaler {
ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
Field: field,
Args: nil,
})
{{- if $field.Args }}
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.{{ $field.ArgsFunc }}(ctx,rawArgs)
if err != nil {
ec.Error(ctx, err)
return nil
}
{{- end }}
// FIXME: subscriptions are missing request middleware stack https://github.com/99designs/gqlgen/issues/259
// and Tracer stack
rctx := ctx
results, err := ec.resolvers.{{ $field.ShortInvocation }}
if err != nil {
ec.Error(ctx, err)
return nil
}
return func() graphql.Marshaler {
res, ok := <-results
if !ok {
return nil
}
return graphql.WriterFunc(func(w io.Writer) {
w.Write([]byte{'{'})
graphql.MarshalString(field.Alias).MarshalGQL(w)
w.Write([]byte{':'})
ec.{{ $field.TypeReference.MarshalFunc }}(ctx, field.Selections, res).MarshalGQL(w)
w.Write([]byte{'}'})
})
}
}
{{ else }}
func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) (ret graphql.Marshaler) {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func () {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
ec.Tracer.EndFieldExecution(ctx)
}()
rctx := &graphql.ResolverContext{
Object: {{$object.Name|quote}},
Field: field,
Args: nil,
IsMethod: {{or $field.IsMethod $field.IsResolver}},
}
ctx = graphql.WithResolverContext(ctx, rctx)
{{- if $field.Args }}
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.{{ $field.ArgsFunc }}(ctx,rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
rctx.Args = args
{{- end }}
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
{{- if $.Directives.LocationDirectives "FIELD" }}
resTmp := ec._fieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) {
{{ template "field" $field }}
})
{{ else }}
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
{{ template "field" $field }}
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
{{- end }}
if resTmp == nil {
{{- if $field.TypeReference.GQL.NonNull }}
if !ec.HasError(rctx) {
ec.Errorf(ctx, "must not be null")
}
{{- end }}
return graphql.Null
}
res := resTmp.({{$field.TypeReference.GO | ref}})
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.{{ $field.TypeReference.MarshalFunc }}(ctx, field.Selections, res)
}
{{ end }}
{{- end }}{{- end}}
{{ define "field" }}
{{- if .HasDirectives -}}
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
{{ template "fieldDefinition" . }}
}
{{ template "implDirectives" . }}
tmp, err := directive{{.ImplDirectives|len}}(rctx)
if err != nil {
return nil, err
}
if data, ok := tmp.({{ .TypeReference.GO | ref }}) ; ok {
return data, nil
}
{{- if .TypeReference.IsNilable -}}
else if tmp == nil {
return nil, nil
}
{{- end }}
return nil, fmt.Errorf(`unexpected type %T from directive, should be {{ .TypeReference.GO }}`, tmp)
{{- else -}}
ctx = rctx // use context from middleware stack in children
{{ template "fieldDefinition" . }}
{{- end -}}
{{ end }}
{{ define "fieldDefinition" }}
{{- if .IsResolver -}}
return ec.resolvers.{{ .ShortInvocation }}
{{- else if .IsMap -}}
switch v := {{.GoReceiverName}}[{{.Name|quote}}].(type) {
case {{.TypeReference.GO | ref}}:
return v, nil
case {{.TypeReference.Elem.GO | ref}}:
return &v, nil
case nil:
return ({{.TypeReference.GO | ref}})(nil), nil
default:
return nil, fmt.Errorf("unexpected type %T for field %s", v, {{ .Name | quote}})
}
{{- else if .IsMethod -}}
{{- if .NoErr -}}
return {{.GoReceiverName}}.{{.GoFieldName}}({{ .CallArgs }}), nil
{{- else -}}
return {{.GoReceiverName}}.{{.GoFieldName}}({{ .CallArgs }})
{{- end -}}
{{- else if .IsVariable -}}
return {{.GoReceiverName}}.{{.GoFieldName}}, nil
{{- end }}
{{- end }}

View File

@ -1,15 +0,0 @@
package codegen
import (
"github.com/99designs/gqlgen/codegen/templates"
)
func GenerateCode(data *Data) error {
return templates.Render(templates.Options{
PackageName: data.Config.Exec.Package,
Filename: data.Config.Exec.Filename,
Data: data,
RegionTags: true,
GeneratedHeader: true,
})
}

View File

@ -1,235 +0,0 @@
{{ reserveImport "context" }}
{{ reserveImport "fmt" }}
{{ reserveImport "io" }}
{{ reserveImport "strconv" }}
{{ reserveImport "time" }}
{{ reserveImport "sync" }}
{{ reserveImport "sync/atomic" }}
{{ reserveImport "errors" }}
{{ reserveImport "bytes" }}
{{ reserveImport "github.com/vektah/gqlparser" }}
{{ reserveImport "github.com/vektah/gqlparser/ast" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }}
// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface.
func NewExecutableSchema(cfg Config) graphql.ExecutableSchema {
return &executableSchema{
resolvers: cfg.Resolvers,
directives: cfg.Directives,
complexity: cfg.Complexity,
}
}
type Config struct {
Resolvers ResolverRoot
Directives DirectiveRoot
Complexity ComplexityRoot
}
type ResolverRoot interface {
{{- range $object := .Objects -}}
{{ if $object.HasResolvers -}}
{{$object.Name}}() {{$object.Name}}Resolver
{{ end }}
{{- end }}
}
type DirectiveRoot struct {
{{ range $directive := .Directives }}
{{ $directive.Declaration }}
{{ end }}
}
type ComplexityRoot struct {
{{ range $object := .Objects }}
{{ if not $object.IsReserved -}}
{{ $object.Name|go }} struct {
{{ range $_, $fields := $object.UniqueFields }}
{{- $field := index $fields 0 -}}
{{ if not $field.IsReserved -}}
{{ $field.GoFieldName }} {{ $field.ComplexitySignature }}
{{ end }}
{{- end }}
}
{{- end }}
{{ end }}
}
{{ range $object := .Objects -}}
{{ if $object.HasResolvers }}
type {{$object.Name}}Resolver interface {
{{ range $field := $object.Fields -}}
{{- if $field.IsResolver }}
{{- $field.GoFieldName}}{{ $field.ShortResolverDeclaration }}
{{- end }}
{{ end }}
}
{{- end }}
{{- end }}
type executableSchema struct {
resolvers ResolverRoot
directives DirectiveRoot
complexity ComplexityRoot
}
func (e *executableSchema) Schema() *ast.Schema {
return parsedSchema
}
func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]interface{}) (int, bool) {
ec := executionContext{nil, e}
_ = ec
switch typeName + "." + field {
{{ range $object := .Objects }}
{{ if not $object.IsReserved }}
{{ range $_, $fields := $object.UniqueFields }}
{{- $len := len $fields }}
{{- range $i, $field := $fields }}
{{- $last := eq (add $i 1) $len }}
{{- if not $field.IsReserved }}
{{- if eq $i 0 }}case {{ end }}"{{$object.Name}}.{{$field.Name}}"{{ if not $last }},{{ else }}:
if e.complexity.{{$object.Name|go}}.{{$field.GoFieldName}} == nil {
break
}
{{ if $field.Args }}
args, err := ec.{{ $field.ArgsFunc }}(context.TODO(),rawArgs)
if err != nil {
return 0, false
}
{{ end }}
return e.complexity.{{$object.Name|go}}.{{$field.GoFieldName}}(childComplexity{{if $field.Args}}, {{$field.ComplexityArgs}} {{ end }}), true
{{ end }}
{{- end }}
{{- end }}
{{ end }}
{{ end }}
{{ end }}
}
return 0, false
}
func (e *executableSchema) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
{{- if .QueryRoot }}
ec := executionContext{graphql.GetRequestContext(ctx), e}
buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {
{{ if .Directives.LocationDirectives "QUERY" -}}
data := ec._queryMiddleware(ctx, op, func(ctx context.Context) (interface{}, error){
return ec._{{.QueryRoot.Name}}(ctx, op.SelectionSet), nil
})
{{- else -}}
data := ec._{{.QueryRoot.Name}}(ctx, op.SelectionSet)
{{- end }}
var buf bytes.Buffer
data.MarshalGQL(&buf)
return buf.Bytes()
})
return &graphql.Response{
Data: buf,
Errors: ec.Errors,
Extensions: ec.Extensions,
}
{{- else }}
return graphql.ErrorResponse(ctx, "queries are not supported")
{{- end }}
}
func (e *executableSchema) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
{{- if .MutationRoot }}
ec := executionContext{graphql.GetRequestContext(ctx), e}
buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {
{{ if .Directives.LocationDirectives "MUTATION" -}}
data := ec._mutationMiddleware(ctx, op, func(ctx context.Context) (interface{}, error){
return ec._{{.MutationRoot.Name}}(ctx, op.SelectionSet), nil
})
{{- else -}}
data := ec._{{.MutationRoot.Name}}(ctx, op.SelectionSet)
{{- end }}
var buf bytes.Buffer
data.MarshalGQL(&buf)
return buf.Bytes()
})
return &graphql.Response{
Data: buf,
Errors: ec.Errors,
Extensions: ec.Extensions,
}
{{- else }}
return graphql.ErrorResponse(ctx, "mutations are not supported")
{{- end }}
}
func (e *executableSchema) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response {
{{- if .SubscriptionRoot }}
ec := executionContext{graphql.GetRequestContext(ctx), e}
{{ if .Directives.LocationDirectives "SUBSCRIPTION" -}}
next := ec._subscriptionMiddleware(ctx, op, func(ctx context.Context) (interface{}, error){
return ec._{{.SubscriptionRoot.Name}}(ctx, op.SelectionSet),nil
})
{{- else -}}
next := ec._{{.SubscriptionRoot.Name}}(ctx, op.SelectionSet)
{{- end }}
if ec.Errors != nil {
return graphql.OneShot(&graphql.Response{Data: []byte("null"), Errors: ec.Errors})
}
var buf bytes.Buffer
return func() *graphql.Response {
buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {
buf.Reset()
data := next()
if data == nil {
return nil
}
data.MarshalGQL(&buf)
return buf.Bytes()
})
if buf == nil {
return nil
}
return &graphql.Response{
Data: buf,
Errors: ec.Errors,
Extensions: ec.Extensions,
}
}
{{- else }}
return graphql.OneShot(graphql.ErrorResponse(ctx, "subscriptions are not supported"))
{{- end }}
}
type executionContext struct {
*graphql.RequestContext
*executableSchema
}
func (ec *executionContext) introspectSchema() (*introspection.Schema, error) {
if ec.DisableIntrospection {
return nil, errors.New("introspection disabled")
}
return introspection.WrapSchema(parsedSchema), nil
}
func (ec *executionContext) introspectType(name string) (*introspection.Type, error) {
if ec.DisableIntrospection {
return nil, errors.New("introspection disabled")
}
return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil
}
var parsedSchema = gqlparser.MustLoadSchema(
{{- range $filename, $schema := .SchemaStr }}
&ast.Source{Name: {{$filename|quote}}, Input: {{$schema|rawQuote}}},
{{- end }}
)

View File

@ -1,48 +0,0 @@
{{- range $input := .Inputs }}
{{- if not .HasUnmarshal }}
func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{.Type | ref}}, error) {
var it {{.Type | ref}}
var asMap = obj.(map[string]interface{})
{{ range $field := .Fields}}
{{- if $field.Default}}
if _, present := asMap[{{$field.Name|quote}}] ; !present {
asMap[{{$field.Name|quote}}] = {{ $field.Default | dump }}
}
{{- end}}
{{- end }}
for k, v := range asMap {
switch k {
{{- range $field := .Fields }}
case {{$field.Name|quote}}:
var err error
{{- if $field.ImplDirectives }}
directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) }
{{ template "implDirectives" $field }}
tmp, err := directive{{$field.ImplDirectives|len}}(ctx)
if err != nil {
return it, err
}
if data, ok := tmp.({{ $field.TypeReference.GO | ref }}) ; ok {
it.{{$field.GoFieldName}} = data
{{- if $field.TypeReference.IsNilable }}
} else if tmp == nil {
it.{{$field.GoFieldName}} = nil
{{- end }}
} else {
return it, fmt.Errorf(`unexpected type %T from directive, should be {{ $field.TypeReference.GO }}`, tmp)
}
{{- else }}
it.{{$field.GoFieldName}}, err = ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v)
if err != nil {
return it, err
}
{{- end }}
{{- end }}
}
}
return it, nil
}
{{- end }}
{{ end }}

View File

@ -1,63 +0,0 @@
package codegen
import (
"go/types"
"github.com/vektah/gqlparser/ast"
)
type Interface struct {
*ast.Definition
Type types.Type
Implementors []InterfaceImplementor
InTypemap bool
}
type InterfaceImplementor struct {
*ast.Definition
Interface *Interface
Type types.Type
}
func (b *builder) buildInterface(typ *ast.Definition) *Interface {
obj, err := b.Binder.DefaultUserObject(typ.Name)
if err != nil {
panic(err)
}
i := &Interface{
Definition: typ,
Type: obj,
InTypemap: b.Config.Models.UserDefined(typ.Name),
}
for _, implementor := range b.Schema.GetPossibleTypes(typ) {
obj, err := b.Binder.DefaultUserObject(implementor.Name)
if err != nil {
panic(err)
}
i.Implementors = append(i.Implementors, InterfaceImplementor{
Definition: implementor,
Type: obj,
Interface: i,
})
}
return i
}
func (i *InterfaceImplementor) ValueReceiver() bool {
interfaceType, err := findGoInterface(i.Interface.Type)
if interfaceType == nil || err != nil {
return true
}
implementorType, err := findGoNamedType(i.Type)
if implementorType == nil || err != nil {
return true
}
return types.Implements(implementorType, interfaceType)
}

View File

@ -1,20 +0,0 @@
{{- range $interface := .Interfaces }}
func (ec *executionContext) _{{$interface.Name}}(ctx context.Context, sel ast.SelectionSet, obj *{{$interface.Type | ref}}) graphql.Marshaler {
switch obj := (*obj).(type) {
case nil:
return graphql.Null
{{- range $implementor := $interface.Implementors }}
{{- if $implementor.ValueReceiver }}
case {{$implementor.Type | ref}}:
return ec._{{$implementor.Name}}(ctx, sel, &obj)
{{- end}}
case *{{$implementor.Type | ref}}:
return ec._{{$implementor.Name}}(ctx, sel, obj)
{{- end }}
default:
panic(fmt.Errorf("unexpected type %T", obj))
}
}
{{- end }}

View File

@ -1,171 +0,0 @@
package codegen
import (
"go/types"
"strconv"
"strings"
"unicode"
"github.com/99designs/gqlgen/codegen/config"
"github.com/pkg/errors"
"github.com/vektah/gqlparser/ast"
)
type GoFieldType int
const (
GoFieldUndefined GoFieldType = iota
GoFieldMethod
GoFieldVariable
GoFieldMap
)
type Object struct {
*ast.Definition
Type types.Type
ResolverInterface types.Type
Root bool
Fields []*Field
Implements []*ast.Definition
DisableConcurrency bool
Stream bool
Directives []*Directive
}
func (b *builder) buildObject(typ *ast.Definition) (*Object, error) {
dirs, err := b.getDirectives(typ.Directives)
if err != nil {
return nil, errors.Wrap(err, typ.Name)
}
obj := &Object{
Definition: typ,
Root: b.Schema.Query == typ || b.Schema.Mutation == typ || b.Schema.Subscription == typ,
DisableConcurrency: typ == b.Schema.Mutation,
Stream: typ == b.Schema.Subscription,
Directives: dirs,
ResolverInterface: types.NewNamed(
types.NewTypeName(0, b.Config.Exec.Pkg(), typ.Name+"Resolver", nil),
nil,
nil,
),
}
if !obj.Root {
goObject, err := b.Binder.DefaultUserObject(typ.Name)
if err != nil {
return nil, err
}
obj.Type = goObject
}
for _, intf := range b.Schema.GetImplements(typ) {
obj.Implements = append(obj.Implements, b.Schema.Types[intf.Name])
}
for _, field := range typ.Fields {
if strings.HasPrefix(field.Name, "__") {
continue
}
var f *Field
f, err = b.buildField(obj, field)
if err != nil {
return nil, err
}
obj.Fields = append(obj.Fields, f)
}
return obj, nil
}
func (o *Object) Reference() types.Type {
switch o.Type.(type) {
case *types.Pointer, *types.Slice, *types.Map:
return o.Type
}
return types.NewPointer(o.Type)
}
type Objects []*Object
func (o *Object) Implementors() string {
satisfiedBy := strconv.Quote(o.Name)
for _, s := range o.Implements {
satisfiedBy += ", " + strconv.Quote(s.Name)
}
return "[]string{" + satisfiedBy + "}"
}
func (o *Object) HasResolvers() bool {
for _, f := range o.Fields {
if f.IsResolver {
return true
}
}
return false
}
func (o *Object) HasUnmarshal() bool {
if o.Type == config.MapType {
return true
}
for i := 0; i < o.Type.(*types.Named).NumMethods(); i++ {
if o.Type.(*types.Named).Method(i).Name() == "UnmarshalGQL" {
return true
}
}
return false
}
func (o *Object) HasDirectives() bool {
if len(o.Directives) > 0 {
return true
}
for _, f := range o.Fields {
if f.HasDirectives() {
return true
}
}
return false
}
func (o *Object) IsConcurrent() bool {
for _, f := range o.Fields {
if f.IsConcurrent() {
return true
}
}
return false
}
func (o *Object) IsReserved() bool {
return strings.HasPrefix(o.Definition.Name, "__")
}
func (o *Object) Description() string {
return o.Definition.Description
}
func (os Objects) ByName(name string) *Object {
for i, o := range os {
if strings.EqualFold(o.Definition.Name, name) {
return os[i]
}
}
return nil
}
func ucFirst(s string) string {
if s == "" {
return ""
}
r := []rune(s)
r[0] = unicode.ToUpper(r[0])
return string(r)
}

View File

@ -1,85 +0,0 @@
{{- range $object := .Objects }}
var {{ $object.Name|lcFirst}}Implementors = {{$object.Implementors}}
{{- if .Stream }}
func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet) func() graphql.Marshaler {
fields := graphql.CollectFields(ec.RequestContext, sel, {{$object.Name|lcFirst}}Implementors)
ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
Object: {{$object.Name|quote}},
})
if len(fields) != 1 {
ec.Errorf(ctx, "must subscribe to exactly one stream")
return nil
}
switch fields[0].Name {
{{- range $field := $object.Fields }}
case "{{$field.Name}}":
return ec._{{$object.Name}}_{{$field.Name}}(ctx, fields[0])
{{- end }}
default:
panic("unknown field " + strconv.Quote(fields[0].Name))
}
}
{{- else }}
func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet{{ if not $object.Root }},obj {{$object.Reference | ref }}{{ end }}) graphql.Marshaler {
fields := graphql.CollectFields(ec.RequestContext, sel, {{$object.Name|lcFirst}}Implementors)
{{if $object.Root}}
ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
Object: {{$object.Name|quote}},
})
{{end}}
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString({{$object.Name|quote}})
{{- range $field := $object.Fields }}
case "{{$field.Name}}":
{{- if $field.IsConcurrent }}
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._{{$object.Name}}_{{$field.Name}}(ctx, field{{if not $object.Root}}, obj{{end}})
{{- if $field.TypeReference.GQL.NonNull }}
if res == graphql.Null {
{{- if $object.IsConcurrent }}
atomic.AddUint32(&invalids, 1)
{{- else }}
invalids++
{{- end }}
}
{{- end }}
return res
})
{{- else }}
out.Values[i] = ec._{{$object.Name}}_{{$field.Name}}(ctx, field{{if not $object.Root}}, obj{{end}})
{{- if $field.TypeReference.GQL.NonNull }}
if out.Values[i] == graphql.Null {
{{- if $object.IsConcurrent }}
atomic.AddUint32(&invalids, 1)
{{- else }}
invalids++
{{- end }}
}
{{- end }}
{{- end }}
{{- end }}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 { return graphql.Null }
return out
}
{{- end }}
{{- end }}

View File

@ -1,138 +0,0 @@
package templates
import (
"fmt"
"go/types"
"strconv"
"strings"
"github.com/99designs/gqlgen/internal/code"
)
type Import struct {
Name string
Path string
Alias string
}
type Imports struct {
imports []*Import
destDir string
}
func (i *Import) String() string {
if strings.HasSuffix(i.Path, i.Alias) {
return strconv.Quote(i.Path)
}
return i.Alias + " " + strconv.Quote(i.Path)
}
func (s *Imports) String() string {
res := ""
for i, imp := range s.imports {
if i != 0 {
res += "\n"
}
res += imp.String()
}
return res
}
func (s *Imports) Reserve(path string, aliases ...string) (string, error) {
if path == "" {
panic("empty ambient import")
}
// if we are referencing our own package we dont need an import
if code.ImportPathForDir(s.destDir) == path {
return "", nil
}
name := code.NameForPackage(path)
var alias string
if len(aliases) != 1 {
alias = name
} else {
alias = aliases[0]
}
if existing := s.findByPath(path); existing != nil {
if existing.Alias == alias {
return "", nil
}
return "", fmt.Errorf("ambient import already exists")
}
if alias := s.findByAlias(alias); alias != nil {
return "", fmt.Errorf("ambient import collides on an alias")
}
s.imports = append(s.imports, &Import{
Name: name,
Path: path,
Alias: alias,
})
return "", nil
}
func (s *Imports) Lookup(path string) string {
if path == "" {
return ""
}
path = code.NormalizeVendor(path)
// if we are referencing our own package we dont need an import
if code.ImportPathForDir(s.destDir) == path {
return ""
}
if existing := s.findByPath(path); existing != nil {
return existing.Alias
}
imp := &Import{
Name: code.NameForPackage(path),
Path: path,
}
s.imports = append(s.imports, imp)
alias := imp.Name
i := 1
for s.findByAlias(alias) != nil {
alias = imp.Name + strconv.Itoa(i)
i++
if i > 10 {
panic(fmt.Errorf("too many collisions, last attempt was %s", alias))
}
}
imp.Alias = alias
return imp.Alias
}
func (s *Imports) LookupType(t types.Type) string {
return types.TypeString(t, func(i *types.Package) string {
return s.Lookup(i.Path())
})
}
func (s Imports) findByPath(importPath string) *Import {
for _, imp := range s.imports {
if imp.Path == importPath {
return imp
}
}
return nil
}
func (s Imports) findByAlias(alias string) *Import {
for _, imp := range s.imports {
if imp.Alias == alias {
return imp
}
}
return nil
}

View File

@ -1,572 +0,0 @@
package templates
import (
"bytes"
"fmt"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"text/template"
"unicode"
"github.com/99designs/gqlgen/internal/imports"
"github.com/pkg/errors"
)
// CurrentImports keeps track of all the import declarations that are needed during the execution of a plugin.
// this is done with a global because subtemplates currently get called in functions. Lets aim to remove this eventually.
var CurrentImports *Imports
// Options specify various parameters to rendering a template.
type Options struct {
// PackageName is a helper that specifies the package header declaration.
// In other words, when you write the template you don't need to specify `package X`
// at the top of the file. By providing PackageName in the Options, the Render
// function will do that for you.
PackageName string
// Template is a string of the entire template that
// will be parsed and rendered. If it's empty,
// the plugin processor will look for .gotpl files
// in the same directory of where you wrote the plugin.
Template string
// Filename is the name of the file that will be
// written to the system disk once the template is rendered.
Filename string
RegionTags bool
GeneratedHeader bool
// Data will be passed to the template execution.
Data interface{}
Funcs template.FuncMap
}
// Render renders a gql plugin template from the given Options. Render is an
// abstraction of the text/template package that makes it easier to write gqlgen
// plugins. If Options.Template is empty, the Render function will look for `.gotpl`
// files inside the directory where you wrote the plugin.
func Render(cfg Options) error {
if CurrentImports != nil {
panic(fmt.Errorf("recursive or concurrent call to RenderToFile detected"))
}
CurrentImports = &Imports{destDir: filepath.Dir(cfg.Filename)}
// load path relative to calling source file
_, callerFile, _, _ := runtime.Caller(1)
rootDir := filepath.Dir(callerFile)
funcs := Funcs()
for n, f := range cfg.Funcs {
funcs[n] = f
}
t := template.New("").Funcs(funcs)
var roots []string
if cfg.Template != "" {
var err error
t, err = t.New("template.gotpl").Parse(cfg.Template)
if err != nil {
return errors.Wrap(err, "error with provided template")
}
roots = append(roots, "template.gotpl")
} else {
// load all the templates in the directory
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
name := filepath.ToSlash(strings.TrimPrefix(path, rootDir+string(os.PathSeparator)))
if !strings.HasSuffix(info.Name(), ".gotpl") {
return nil
}
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
t, err = t.New(name).Parse(string(b))
if err != nil {
return errors.Wrap(err, cfg.Filename)
}
roots = append(roots, name)
return nil
})
if err != nil {
return errors.Wrap(err, "locating templates")
}
}
// then execute all the important looking ones in order, adding them to the same file
sort.Slice(roots, func(i, j int) bool {
// important files go first
if strings.HasSuffix(roots[i], "!.gotpl") {
return true
}
if strings.HasSuffix(roots[j], "!.gotpl") {
return false
}
return roots[i] < roots[j]
})
var buf bytes.Buffer
for _, root := range roots {
if cfg.RegionTags {
buf.WriteString("\n// region " + center(70, "*", " "+root+" ") + "\n")
}
err := t.Lookup(root).Execute(&buf, cfg.Data)
if err != nil {
return errors.Wrap(err, root)
}
if cfg.RegionTags {
buf.WriteString("\n// endregion " + center(70, "*", " "+root+" ") + "\n")
}
}
var result bytes.Buffer
if cfg.GeneratedHeader {
result.WriteString("// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.\n\n")
}
result.WriteString("package ")
result.WriteString(cfg.PackageName)
result.WriteString("\n\n")
result.WriteString("import (\n")
result.WriteString(CurrentImports.String())
result.WriteString(")\n")
_, err := buf.WriteTo(&result)
if err != nil {
return err
}
CurrentImports = nil
return write(cfg.Filename, result.Bytes())
}
func center(width int, pad string, s string) string {
if len(s)+2 > width {
return s
}
lpad := (width - len(s)) / 2
rpad := width - (lpad + len(s))
return strings.Repeat(pad, lpad) + s + strings.Repeat(pad, rpad)
}
func Funcs() template.FuncMap {
return template.FuncMap{
"ucFirst": ucFirst,
"lcFirst": lcFirst,
"quote": strconv.Quote,
"rawQuote": rawQuote,
"dump": Dump,
"ref": ref,
"ts": TypeIdentifier,
"call": Call,
"prefixLines": prefixLines,
"notNil": notNil,
"reserveImport": CurrentImports.Reserve,
"lookupImport": CurrentImports.Lookup,
"go": ToGo,
"goPrivate": ToGoPrivate,
"add": func(a, b int) int {
return a + b
},
"render": func(filename string, tpldata interface{}) (*bytes.Buffer, error) {
return render(resolveName(filename, 0), tpldata)
},
}
}
func ucFirst(s string) string {
if s == "" {
return ""
}
r := []rune(s)
r[0] = unicode.ToUpper(r[0])
return string(r)
}
func lcFirst(s string) string {
if s == "" {
return ""
}
r := []rune(s)
r[0] = unicode.ToLower(r[0])
return string(r)
}
func isDelimiter(c rune) bool {
return c == '-' || c == '_' || unicode.IsSpace(c)
}
func ref(p types.Type) string {
return CurrentImports.LookupType(p)
}
var pkgReplacer = strings.NewReplacer(
"/", "ᚋ",
".", "ᚗ",
"-", "ᚑ",
)
func TypeIdentifier(t types.Type) string {
res := ""
for {
switch it := t.(type) {
case *types.Pointer:
t.Underlying()
res += "ᚖ"
t = it.Elem()
case *types.Slice:
res += "ᚕ"
t = it.Elem()
case *types.Named:
res += pkgReplacer.Replace(it.Obj().Pkg().Path())
res += "ᚐ"
res += it.Obj().Name()
return res
case *types.Basic:
res += it.Name()
return res
case *types.Map:
res += "map"
return res
case *types.Interface:
res += "interface"
return res
default:
panic(fmt.Errorf("unexpected type %T", it))
}
}
}
func Call(p *types.Func) string {
pkg := CurrentImports.Lookup(p.Pkg().Path())
if pkg != "" {
pkg += "."
}
if p.Type() != nil {
// make sure the returned type is listed in our imports.
ref(p.Type().(*types.Signature).Results().At(0).Type())
}
return pkg + p.Name()
}
func ToGo(name string) string {
runes := make([]rune, 0, len(name))
wordWalker(name, func(info *wordInfo) {
word := info.Word
if info.MatchCommonInitial {
word = strings.ToUpper(word)
} else if !info.HasCommonInitial {
if strings.ToUpper(word) == word || strings.ToLower(word) == word {
// FOO or foo → Foo
// FOo → FOo
word = ucFirst(strings.ToLower(word))
}
}
runes = append(runes, []rune(word)...)
})
return string(runes)
}
func ToGoPrivate(name string) string {
runes := make([]rune, 0, len(name))
first := true
wordWalker(name, func(info *wordInfo) {
word := info.Word
switch {
case first:
if strings.ToUpper(word) == word || strings.ToLower(word) == word {
// ID → id, CAMEL → camel
word = strings.ToLower(info.Word)
} else {
// ITicket → iTicket
word = lcFirst(info.Word)
}
first = false
case info.MatchCommonInitial:
word = strings.ToUpper(word)
case !info.HasCommonInitial:
word = ucFirst(strings.ToLower(word))
}
runes = append(runes, []rune(word)...)
})
return sanitizeKeywords(string(runes))
}
type wordInfo struct {
Word string
MatchCommonInitial bool
HasCommonInitial bool
}
// This function is based on the following code.
// https://github.com/golang/lint/blob/06c8688daad7faa9da5a0c2f163a3d14aac986ca/lint.go#L679
func wordWalker(str string, f func(*wordInfo)) {
runes := []rune(str)
w, i := 0, 0 // index of start of word, scan
hasCommonInitial := false
for i+1 <= len(runes) {
eow := false // whether we hit the end of a word
switch {
case i+1 == len(runes):
eow = true
case isDelimiter(runes[i+1]):
// underscore; shift the remainder forward over any run of underscores
eow = true
n := 1
for i+n+1 < len(runes) && isDelimiter(runes[i+n+1]) {
n++
}
// Leave at most one underscore if the underscore is between two digits
if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) {
n--
}
copy(runes[i+1:], runes[i+n+1:])
runes = runes[:len(runes)-n]
case unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]):
// lower->non-lower
eow = true
}
i++
// [w,i) is a word.
word := string(runes[w:i])
if !eow && commonInitialisms[word] && !unicode.IsLower(runes[i]) {
// through
// split IDFoo → ID, Foo
// but URLs → URLs
} else if !eow {
if commonInitialisms[word] {
hasCommonInitial = true
}
continue
}
matchCommonInitial := false
if commonInitialisms[strings.ToUpper(word)] {
hasCommonInitial = true
matchCommonInitial = true
}
f(&wordInfo{
Word: word,
MatchCommonInitial: matchCommonInitial,
HasCommonInitial: hasCommonInitial,
})
hasCommonInitial = false
w = i
}
}
var keywords = []string{
"break",
"default",
"func",
"interface",
"select",
"case",
"defer",
"go",
"map",
"struct",
"chan",
"else",
"goto",
"package",
"switch",
"const",
"fallthrough",
"if",
"range",
"type",
"continue",
"for",
"import",
"return",
"var",
"_",
}
// sanitizeKeywords prevents collisions with go keywords for arguments to resolver functions
func sanitizeKeywords(name string) string {
for _, k := range keywords {
if name == k {
return name + "Arg"
}
}
return name
}
// commonInitialisms is a set of common initialisms.
// Only add entries that are highly unlikely to be non-initialisms.
// For instance, "ID" is fine (Freudian code is rare), but "AND" is not.
var commonInitialisms = map[string]bool{
"ACL": true,
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"DNS": true,
"EOF": true,
"GUID": true,
"HTML": true,
"HTTP": true,
"HTTPS": true,
"ID": true,
"IP": true,
"JSON": true,
"LHS": true,
"QPS": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SLA": true,
"SMTP": true,
"SQL": true,
"SSH": true,
"TCP": true,
"TLS": true,
"TTL": true,
"UDP": true,
"UI": true,
"UID": true,
"UUID": true,
"URI": true,
"URL": true,
"UTF8": true,
"VM": true,
"XML": true,
"XMPP": true,
"XSRF": true,
"XSS": true,
}
func rawQuote(s string) string {
return "`" + strings.Replace(s, "`", "`+\"`\"+`", -1) + "`"
}
func notNil(field string, data interface{}) bool {
v := reflect.ValueOf(data)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return false
}
val := v.FieldByName(field)
return val.IsValid() && !val.IsNil()
}
func Dump(val interface{}) string {
switch val := val.(type) {
case int:
return strconv.Itoa(val)
case int64:
return fmt.Sprintf("%d", val)
case float64:
return fmt.Sprintf("%f", val)
case string:
return strconv.Quote(val)
case bool:
return strconv.FormatBool(val)
case nil:
return "nil"
case []interface{}:
var parts []string
for _, part := range val {
parts = append(parts, Dump(part))
}
return "[]interface{}{" + strings.Join(parts, ",") + "}"
case map[string]interface{}:
buf := bytes.Buffer{}
buf.WriteString("map[string]interface{}{")
var keys []string
for key := range val {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
data := val[key]
buf.WriteString(strconv.Quote(key))
buf.WriteString(":")
buf.WriteString(Dump(data))
buf.WriteString(",")
}
buf.WriteString("}")
return buf.String()
default:
panic(fmt.Errorf("unsupported type %T", val))
}
}
func prefixLines(prefix, s string) string {
return prefix + strings.Replace(s, "\n", "\n"+prefix, -1)
}
func resolveName(name string, skip int) string {
if name[0] == '.' {
// load path relative to calling source file
_, callerFile, _, _ := runtime.Caller(skip + 1)
return filepath.Join(filepath.Dir(callerFile), name[1:])
}
// load path relative to this directory
_, callerFile, _, _ := runtime.Caller(0)
return filepath.Join(filepath.Dir(callerFile), name)
}
func render(filename string, tpldata interface{}) (*bytes.Buffer, error) {
t := template.New("").Funcs(Funcs())
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
t, err = t.New(filepath.Base(filename)).Parse(string(b))
if err != nil {
panic(err)
}
buf := &bytes.Buffer{}
return buf, t.Execute(buf, tpldata)
}
func write(filename string, b []byte) error {
err := os.MkdirAll(filepath.Dir(filename), 0755)
if err != nil {
return errors.Wrap(err, "failed to create directory")
}
formatted, err := imports.Prune(filename, b)
if err != nil {
fmt.Fprintf(os.Stderr, "gofmt failed on %s: %s\n", filepath.Base(filename), err.Error())
formatted = b
}
err = ioutil.WriteFile(filename, formatted, 0644)
if err != nil {
return errors.Wrapf(err, "failed to write %s", filename)
}
return nil
}

View File

@ -1,18 +0,0 @@
package codegen
import (
"github.com/99designs/gqlgen/codegen/config"
)
func (b *builder) buildTypes() map[string]*config.TypeReference {
ret := map[string]*config.TypeReference{}
for _, ref := range b.Binder.References {
for ref != nil {
ret[ref.UniquenessKey()] = ref
ref = ref.Elem()
}
}
return ret
}

View File

@ -1,130 +0,0 @@
{{- range $type := .ReferencedTypes }}
{{ with $type.UnmarshalFunc }}
func (ec *executionContext) {{ . }}(ctx context.Context, v interface{}) ({{ $type.GO | ref }}, error) {
{{- if $type.IsNilable }}
if v == nil { return nil, nil }
{{- end }}
{{- if $type.IsPtr }}
res, err := ec.{{ $type.Elem.UnmarshalFunc }}(ctx, v)
return &res, err
{{- else if $type.IsSlice }}
var vSlice []interface{}
if v != nil {
if tmp1, ok := v.([]interface{}); ok {
vSlice = tmp1
} else {
vSlice = []interface{}{ v }
}
}
var err error
res := make([]{{$type.GO.Elem | ref}}, len(vSlice))
for i := range vSlice {
res[i], err = ec.{{ $type.Elem.UnmarshalFunc }}(ctx, vSlice[i])
if err != nil {
return nil, err
}
}
return res, nil
{{- else }}
{{- if $type.Unmarshaler }}
{{- if $type.CastType }}
tmp, err := {{ $type.Unmarshaler | call }}(v)
return {{ $type.GO | ref }}(tmp), err
{{- else}}
return {{ $type.Unmarshaler | call }}(v)
{{- end }}
{{- else if eq ($type.GO | ref) "map[string]interface{}" }}
return v.(map[string]interface{}), nil
{{- else if $type.IsMarshaler -}}
var res {{ $type.GO | ref }}
return res, res.UnmarshalGQL(v)
{{- else }}
return ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v)
{{- end }}
{{- end }}
}
{{- end }}
{{ with $type.MarshalFunc }}
func (ec *executionContext) {{ . }}(ctx context.Context, sel ast.SelectionSet, v {{ $type.GO | ref }}) graphql.Marshaler {
{{- if $type.IsNilable }}
if v == nil {
{{- if $type.GQL.NonNull }}
if !ec.HasError(graphql.GetResolverContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
{{- end }}
return graphql.Null
}
{{- end }}
{{- if $type.IsSlice }}
{{- if not $type.GQL.NonNull }}
if v == nil {
return graphql.Null
}
{{- end }}
ret := make(graphql.Array, len(v))
{{- if not $type.IsScalar }}
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
{{- end }}
for i := range v {
{{- if not $type.IsScalar }}
i := i
rctx := &graphql.ResolverContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithResolverContext(ctx, rctx)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
{{ else }}
ret[i] = ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, v[i])
{{- end}}
}
{{ if not $type.IsScalar }} wg.Wait() {{ end }}
return ret
{{- else }}
{{- if $type.IsMarshaler }}
return v
{{- else if $type.Marshaler }}
{{- if $type.IsPtr }}
return ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, *v)
{{- else if $type.GQL.NonNull }}
res := {{ $type.Marshaler | call }}({{- if $type.CastType }}{{ $type.CastType | ref }}(v){{else}}v{{- end }})
if res == graphql.Null {
if !ec.HasError(graphql.GetResolverContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
}
return res
{{- else }}
return {{ $type.Marshaler | call }}({{- if $type.CastType }}{{ $type.CastType | ref }}(v){{else}}v{{- end }})
{{- end }}
{{- else }}
return ec._{{$type.Definition.Name}}(ctx, sel, {{ if not $type.IsNilable}}&{{end}} v)
{{- end }}
{{- end }}
}
{{- end }}
{{- end }}

View File

@ -1,47 +0,0 @@
package codegen
import (
"go/types"
"strings"
"github.com/pkg/errors"
)
func findGoNamedType(def types.Type) (*types.Named, error) {
if def == nil {
return nil, nil
}
namedType, ok := def.(*types.Named)
if !ok {
return nil, errors.Errorf("expected %s to be a named type, instead found %T\n", def.String(), def)
}
return namedType, nil
}
func findGoInterface(def types.Type) (*types.Interface, error) {
if def == nil {
return nil, nil
}
namedType, err := findGoNamedType(def)
if err != nil {
return nil, err
}
if namedType == nil {
return nil, nil
}
underlying, ok := namedType.Underlying().(*types.Interface)
if !ok {
return nil, errors.Errorf("expected %s to be a named interface, instead found %s", def.String(), namedType.String())
}
return underlying, nil
}
func equalFieldName(source, target string) bool {
source = strings.Replace(source, "_", "", -1)
target = strings.Replace(target, "_", "", -1)
return strings.EqualFold(source, target)
}

View File

@ -1,104 +0,0 @@
package complexity
import (
"github.com/99designs/gqlgen/graphql"
"github.com/vektah/gqlparser/ast"
)
func Calculate(es graphql.ExecutableSchema, op *ast.OperationDefinition, vars map[string]interface{}) int {
walker := complexityWalker{
es: es,
schema: es.Schema(),
vars: vars,
}
return walker.selectionSetComplexity(op.SelectionSet)
}
type complexityWalker struct {
es graphql.ExecutableSchema
schema *ast.Schema
vars map[string]interface{}
}
func (cw complexityWalker) selectionSetComplexity(selectionSet ast.SelectionSet) int {
var complexity int
for _, selection := range selectionSet {
switch s := selection.(type) {
case *ast.Field:
fieldDefinition := cw.schema.Types[s.Definition.Type.Name()]
var childComplexity int
switch fieldDefinition.Kind {
case ast.Object, ast.Interface, ast.Union:
childComplexity = cw.selectionSetComplexity(s.SelectionSet)
}
args := s.ArgumentMap(cw.vars)
var fieldComplexity int
if s.ObjectDefinition.Kind == ast.Interface {
fieldComplexity = cw.interfaceFieldComplexity(s.ObjectDefinition, s.Name, childComplexity, args)
} else {
fieldComplexity = cw.fieldComplexity(s.ObjectDefinition.Name, s.Name, childComplexity, args)
}
complexity = safeAdd(complexity, fieldComplexity)
case *ast.FragmentSpread:
complexity = safeAdd(complexity, cw.selectionSetComplexity(s.Definition.SelectionSet))
case *ast.InlineFragment:
complexity = safeAdd(complexity, cw.selectionSetComplexity(s.SelectionSet))
}
}
return complexity
}
func (cw complexityWalker) interfaceFieldComplexity(def *ast.Definition, field string, childComplexity int, args map[string]interface{}) int {
// Interfaces don't have their own separate field costs, so they have to assume the worst case.
// We iterate over all implementors and choose the most expensive one.
maxComplexity := 0
implementors := cw.schema.GetPossibleTypes(def)
for _, t := range implementors {
fieldComplexity := cw.fieldComplexity(t.Name, field, childComplexity, args)
if fieldComplexity > maxComplexity {
maxComplexity = fieldComplexity
}
}
return maxComplexity
}
func (cw complexityWalker) fieldComplexity(object, field string, childComplexity int, args map[string]interface{}) int {
if customComplexity, ok := cw.es.Complexity(object, field, childComplexity, args); ok && customComplexity >= childComplexity {
return customComplexity
}
// default complexity calculation
return safeAdd(1, childComplexity)
}
const maxInt = int(^uint(0) >> 1)
// safeAdd is a saturating add of a and b that ignores negative operands.
// If a + b would overflow through normal Go addition,
// it returns the maximum integer value instead.
//
// Adding complexities with this function prevents attackers from intentionally
// overflowing the complexity calculation to allow overly-complex queries.
//
// It also helps mitigate the impact of custom complexities that accidentally
// return negative values.
func safeAdd(a, b int) int {
// Ignore negative operands.
if a < 0 {
if b < 0 {
return 1
}
return b
} else if b < 0 {
return a
}
c := a + b
if c < a {
// Set c to maximum integer instead of overflowing.
c = maxInt
}
return c
}

View File

@ -1 +0,0 @@
../../README.md

View File

@ -1,19 +0,0 @@
package graphql
import (
"encoding/json"
"io"
)
func MarshalAny(v interface{}) Marshaler {
return WriterFunc(func(w io.Writer) {
err := json.NewEncoder(w).Encode(v)
if err != nil {
panic(err)
}
})
}
func UnmarshalAny(v interface{}) (interface{}, error) {
return v, nil
}

View File

@ -1,30 +0,0 @@
package graphql
import (
"fmt"
"io"
"strings"
)
func MarshalBoolean(b bool) Marshaler {
return WriterFunc(func(w io.Writer) {
if b {
w.Write(trueLit)
} else {
w.Write(falseLit)
}
})
}
func UnmarshalBoolean(v interface{}) (bool, error) {
switch v := v.(type) {
case string:
return strings.ToLower(v) == "true", nil
case int:
return v != 0, nil
case bool:
return v, nil
default:
return false, fmt.Errorf("%T is not a bool", v)
}
}

View File

@ -1,274 +0,0 @@
package graphql
import (
"context"
"fmt"
"sync"
"github.com/vektah/gqlparser/ast"
"github.com/vektah/gqlparser/gqlerror"
)
type Resolver func(ctx context.Context) (res interface{}, err error)
type FieldMiddleware func(ctx context.Context, next Resolver) (res interface{}, err error)
type RequestMiddleware func(ctx context.Context, next func(ctx context.Context) []byte) []byte
type ComplexityLimitFunc func(ctx context.Context) int
type RequestContext struct {
RawQuery string
Variables map[string]interface{}
Doc *ast.QueryDocument
ComplexityLimit int
OperationComplexity int
DisableIntrospection bool
// ErrorPresenter will be used to generate the error
// message from errors given to Error().
ErrorPresenter ErrorPresenterFunc
Recover RecoverFunc
ResolverMiddleware FieldMiddleware
DirectiveMiddleware FieldMiddleware
RequestMiddleware RequestMiddleware
Tracer Tracer
errorsMu sync.Mutex
Errors gqlerror.List
extensionsMu sync.Mutex
Extensions map[string]interface{}
}
func DefaultResolverMiddleware(ctx context.Context, next Resolver) (res interface{}, err error) {
return next(ctx)
}
func DefaultDirectiveMiddleware(ctx context.Context, next Resolver) (res interface{}, err error) {
return next(ctx)
}
func DefaultRequestMiddleware(ctx context.Context, next func(ctx context.Context) []byte) []byte {
return next(ctx)
}
func NewRequestContext(doc *ast.QueryDocument, query string, variables map[string]interface{}) *RequestContext {
return &RequestContext{
Doc: doc,
RawQuery: query,
Variables: variables,
ResolverMiddleware: DefaultResolverMiddleware,
DirectiveMiddleware: DefaultDirectiveMiddleware,
RequestMiddleware: DefaultRequestMiddleware,
Recover: DefaultRecover,
ErrorPresenter: DefaultErrorPresenter,
Tracer: &NopTracer{},
}
}
type key string
const (
request key = "request_context"
resolver key = "resolver_context"
)
func GetRequestContext(ctx context.Context) *RequestContext {
if val, ok := ctx.Value(request).(*RequestContext); ok {
return val
}
return nil
}
func WithRequestContext(ctx context.Context, rc *RequestContext) context.Context {
return context.WithValue(ctx, request, rc)
}
type ResolverContext struct {
Parent *ResolverContext
// The name of the type this field belongs to
Object string
// These are the args after processing, they can be mutated in middleware to change what the resolver will get.
Args map[string]interface{}
// The raw field
Field CollectedField
// The index of array in path.
Index *int
// The result object of resolver
Result interface{}
// IsMethod indicates if the resolver is a method
IsMethod bool
}
func (r *ResolverContext) Path() []interface{} {
var path []interface{}
for it := r; it != nil; it = it.Parent {
if it.Index != nil {
path = append(path, *it.Index)
} else if it.Field.Field != nil {
path = append(path, it.Field.Alias)
}
}
// because we are walking up the chain, all the elements are backwards, do an inplace flip.
for i := len(path)/2 - 1; i >= 0; i-- {
opp := len(path) - 1 - i
path[i], path[opp] = path[opp], path[i]
}
return path
}
func GetResolverContext(ctx context.Context) *ResolverContext {
if val, ok := ctx.Value(resolver).(*ResolverContext); ok {
return val
}
return nil
}
func WithResolverContext(ctx context.Context, rc *ResolverContext) context.Context {
rc.Parent = GetResolverContext(ctx)
return context.WithValue(ctx, resolver, rc)
}
// This is just a convenient wrapper method for CollectFields
func CollectFieldsCtx(ctx context.Context, satisfies []string) []CollectedField {
resctx := GetResolverContext(ctx)
return CollectFields(GetRequestContext(ctx), resctx.Field.Selections, satisfies)
}
// CollectAllFields returns a slice of all GraphQL field names that were selected for the current resolver context.
// The slice will contain the unique set of all field names requested regardless of fragment type conditions.
func CollectAllFields(ctx context.Context) []string {
resctx := GetResolverContext(ctx)
collected := CollectFields(GetRequestContext(ctx), resctx.Field.Selections, nil)
uniq := make([]string, 0, len(collected))
Next:
for _, f := range collected {
for _, name := range uniq {
if name == f.Name {
continue Next
}
}
uniq = append(uniq, f.Name)
}
return uniq
}
// Errorf sends an error string to the client, passing it through the formatter.
func (c *RequestContext) Errorf(ctx context.Context, format string, args ...interface{}) {
c.errorsMu.Lock()
defer c.errorsMu.Unlock()
c.Errors = append(c.Errors, c.ErrorPresenter(ctx, fmt.Errorf(format, args...)))
}
// Error sends an error to the client, passing it through the formatter.
func (c *RequestContext) Error(ctx context.Context, err error) {
c.errorsMu.Lock()
defer c.errorsMu.Unlock()
c.Errors = append(c.Errors, c.ErrorPresenter(ctx, err))
}
// HasError returns true if the current field has already errored
func (c *RequestContext) HasError(rctx *ResolverContext) bool {
c.errorsMu.Lock()
defer c.errorsMu.Unlock()
path := rctx.Path()
for _, err := range c.Errors {
if equalPath(err.Path, path) {
return true
}
}
return false
}
// GetErrors returns a list of errors that occurred in the current field
func (c *RequestContext) GetErrors(rctx *ResolverContext) gqlerror.List {
c.errorsMu.Lock()
defer c.errorsMu.Unlock()
path := rctx.Path()
var errs gqlerror.List
for _, err := range c.Errors {
if equalPath(err.Path, path) {
errs = append(errs, err)
}
}
return errs
}
func equalPath(a []interface{}, b []interface{}) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
// AddError is a convenience method for adding an error to the current response
func AddError(ctx context.Context, err error) {
GetRequestContext(ctx).Error(ctx, err)
}
// AddErrorf is a convenience method for adding an error to the current response
func AddErrorf(ctx context.Context, format string, args ...interface{}) {
GetRequestContext(ctx).Errorf(ctx, format, args...)
}
// RegisterExtension registers an extension, returns error if extension has already been registered
func (c *RequestContext) RegisterExtension(key string, value interface{}) error {
c.extensionsMu.Lock()
defer c.extensionsMu.Unlock()
if c.Extensions == nil {
c.Extensions = make(map[string]interface{})
}
if _, ok := c.Extensions[key]; ok {
return fmt.Errorf("extension already registered for key %s", key)
}
c.Extensions[key] = value
return nil
}
// ChainFieldMiddleware add chain by FieldMiddleware
func ChainFieldMiddleware(handleFunc ...FieldMiddleware) FieldMiddleware {
n := len(handleFunc)
if n > 1 {
lastI := n - 1
return func(ctx context.Context, next Resolver) (interface{}, error) {
var (
chainHandler Resolver
curI int
)
chainHandler = func(currentCtx context.Context) (interface{}, error) {
if curI == lastI {
return next(currentCtx)
}
curI++
res, err := handleFunc[curI](currentCtx, chainHandler)
curI--
return res, err
}
return handleFunc[0](ctx, chainHandler)
}
}
if n == 1 {
return handleFunc[0]
}
return func(ctx context.Context, next Resolver) (interface{}, error) {
return next(ctx)
}
}

View File

@ -1,33 +0,0 @@
package graphql
import (
"context"
"github.com/vektah/gqlparser/gqlerror"
)
type ErrorPresenterFunc func(context.Context, error) *gqlerror.Error
type ExtendedError interface {
Extensions() map[string]interface{}
}
func DefaultErrorPresenter(ctx context.Context, err error) *gqlerror.Error {
if gqlerr, ok := err.(*gqlerror.Error); ok {
if gqlerr.Path == nil {
gqlerr.Path = GetResolverContext(ctx).Path()
}
return gqlerr
}
var extensions map[string]interface{}
if ee, ok := err.(ExtendedError); ok {
extensions = ee.Extensions()
}
return &gqlerror.Error{
Message: err.Error(),
Path: GetResolverContext(ctx).Path(),
Extensions: extensions,
}
}

View File

@ -1,144 +0,0 @@
package graphql
import (
"context"
"fmt"
"github.com/vektah/gqlparser/ast"
)
type ExecutableSchema interface {
Schema() *ast.Schema
Complexity(typeName, fieldName string, childComplexity int, args map[string]interface{}) (int, bool)
Query(ctx context.Context, op *ast.OperationDefinition) *Response
Mutation(ctx context.Context, op *ast.OperationDefinition) *Response
Subscription(ctx context.Context, op *ast.OperationDefinition) func() *Response
}
// CollectFields returns the set of fields from an ast.SelectionSet where all collected fields satisfy at least one of the GraphQL types
// passed through satisfies. Providing an empty or nil slice for satisfies will return collect all fields regardless of fragment
// type conditions.
func CollectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies []string) []CollectedField {
return collectFields(reqCtx, selSet, satisfies, map[string]bool{})
}
func collectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies []string, visited map[string]bool) []CollectedField {
groupedFields := make([]CollectedField, 0, len(selSet))
for _, sel := range selSet {
switch sel := sel.(type) {
case *ast.Field:
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
f := getOrCreateAndAppendField(&groupedFields, sel.Alias, func() CollectedField {
return CollectedField{Field: sel}
})
f.Selections = append(f.Selections, sel.SelectionSet...)
case *ast.InlineFragment:
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
if len(satisfies) > 0 && !instanceOf(sel.TypeCondition, satisfies) {
continue
}
for _, childField := range collectFields(reqCtx, sel.SelectionSet, satisfies, visited) {
f := getOrCreateAndAppendField(&groupedFields, childField.Name, func() CollectedField { return childField })
f.Selections = append(f.Selections, childField.Selections...)
}
case *ast.FragmentSpread:
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
fragmentName := sel.Name
if _, seen := visited[fragmentName]; seen {
continue
}
visited[fragmentName] = true
fragment := reqCtx.Doc.Fragments.ForName(fragmentName)
if fragment == nil {
// should never happen, validator has already run
panic(fmt.Errorf("missing fragment %s", fragmentName))
}
if len(satisfies) > 0 && !instanceOf(fragment.TypeCondition, satisfies) {
continue
}
for _, childField := range collectFields(reqCtx, fragment.SelectionSet, satisfies, visited) {
f := getOrCreateAndAppendField(&groupedFields, childField.Name, func() CollectedField { return childField })
f.Selections = append(f.Selections, childField.Selections...)
}
default:
panic(fmt.Errorf("unsupported %T", sel))
}
}
return groupedFields
}
type CollectedField struct {
*ast.Field
Selections ast.SelectionSet
}
func instanceOf(val string, satisfies []string) bool {
for _, s := range satisfies {
if val == s {
return true
}
}
return false
}
func getOrCreateAndAppendField(c *[]CollectedField, name string, creator func() CollectedField) *CollectedField {
for i, cf := range *c {
if cf.Alias == name {
return &(*c)[i]
}
}
f := creator()
*c = append(*c, f)
return &(*c)[len(*c)-1]
}
func shouldIncludeNode(directives ast.DirectiveList, variables map[string]interface{}) bool {
if len(directives) == 0 {
return true
}
skip, include := false, true
if d := directives.ForName("skip"); d != nil {
skip = resolveIfArgument(d, variables)
}
if d := directives.ForName("include"); d != nil {
include = resolveIfArgument(d, variables)
}
return !skip && include
}
func resolveIfArgument(d *ast.Directive, variables map[string]interface{}) bool {
arg := d.Arguments.ForName("if")
if arg == nil {
panic(fmt.Sprintf("%s: argument 'if' not defined", d.Name))
}
value, err := arg.Value.Value(variables)
if err != nil {
panic(err)
}
ret, ok := value.(bool)
if !ok {
panic(fmt.Sprintf("%s: argument 'if' is not a boolean", d.Name))
}
return ret
}

View File

@ -1,63 +0,0 @@
package graphql
import (
"io"
"sync"
)
type FieldSet struct {
fields []CollectedField
Values []Marshaler
delayed []delayedResult
}
type delayedResult struct {
i int
f func() Marshaler
}
func NewFieldSet(fields []CollectedField) *FieldSet {
return &FieldSet{
fields: fields,
Values: make([]Marshaler, len(fields)),
}
}
func (m *FieldSet) Concurrently(i int, f func() Marshaler) {
m.delayed = append(m.delayed, delayedResult{i: i, f: f})
}
func (m *FieldSet) Dispatch() {
if len(m.delayed) == 1 {
// only one concurrent task, no need to spawn a goroutine or deal create waitgroups
d := m.delayed[0]
m.Values[d.i] = d.f()
} else if len(m.delayed) > 1 {
// more than one concurrent task, use the main goroutine to do one, only spawn goroutines for the others
var wg sync.WaitGroup
for _, d := range m.delayed[1:] {
wg.Add(1)
go func(d delayedResult) {
m.Values[d.i] = d.f()
wg.Done()
}(d)
}
m.Values[m.delayed[0].i] = m.delayed[0].f()
wg.Wait()
}
}
func (m *FieldSet) MarshalGQL(writer io.Writer) {
writer.Write(openBrace)
for i, field := range m.fields {
if i != 0 {
writer.Write(comma)
}
writeQuotedString(writer, field.Alias)
writer.Write(colon)
m.Values[i].MarshalGQL(writer)
}
writer.Write(closeBrace)
}

View File

@ -1,31 +0,0 @@
package graphql
import (
"encoding/json"
"fmt"
"io"
"strconv"
)
func MarshalFloat(f float64) Marshaler {
return WriterFunc(func(w io.Writer) {
io.WriteString(w, fmt.Sprintf("%g", f))
})
}
func UnmarshalFloat(v interface{}) (float64, error) {
switch v := v.(type) {
case string:
return strconv.ParseFloat(v, 64)
case int:
return float64(v), nil
case int64:
return float64(v), nil
case float64:
return v, nil
case json.Number:
return strconv.ParseFloat(string(v), 64)
default:
return 0, fmt.Errorf("%T is not an float", v)
}
}

View File

@ -1,57 +0,0 @@
package graphql
import (
"encoding/json"
"fmt"
"io"
"strconv"
)
func MarshalID(s string) Marshaler {
return WriterFunc(func(w io.Writer) {
io.WriteString(w, strconv.Quote(s))
})
}
func UnmarshalID(v interface{}) (string, error) {
switch v := v.(type) {
case string:
return v, nil
case json.Number:
return string(v), nil
case int:
return strconv.Itoa(v), nil
case float64:
return fmt.Sprintf("%f", v), nil
case bool:
if v {
return "true", nil
} else {
return "false", nil
}
case nil:
return "null", nil
default:
return "", fmt.Errorf("%T is not a string", v)
}
}
func MarshalIntID(i int) Marshaler {
return WriterFunc(func(w io.Writer) {
writeQuotedString(w, strconv.Itoa(i))
})
}
func UnmarshalIntID(v interface{}) (int, error) {
switch v := v.(type) {
case string:
return strconv.Atoi(v)
case int:
return v, nil
case int64:
return int(v), nil
case json.Number:
return strconv.Atoi(string(v))
default:
return 0, fmt.Errorf("%T is not an int", v)
}
}

View File

@ -1,79 +0,0 @@
package graphql
import (
"encoding/json"
"fmt"
"io"
"strconv"
)
func MarshalInt(i int) Marshaler {
return WriterFunc(func(w io.Writer) {
io.WriteString(w, strconv.Itoa(i))
})
}
func UnmarshalInt(v interface{}) (int, error) {
switch v := v.(type) {
case string:
return strconv.Atoi(v)
case int:
return v, nil
case int64:
return int(v), nil
case json.Number:
return strconv.Atoi(string(v))
default:
return 0, fmt.Errorf("%T is not an int", v)
}
}
func MarshalInt64(i int64) Marshaler {
return WriterFunc(func(w io.Writer) {
io.WriteString(w, strconv.FormatInt(i, 10))
})
}
func UnmarshalInt64(v interface{}) (int64, error) {
switch v := v.(type) {
case string:
return strconv.ParseInt(v, 10, 64)
case int:
return int64(v), nil
case int64:
return v, nil
case json.Number:
return strconv.ParseInt(string(v), 10, 64)
default:
return 0, fmt.Errorf("%T is not an int", v)
}
}
func MarshalInt32(i int32) Marshaler {
return WriterFunc(func(w io.Writer) {
io.WriteString(w, strconv.FormatInt(int64(i), 10))
})
}
func UnmarshalInt32(v interface{}) (int32, error) {
switch v := v.(type) {
case string:
iv, err := strconv.ParseInt(v, 10, 32)
if err != nil {
return 0, err
}
return int32(iv), nil
case int:
return int32(v), nil
case int64:
return int32(v), nil
case json.Number:
iv, err := strconv.ParseInt(string(v), 10, 32)
if err != nil {
return 0, err
}
return int32(iv), nil
default:
return 0, fmt.Errorf("%T is not an int", v)
}
}

View File

@ -1,72 +0,0 @@
// introspection implements the spec defined in https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md#schema-introspection
package introspection
import "github.com/vektah/gqlparser/ast"
type (
Directive struct {
Name string
Description string
Locations []string
Args []InputValue
}
EnumValue struct {
Name string
Description string
deprecation *ast.Directive
}
Field struct {
Name string
Description string
Type *Type
Args []InputValue
deprecation *ast.Directive
}
InputValue struct {
Name string
Description string
DefaultValue *string
Type *Type
}
)
func WrapSchema(schema *ast.Schema) *Schema {
return &Schema{schema: schema}
}
func (f *EnumValue) IsDeprecated() bool {
return f.deprecation != nil
}
func (f *EnumValue) DeprecationReason() *string {
if f.deprecation == nil {
return nil
}
reason := f.deprecation.Arguments.ForName("reason")
if reason == nil {
return nil
}
return &reason.Value.Raw
}
func (f *Field) IsDeprecated() bool {
return f.deprecation != nil
}
func (f *Field) DeprecationReason() *string {
if f.deprecation == nil {
return nil
}
reason := f.deprecation.Arguments.ForName("reason")
if reason == nil {
return nil
}
return &reason.Value.Raw
}

View File

@ -1,104 +0,0 @@
package introspection
// Query is the query generated by graphiql to determine type information
const Query = `
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
`

View File

@ -1,68 +0,0 @@
package introspection
import (
"strings"
"github.com/vektah/gqlparser/ast"
)
type Schema struct {
schema *ast.Schema
}
func (s *Schema) Types() []Type {
types := make([]Type, 0, len(s.schema.Types))
for _, typ := range s.schema.Types {
if strings.HasPrefix(typ.Name, "__") {
continue
}
types = append(types, *WrapTypeFromDef(s.schema, typ))
}
return types
}
func (s *Schema) QueryType() *Type {
return WrapTypeFromDef(s.schema, s.schema.Query)
}
func (s *Schema) MutationType() *Type {
return WrapTypeFromDef(s.schema, s.schema.Mutation)
}
func (s *Schema) SubscriptionType() *Type {
return WrapTypeFromDef(s.schema, s.schema.Subscription)
}
func (s *Schema) Directives() []Directive {
res := make([]Directive, 0, len(s.schema.Directives))
for _, d := range s.schema.Directives {
res = append(res, s.directiveFromDef(d))
}
return res
}
func (s *Schema) directiveFromDef(d *ast.DirectiveDefinition) Directive {
locs := make([]string, len(d.Locations))
for i, loc := range d.Locations {
locs[i] = string(loc)
}
args := make([]InputValue, len(d.Arguments))
for i, arg := range d.Arguments {
args[i] = InputValue{
Name: arg.Name,
Description: arg.Description,
DefaultValue: defaultValue(arg.DefaultValue),
Type: WrapTypeFromType(s.schema, arg.Type),
}
}
return Directive{
Name: d.Name,
Description: d.Description,
Locations: locs,
Args: args,
}
}

View File

@ -1,176 +0,0 @@
package introspection
import (
"strings"
"github.com/vektah/gqlparser/ast"
)
type Type struct {
schema *ast.Schema
def *ast.Definition
typ *ast.Type
}
func WrapTypeFromDef(s *ast.Schema, def *ast.Definition) *Type {
if def == nil {
return nil
}
return &Type{schema: s, def: def}
}
func WrapTypeFromType(s *ast.Schema, typ *ast.Type) *Type {
if typ == nil {
return nil
}
if !typ.NonNull && typ.NamedType != "" {
return &Type{schema: s, def: s.Types[typ.NamedType]}
}
return &Type{schema: s, typ: typ}
}
func (t *Type) Kind() string {
if t.typ != nil {
if t.typ.NonNull {
return "NON_NULL"
}
if t.typ.Elem != nil {
return "LIST"
}
} else {
return string(t.def.Kind)
}
panic("UNKNOWN")
}
func (t *Type) Name() *string {
if t.def == nil {
return nil
}
return &t.def.Name
}
func (t *Type) Description() string {
if t.def == nil {
return ""
}
return t.def.Description
}
func (t *Type) Fields(includeDeprecated bool) []Field {
if t.def == nil || (t.def.Kind != ast.Object && t.def.Kind != ast.Interface) {
return []Field{}
}
fields := []Field{}
for _, f := range t.def.Fields {
if strings.HasPrefix(f.Name, "__") {
continue
}
if !includeDeprecated && f.Directives.ForName("deprecated") != nil {
continue
}
var args []InputValue
for _, arg := range f.Arguments {
args = append(args, InputValue{
Type: WrapTypeFromType(t.schema, arg.Type),
Name: arg.Name,
Description: arg.Description,
DefaultValue: defaultValue(arg.DefaultValue),
})
}
fields = append(fields, Field{
Name: f.Name,
Description: f.Description,
Args: args,
Type: WrapTypeFromType(t.schema, f.Type),
deprecation: f.Directives.ForName("deprecated"),
})
}
return fields
}
func (t *Type) InputFields() []InputValue {
if t.def == nil || t.def.Kind != ast.InputObject {
return []InputValue{}
}
res := []InputValue{}
for _, f := range t.def.Fields {
res = append(res, InputValue{
Name: f.Name,
Description: f.Description,
Type: WrapTypeFromType(t.schema, f.Type),
DefaultValue: defaultValue(f.DefaultValue),
})
}
return res
}
func defaultValue(value *ast.Value) *string {
if value == nil {
return nil
}
val := value.String()
return &val
}
func (t *Type) Interfaces() []Type {
if t.def == nil || t.def.Kind != ast.Object {
return []Type{}
}
res := []Type{}
for _, intf := range t.def.Interfaces {
res = append(res, *WrapTypeFromDef(t.schema, t.schema.Types[intf]))
}
return res
}
func (t *Type) PossibleTypes() []Type {
if t.def == nil || (t.def.Kind != ast.Interface && t.def.Kind != ast.Union) {
return []Type{}
}
res := []Type{}
for _, pt := range t.schema.GetPossibleTypes(t.def) {
res = append(res, *WrapTypeFromDef(t.schema, pt))
}
return res
}
func (t *Type) EnumValues(includeDeprecated bool) []EnumValue {
if t.def == nil || t.def.Kind != ast.Enum {
return []EnumValue{}
}
res := []EnumValue{}
for _, val := range t.def.EnumValues {
res = append(res, EnumValue{
Name: val.Name,
Description: val.Description,
deprecation: val.Directives.ForName("deprecated"),
})
}
return res
}
func (t *Type) OfType() *Type {
if t.typ == nil {
return nil
}
if t.typ.NonNull {
// fake non null nodes
cpy := *t.typ
cpy.NonNull = false
return WrapTypeFromType(t.schema, &cpy)
}
return WrapTypeFromType(t.schema, t.typ.Elem)
}

View File

@ -1,52 +0,0 @@
package graphql
import (
"io"
)
var nullLit = []byte(`null`)
var trueLit = []byte(`true`)
var falseLit = []byte(`false`)
var openBrace = []byte(`{`)
var closeBrace = []byte(`}`)
var openBracket = []byte(`[`)
var closeBracket = []byte(`]`)
var colon = []byte(`:`)
var comma = []byte(`,`)
var Null = &lit{nullLit}
var True = &lit{trueLit}
var False = &lit{falseLit}
type Marshaler interface {
MarshalGQL(w io.Writer)
}
type Unmarshaler interface {
UnmarshalGQL(v interface{}) error
}
type WriterFunc func(writer io.Writer)
func (f WriterFunc) MarshalGQL(w io.Writer) {
f(w)
}
type Array []Marshaler
func (a Array) MarshalGQL(writer io.Writer) {
writer.Write(openBracket)
for i, val := range a {
if i != 0 {
writer.Write(comma)
}
val.MarshalGQL(writer)
}
writer.Write(closeBracket)
}
type lit struct{ b []byte }
func (l lit) MarshalGQL(w io.Writer) {
w.Write(l.b)
}

View File

@ -1,24 +0,0 @@
package graphql
import (
"encoding/json"
"fmt"
"io"
)
func MarshalMap(val map[string]interface{}) Marshaler {
return WriterFunc(func(w io.Writer) {
err := json.NewEncoder(w).Encode(val)
if err != nil {
panic(err)
}
})
}
func UnmarshalMap(v interface{}) (map[string]interface{}, error) {
if m, ok := v.(map[string]interface{}); ok {
return m, nil
}
return nil, fmt.Errorf("%T is not a map", v)
}

View File

@ -1,14 +0,0 @@
package graphql
func OneShot(resp *Response) func() *Response {
var oneshot bool
return func() *Response {
if oneshot {
return nil
}
oneshot = true
return resp
}
}

View File

@ -1,19 +0,0 @@
package graphql
import (
"context"
"errors"
"fmt"
"os"
"runtime/debug"
)
type RecoverFunc func(ctx context.Context, err interface{}) (userMessage error)
func DefaultRecover(ctx context.Context, err interface{}) error {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr)
debug.PrintStack()
return errors.New("internal system error")
}

View File

@ -1,24 +0,0 @@
package graphql
import (
"context"
"encoding/json"
"fmt"
"github.com/vektah/gqlparser/gqlerror"
)
// Errors are intentionally serialized first based on the advice in
// https://github.com/facebook/graphql/commit/7b40390d48680b15cb93e02d46ac5eb249689876#diff-757cea6edf0288677a9eea4cfc801d87R107
// and https://github.com/facebook/graphql/pull/384
type Response struct {
Errors gqlerror.List `json:"errors,omitempty"`
Data json.RawMessage `json:"data"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
}
func ErrorResponse(ctx context.Context, messagef string, args ...interface{}) *Response {
return &Response{
Errors: gqlerror.List{{Message: fmt.Sprintf(messagef, args...)}},
}
}

View File

@ -1,7 +0,0 @@
package graphql
type Query struct{}
type Mutation struct{}
type Subscription struct{}

View File

@ -1,68 +0,0 @@
package graphql
import (
"fmt"
"io"
"strconv"
)
const encodeHex = "0123456789ABCDEF"
func MarshalString(s string) Marshaler {
return WriterFunc(func(w io.Writer) {
writeQuotedString(w, s)
})
}
func writeQuotedString(w io.Writer, s string) {
start := 0
io.WriteString(w, `"`)
for i, c := range s {
if c < 0x20 || c == '\\' || c == '"' {
io.WriteString(w, s[start:i])
switch c {
case '\t':
io.WriteString(w, `\t`)
case '\r':
io.WriteString(w, `\r`)
case '\n':
io.WriteString(w, `\n`)
case '\\':
io.WriteString(w, `\\`)
case '"':
io.WriteString(w, `\"`)
default:
io.WriteString(w, `\u00`)
w.Write([]byte{encodeHex[c>>4], encodeHex[c&0xf]})
}
start = i + 1
}
}
io.WriteString(w, s[start:])
io.WriteString(w, `"`)
}
func UnmarshalString(v interface{}) (string, error) {
switch v := v.(type) {
case string:
return v, nil
case int:
return strconv.Itoa(v), nil
case float64:
return fmt.Sprintf("%f", v), nil
case bool:
if v {
return "true", nil
} else {
return "false", nil
}
case nil:
return "null", nil
default:
return "", fmt.Errorf("%T is not a string", v)
}
}

View File

@ -1,25 +0,0 @@
package graphql
import (
"errors"
"io"
"strconv"
"time"
)
func MarshalTime(t time.Time) Marshaler {
if t.IsZero() {
return Null
}
return WriterFunc(func(w io.Writer) {
io.WriteString(w, strconv.Quote(t.Format(time.RFC3339)))
})
}
func UnmarshalTime(v interface{}) (time.Time, error) {
if tmpStr, ok := v.(string); ok {
return time.Parse(time.RFC3339, tmpStr)
}
return time.Time{}, errors.New("time should be RFC3339 formatted string")
}

View File

@ -1,58 +0,0 @@
package graphql
import (
"context"
)
var _ Tracer = (*NopTracer)(nil)
type Tracer interface {
StartOperationParsing(ctx context.Context) context.Context
EndOperationParsing(ctx context.Context)
StartOperationValidation(ctx context.Context) context.Context
EndOperationValidation(ctx context.Context)
StartOperationExecution(ctx context.Context) context.Context
StartFieldExecution(ctx context.Context, field CollectedField) context.Context
StartFieldResolverExecution(ctx context.Context, rc *ResolverContext) context.Context
StartFieldChildExecution(ctx context.Context) context.Context
EndFieldExecution(ctx context.Context)
EndOperationExecution(ctx context.Context)
}
type NopTracer struct{}
func (NopTracer) StartOperationParsing(ctx context.Context) context.Context {
return ctx
}
func (NopTracer) EndOperationParsing(ctx context.Context) {
}
func (NopTracer) StartOperationValidation(ctx context.Context) context.Context {
return ctx
}
func (NopTracer) EndOperationValidation(ctx context.Context) {
}
func (NopTracer) StartOperationExecution(ctx context.Context) context.Context {
return ctx
}
func (NopTracer) StartFieldExecution(ctx context.Context, field CollectedField) context.Context {
return ctx
}
func (NopTracer) StartFieldResolverExecution(ctx context.Context, rc *ResolverContext) context.Context {
return ctx
}
func (NopTracer) StartFieldChildExecution(ctx context.Context) context.Context {
return ctx
}
func (NopTracer) EndFieldExecution(ctx context.Context) {
}
func (NopTracer) EndOperationExecution(ctx context.Context) {
}

View File

@ -1,26 +0,0 @@
package graphql
import (
"fmt"
"io"
)
type Upload struct {
File io.Reader
Filename string
Size int64
}
func MarshalUpload(f Upload) Marshaler {
return WriterFunc(func(w io.Writer) {
io.Copy(w, f.File)
})
}
func UnmarshalUpload(v interface{}) (Upload, error) {
upload, ok := v.(Upload)
if !ok {
return Upload{}, fmt.Errorf("%T is not an Upload", v)
}
return upload, nil
}

View File

@ -1,3 +0,0 @@
package graphql
const Version = "v0.9.2"

View File

@ -1,57 +0,0 @@
package handler
import "context"
type key string
const (
initpayload key = "ws_initpayload_context"
)
// InitPayload is a structure that is parsed from the websocket init message payload. TO use
// request headers for non-websocket, instead wrap the graphql handler in a middleware.
type InitPayload map[string]interface{}
// GetString safely gets a string value from the payload. It returns an empty string if the
// payload is nil or the value isn't set.
func (payload InitPayload) GetString(key string) string {
if payload == nil {
return ""
}
if value, ok := payload[key]; ok {
res, _ := value.(string)
return res
}
return ""
}
// Authorization is a short hand for getting the Authorization header from the
// payload.
func (payload InitPayload) Authorization() string {
if value := payload.GetString("Authorization"); value != "" {
return value
}
if value := payload.GetString("authorization"); value != "" {
return value
}
return ""
}
func withInitPayload(ctx context.Context, payload InitPayload) context.Context {
return context.WithValue(ctx, initpayload, payload)
}
// GetInitPayload gets a map of the data sent with the connection_init message, which is used by
// graphql clients as a stand-in for HTTP headers.
func GetInitPayload(ctx context.Context) InitPayload {
payload, ok := ctx.Value(initpayload).(InitPayload)
if !ok {
return nil
}
return payload
}

View File

@ -1,802 +0,0 @@
package handler
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/99designs/gqlgen/complexity"
"github.com/99designs/gqlgen/graphql"
"github.com/gorilla/websocket"
lru "github.com/hashicorp/golang-lru"
"github.com/vektah/gqlparser/ast"
"github.com/vektah/gqlparser/gqlerror"
"github.com/vektah/gqlparser/parser"
"github.com/vektah/gqlparser/validator"
)
type params struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]interface{} `json:"variables"`
Extensions *extensions `json:"extensions"`
}
type extensions struct {
PersistedQuery *persistedQuery `json:"persistedQuery"`
}
type persistedQuery struct {
Sha256 string `json:"sha256Hash"`
Version int64 `json:"version"`
}
const (
errPersistedQueryNotSupported = "PersistedQueryNotSupported"
errPersistedQueryNotFound = "PersistedQueryNotFound"
)
type PersistedQueryCache interface {
Add(ctx context.Context, hash string, query string)
Get(ctx context.Context, hash string) (string, bool)
}
type websocketInitFunc func(ctx context.Context, initPayload InitPayload) error
type Config struct {
cacheSize int
upgrader websocket.Upgrader
recover graphql.RecoverFunc
errorPresenter graphql.ErrorPresenterFunc
resolverHook graphql.FieldMiddleware
requestHook graphql.RequestMiddleware
tracer graphql.Tracer
complexityLimit int
complexityLimitFunc graphql.ComplexityLimitFunc
websocketInitFunc websocketInitFunc
disableIntrospection bool
connectionKeepAlivePingInterval time.Duration
uploadMaxMemory int64
uploadMaxSize int64
apqCache PersistedQueryCache
}
func (c *Config) newRequestContext(es graphql.ExecutableSchema, doc *ast.QueryDocument, op *ast.OperationDefinition, query string, variables map[string]interface{}) *graphql.RequestContext {
reqCtx := graphql.NewRequestContext(doc, query, variables)
reqCtx.DisableIntrospection = c.disableIntrospection
if hook := c.recover; hook != nil {
reqCtx.Recover = hook
}
if hook := c.errorPresenter; hook != nil {
reqCtx.ErrorPresenter = hook
}
if hook := c.resolverHook; hook != nil {
reqCtx.ResolverMiddleware = hook
}
if hook := c.requestHook; hook != nil {
reqCtx.RequestMiddleware = hook
}
if hook := c.tracer; hook != nil {
reqCtx.Tracer = hook
}
if c.complexityLimit > 0 || c.complexityLimitFunc != nil {
reqCtx.ComplexityLimit = c.complexityLimit
operationComplexity := complexity.Calculate(es, op, variables)
reqCtx.OperationComplexity = operationComplexity
}
return reqCtx
}
type Option func(cfg *Config)
func WebsocketUpgrader(upgrader websocket.Upgrader) Option {
return func(cfg *Config) {
cfg.upgrader = upgrader
}
}
func RecoverFunc(recover graphql.RecoverFunc) Option {
return func(cfg *Config) {
cfg.recover = recover
}
}
// ErrorPresenter transforms errors found while resolving into errors that will be returned to the user. It provides
// a good place to add any extra fields, like error.type, that might be desired by your frontend. Check the default
// implementation in graphql.DefaultErrorPresenter for an example.
func ErrorPresenter(f graphql.ErrorPresenterFunc) Option {
return func(cfg *Config) {
cfg.errorPresenter = f
}
}
// IntrospectionEnabled = false will forbid clients from calling introspection endpoints. Can be useful in prod when you dont
// want clients introspecting the full schema.
func IntrospectionEnabled(enabled bool) Option {
return func(cfg *Config) {
cfg.disableIntrospection = !enabled
}
}
// ComplexityLimit sets a maximum query complexity that is allowed to be executed.
// If a query is submitted that exceeds the limit, a 422 status code will be returned.
func ComplexityLimit(limit int) Option {
return func(cfg *Config) {
cfg.complexityLimit = limit
}
}
// ComplexityLimitFunc allows you to define a function to dynamically set the maximum query complexity that is allowed
// to be executed.
// If a query is submitted that exceeds the limit, a 422 status code will be returned.
func ComplexityLimitFunc(complexityLimitFunc graphql.ComplexityLimitFunc) Option {
return func(cfg *Config) {
cfg.complexityLimitFunc = complexityLimitFunc
}
}
// ResolverMiddleware allows you to define a function that will be called around every resolver,
// useful for logging.
func ResolverMiddleware(middleware graphql.FieldMiddleware) Option {
return func(cfg *Config) {
if cfg.resolverHook == nil {
cfg.resolverHook = middleware
return
}
lastResolve := cfg.resolverHook
cfg.resolverHook = func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
return lastResolve(ctx, func(ctx context.Context) (res interface{}, err error) {
return middleware(ctx, next)
})
}
}
}
// RequestMiddleware allows you to define a function that will be called around the root request,
// after the query has been parsed. This is useful for logging
func RequestMiddleware(middleware graphql.RequestMiddleware) Option {
return func(cfg *Config) {
if cfg.requestHook == nil {
cfg.requestHook = middleware
return
}
lastResolve := cfg.requestHook
cfg.requestHook = func(ctx context.Context, next func(ctx context.Context) []byte) []byte {
return lastResolve(ctx, func(ctx context.Context) []byte {
return middleware(ctx, next)
})
}
}
}
// Tracer allows you to add a request/resolver tracer that will be called around the root request,
// calling resolver. This is useful for tracing
func Tracer(tracer graphql.Tracer) Option {
return func(cfg *Config) {
if cfg.tracer == nil {
cfg.tracer = tracer
} else {
lastResolve := cfg.tracer
cfg.tracer = &tracerWrapper{
tracer1: lastResolve,
tracer2: tracer,
}
}
opt := RequestMiddleware(func(ctx context.Context, next func(ctx context.Context) []byte) []byte {
ctx = tracer.StartOperationExecution(ctx)
resp := next(ctx)
tracer.EndOperationExecution(ctx)
return resp
})
opt(cfg)
}
}
type tracerWrapper struct {
tracer1 graphql.Tracer
tracer2 graphql.Tracer
}
func (tw *tracerWrapper) StartOperationParsing(ctx context.Context) context.Context {
ctx = tw.tracer1.StartOperationParsing(ctx)
ctx = tw.tracer2.StartOperationParsing(ctx)
return ctx
}
func (tw *tracerWrapper) EndOperationParsing(ctx context.Context) {
tw.tracer2.EndOperationParsing(ctx)
tw.tracer1.EndOperationParsing(ctx)
}
func (tw *tracerWrapper) StartOperationValidation(ctx context.Context) context.Context {
ctx = tw.tracer1.StartOperationValidation(ctx)
ctx = tw.tracer2.StartOperationValidation(ctx)
return ctx
}
func (tw *tracerWrapper) EndOperationValidation(ctx context.Context) {
tw.tracer2.EndOperationValidation(ctx)
tw.tracer1.EndOperationValidation(ctx)
}
func (tw *tracerWrapper) StartOperationExecution(ctx context.Context) context.Context {
ctx = tw.tracer1.StartOperationExecution(ctx)
ctx = tw.tracer2.StartOperationExecution(ctx)
return ctx
}
func (tw *tracerWrapper) StartFieldExecution(ctx context.Context, field graphql.CollectedField) context.Context {
ctx = tw.tracer1.StartFieldExecution(ctx, field)
ctx = tw.tracer2.StartFieldExecution(ctx, field)
return ctx
}
func (tw *tracerWrapper) StartFieldResolverExecution(ctx context.Context, rc *graphql.ResolverContext) context.Context {
ctx = tw.tracer1.StartFieldResolverExecution(ctx, rc)
ctx = tw.tracer2.StartFieldResolverExecution(ctx, rc)
return ctx
}
func (tw *tracerWrapper) StartFieldChildExecution(ctx context.Context) context.Context {
ctx = tw.tracer1.StartFieldChildExecution(ctx)
ctx = tw.tracer2.StartFieldChildExecution(ctx)
return ctx
}
func (tw *tracerWrapper) EndFieldExecution(ctx context.Context) {
tw.tracer2.EndFieldExecution(ctx)
tw.tracer1.EndFieldExecution(ctx)
}
func (tw *tracerWrapper) EndOperationExecution(ctx context.Context) {
tw.tracer2.EndOperationExecution(ctx)
tw.tracer1.EndOperationExecution(ctx)
}
// WebsocketInitFunc is called when the server receives connection init message from the client.
// This can be used to check initial payload to see whether to accept the websocket connection.
func WebsocketInitFunc(websocketInitFunc func(ctx context.Context, initPayload InitPayload) error) Option {
return func(cfg *Config) {
cfg.websocketInitFunc = websocketInitFunc
}
}
// CacheSize sets the maximum size of the query cache.
// If size is less than or equal to 0, the cache is disabled.
func CacheSize(size int) Option {
return func(cfg *Config) {
cfg.cacheSize = size
}
}
// UploadMaxSize sets the maximum number of bytes used to parse a request body
// as multipart/form-data.
func UploadMaxSize(size int64) Option {
return func(cfg *Config) {
cfg.uploadMaxSize = size
}
}
// UploadMaxMemory sets the maximum number of bytes used to parse a request body
// as multipart/form-data in memory, with the remainder stored on disk in
// temporary files.
func UploadMaxMemory(size int64) Option {
return func(cfg *Config) {
cfg.uploadMaxMemory = size
}
}
// WebsocketKeepAliveDuration allows you to reconfigure the keepalive behavior.
// By default, keepalive is enabled with a DefaultConnectionKeepAlivePingInterval
// duration. Set handler.connectionKeepAlivePingInterval = 0 to disable keepalive
// altogether.
func WebsocketKeepAliveDuration(duration time.Duration) Option {
return func(cfg *Config) {
cfg.connectionKeepAlivePingInterval = duration
}
}
// Add cache that will hold queries for automatic persisted queries (APQ)
func EnablePersistedQueryCache(cache PersistedQueryCache) Option {
return func(cfg *Config) {
cfg.apqCache = cache
}
}
const DefaultCacheSize = 1000
const DefaultConnectionKeepAlivePingInterval = 25 * time.Second
// DefaultUploadMaxMemory is the maximum number of bytes used to parse a request body
// as multipart/form-data in memory, with the remainder stored on disk in
// temporary files.
const DefaultUploadMaxMemory = 32 << 20
// DefaultUploadMaxSize is maximum number of bytes used to parse a request body
// as multipart/form-data.
const DefaultUploadMaxSize = 32 << 20
func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc {
cfg := &Config{
cacheSize: DefaultCacheSize,
uploadMaxMemory: DefaultUploadMaxMemory,
uploadMaxSize: DefaultUploadMaxSize,
connectionKeepAlivePingInterval: DefaultConnectionKeepAlivePingInterval,
upgrader: websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
},
}
for _, option := range options {
option(cfg)
}
var cache *lru.Cache
if cfg.cacheSize > 0 {
var err error
cache, err = lru.New(cfg.cacheSize)
if err != nil {
// An error is only returned for non-positive cache size
// and we already checked for that.
panic("unexpected error creating cache: " + err.Error())
}
}
if cfg.tracer == nil {
cfg.tracer = &graphql.NopTracer{}
}
handler := &graphqlHandler{
cfg: cfg,
cache: cache,
exec: exec,
}
return handler.ServeHTTP
}
var _ http.Handler = (*graphqlHandler)(nil)
type graphqlHandler struct {
cfg *Config
cache *lru.Cache
exec graphql.ExecutableSchema
}
func computeQueryHash(query string) string {
b := sha256.Sum256([]byte(query))
return hex.EncodeToString(b[:])
}
func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions {
w.Header().Set("Allow", "OPTIONS, GET, POST")
w.WriteHeader(http.StatusOK)
return
}
if strings.Contains(r.Header.Get("Upgrade"), "websocket") {
connectWs(gh.exec, w, r, gh.cfg, gh.cache)
return
}
w.Header().Set("Content-Type", "application/json")
var reqParams params
switch r.Method {
case http.MethodGet:
reqParams.Query = r.URL.Query().Get("query")
reqParams.OperationName = r.URL.Query().Get("operationName")
if variables := r.URL.Query().Get("variables"); variables != "" {
if err := jsonDecode(strings.NewReader(variables), &reqParams.Variables); err != nil {
sendErrorf(w, http.StatusBadRequest, "variables could not be decoded")
return
}
}
if extensions := r.URL.Query().Get("extensions"); extensions != "" {
if err := jsonDecode(strings.NewReader(extensions), &reqParams.Extensions); err != nil {
sendErrorf(w, http.StatusBadRequest, "extensions could not be decoded")
return
}
}
case http.MethodPost:
mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
sendErrorf(w, http.StatusBadRequest, "error parsing request Content-Type")
return
}
switch mediaType {
case "application/json":
if err := jsonDecode(r.Body, &reqParams); err != nil {
sendErrorf(w, http.StatusBadRequest, "json body could not be decoded: "+err.Error())
return
}
case "multipart/form-data":
var closers []io.Closer
var tmpFiles []string
defer func() {
for i := len(closers) - 1; 0 <= i; i-- {
_ = closers[i].Close()
}
for _, tmpFile := range tmpFiles {
_ = os.Remove(tmpFile)
}
}()
if err := processMultipart(w, r, &reqParams, &closers, &tmpFiles, gh.cfg.uploadMaxSize, gh.cfg.uploadMaxMemory); err != nil {
sendErrorf(w, http.StatusBadRequest, "multipart body could not be decoded: "+err.Error())
return
}
default:
sendErrorf(w, http.StatusBadRequest, "unsupported Content-Type: "+mediaType)
return
}
default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
ctx := r.Context()
var queryHash string
apqRegister := false
apq := reqParams.Extensions != nil && reqParams.Extensions.PersistedQuery != nil
if apq {
// client has enabled apq
queryHash = reqParams.Extensions.PersistedQuery.Sha256
if gh.cfg.apqCache == nil {
// server has disabled apq
sendErrorf(w, http.StatusOK, errPersistedQueryNotSupported)
return
}
if reqParams.Extensions.PersistedQuery.Version != 1 {
sendErrorf(w, http.StatusOK, "Unsupported persisted query version")
return
}
if reqParams.Query == "" {
// client sent optimistic query hash without query string
query, ok := gh.cfg.apqCache.Get(ctx, queryHash)
if !ok {
sendErrorf(w, http.StatusOK, errPersistedQueryNotFound)
return
}
reqParams.Query = query
} else {
if computeQueryHash(reqParams.Query) != queryHash {
sendErrorf(w, http.StatusOK, "provided sha does not match query")
return
}
apqRegister = true
}
} else if reqParams.Query == "" {
sendErrorf(w, http.StatusUnprocessableEntity, "Must provide query string")
return
}
var doc *ast.QueryDocument
var cacheHit bool
if gh.cache != nil {
val, ok := gh.cache.Get(reqParams.Query)
if ok {
doc = val.(*ast.QueryDocument)
cacheHit = true
}
}
ctx, doc, gqlErr := gh.parseOperation(ctx, &parseOperationArgs{
Query: reqParams.Query,
CachedDoc: doc,
})
if gqlErr != nil {
sendError(w, http.StatusUnprocessableEntity, gqlErr)
return
}
ctx, op, vars, listErr := gh.validateOperation(ctx, &validateOperationArgs{
Doc: doc,
OperationName: reqParams.OperationName,
CacheHit: cacheHit,
R: r,
Variables: reqParams.Variables,
})
if len(listErr) != 0 {
sendError(w, http.StatusUnprocessableEntity, listErr...)
return
}
if gh.cache != nil && !cacheHit {
gh.cache.Add(reqParams.Query, doc)
}
reqCtx := gh.cfg.newRequestContext(gh.exec, doc, op, reqParams.Query, vars)
ctx = graphql.WithRequestContext(ctx, reqCtx)
defer func() {
if err := recover(); err != nil {
userErr := reqCtx.Recover(ctx, err)
sendErrorf(w, http.StatusUnprocessableEntity, userErr.Error())
}
}()
if gh.cfg.complexityLimitFunc != nil {
reqCtx.ComplexityLimit = gh.cfg.complexityLimitFunc(ctx)
}
if reqCtx.ComplexityLimit > 0 && reqCtx.OperationComplexity > reqCtx.ComplexityLimit {
sendErrorf(w, http.StatusUnprocessableEntity, "operation has complexity %d, which exceeds the limit of %d", reqCtx.OperationComplexity, reqCtx.ComplexityLimit)
return
}
if apqRegister && gh.cfg.apqCache != nil {
// Add to persisted query cache
gh.cfg.apqCache.Add(ctx, queryHash, reqParams.Query)
}
switch op.Operation {
case ast.Query:
b, err := json.Marshal(gh.exec.Query(ctx, op))
if err != nil {
panic(err)
}
w.Write(b)
case ast.Mutation:
b, err := json.Marshal(gh.exec.Mutation(ctx, op))
if err != nil {
panic(err)
}
w.Write(b)
default:
sendErrorf(w, http.StatusBadRequest, "unsupported operation type")
}
}
type parseOperationArgs struct {
Query string
CachedDoc *ast.QueryDocument
}
func (gh *graphqlHandler) parseOperation(ctx context.Context, args *parseOperationArgs) (context.Context, *ast.QueryDocument, *gqlerror.Error) {
ctx = gh.cfg.tracer.StartOperationParsing(ctx)
defer func() { gh.cfg.tracer.EndOperationParsing(ctx) }()
if args.CachedDoc != nil {
return ctx, args.CachedDoc, nil
}
doc, gqlErr := parser.ParseQuery(&ast.Source{Input: args.Query})
if gqlErr != nil {
return ctx, nil, gqlErr
}
return ctx, doc, nil
}
type validateOperationArgs struct {
Doc *ast.QueryDocument
OperationName string
CacheHit bool
R *http.Request
Variables map[string]interface{}
}
func (gh *graphqlHandler) validateOperation(ctx context.Context, args *validateOperationArgs) (context.Context, *ast.OperationDefinition, map[string]interface{}, gqlerror.List) {
ctx = gh.cfg.tracer.StartOperationValidation(ctx)
defer func() { gh.cfg.tracer.EndOperationValidation(ctx) }()
if !args.CacheHit {
listErr := validator.Validate(gh.exec.Schema(), args.Doc)
if len(listErr) != 0 {
return ctx, nil, nil, listErr
}
}
op := args.Doc.Operations.ForName(args.OperationName)
if op == nil {
return ctx, nil, nil, gqlerror.List{gqlerror.Errorf("operation %s not found", args.OperationName)}
}
if op.Operation != ast.Query && args.R.Method == http.MethodGet {
return ctx, nil, nil, gqlerror.List{gqlerror.Errorf("GET requests only allow query operations")}
}
vars, err := validator.VariableValues(gh.exec.Schema(), op, args.Variables)
if err != nil {
return ctx, nil, nil, gqlerror.List{err}
}
return ctx, op, vars, nil
}
func jsonDecode(r io.Reader, val interface{}) error {
dec := json.NewDecoder(r)
dec.UseNumber()
return dec.Decode(val)
}
func sendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) {
w.WriteHeader(code)
b, err := json.Marshal(&graphql.Response{Errors: errors})
if err != nil {
panic(err)
}
w.Write(b)
}
func sendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) {
sendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)})
}
type bytesReader struct {
s *[]byte
i int64 // current reading index
prevRune int // index of previous rune; or < 0
}
func (r *bytesReader) Read(b []byte) (n int, err error) {
if r.s == nil {
return 0, errors.New("byte slice pointer is nil")
}
if r.i >= int64(len(*r.s)) {
return 0, io.EOF
}
r.prevRune = -1
n = copy(b, (*r.s)[r.i:])
r.i += int64(n)
return
}
func processMultipart(w http.ResponseWriter, r *http.Request, request *params, closers *[]io.Closer, tmpFiles *[]string, uploadMaxSize, uploadMaxMemory int64) error {
var err error
if r.ContentLength > uploadMaxSize {
return errors.New("failed to parse multipart form, request body too large")
}
r.Body = http.MaxBytesReader(w, r.Body, uploadMaxSize)
if err = r.ParseMultipartForm(uploadMaxMemory); err != nil {
if strings.Contains(err.Error(), "request body too large") {
return errors.New("failed to parse multipart form, request body too large")
}
return errors.New("failed to parse multipart form")
}
*closers = append(*closers, r.Body)
if err = jsonDecode(strings.NewReader(r.Form.Get("operations")), &request); err != nil {
return errors.New("operations form field could not be decoded")
}
var uploadsMap = map[string][]string{}
if err = json.Unmarshal([]byte(r.Form.Get("map")), &uploadsMap); err != nil {
return errors.New("map form field could not be decoded")
}
var upload graphql.Upload
for key, paths := range uploadsMap {
if len(paths) == 0 {
return fmt.Errorf("invalid empty operations paths list for key %s", key)
}
file, header, err := r.FormFile(key)
if err != nil {
return fmt.Errorf("failed to get key %s from form", key)
}
*closers = append(*closers, file)
if len(paths) == 1 {
upload = graphql.Upload{
File: file,
Size: header.Size,
Filename: header.Filename,
}
err = addUploadToOperations(request, upload, key, paths[0])
if err != nil {
return err
}
} else {
if r.ContentLength < uploadMaxMemory {
fileBytes, err := ioutil.ReadAll(file)
if err != nil {
return fmt.Errorf("failed to read file for key %s", key)
}
for _, path := range paths {
upload = graphql.Upload{
File: &bytesReader{s: &fileBytes, i: 0, prevRune: -1},
Size: header.Size,
Filename: header.Filename,
}
err = addUploadToOperations(request, upload, key, path)
if err != nil {
return err
}
}
} else {
tmpFile, err := ioutil.TempFile(os.TempDir(), "gqlgen-")
if err != nil {
return fmt.Errorf("failed to create temp file for key %s", key)
}
tmpName := tmpFile.Name()
*tmpFiles = append(*tmpFiles, tmpName)
_, err = io.Copy(tmpFile, file)
if err != nil {
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("failed to copy to temp file and close temp file for key %s", key)
}
return fmt.Errorf("failed to copy to temp file for key %s", key)
}
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("failed to close temp file for key %s", key)
}
for _, path := range paths {
pathTmpFile, err := os.Open(tmpName)
if err != nil {
return fmt.Errorf("failed to open temp file for key %s", key)
}
*closers = append(*closers, pathTmpFile)
upload = graphql.Upload{
File: pathTmpFile,
Size: header.Size,
Filename: header.Filename,
}
err = addUploadToOperations(request, upload, key, path)
if err != nil {
return err
}
}
}
}
}
return nil
}
func addUploadToOperations(request *params, upload graphql.Upload, key, path string) error {
if !strings.HasPrefix(path, "variables.") {
return fmt.Errorf("invalid operations paths for key %s", key)
}
var ptr interface{} = request.Variables
parts := strings.Split(path, ".")
// skip the first part (variables) because we started there
for i, p := range parts[1:] {
last := i == len(parts)-2
if ptr == nil {
return fmt.Errorf("path is missing \"variables.\" prefix, key: %s, path: %s", key, path)
}
if index, parseNbrErr := strconv.Atoi(p); parseNbrErr == nil {
if last {
ptr.([]interface{})[index] = upload
} else {
ptr = ptr.([]interface{})[index]
}
} else {
if last {
ptr.(map[string]interface{})[p] = upload
} else {
ptr = ptr.(map[string]interface{})[p]
}
}
}
return nil
}

View File

@ -1,57 +0,0 @@
package handler
import (
"context"
"github.com/99designs/gqlgen/graphql"
"github.com/vektah/gqlparser"
"github.com/vektah/gqlparser/ast"
)
type executableSchemaMock struct {
MutationFunc func(ctx context.Context, op *ast.OperationDefinition) *graphql.Response
}
var _ graphql.ExecutableSchema = &executableSchemaMock{}
func (e *executableSchemaMock) Schema() *ast.Schema {
return gqlparser.MustLoadSchema(&ast.Source{Input: `
schema { query: Query, mutation: Mutation }
type Query {
empty: String!
}
scalar Upload
type File {
id: Int!
}
input UploadFile {
id: Int!
file: Upload!
}
type Mutation {
singleUpload(file: Upload!): File!
singleUploadWithPayload(req: UploadFile!): File!
multipleUpload(files: [Upload!]!): [File!]!
multipleUploadWithPayload(req: [UploadFile!]!): [File!]!
}
`})
}
func (e *executableSchemaMock) Complexity(typeName, field string, childComplexity int, args map[string]interface{}) (int, bool) {
return 0, false
}
func (e *executableSchemaMock) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
return graphql.ErrorResponse(ctx, "queries are not supported")
}
func (e *executableSchemaMock) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
return e.MutationFunc(ctx, op)
}
func (e *executableSchemaMock) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response {
return func() *graphql.Response {
<-ctx.Done()
return nil
}
}

View File

@ -1,61 +0,0 @@
package handler
import (
"html/template"
"net/http"
)
var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8/>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<link rel="shortcut icon" href="https://graphcool-playground.netlify.com/favicon.png">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/static/css/index.css"
integrity="{{ .cssSRI }}" crossorigin="anonymous"/>
<link rel="shortcut icon" href="https://cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/favicon.png"
integrity="{{ .faviconSRI }}" crossorigin="anonymous"/>
<script src="https://cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/static/js/middleware.js"
integrity="{{ .jsSRI }}" crossorigin="anonymous"></script>
<title>{{.title}}</title>
</head>
<body>
<style type="text/css">
html { font-family: "Open Sans", sans-serif; overflow: hidden; }
body { margin: 0; background: #172a3a; }
</style>
<div id="root"/>
<script type="text/javascript">
window.addEventListener('load', function (event) {
const root = document.getElementById('root');
root.classList.add('playgroundIn');
const wsProto = location.protocol == 'https:' ? 'wss:' : 'ws:'
GraphQLPlayground.init(root, {
endpoint: location.protocol + '//' + location.host + '{{.endpoint}}',
subscriptionsEndpoint: wsProto + '//' + location.host + '{{.endpoint }}',
settings: {
'request.credentials': 'same-origin'
}
})
})
</script>
</body>
</html>
`))
func Playground(title string, endpoint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html")
err := page.Execute(w, map[string]string{
"title": title,
"endpoint": endpoint,
"version": "1.7.20",
"cssSRI": "sha256-cS9Vc2OBt9eUf4sykRWukeFYaInL29+myBmFDSa7F/U=",
"faviconSRI": "sha256-GhTyE+McTU79R4+pRO6ih+4TfsTOrpPwD8ReKFzb3PM=",
"jsSRI": "sha256-4QG1Uza2GgGdlBL3RCBCGtGeZB6bDbsw8OltCMGeJsA=",
})
if err != nil {
panic(err)
}
}
}

View File

@ -1,51 +0,0 @@
package handler
import (
"context"
"github.com/99designs/gqlgen/graphql"
"github.com/vektah/gqlparser"
"github.com/vektah/gqlparser/ast"
)
type executableSchemaStub struct {
NextResp chan struct{}
}
var _ graphql.ExecutableSchema = &executableSchemaStub{}
func (e *executableSchemaStub) Schema() *ast.Schema {
return gqlparser.MustLoadSchema(&ast.Source{Input: `
schema { query: Query }
type Query {
me: User!
user(id: Int): User!
}
type User { name: String! }
`})
}
func (e *executableSchemaStub) Complexity(typeName, field string, childComplexity int, args map[string]interface{}) (int, bool) {
return 0, false
}
func (e *executableSchemaStub) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
return &graphql.Response{Data: []byte(`{"name":"test"}`)}
}
func (e *executableSchemaStub) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response {
return graphql.ErrorResponse(ctx, "mutations are not supported")
}
func (e *executableSchemaStub) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response {
return func() *graphql.Response {
select {
case <-ctx.Done():
return nil
case <-e.NextResp:
return &graphql.Response{
Data: []byte(`{"name":"test"}`),
}
}
}
}

View File

@ -1,323 +0,0 @@
package handler
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"time"
"github.com/99designs/gqlgen/graphql"
"github.com/gorilla/websocket"
lru "github.com/hashicorp/golang-lru"
"github.com/vektah/gqlparser"
"github.com/vektah/gqlparser/ast"
"github.com/vektah/gqlparser/gqlerror"
"github.com/vektah/gqlparser/validator"
)
const (
connectionInitMsg = "connection_init" // Client -> Server
connectionTerminateMsg = "connection_terminate" // Client -> Server
startMsg = "start" // Client -> Server
stopMsg = "stop" // Client -> Server
connectionAckMsg = "connection_ack" // Server -> Client
connectionErrorMsg = "connection_error" // Server -> Client
dataMsg = "data" // Server -> Client
errorMsg = "error" // Server -> Client
completeMsg = "complete" // Server -> Client
connectionKeepAliveMsg = "ka" // Server -> Client
)
type operationMessage struct {
Payload json.RawMessage `json:"payload,omitempty"`
ID string `json:"id,omitempty"`
Type string `json:"type"`
}
type wsConnection struct {
ctx context.Context
conn *websocket.Conn
exec graphql.ExecutableSchema
active map[string]context.CancelFunc
mu sync.Mutex
cfg *Config
cache *lru.Cache
keepAliveTicker *time.Ticker
initPayload InitPayload
}
func connectWs(exec graphql.ExecutableSchema, w http.ResponseWriter, r *http.Request, cfg *Config, cache *lru.Cache) {
ws, err := cfg.upgrader.Upgrade(w, r, http.Header{
"Sec-Websocket-Protocol": []string{"graphql-ws"},
})
if err != nil {
log.Printf("unable to upgrade %T to websocket %s: ", w, err.Error())
sendErrorf(w, http.StatusBadRequest, "unable to upgrade")
return
}
conn := wsConnection{
active: map[string]context.CancelFunc{},
exec: exec,
conn: ws,
ctx: r.Context(),
cfg: cfg,
cache: cache,
}
if !conn.init() {
return
}
conn.run()
}
func (c *wsConnection) init() bool {
message := c.readOp()
if message == nil {
c.close(websocket.CloseProtocolError, "decoding error")
return false
}
switch message.Type {
case connectionInitMsg:
if len(message.Payload) > 0 {
c.initPayload = make(InitPayload)
err := json.Unmarshal(message.Payload, &c.initPayload)
if err != nil {
return false
}
}
if c.cfg.websocketInitFunc != nil {
if err := c.cfg.websocketInitFunc(c.ctx, c.initPayload); err != nil {
c.sendConnectionError(err.Error())
c.close(websocket.CloseNormalClosure, "terminated")
return false
}
}
c.write(&operationMessage{Type: connectionAckMsg})
c.write(&operationMessage{Type: connectionKeepAliveMsg})
case connectionTerminateMsg:
c.close(websocket.CloseNormalClosure, "terminated")
return false
default:
c.sendConnectionError("unexpected message %s", message.Type)
c.close(websocket.CloseProtocolError, "unexpected message")
return false
}
return true
}
func (c *wsConnection) write(msg *operationMessage) {
c.mu.Lock()
c.conn.WriteJSON(msg)
c.mu.Unlock()
}
func (c *wsConnection) run() {
// We create a cancellation that will shutdown the keep-alive when we leave
// this function.
ctx, cancel := context.WithCancel(c.ctx)
defer cancel()
// Create a timer that will fire every interval to keep the connection alive.
if c.cfg.connectionKeepAlivePingInterval != 0 {
c.mu.Lock()
c.keepAliveTicker = time.NewTicker(c.cfg.connectionKeepAlivePingInterval)
c.mu.Unlock()
go c.keepAlive(ctx)
}
for {
message := c.readOp()
if message == nil {
return
}
switch message.Type {
case startMsg:
if !c.subscribe(message) {
return
}
case stopMsg:
c.mu.Lock()
closer := c.active[message.ID]
c.mu.Unlock()
if closer == nil {
c.sendError(message.ID, gqlerror.Errorf("%s is not running, cannot stop", message.ID))
continue
}
closer()
case connectionTerminateMsg:
c.close(websocket.CloseNormalClosure, "terminated")
return
default:
c.sendConnectionError("unexpected message %s", message.Type)
c.close(websocket.CloseProtocolError, "unexpected message")
return
}
}
}
func (c *wsConnection) keepAlive(ctx context.Context) {
for {
select {
case <-ctx.Done():
c.keepAliveTicker.Stop()
return
case <-c.keepAliveTicker.C:
c.write(&operationMessage{Type: connectionKeepAliveMsg})
}
}
}
func (c *wsConnection) subscribe(message *operationMessage) bool {
var reqParams params
if err := jsonDecode(bytes.NewReader(message.Payload), &reqParams); err != nil {
c.sendConnectionError("invalid json")
return false
}
var (
doc *ast.QueryDocument
cacheHit bool
)
if c.cache != nil {
val, ok := c.cache.Get(reqParams.Query)
if ok {
doc = val.(*ast.QueryDocument)
cacheHit = true
}
}
if !cacheHit {
var qErr gqlerror.List
doc, qErr = gqlparser.LoadQuery(c.exec.Schema(), reqParams.Query)
if qErr != nil {
c.sendError(message.ID, qErr...)
return true
}
if c.cache != nil {
c.cache.Add(reqParams.Query, doc)
}
}
op := doc.Operations.ForName(reqParams.OperationName)
if op == nil {
c.sendError(message.ID, gqlerror.Errorf("operation %s not found", reqParams.OperationName))
return true
}
vars, err := validator.VariableValues(c.exec.Schema(), op, reqParams.Variables)
if err != nil {
c.sendError(message.ID, err)
return true
}
reqCtx := c.cfg.newRequestContext(c.exec, doc, op, reqParams.Query, vars)
ctx := graphql.WithRequestContext(c.ctx, reqCtx)
if c.initPayload != nil {
ctx = withInitPayload(ctx, c.initPayload)
}
if op.Operation != ast.Subscription {
var result *graphql.Response
if op.Operation == ast.Query {
result = c.exec.Query(ctx, op)
} else {
result = c.exec.Mutation(ctx, op)
}
c.sendData(message.ID, result)
c.write(&operationMessage{ID: message.ID, Type: completeMsg})
return true
}
ctx, cancel := context.WithCancel(ctx)
c.mu.Lock()
c.active[message.ID] = cancel
c.mu.Unlock()
go func() {
defer func() {
if r := recover(); r != nil {
userErr := reqCtx.Recover(ctx, r)
c.sendError(message.ID, &gqlerror.Error{Message: userErr.Error()})
}
}()
next := c.exec.Subscription(ctx, op)
for result := next(); result != nil; result = next() {
c.sendData(message.ID, result)
}
c.write(&operationMessage{ID: message.ID, Type: completeMsg})
c.mu.Lock()
delete(c.active, message.ID)
c.mu.Unlock()
cancel()
}()
return true
}
func (c *wsConnection) sendData(id string, response *graphql.Response) {
b, err := json.Marshal(response)
if err != nil {
c.sendError(id, gqlerror.Errorf("unable to encode json response: %s", err.Error()))
return
}
c.write(&operationMessage{Type: dataMsg, ID: id, Payload: b})
}
func (c *wsConnection) sendError(id string, errors ...*gqlerror.Error) {
errs := make([]error, len(errors))
for i, err := range errors {
errs[i] = err
}
b, err := json.Marshal(errs)
if err != nil {
panic(err)
}
c.write(&operationMessage{Type: errorMsg, ID: id, Payload: b})
}
func (c *wsConnection) sendConnectionError(format string, args ...interface{}) {
b, err := json.Marshal(&gqlerror.Error{Message: fmt.Sprintf(format, args...)})
if err != nil {
panic(err)
}
c.write(&operationMessage{Type: connectionErrorMsg, Payload: b})
}
func (c *wsConnection) readOp() *operationMessage {
_, r, err := c.conn.NextReader()
if err != nil {
c.sendConnectionError("invalid json")
return nil
}
message := operationMessage{}
if err := jsonDecode(r, &message); err != nil {
c.sendConnectionError("invalid json")
return nil
}
return &message
}
func (c *wsConnection) close(closeCode int, message string) {
c.mu.Lock()
_ = c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(closeCode, message))
c.mu.Unlock()
_ = c.conn.Close()
}

View File

@ -1,163 +0,0 @@
package code
import (
"fmt"
"go/types"
)
// CompatibleTypes isnt a strict comparison, it allows for pointer differences
func CompatibleTypes(expected types.Type, actual types.Type) error {
//fmt.Println("Comparing ", expected.String(), actual.String())
// Special case to deal with pointer mismatches
{
expectedPtr, expectedIsPtr := expected.(*types.Pointer)
actualPtr, actualIsPtr := actual.(*types.Pointer)
if expectedIsPtr && actualIsPtr {
return CompatibleTypes(expectedPtr.Elem(), actualPtr.Elem())
}
if expectedIsPtr && !actualIsPtr {
return CompatibleTypes(expectedPtr.Elem(), actual)
}
if !expectedIsPtr && actualIsPtr {
return CompatibleTypes(expected, actualPtr.Elem())
}
}
switch expected := expected.(type) {
case *types.Slice:
if actual, ok := actual.(*types.Slice); ok {
return CompatibleTypes(expected.Elem(), actual.Elem())
}
case *types.Array:
if actual, ok := actual.(*types.Array); ok {
if expected.Len() != actual.Len() {
return fmt.Errorf("array length differs")
}
return CompatibleTypes(expected.Elem(), actual.Elem())
}
case *types.Basic:
if actual, ok := actual.(*types.Basic); ok {
if actual.Kind() != expected.Kind() {
return fmt.Errorf("basic kind differs, %s != %s", expected.Name(), actual.Name())
}
return nil
}
case *types.Struct:
if actual, ok := actual.(*types.Struct); ok {
if expected.NumFields() != actual.NumFields() {
return fmt.Errorf("number of struct fields differ")
}
for i := 0; i < expected.NumFields(); i++ {
if expected.Field(i).Name() != actual.Field(i).Name() {
return fmt.Errorf("struct field %d name differs, %s != %s", i, expected.Field(i).Name(), actual.Field(i).Name())
}
if err := CompatibleTypes(expected.Field(i).Type(), actual.Field(i).Type()); err != nil {
return err
}
}
return nil
}
case *types.Tuple:
if actual, ok := actual.(*types.Tuple); ok {
if expected.Len() != actual.Len() {
return fmt.Errorf("tuple length differs, %d != %d", expected.Len(), actual.Len())
}
for i := 0; i < expected.Len(); i++ {
if err := CompatibleTypes(expected.At(i).Type(), actual.At(i).Type()); err != nil {
return err
}
}
return nil
}
case *types.Signature:
if actual, ok := actual.(*types.Signature); ok {
if err := CompatibleTypes(expected.Params(), actual.Params()); err != nil {
return err
}
if err := CompatibleTypes(expected.Results(), actual.Results()); err != nil {
return err
}
return nil
}
case *types.Interface:
if actual, ok := actual.(*types.Interface); ok {
if expected.NumMethods() != actual.NumMethods() {
return fmt.Errorf("interface method count differs, %d != %d", expected.NumMethods(), actual.NumMethods())
}
for i := 0; i < expected.NumMethods(); i++ {
if expected.Method(i).Name() != actual.Method(i).Name() {
return fmt.Errorf("interface method %d name differs, %s != %s", i, expected.Method(i).Name(), actual.Method(i).Name())
}
if err := CompatibleTypes(expected.Method(i).Type(), actual.Method(i).Type()); err != nil {
return err
}
}
return nil
}
case *types.Map:
if actual, ok := actual.(*types.Map); ok {
if err := CompatibleTypes(expected.Key(), actual.Key()); err != nil {
return err
}
if err := CompatibleTypes(expected.Elem(), actual.Elem()); err != nil {
return err
}
return nil
}
case *types.Chan:
if actual, ok := actual.(*types.Chan); ok {
return CompatibleTypes(expected.Elem(), actual.Elem())
}
case *types.Named:
if actual, ok := actual.(*types.Named); ok {
if NormalizeVendor(expected.Obj().Pkg().Path()) != NormalizeVendor(actual.Obj().Pkg().Path()) {
return fmt.Errorf(
"package name of named type differs, %s != %s",
NormalizeVendor(expected.Obj().Pkg().Path()),
NormalizeVendor(actual.Obj().Pkg().Path()),
)
}
if expected.Obj().Name() != actual.Obj().Name() {
return fmt.Errorf(
"named type name differs, %s != %s",
NormalizeVendor(expected.Obj().Name()),
NormalizeVendor(actual.Obj().Name()),
)
}
return nil
}
// Before models are generated all missing references will be Invalid Basic references.
// lets assume these are valid too.
if actual, ok := actual.(*types.Basic); ok && actual.Kind() == types.Invalid {
return nil
}
default:
return fmt.Errorf("missing support for %T", expected)
}
return fmt.Errorf("type mismatch %T != %T", expected, actual)
}

View File

@ -1,115 +0,0 @@
package code
import (
"errors"
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
"sync"
"golang.org/x/tools/go/packages"
)
var nameForPackageCache = sync.Map{}
var gopaths []string
func init() {
gopaths = filepath.SplitList(build.Default.GOPATH)
for i, p := range gopaths {
gopaths[i] = filepath.ToSlash(filepath.Join(p, "src"))
}
}
// NameForDir manually looks for package stanzas in files located in the given directory. This can be
// much faster than having to consult go list, because we already know exactly where to look.
func NameForDir(dir string) string {
dir, err := filepath.Abs(dir)
if err != nil {
return SanitizePackageName(filepath.Base(dir))
}
files, err := ioutil.ReadDir(dir)
if err != nil {
return SanitizePackageName(filepath.Base(dir))
}
fset := token.NewFileSet()
for _, file := range files {
if !strings.HasSuffix(strings.ToLower(file.Name()), ".go") {
continue
}
filename := filepath.Join(dir, file.Name())
if src, err := parser.ParseFile(fset, filename, nil, parser.PackageClauseOnly); err == nil {
return src.Name.Name
}
}
return SanitizePackageName(filepath.Base(dir))
}
// ImportPathForDir takes a path and returns a golang import path for the package
func ImportPathForDir(dir string) (res string) {
dir, err := filepath.Abs(dir)
if err != nil {
panic(err)
}
dir = filepath.ToSlash(dir)
modDir := dir
assumedPart := ""
for {
f, err := ioutil.ReadFile(filepath.Join(modDir, "go.mod"))
if err == nil {
// found it, stop searching
return string(modregex.FindSubmatch(f)[1]) + assumedPart
}
assumedPart = "/" + filepath.Base(modDir) + assumedPart
parentDir, err := filepath.Abs(filepath.Join(modDir, ".."))
if err != nil {
panic(err)
}
if parentDir == modDir {
// Walked all the way to the root and didnt find anything :'(
break
}
modDir = parentDir
}
for _, gopath := range gopaths {
if len(gopath) < len(dir) && strings.EqualFold(gopath, dir[0:len(gopath)]) {
return dir[len(gopath)+1:]
}
}
return ""
}
var modregex = regexp.MustCompile("module (.*)\n")
// NameForPackage returns the package name for a given import path. This can be really slow.
func NameForPackage(importPath string) string {
if importPath == "" {
panic(errors.New("import path can not be empty"))
}
if v, ok := nameForPackageCache.Load(importPath); ok {
return v.(string)
}
importPath = QualifyPackagePath(importPath)
p, _ := packages.Load(&packages.Config{
Mode: packages.NeedName,
}, importPath)
if len(p) != 1 || p[0].Name == "" {
return SanitizePackageName(filepath.Base(importPath))
}
nameForPackageCache.Store(importPath, p[0].Name)
return p[0].Name
}

View File

@ -1,56 +0,0 @@
package code
import (
"go/build"
"os"
"path/filepath"
"regexp"
"strings"
)
// take a string in the form github.com/package/blah.Type and split it into package and type
func PkgAndType(name string) (string, string) {
parts := strings.Split(name, ".")
if len(parts) == 1 {
return "", name
}
return strings.Join(parts[:len(parts)-1], "."), parts[len(parts)-1]
}
var modsRegex = regexp.MustCompile(`^(\*|\[\])*`)
// NormalizeVendor takes a qualified package path and turns it into normal one.
// eg .
// github.com/foo/vendor/github.com/99designs/gqlgen/graphql becomes
// github.com/99designs/gqlgen/graphql
func NormalizeVendor(pkg string) string {
modifiers := modsRegex.FindAllString(pkg, 1)[0]
pkg = strings.TrimPrefix(pkg, modifiers)
parts := strings.Split(pkg, "/vendor/")
return modifiers + parts[len(parts)-1]
}
// QualifyPackagePath takes an import and fully qualifies it with a vendor dir, if one is required.
// eg .
// github.com/99designs/gqlgen/graphql becomes
// github.com/foo/vendor/github.com/99designs/gqlgen/graphql
//
// x/tools/packages only supports 'qualified package paths' so this will need to be done prior to calling it
// See https://github.com/golang/go/issues/30289
func QualifyPackagePath(importPath string) string {
wd, _ := os.Getwd()
pkg, err := build.Import(importPath, wd, 0)
if err != nil {
return importPath
}
return pkg.ImportPath
}
var invalidPackageNameChar = regexp.MustCompile(`[^\w]`)
func SanitizePackageName(pkg string) string {
return invalidPackageNameChar.ReplaceAllLiteralString(filepath.Base(pkg), "_")
}

View File

@ -1,100 +0,0 @@
// Wrapper around x/tools/imports that only removes imports, never adds new ones.
package imports
import (
"bytes"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"strings"
"github.com/99designs/gqlgen/internal/code"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/imports"
)
type visitFn func(node ast.Node)
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
fn(node)
return fn
}
// Prune removes any unused imports
func Prune(filename string, src []byte) ([]byte, error) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, filename, src, parser.ParseComments|parser.AllErrors)
if err != nil {
return nil, err
}
unused := getUnusedImports(file)
for ipath, name := range unused {
astutil.DeleteNamedImport(fset, file, name, ipath)
}
printConfig := &printer.Config{Mode: printer.TabIndent, Tabwidth: 8}
var buf bytes.Buffer
if err := printConfig.Fprint(&buf, fset, file); err != nil {
return nil, err
}
return imports.Process(filename, buf.Bytes(), &imports.Options{FormatOnly: true, Comments: true, TabIndent: true, TabWidth: 8})
}
func getUnusedImports(file ast.Node) map[string]string {
imported := map[string]*ast.ImportSpec{}
used := map[string]bool{}
ast.Walk(visitFn(func(node ast.Node) {
if node == nil {
return
}
switch v := node.(type) {
case *ast.ImportSpec:
if v.Name != nil {
imported[v.Name.Name] = v
break
}
ipath := strings.Trim(v.Path.Value, `"`)
if ipath == "C" {
break
}
local := code.NameForPackage(ipath)
imported[local] = v
case *ast.SelectorExpr:
xident, ok := v.X.(*ast.Ident)
if !ok {
break
}
if xident.Obj != nil {
// if the parser can resolve it, it's not a package ref
break
}
used[xident.Name] = true
}
}), file)
for pkg := range used {
delete(imported, pkg)
}
unusedImport := map[string]string{}
for pkg, is := range imported {
if !used[pkg] && pkg != "_" && pkg != "." {
name := ""
if is.Name != nil {
name = is.Name.Name
}
unusedImport[strings.Trim(is.Path.Value, `"`)] = name
}
}
return unusedImport
}

View File

@ -1,237 +0,0 @@
package modelgen
import (
"fmt"
"go/types"
"sort"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/internal/code"
"github.com/99designs/gqlgen/plugin"
"github.com/vektah/gqlparser/ast"
)
type ModelBuild struct {
PackageName string
Interfaces []*Interface
Models []*Object
Enums []*Enum
Scalars []string
}
type Interface struct {
Description string
Name string
}
type Object struct {
Description string
Name string
Fields []*Field
Implements []string
}
type Field struct {
Description string
Name string
Type types.Type
Tag string
}
type Enum struct {
Description string
Name string
Values []*EnumValue
}
type EnumValue struct {
Description string
Name string
}
func New() plugin.Plugin {
return &Plugin{}
}
type Plugin struct{}
var _ plugin.ConfigMutator = &Plugin{}
func (m *Plugin) Name() string {
return "modelgen"
}
func (m *Plugin) MutateConfig(cfg *config.Config) error {
if err := cfg.Check(); err != nil {
return err
}
schema, _, err := cfg.LoadSchema()
if err != nil {
return err
}
err = cfg.Autobind(schema)
if err != nil {
return err
}
cfg.InjectBuiltins(schema)
binder, err := cfg.NewBinder(schema)
if err != nil {
return err
}
b := &ModelBuild{
PackageName: cfg.Model.Package,
}
for _, schemaType := range schema.Types {
if cfg.Models.UserDefined(schemaType.Name) {
continue
}
switch schemaType.Kind {
case ast.Interface, ast.Union:
it := &Interface{
Description: schemaType.Description,
Name: schemaType.Name,
}
b.Interfaces = append(b.Interfaces, it)
case ast.Object, ast.InputObject:
if schemaType == schema.Query || schemaType == schema.Mutation || schemaType == schema.Subscription {
continue
}
it := &Object{
Description: schemaType.Description,
Name: schemaType.Name,
}
for _, implementor := range schema.GetImplements(schemaType) {
it.Implements = append(it.Implements, implementor.Name)
}
for _, field := range schemaType.Fields {
var typ types.Type
fieldDef := schema.Types[field.Type.Name()]
if cfg.Models.UserDefined(field.Type.Name()) {
pkg, typeName := code.PkgAndType(cfg.Models[field.Type.Name()].Model[0])
typ, err = binder.FindType(pkg, typeName)
if err != nil {
return err
}
} else {
switch fieldDef.Kind {
case ast.Scalar:
// no user defined model, referencing a default scalar
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), "string", nil),
nil,
nil,
)
case ast.Interface, ast.Union:
// no user defined model, referencing a generated interface type
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil),
types.NewInterfaceType([]*types.Func{}, []types.Type{}),
nil,
)
case ast.Enum:
// no user defined model, must reference a generated enum
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil),
nil,
nil,
)
case ast.Object, ast.InputObject:
// no user defined model, must reference a generated struct
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil),
types.NewStruct(nil, nil),
nil,
)
default:
panic(fmt.Errorf("unknown ast type %s", fieldDef.Kind))
}
}
name := field.Name
if nameOveride := cfg.Models[schemaType.Name].Fields[field.Name].FieldName; nameOveride != "" {
name = nameOveride
}
typ = binder.CopyModifiersFromAst(field.Type, typ)
if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) {
typ = types.NewPointer(typ)
}
it.Fields = append(it.Fields, &Field{
Name: name,
Type: typ,
Description: field.Description,
Tag: `json:"` + field.Name + `"`,
})
}
b.Models = append(b.Models, it)
case ast.Enum:
it := &Enum{
Name: schemaType.Name,
Description: schemaType.Description,
}
for _, v := range schemaType.EnumValues {
it.Values = append(it.Values, &EnumValue{
Name: v.Name,
Description: v.Description,
})
}
b.Enums = append(b.Enums, it)
case ast.Scalar:
b.Scalars = append(b.Scalars, schemaType.Name)
}
}
sort.Slice(b.Enums, func(i, j int) bool { return b.Enums[i].Name < b.Enums[j].Name })
sort.Slice(b.Models, func(i, j int) bool { return b.Models[i].Name < b.Models[j].Name })
sort.Slice(b.Interfaces, func(i, j int) bool { return b.Interfaces[i].Name < b.Interfaces[j].Name })
for _, it := range b.Enums {
cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+templates.ToGo(it.Name))
}
for _, it := range b.Models {
cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+templates.ToGo(it.Name))
}
for _, it := range b.Interfaces {
cfg.Models.Add(it.Name, cfg.Model.ImportPath()+"."+templates.ToGo(it.Name))
}
for _, it := range b.Scalars {
cfg.Models.Add(it, "github.com/99designs/gqlgen/graphql.String")
}
if len(b.Models) == 0 && len(b.Enums) == 0 {
return nil
}
return templates.Render(templates.Options{
PackageName: cfg.Model.Package,
Filename: cfg.Model.Filename,
Data: b,
GeneratedHeader: true,
})
}
func isStruct(t types.Type) bool {
_, is := t.Underlying().(*types.Struct)
return is
}

View File

@ -1,85 +0,0 @@
{{ reserveImport "context" }}
{{ reserveImport "fmt" }}
{{ reserveImport "io" }}
{{ reserveImport "strconv" }}
{{ reserveImport "time" }}
{{ reserveImport "sync" }}
{{ reserveImport "errors" }}
{{ reserveImport "bytes" }}
{{ reserveImport "github.com/vektah/gqlparser" }}
{{ reserveImport "github.com/vektah/gqlparser/ast" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }}
{{- range $model := .Interfaces }}
{{ with .Description }} {{.|prefixLines "// "}} {{ end }}
type {{.Name|go }} interface {
Is{{.Name|go }}()
}
{{- end }}
{{ range $model := .Models }}
{{with .Description }} {{.|prefixLines "// "}} {{end}}
type {{ .Name|go }} struct {
{{- range $field := .Fields }}
{{- with .Description }}
{{.|prefixLines "// "}}
{{- end}}
{{ $field.Name|go }} {{$field.Type | ref}} `{{$field.Tag}}`
{{- end }}
}
{{- range $iface := .Implements }}
func ({{ $model.Name|go }}) Is{{ $iface|go }}() {}
{{- end }}
{{- end}}
{{ range $enum := .Enums }}
{{ with .Description }} {{.|prefixLines "// "}} {{end}}
type {{.Name|go }} string
const (
{{- range $value := .Values}}
{{- with .Description}}
{{.|prefixLines "// "}}
{{- end}}
{{ $enum.Name|go }}{{ .Name|go }} {{$enum.Name|go }} = {{.Name|quote}}
{{- end }}
)
var All{{.Name|go }} = []{{ .Name|go }}{
{{- range $value := .Values}}
{{$enum.Name|go }}{{ .Name|go }},
{{- end }}
}
func (e {{.Name|go }}) IsValid() bool {
switch e {
case {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ $enum.Name|go }}{{ $element.Name|go }}{{end}}:
return true
}
return false
}
func (e {{.Name|go }}) String() string {
return string(e)
}
func (e *{{.Name|go }}) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = {{ .Name|go }}(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid {{ .Name }}", str)
}
return nil
}
func (e {{.Name|go }}) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
{{- end }}

View File

@ -1,20 +0,0 @@
// plugin package interfaces are EXPERIMENTAL.
package plugin
import (
"github.com/99designs/gqlgen/codegen"
"github.com/99designs/gqlgen/codegen/config"
)
type Plugin interface {
Name() string
}
type ConfigMutator interface {
MutateConfig(cfg *config.Config) error
}
type CodeGenerator interface {
GenerateCode(cfg *codegen.Data) error
}

View File

@ -1,54 +0,0 @@
package resolvergen
import (
"log"
"os"
"github.com/99designs/gqlgen/codegen"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/plugin"
"github.com/pkg/errors"
)
func New() plugin.Plugin {
return &Plugin{}
}
type Plugin struct{}
var _ plugin.CodeGenerator = &Plugin{}
func (m *Plugin) Name() string {
// TODO: typo, should be resolvergen
return "resovlergen"
}
func (m *Plugin) GenerateCode(data *codegen.Data) error {
if !data.Config.Resolver.IsDefined() {
return nil
}
resolverBuild := &ResolverBuild{
Data: data,
PackageName: data.Config.Resolver.Package,
ResolverType: data.Config.Resolver.Type,
}
filename := data.Config.Resolver.Filename
if _, err := os.Stat(filename); os.IsNotExist(errors.Cause(err)) {
return templates.Render(templates.Options{
PackageName: data.Config.Resolver.Package,
Filename: data.Config.Resolver.Filename,
Data: resolverBuild,
})
}
log.Printf("Skipped resolver: %s already exists\n", filename)
return nil
}
type ResolverBuild struct {
*codegen.Data
PackageName string
ResolverType string
}

View File

@ -1,40 +0,0 @@
// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
{{ reserveImport "context" }}
{{ reserveImport "fmt" }}
{{ reserveImport "io" }}
{{ reserveImport "strconv" }}
{{ reserveImport "time" }}
{{ reserveImport "sync" }}
{{ reserveImport "errors" }}
{{ reserveImport "bytes" }}
{{ reserveImport "github.com/99designs/gqlgen/handler" }}
{{ reserveImport "github.com/vektah/gqlparser" }}
{{ reserveImport "github.com/vektah/gqlparser/ast" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }}
type {{.ResolverType}} struct {}
{{ range $object := .Objects -}}
{{- if $object.HasResolvers -}}
func (r *{{$.ResolverType}}) {{$object.Name}}() {{ $object.ResolverInterface | ref }} {
return &{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}{r}
}
{{ end -}}
{{ end }}
{{ range $object := .Objects -}}
{{- if $object.HasResolvers -}}
type {{lcFirst $object.Name}}{{ucFirst $.ResolverType}} struct { *{{$.ResolverType}} }
{{ range $field := $object.Fields -}}
{{- if $field.IsResolver -}}
func (r *{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}) {{$field.GoFieldName}}{{ $field.ShortResolverDeclaration }} {
panic("not implemented")
}
{{ end -}}
{{ end -}}
{{ end -}}
{{ end }}

View File

@ -1,93 +0,0 @@
package schemaconfig
import (
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin"
"github.com/vektah/gqlparser/ast"
)
func New() plugin.Plugin {
return &Plugin{}
}
type Plugin struct{}
var _ plugin.ConfigMutator = &Plugin{}
func (m *Plugin) Name() string {
return "schemaconfig"
}
func (m *Plugin) MutateConfig(cfg *config.Config) error {
if err := cfg.Check(); err != nil {
return err
}
schema, _, err := cfg.LoadSchema()
if err != nil {
return err
}
cfg.Directives["goModel"] = config.DirectiveConfig{
SkipRuntime: true,
}
cfg.Directives["goField"] = config.DirectiveConfig{
SkipRuntime: true,
}
for _, schemaType := range schema.Types {
if schemaType == schema.Query || schemaType == schema.Mutation || schemaType == schema.Subscription {
continue
}
if bd := schemaType.Directives.ForName("goModel"); bd != nil {
if ma := bd.Arguments.ForName("model"); ma != nil {
if mv, err := ma.Value.Value(nil); err == nil {
cfg.Models.Add(schemaType.Name, mv.(string))
}
}
if ma := bd.Arguments.ForName("models"); ma != nil {
if mvs, err := ma.Value.Value(nil); err == nil {
for _, mv := range mvs.([]interface{}) {
cfg.Models.Add(schemaType.Name, mv.(string))
}
}
}
}
if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject {
for _, field := range schemaType.Fields {
if fd := field.Directives.ForName("goField"); fd != nil {
forceResolver := cfg.Models[schemaType.Name].Fields[field.Name].Resolver
fieldName := cfg.Models[schemaType.Name].Fields[field.Name].FieldName
if ra := fd.Arguments.ForName("forceResolver"); ra != nil {
if fr, err := ra.Value.Value(nil); err == nil {
forceResolver = fr.(bool)
}
}
if na := fd.Arguments.ForName("name"); na != nil {
if fr, err := na.Value.Value(nil); err == nil {
fieldName = fr.(string)
}
}
if cfg.Models[schemaType.Name].Fields == nil {
cfg.Models[schemaType.Name] = config.TypeMapEntry{
Model: cfg.Models[schemaType.Name].Model,
Fields: map[string]config.TypeMapField{},
}
}
cfg.Models[schemaType.Name].Fields[field.Name] = config.TypeMapField{
FieldName: fieldName,
Resolver: forceResolver,
}
}
}
}
}
return nil
}

View File

@ -1 +0,0 @@
.idea/

View File

@ -1,16 +0,0 @@
language: go
go:
- 1.11.x
- 1.12.x
- 1.13.x
env:
- GO111MODULE=on
script:
- go build
- go test -v -bench=. -race -coverprofile=coverage.txt -covermode=atomic ./...
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

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

View File

@ -1,74 +0,0 @@
# go-term-text
[![Build Status](https://travis-ci.org/MichaelMure/go-term-text.svg?branch=master)](https://travis-ci.org/MichaelMure/go-term-text)
[![GoDoc](https://godoc.org/github.com/MichaelMure/go-term-text?status.svg)](https://godoc.org/github.com/MichaelMure/go-term-text)
[![Go Report Card](https://goreportcard.com/badge/github.com/MichaelMure/go-term-text)](https://goreportcard.com/report/github.com/MichaelMure/go-term-text)
[![codecov](https://codecov.io/gh/MichaelMure/go-term-text/branch/master/graph/badge.svg)](https://codecov.io/gh/MichaelMure/go-term-text)
[![GitHub license](https://img.shields.io/github/license/MichaelMure/go-term-text.svg)](https://github.com/MichaelMure/go-term-text/blob/master/LICENSE)
[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/the-git-bug/Lobby)
`go-term-text` is a go package implementing a collection of algorithms to help format and manipulate text for the terminal.
In particular, `go-term-text`:
- support wide characters (chinese, japanese ...) and emoji
- handle properly ANSI escape sequences
Included algorithms cover:
- wrapping with padding and indentation
- padding
- text length
- trimming
- alignment
- escape sequence extraction and reapplication
- escape sequence snapshot and simplification
- truncation
## Example
```go
package main
import (
"fmt"
"strings"
"github.com/MichaelMure/go-term-text"
)
func main() {
input := "The \x1b[1mLorem ipsum\x1b[0m text is typically composed of " +
"pseudo-Latin words. It is commonly used as \x1b[3mplaceholder\x1b[0m" +
" text to examine or demonstrate the \x1b[9mvisual effects\x1b[0m of " +
"various graphic design. 一只 A Quick \x1b[31m敏捷的狐 Fox " +
"狸跳过了\x1b[0mDog一只懒狗。"
output, n := text.Wrap(input, 60,
text.WrapIndent("\x1b[34m<-indent-> \x1b[0m"),
text.WrapPad("\x1b[33m<-pad-> \x1b[0m"),
)
fmt.Printf("output has %d lines\n\n", n)
fmt.Println("|" + strings.Repeat("-", 58) + "|")
fmt.Println(output)
fmt.Println("|" + strings.Repeat("-", 58) + "|")
}
```
This will print:
![example output](/img/example.png)
For more details, have a look at the [GoDoc](https://godoc.org/github.com/MichaelMure/go-term-text).
## Origin
This package has been extracted from the [git-bug](https://github.com/MichaelMure/git-bug) project. As such, its aim is to support this project and not to provide an all-in-one solution. Contributions as welcome though.
## Contribute
PRs accepted.
## License
MIT

View File

@ -1,67 +0,0 @@
package text
import (
"strings"
)
type Alignment int
const (
NoAlign Alignment = iota
AlignLeft
AlignCenter
AlignRight
)
// LineAlign align the given line as asked and apply the needed padding to match the given
// lineWidth, while ignoring the terminal escape sequences.
// If the given lineWidth is too small to fit the given line, it's returned without
// padding, overflowing lineWidth.
func LineAlign(line string, lineWidth int, align Alignment) string {
switch align {
case NoAlign:
return line
case AlignLeft:
return LineAlignLeft(line, lineWidth)
case AlignCenter:
return LineAlignCenter(line, lineWidth)
case AlignRight:
return LineAlignRight(line, lineWidth)
}
panic("unknown alignment")
}
// LineAlignLeft align the given line on the left while ignoring the terminal escape sequences.
// If the given lineWidth is too small to fit the given line, it's returned without
// padding, overflowing lineWidth.
func LineAlignLeft(line string, lineWidth int) string {
return TrimSpace(line)
}
// LineAlignCenter align the given line on the center and apply the needed left
// padding, while ignoring the terminal escape sequences.
// If the given lineWidth is too small to fit the given line, it's returned without
// padding, overflowing lineWidth.
func LineAlignCenter(line string, lineWidth int) string {
trimmed := TrimSpace(line)
totalPadLen := lineWidth - Len(trimmed)
if totalPadLen < 0 {
totalPadLen = 0
}
pad := strings.Repeat(" ", totalPadLen/2)
return pad + trimmed
}
// LineAlignRight align the given line on the right and apply the needed left
// padding to match the given lineWidth, while ignoring the terminal escape sequences.
// If the given lineWidth is too small to fit the given line, it's returned without
// padding, overflowing lineWidth.
func LineAlignRight(line string, lineWidth int) string {
trimmed := TrimSpace(line)
padLen := lineWidth - Len(trimmed)
if padLen < 0 {
padLen = 0
}
pad := strings.Repeat(" ", padLen)
return pad + trimmed
}

View File

@ -1,265 +0,0 @@
package text
import (
"fmt"
"strconv"
"strings"
)
const Escape = '\x1b'
type EscapeState struct {
Bold bool
Dim bool
Italic bool
Underlined bool
Blink bool
Reverse bool
Hidden bool
CrossedOut bool
FgColor Color
BgColor Color
}
type Color interface {
Codes() []string
}
func (es *EscapeState) Witness(s string) {
inEscape := false
var start int
runes := []rune(s)
for i, r := range runes {
if r == Escape {
inEscape = true
start = i
continue
}
if inEscape {
if r == 'm' {
inEscape = false
es.witnessCode(string(runes[start+1 : i]))
}
continue
}
}
}
func (es *EscapeState) witnessCode(s string) {
if s == "" {
return
}
if s == "[" {
es.reset()
return
}
if len(s) < 2 {
return
}
if s[0] != '[' {
return
}
s = s[1:]
split := strings.Split(s, ";")
dequeue := func() {
split = split[1:]
}
color := func(ground int) Color {
if len(split) < 1 {
// the whole sequence is broken, ignoring the rest
return nil
}
subCode := split[0]
dequeue()
switch subCode {
case "2":
if len(split) < 3 {
return nil
}
r, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
g, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
b, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
return &ColorRGB{ground: ground, R: r, G: g, B: b}
case "5":
if len(split) < 1 {
return nil
}
index, err := strconv.Atoi(split[0])
dequeue()
if err != nil {
return nil
}
return &Color256{ground: ground, Index: index}
}
return nil
}
for len(split) > 0 {
code, err := strconv.Atoi(split[0])
if err != nil {
return
}
dequeue()
switch {
case code == 0:
es.reset()
case code == 1:
es.Bold = true
case code == 2:
es.Dim = true
case code == 3:
es.Italic = true
case code == 4:
es.Underlined = true
case code == 5:
es.Blink = true
// case code == 6:
case code == 7:
es.Reverse = true
case code == 8:
es.Hidden = true
case code == 9:
es.CrossedOut = true
case code == 21:
es.Bold = false
case code == 22:
es.Dim = false
case code == 23:
es.Italic = false
case code == 24:
es.Underlined = false
case code == 25:
es.Blink = false
// case code == 26:
case code == 27:
es.Reverse = false
case code == 28:
es.Hidden = false
case code == 29:
es.CrossedOut = false
case (code >= 30 && code <= 37) || code == 39 || (code >= 90 && code <= 97):
es.FgColor = ColorIndex(code)
case (code >= 40 && code <= 47) || code == 49 || (code >= 100 && code <= 107):
es.BgColor = ColorIndex(code)
case code == 38:
es.FgColor = color(code)
if es.FgColor == nil {
return
}
case code == 48:
es.BgColor = color(code)
if es.BgColor == nil {
return
}
}
}
}
func (es *EscapeState) reset() {
*es = EscapeState{}
}
func (es *EscapeState) String() string {
var codes []string
if es.Bold {
codes = append(codes, strconv.Itoa(1))
}
if es.Dim {
codes = append(codes, strconv.Itoa(2))
}
if es.Italic {
codes = append(codes, strconv.Itoa(3))
}
if es.Underlined {
codes = append(codes, strconv.Itoa(4))
}
if es.Blink {
codes = append(codes, strconv.Itoa(5))
}
if es.Reverse {
codes = append(codes, strconv.Itoa(7))
}
if es.Hidden {
codes = append(codes, strconv.Itoa(8))
}
if es.CrossedOut {
codes = append(codes, strconv.Itoa(9))
}
if es.FgColor != nil {
codes = append(codes, es.FgColor.Codes()...)
}
if es.BgColor != nil {
codes = append(codes, es.BgColor.Codes()...)
}
if len(codes) == 0 {
return "\x1b[0m"
}
return fmt.Sprintf("\x1b[%sm", strings.Join(codes, ";"))
}
type ColorIndex int
func (cInd ColorIndex) Codes() []string {
return []string{strconv.Itoa(int(cInd))}
}
type Color256 struct {
ground int
Index int
}
func (c256 Color256) Codes() []string {
return []string{
strconv.Itoa(c256.ground),
"5",
strconv.Itoa(c256.Index),
}
}
type ColorRGB struct {
ground int
R, G, B int
}
func (cRGB ColorRGB) Codes() []string {
return []string{
strconv.Itoa(cRGB.ground),
"2",
strconv.Itoa(cRGB.R),
strconv.Itoa(cRGB.G),
strconv.Itoa(cRGB.B),
}
}

View File

@ -1,95 +0,0 @@
package text
import (
"strings"
"unicode/utf8"
)
// EscapeItem hold the description of terminal escapes in a line.
// 'item' is the actual escape command
// 'pos' is the index in the rune array where the 'item' shall be inserted back.
// For example, the escape item in "F\x1b33mox" is {"\x1b33m", 1}.
type EscapeItem struct {
Item string
Pos int
}
// ExtractTermEscapes extract terminal escapes out of a line and returns a new
// line without terminal escapes and a slice of escape items. The terminal escapes
// can be inserted back into the new line at rune index 'item.pos' to recover the
// original line.
//
// Required: The line shall not contain "\n"
func ExtractTermEscapes(line string) (string, []EscapeItem) {
var termEscapes []EscapeItem
var line1 strings.Builder
pos := 0
item := ""
occupiedRuneCount := 0
inEscape := false
for i, r := range []rune(line) {
if r == '\x1b' {
pos = i
item = string(r)
inEscape = true
continue
}
if inEscape {
item += string(r)
if r == 'm' {
termEscapes = append(termEscapes, EscapeItem{item, pos - occupiedRuneCount})
occupiedRuneCount += utf8.RuneCountInString(item)
inEscape = false
}
continue
}
line1.WriteRune(r)
}
return line1.String(), termEscapes
}
// ApplyTermEscapes apply the extracted terminal escapes to the edited line.
// Escape sequences need to be ordered by their position.
// If the position is < 0, the escape is applied at the beginning of the line.
// If the position is > len(line), the escape is applied at the end of the line.
func ApplyTermEscapes(line string, escapes []EscapeItem) string {
if len(escapes) == 0 {
return line
}
var out strings.Builder
currPos := 0
currItem := 0
for _, r := range line {
for currItem < len(escapes) && currPos >= escapes[currItem].Pos {
out.WriteString(escapes[currItem].Item)
currItem++
}
out.WriteRune(r)
currPos++
}
// Don't forget the trailing escapes, if any.
for currItem < len(escapes) {
out.WriteString(escapes[currItem].Item)
currItem++
}
return out.String()
}
// OffsetEscapes is a utility function to offset the position of a
// collection of EscapeItem.
func OffsetEscapes(escapes []EscapeItem, offset int) []EscapeItem {
result := make([]EscapeItem, len(escapes))
for i, e := range escapes {
result[i] = EscapeItem{
Item: e.Item,
Pos: e.Pos + offset,
}
}
return result
}

View File

@ -1,8 +0,0 @@
module github.com/MichaelMure/go-term-text
go 1.11
require (
github.com/mattn/go-runewidth v0.0.6
github.com/stretchr/testify v1.3.0
)

View File

@ -1,9 +0,0 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

View File

@ -1,50 +0,0 @@
package text
import (
"bytes"
"strings"
"github.com/mattn/go-runewidth"
)
// LeftPadMaxLine pads a line on the left by a specified amount and pads the
// string on the right to fill the maxLength.
// If the given string is too long, it is truncated with an ellipsis.
// Handle properly terminal color escape code
func LeftPadMaxLine(line string, length, leftPad int) string {
cleaned, escapes := ExtractTermEscapes(line)
scrWidth := runewidth.StringWidth(cleaned)
// truncate and ellipse if needed
if scrWidth+leftPad > length {
cleaned = runewidth.Truncate(cleaned, length-leftPad, "…")
} else if scrWidth+leftPad < length {
cleaned = runewidth.FillRight(cleaned, length-leftPad)
}
rightPart := ApplyTermEscapes(cleaned, escapes)
pad := strings.Repeat(" ", leftPad)
return pad + rightPart
}
// LeftPad left pad each line of the given text
func LeftPadLines(text string, leftPad int) string {
var result bytes.Buffer
pad := strings.Repeat(" ", leftPad)
lines := strings.Split(text, "\n")
for i, line := range lines {
result.WriteString(pad)
result.WriteString(line)
// no additional line break at the end
if i < len(lines)-1 {
result.WriteString("\n")
}
}
return result.String()
}

View File

@ -1,45 +0,0 @@
package text
import (
"strings"
"github.com/mattn/go-runewidth"
)
// Len return the length of a string in a terminal, while ignoring the terminal
// escape sequences.
func Len(text string) int {
length := 0
escape := false
for _, char := range text {
if char == '\x1b' {
escape = true
}
if !escape {
length += runewidth.RuneWidth(char)
}
if char == 'm' {
escape = false
}
}
return length
}
// MaxLineLen return the length in a terminal of the longest line, while
// ignoring the terminal escape sequences.
func MaxLineLen(text string) int {
lines := strings.Split(text, "\n")
max := 0
for _, line := range lines {
length := Len(line)
if length > max {
max = length
}
}
return max
}

View File

@ -1,28 +0,0 @@
package text
import (
"strings"
"unicode"
)
// TrimSpace remove the leading and trailing whitespace while ignoring the
// terminal escape sequences.
// Returns the number of trimmed space on both side.
func TrimSpace(line string) string {
cleaned, escapes := ExtractTermEscapes(line)
// trim left while counting
left := 0
trimmed := strings.TrimLeftFunc(cleaned, func(r rune) bool {
if unicode.IsSpace(r) {
left++
return true
}
return false
})
trimmed = strings.TrimRightFunc(trimmed, unicode.IsSpace)
escapes = OffsetEscapes(escapes, -left)
return ApplyTermEscapes(trimmed, escapes)
}

View File

@ -1,24 +0,0 @@
package text
import "github.com/mattn/go-runewidth"
// TruncateMax truncate a line if its length is greater
// than the given length. Otherwise, the line is returned
// as is. If truncating occur, an ellipsis is inserted at
// the end.
// Handle properly terminal color escape code
func TruncateMax(line string, length int) string {
if length <= 0 {
return "…"
}
l := Len(line)
if l <= length || l == 0 {
return line
}
cleaned, escapes := ExtractTermEscapes(line)
truncated := runewidth.Truncate(cleaned, length-1, "")
return ApplyTermEscapes(truncated, escapes) + "…"
}

View File

@ -1,407 +0,0 @@
package text
import (
"strings"
"github.com/mattn/go-runewidth"
)
// Force runewidth not to treat ambiguous runes as wide chars, so that things
// like unicode ellipsis/up/down/left/right glyphs can have correct runewidth
// and can be displayed correctly in terminals.
func init() {
runewidth.DefaultCondition.EastAsianWidth = false
}
type wrapOpts struct {
indent string
pad string
align Alignment
}
// WrapOption is a functional option for the Wrap() function
type WrapOption func(opts *wrapOpts)
// WrapPad configure the padding with a string for Wrap()
func WrapPad(pad string) WrapOption {
return func(opts *wrapOpts) {
opts.pad = pad
}
}
// WrapPadded configure the padding with a number of space characters for Wrap()
func WrapPadded(padLen int) WrapOption {
return func(opts *wrapOpts) {
opts.pad = strings.Repeat(" ", padLen)
}
}
// WrapPad configure the indentation on the first line for Wrap()
func WrapIndent(indent string) WrapOption {
return func(opts *wrapOpts) {
opts.indent = indent
}
}
// WrapAlign configure the text alignment for Wrap()
func WrapAlign(align Alignment) WrapOption {
return func(opts *wrapOpts) {
opts.align = align
}
}
// allWrapOpts compile the set of WrapOption into a final wrapOpts
// from the default values.
func allWrapOpts(opts []WrapOption) *wrapOpts {
wrapOpts := &wrapOpts{
indent: "",
pad: "",
align: NoAlign,
}
for _, opt := range opts {
opt(wrapOpts)
}
if wrapOpts.indent == "" {
wrapOpts.indent = wrapOpts.pad
}
return wrapOpts
}
// Wrap a text for a given line size.
// Handle properly terminal color escape code
// Options are accepted to configure things like indent, padding or alignment.
// Return the wrapped text and the number of lines
func Wrap(text string, lineWidth int, opts ...WrapOption) (string, int) {
wrapOpts := allWrapOpts(opts)
if lineWidth <= 0 {
return "", 1
}
var lines []string
nbLine := 0
if len(wrapOpts.indent) >= lineWidth {
// fallback rendering
lines = append(lines, strings.Repeat("⭬", lineWidth))
nbLine++
wrapOpts.indent = wrapOpts.pad
}
if len(wrapOpts.pad) >= lineWidth {
// fallback rendering
line := strings.Repeat("⭬", lineWidth)
return strings.Repeat(line+"\n", 5), 5
}
// Start with the indent
padStr := wrapOpts.indent
padLen := Len(wrapOpts.indent)
// tabs are formatted as 4 spaces
text = strings.Replace(text, "\t", " ", -1)
// NOTE: text is first segmented into lines so that softwrapLine can handle.
for i, line := range strings.Split(text, "\n") {
// on the second line, use the padding instead
if i == 1 {
padStr = wrapOpts.pad
padLen = Len(wrapOpts.pad)
}
if line == "" || strings.TrimSpace(line) == "" {
// nothing in the line, we just add the non-empty part of the padding
lines = append(lines, strings.TrimRight(padStr, " "))
nbLine++
continue
}
wrapped := softwrapLine(line, lineWidth-padLen)
split := strings.Split(wrapped, "\n")
if i == 0 && len(split) > 1 {
// the very first line got wrapped
// that means we need to switch to the normal padding
// use the first wrapped line, ignore everything else and
// wrap the remaining of the line with the normal padding.
content := LineAlign(strings.TrimRight(split[0], " "), lineWidth-padLen, wrapOpts.align)
lines = append(lines, padStr+content)
nbLine++
line = strings.TrimPrefix(line, split[0])
line = strings.TrimLeft(line, " ")
padStr = wrapOpts.pad
padLen = Len(wrapOpts.pad)
wrapped = softwrapLine(line, lineWidth-padLen)
split = strings.Split(wrapped, "\n")
}
for j, seg := range split {
if j == 0 {
// keep the left padding of the wrapped line
content := LineAlign(strings.TrimRight(seg, " "), lineWidth-padLen, wrapOpts.align)
lines = append(lines, padStr+content)
} else {
content := LineAlign(strings.TrimSpace(seg), lineWidth-padLen, wrapOpts.align)
lines = append(lines, padStr+content)
}
nbLine++
}
}
return strings.Join(lines, "\n"), nbLine
}
// WrapLeftPadded wrap a text for a given line size with a left padding.
// Handle properly terminal color escape code
func WrapLeftPadded(text string, lineWidth int, leftPad int) (string, int) {
return Wrap(text, lineWidth, WrapPadded(leftPad))
}
// WrapWithPad wrap a text for a given line size with a custom left padding
// Handle properly terminal color escape code
func WrapWithPad(text string, lineWidth int, pad string) (string, int) {
return Wrap(text, lineWidth, WrapPad(pad))
}
// WrapWithPad wrap a text for a given line size with a custom left padding
// This function also align the result depending on the requested alignment.
// Handle properly terminal color escape code
func WrapWithPadAlign(text string, lineWidth int, pad string, align Alignment) (string, int) {
return Wrap(text, lineWidth, WrapPad(pad), WrapAlign(align))
}
// WrapWithPadIndent wrap a text for a given line size with a custom left padding
// and a first line indent. The padding is not effective on the first line, indent
// is used instead, which allow to implement indents and outdents.
// Handle properly terminal color escape code
func WrapWithPadIndent(text string, lineWidth int, indent string, pad string) (string, int) {
return Wrap(text, lineWidth, WrapIndent(indent), WrapPad(pad))
}
// WrapWithPadIndentAlign wrap a text for a given line size with a custom left padding
// and a first line indent. The padding is not effective on the first line, indent
// is used instead, which allow to implement indents and outdents.
// This function also align the result depending on the requested alignment.
// Handle properly terminal color escape code
func WrapWithPadIndentAlign(text string, lineWidth int, indent string, pad string, align Alignment) (string, int) {
return Wrap(text, lineWidth, WrapIndent(indent), WrapPad(pad), WrapAlign(align))
}
// Break a line into several lines so that each line consumes at most
// 'textWidth' cells. Lines break at groups of white spaces and multibyte
// chars. Nothing is removed from the original text so that it behaves like a
// softwrap.
//
// Required: The line shall not contain '\n'
//
// WRAPPING ALGORITHM: The line is broken into non-breakable chunks, then line
// breaks ("\n") are inserted between these groups so that the total length
// between breaks does not exceed the required width. Words that are longer than
// the textWidth are broken into pieces no longer than textWidth.
func softwrapLine(line string, textWidth int) string {
escaped, escapes := ExtractTermEscapes(line)
chunks := segmentLine(escaped)
// Reverse the chunk array so we can use it as a stack.
for i, j := 0, len(chunks)-1; i < j; i, j = i+1, j-1 {
chunks[i], chunks[j] = chunks[j], chunks[i]
}
// for readability, minimal implementation of a stack:
pop := func() string {
result := chunks[len(chunks)-1]
chunks = chunks[:len(chunks)-1]
return result
}
push := func(chunk string) {
chunks = append(chunks, chunk)
}
peek := func() string {
return chunks[len(chunks)-1]
}
empty := func() bool {
return len(chunks) == 0
}
var out strings.Builder
// helper to write in the output while interleaving the escape
// sequence at the correct places.
// note: the final algorithm will add additional line break in the original
// text. Those line break are *not* fed to this helper so the positions don't
// need to be offset, which make the whole thing much easier.
currPos := 0
currItem := 0
outputString := func(s string) {
for _, r := range s {
for currItem < len(escapes) && currPos == escapes[currItem].Pos {
out.WriteString(escapes[currItem].Item)
currItem++
}
out.WriteRune(r)
currPos++
}
}
width := 0
for !empty() {
wl := Len(peek())
if width+wl <= textWidth {
// the chunk fit in the available space
outputString(pop())
width += wl
if width == textWidth && !empty() {
// only add line break when there is more chunk to come
out.WriteRune('\n')
width = 0
}
} else if wl > textWidth {
// words too long for a full line are split to fill the remaining space.
// But if the long words is the first non-space word in the middle of the
// line, preceding spaces shall not be counted in word splitting.
splitWidth := textWidth - width
if strings.HasSuffix(out.String(), "\n"+strings.Repeat(" ", width)) {
splitWidth += width
}
left, right := splitWord(pop(), splitWidth)
// remainder is pushed back to the stack for next round
push(right)
outputString(left)
out.WriteRune('\n')
width = 0
} else {
// normal line overflow, we add a line break and try again
out.WriteRune('\n')
width = 0
}
}
// Don't forget the trailing escapes, if any.
for currItem < len(escapes) && currPos >= escapes[currItem].Pos {
out.WriteString(escapes[currItem].Item)
currItem++
}
return out.String()
}
// Segment a line into chunks, where each chunk consists of chars with the same
// type and is not breakable.
func segmentLine(s string) []string {
var chunks []string
var word string
wordType := none
flushWord := func() {
chunks = append(chunks, word)
word = ""
wordType = none
}
for _, r := range s {
// A WIDE_CHAR itself constitutes a chunk.
thisType := runeType(r)
if thisType == wideChar {
if wordType != none {
flushWord()
}
chunks = append(chunks, string(r))
continue
}
// Other type of chunks starts with a char of that type, and ends with a
// char with different type or end of string.
if thisType != wordType {
if wordType != none {
flushWord()
}
word = string(r)
wordType = thisType
} else {
word += string(r)
}
}
if word != "" {
flushWord()
}
return chunks
}
type RuneType int
// Rune categories
//
// These categories are so defined that each category forms a non-breakable
// chunk. It IS NOT the same as unicode code point categories.
const (
none RuneType = iota
wideChar
invisible
shortUnicode
space
visibleAscii
)
// Determine the category of a rune.
func runeType(r rune) RuneType {
rw := runewidth.RuneWidth(r)
if rw > 1 {
return wideChar
} else if rw == 0 {
return invisible
} else if r > 127 {
return shortUnicode
} else if r == ' ' {
return space
} else {
return visibleAscii
}
}
// splitWord split a word at the given length, while ignoring the terminal escape sequences
func splitWord(word string, length int) (string, string) {
runes := []rune(word)
var result []rune
added := 0
escape := false
if length == 0 {
return "", word
}
for _, r := range runes {
if r == '\x1b' {
escape = true
}
width := runewidth.RuneWidth(r)
if width+added > length {
// wide character made the length overflow
break
}
result = append(result, r)
if !escape {
added += width
if added >= length {
break
}
}
if r == 'm' {
escape = false
}
}
leftover := runes[len(result):]
return string(result), string(leftover)
}

View File

@ -1,5 +0,0 @@
coverage.txt
fuzz/fuzz-fuzz.zip
fuzz/corpus/corpus/*
fuzz/corpus/suppressions/*
fuzz/corpus/crashes/*

View File

@ -1,7 +0,0 @@
language: go
go:
- 1.8.x
- 1.9.x
- 1.10.x
- tip

View File

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

View File

@ -1,13 +0,0 @@
all: test install
install:
go install
lint:
gofmt -l -s -w . && go tool vet -all . && golint
test:
go test -race -v -coverprofile=coverage.txt -covermode=atomic
bench:
go test -run=XXX -bench=. -benchmem

View File

@ -1,57 +0,0 @@
levenshtein [![Build Status](https://travis-ci.org/agnivade/levenshtein.svg?branch=master)](https://travis-ci.org/agnivade/levenshtein) [![Go Report Card](https://goreportcard.com/badge/github.com/agnivade/levenshtein)](https://goreportcard.com/report/github.com/agnivade/levenshtein) [![GoDoc](https://godoc.org/github.com/agnivade/levenshtein?status.svg)](https://godoc.org/github.com/agnivade/levenshtein)
===========
[Go](http://golang.org) package to calculate the [Levenshtein Distance](http://en.wikipedia.org/wiki/Levenshtein_distance)
The library is fully capable of working with non-ascii strings. But the strings are not normalized. That is left as a user-dependant use case. Please normalize the strings before passing it to the library if you have such a requirement.
- https://blog.golang.org/normalization
Install
-------
go get github.com/agnivade/levenshtein
Example
-------
```go
package main
import (
"fmt"
"github.com/agnivade/levenshtein"
)
func main() {
s1 := "kitten"
s2 := "sitting"
distance := levenshtein.ComputeDistance(s1, s2)
fmt.Printf("The distance between %s and %s is %d.\n", s1, s2, distance)
// Output:
// The distance between kitten and sitting is 3.
}
```
Benchmarks
----------
```
name time/op
Simple/ASCII-4 537ns ± 2%
Simple/French-4 956ns ± 0%
Simple/Nordic-4 1.95µs ± 1%
Simple/Tibetan-4 1.53µs ± 2%
name alloc/op
Simple/ASCII-4 96.0B ± 0%
Simple/French-4 128B ± 0%
Simple/Nordic-4 192B ± 0%
Simple/Tibetan-4 144B ± 0%
name allocs/op
Simple/ASCII-4 1.00 ± 0%
Simple/French-4 1.00 ± 0%
Simple/Nordic-4 1.00 ± 0%
Simple/Tibetan-4 1.00 ± 0%
```

View File

@ -1 +0,0 @@
module github.com/agnivade/levenshtein

View File

@ -1,71 +0,0 @@
// Package levenshtein is a Go implementation to calculate Levenshtein Distance.
//
// Implementation taken from
// https://gist.github.com/andrei-m/982927#gistcomment-1931258
package levenshtein
// ComputeDistance computes the levenshtein distance between the two
// strings passed as an argument. The return value is the levenshtein distance
//
// Works on runes (Unicode code points) but does not normalize
// the input strings. See https://blog.golang.org/normalization
// and the golang.org/x/text/unicode/norm pacage.
func ComputeDistance(a, b string) int {
if a == b {
return 0
}
// We need to convert to []rune if the strings are non-ascii.
// This could be avoided by using utf8.RuneCountInString
// and then doing some juggling with rune indices.
// The primary challenge is keeping track of the previous rune.
// With a range loop, its not that easy. And with a for-loop
// we need to keep track of the inter-rune width using utf8.DecodeRuneInString
s1 := []rune(a)
s2 := []rune(b)
// swap to save some memory O(min(a,b)) instead of O(a)
if len(s1) > len(s2) {
s1, s2 = s2, s1
}
lenS1 := len(s1)
lenS2 := len(s2)
// init the row
x := make([]int, lenS1+1)
for i := 0; i <= lenS1; i++ {
x[i] = i
}
// fill in the rest
for i := 1; i <= lenS2; i++ {
prev := i
var current int
for j := 1; j <= lenS1; j++ {
if s2[i-1] == s1[j-1] {
current = x[j-1] // match
} else {
current = min(x[j-1]+1, prev+1, x[j]+1)
}
x[j-1] = prev
prev = current
}
x[lenS1] = prev
}
return x[lenS1]
}
func min(a, b, c int) int {
if a < b {
if a < c {
return a
}
} else {
if b < c {
return b
}
}
return c
}

View File

@ -1,14 +0,0 @@
language: go
go:
- 1.10.x
- 1.11.x
before_install:
- go get -t -v ./...
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

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