diff --git a/.editorconfig b/.editorconfig index 17934a118c..157d83ec0d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,3 +10,6 @@ indent_size = 4 [*.{go,gotpl}] indent_style = tab + +[*.{yml,md}] +indent_size = 2 diff --git a/.gitignore b/.gitignore index e6ac9bd268..10ed19500d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /internal/tests/testdata/graphql-js /vendor +/docs/public diff --git a/README.md b/README.md index d2cf50ccad..7724ef0e4a 100644 --- a/README.md +++ b/README.md @@ -2,294 +2,4 @@ This is a library for quickly creating strictly typed graphql servers in golang. -### Getting started - -#### install gqlgen -```bash -go get github.com/vektah/gqlgen -``` - - -#### define a schema -schema.graphql -```graphql schema -schema { - query: Query - mutation: Mutation -} - -type Query { - todos: [Todo!]! -} - -type Mutation { - createTodo(text: String!): Todo! -} - -type Todo { - id: ID! - text: String! - done: Boolean! - user: User! -} - -type User { - id: ID! - name: String! -} -``` - - -#### generate the bindings - - -gqlgen can then take the schema and generate all the code needed to execute incoming graphql queries in a safe, -strictly typed manner: -```bash -gqlgen -out generated.go -package main -``` - -If you look at the top of `generated.go` it has created an interface and some temporary models: - -```go -func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { - return &executableSchema{resolvers} -} - -type Resolvers interface { - Mutation_createTodo(ctx context.Context, text string) (Todo, error) - Query_todos(ctx context.Context) ([]Todo, error) - Todo_user(ctx context.Context, it *Todo) (User, error) -} - -type Todo struct { - ID string - Text string - Done bool - UserID string -} - -type User struct { - ID string - Name string -} - -type executableSchema struct { - resolvers Resolvers -} - -func (e *executableSchema) Schema() *schema.Schema { - return parsedSchema -} -``` - -Notice that only the scalar types were added to the model? Todo.user doesnt exist on the struct, instead a resolver -method has been added. Resolver methods have a simple naming convention of {Type}_{field}. - -You're probably thinking why not just have a method on the user struct? Well, you can. But its assumed it will be a -getter method and wont be hitting the database, so parallel execution is disabled and you dont have access to any -database context. Plus, your models probably shouldn't be responsible for fetching more data. To define methods on the -model you will need to copy it out of the generated code and define it in types.json. - - -**Note**: ctx here is the golang context.Context, its used to pass per-request context like url params, tracing -information, cancellation, and also the current selection set. This makes it more like the `info` argument in -`graphql-js`. Because the caller will create an object to satisfy the interface, they can inject any dependencies in -directly. - -#### write our resolvers -Now we need to join the edges of the graph up. - -main.go: -```go -package main - -import ( - "context" - "fmt" - "log" - "math/rand" - "net/http" - - "github.com/vektah/gqlgen/handler" -) - -type MyApp struct { - todos []Todo -} - -func (a *MyApp) Mutation_createTodo(ctx context.Context, text string) (Todo, error) { - todo := Todo{ - Text: text, - ID: fmt.Sprintf("T%d", rand.Int()), - UserID: fmt.Sprintf("U%d", rand.Int()), - } - a.todos = append(a.todos, todo) - return todo, nil -} - -func (a *MyApp) Query_todos(ctx context.Context) ([]Todo, error) { - return a.todos, nil -} - -func (a *MyApp) Todo_user(ctx context.Context, it *Todo) (User, error) { - return User{ID: it.UserID, Name: "user " + it.UserID}, nil -} - -func main() { - app := &MyApp{ - todos: []Todo{}, // this would normally be a reference to the db - } - http.Handle("/", handler.Playground("Dataloader", "/query")) - http.Handle("/query", handler.GraphQL(MakeExecutableSchema(app))) - - fmt.Println("Listening on :8080") - log.Fatal(http.ListenAndServe(":8080", nil)) -} -``` - -We now have a working server, to start it: -```bash -go run *.go -``` - -then open http://localhost:8080 in a browser. here are some queries to try: -```graphql -mutation createTodo { - createTodo(text:"test") { - user { - id - } - text - done - } -} - -query findTodos { - todos { - text - done - user { - name - } - } -} -``` - -#### customizing the models or reusing your existing ones - -Generated models are nice to get moving quickly, but you probably want control over them at some point. To do that -create a types.json, eg: -```json -{ - "Todo": "github.com/vektah/gettingstarted.Todo" -} -``` - -and create the model yourself: -```go -type Todo struct { - ID string - Text string - done bool - userID string // I've made userID private now. -} - -// lets define a getter too. it could also return an error if we needed. -func (t Todo) Done() bool { - return t.done -} - -``` - -then regenerate, this time specifying the type map: - -```bash -gqlgen -out generated.go -package main -typemap types.json -``` - -gqlgen will look at the user defined types and match the fields up finding fields and functions by matching names. - - -#### Finishing touches - -gqlgen is still unstable, and the APIs may change at any time. To prevent changes from ruining your day make sure -to lock your dependencies: - -```bash -dep init -dep ensure -go get github.com/vektah/gorunpkg -``` - -at the top of our main.go: -```go -//go:generate gorunpkg github.com/vektah/gqlgen -typemap types.json -out generated.go -package main - -package main -``` -**Note:** be careful formatting this, there must no space between the `//` and `go:generate`, and one empty line -between it and the `package main`. - - -This magic comment tells `go generate` what command to run when we want to regenerate our code. to do so run: -```go -go generate ./.. -``` - -*gorunpkg* will build and run the version of gqlgen we just installed into vendor with dep. This makes sure -that everyone working on your project generates code the same way regardless which binaries are installed in their gopath. - - -### Included custom scalar types - -Included in gqlgen there are some custom scalar types that will just work out of the box. - -- Time: An RFC3339 date as a quoted string -- Map: a json object represented as a map[string]interface{}. Useful change sets. - -You are free to redefine these any way you want in types.json, see the [custom scalar example](./example/scalars). - -### Prior art - -#### neelance/graphql-go - -The gold standard of graphql servers in golang. It provided the inspiration, and a good chunk of code for gqlgen. Its -strictly typed and uses your schema and some reflection to build up a resolver graph. The biggest downside is the amount -of work building up all of the resolvers, wrapping every object manually. - -Reasons to use gqlgen instead: - - We bind directly to your types, you dont need to bind manually https://github.com/neelance/graphql-go/issues/28 - - We show you the interface required, no guess work https://github.com/neelance/graphql-go/issues/159 - - We use separate resolvers for query and mutation https://github.com/neelance/graphql-go/issues/145 - - Code generation makes nil pointer juggling explicit, fixing issues like https://github.com/neelance/graphql-go/issues/125 - - Code generating makes binding issues obvious https://github.com/neelance/graphql-go/issues/33 - - Separating the resolvers from the data graph means we only need gofuncs around database calls, reducing the cost of https://github.com/neelance/graphql-go/pull/102 - - arrays work just fine https://github.com/neelance/graphql-go/issues/144 - - first class dataloader support, see examples/dataloader - -https://github.com/neelance/graphql-go - -#### graphql-go/graphql - -With this library you write the schema using its internal DSL as go code, and bind in all your resolvers. No go type -information is used so you can dynamically define new schemas which could be useful for building schema stitching -servers at runtime. - -Reasons to use gqlgen instead: - - strict types. Why go to all the effort of defining gql schemas and then bind it to interface{} everywhere? - - first class dataloader support, see examples/dataloader - - horrible runtime error messages when you mess up defining your schema https://github.com/graphql-go/graphql/issues/234 - - reviewing schema changes written in a go dsl is really hard across teams - -see https://github.com/graphql-go/graphql - -#### Applifier/graphql-codegen and euforic/graphql-gen-go - -Very similar idea, take your schema and generate the code from it. - -gqlgen will build the entire execution environment statically, allowing go's type checker to validate everything across -the the graph. These two libraries generate resolvers that are loaded using reflection by the neelance library, so they -have most of the downsides of that with an added layer of complexity. - -see https://github.com/Applifier/graphql-codegen and https://github.com/euforic/graphql-gen-go +See the [docs](https://vektah.github.io/gqlgen/) for a getting started guide. diff --git a/docs/config.yml b/docs/config.yml new file mode 100644 index 0000000000..a94890a630 --- /dev/null +++ b/docs/config.yml @@ -0,0 +1,18 @@ +baseurl: https://vektah.github.io/gqlgen/ +metadataformat: yaml +title: gqlgen +enableGitInfo: true +pygmentsCodeFences: true +pygmentsUseClasses: true + +params: + name: gqlgen + description: graphql servers the easy way + +menu: + main: + - name: Tutorials + identifier: tutorial + weight: 10 + - name: Reference + identifier: reference diff --git a/docs/content/_index.md b/docs/content/_index.md new file mode 100644 index 0000000000..25853b7cfc --- /dev/null +++ b/docs/content/_index.md @@ -0,0 +1,28 @@ +--- +title: Introduction +description: Type-safe graphql for golang +type: homepage +menu: main +weight: 10 +date: 2018-03-17T13:06:47+11:00 +--- + +## What is gqlgen? + +[gqlgen](https://github.com/vektah/gqlgen) is golang library for building graphql servers without any fuss. gqlgen is: + + - Schema first: You define your API using the graphql [Schema Definition Language](http://graphql.org/learn/schema/) + - Type safe: You should never see `map[string]interface{}` here. + - Codegen: Let us generate the boring bits, so you can build your app quickly. + + +## Getting started + +First take a look at the [Getting Started](tutorial/getting-started) tutorial. + +If you cant find what your looking for, maybe the [examples](https://github.com/vektah/gqlgen/tree/master/example) will help. + + +## Getting help + +If you think you've found bug, or something isnt behaving the way you think it should please raise an [issue](https://github.com/vektah/gqlgen/issues) on github. diff --git a/docs/content/reference/scalars.md b/docs/content/reference/scalars.md new file mode 100644 index 0000000000..ff86d9fc0e --- /dev/null +++ b/docs/content/reference/scalars.md @@ -0,0 +1,106 @@ +--- +title: "Custom Scalars" +description: Using custom graphql types in golang +date: 2018-03-17T13:06:47+11:00 +menu: {"main": {"parent": "reference"}} +--- + +There are two different ways to implement scalars in gqlgen, depending on your need. + + +## With user defined types +For user defined types you can implement the graphql.Marshal and graphql.Unmarshal interfaces and they will be called. + +```go +package mypkg + +import ( + "fmt" + "io" + "strings" +) + +type YesNo bool + +// UnmarshalGQL implements the graphql.Marshaler interface +func (y *YesNo) UnmarshalGQL(v interface{}) error { + yes, ok := v.(string) + if !ok { + return fmt.Errorf("points must be strings") + } + + if yes == "yes" { + *y = true + } else { + *y = false + } + return nil +} + +// MarshalGQL implements the graphql.Marshaler interface +func (y YesNo) MarshalGQL(w io.Writer) { + if y { + w.Write([]byte(`"yes"`)) + } else { + w.Write([]byte(`"no"`)) + } +} +``` + +and then in types.json point to the name without the Marshal|Unmarshal in front: +```json +{ + "YesNo": "github.com/me/mypkg.YesNo" +} +``` + + +## Custom scalars for types you don't control + +Sometimes you cant add methods to a type because its in another repo, part of the standard +library (eg string or time.Time). To do this we can build an external marshaler: + +```go +package mypkg + +import ( + "fmt" + "io" + "strings" + + "github.com/vektah/gqlgen/graphql" +) + + +func MarshalMyCustomBooleanScalar(b bool) graphql.Marshaler { + return graphql.WriterFunc(func(w io.Writer) { + if b { + w.Write([]byte("true")) + } else { + w.Write([]byte("false")) + } + }) +} + +func UnmarshalMyCustomBooleanScalar(v interface{}) (bool, error) { + switch v := v.(type) { + case string: + return "true" == strings.ToLower(v), nil + case int: + return v != 0, nil + case bool: + return v, nil + default: + return false, fmt.Errorf("%T is not a bool", v) + } +} +``` + +and then in types.json point to the name without the Marshal|Unmarshal in front: +```json +{ + "MyCustomBooleanScalar": "github.com/me/mypkg.MyCustomBooleanScalar" +} +``` + +see the [example/scalars](https://github.com/vektah/gqlgen/tree/master/example/scalars) package for more examples. diff --git a/docs/content/tutorial/getting-started.md b/docs/content/tutorial/getting-started.md new file mode 100644 index 0000000000..a89f72ba06 --- /dev/null +++ b/docs/content/tutorial/getting-started.md @@ -0,0 +1,240 @@ +--- +title: "Getting Started" +description: Creating a todo graphql server in golang +date: 2018-03-17T13:06:47+11:00 +menu: {"main": {"parent": "tutorial"}} +--- + +## Goal + +The aim for this tutorial is to build a "todo" graphql server that can: + + - get a list of all todos + - create new todos + - mark off todos as they are completed + + +## Install gqlgen + +Assuming you already have a working [go environment](https://golang.org/doc/install) you can simply go get: + +```sh +go get github.com/vektah/gqlgen +``` + + +## Define the schema + +gqlgen is a schema-first library, so before touching any code we write out the API we want using the graphql +[Schema Definition Language](http://graphql.org/learn/schema/). This usually goes into a file called schema.graphql + +```graphql +type Todo { + id: ID! + text: String! + done: Boolean! + user: User! +} + +type User { + id: ID! + name: String! +} + +type Query { + todos: [Todo!]! +} + +type Mutation { + createTodo(text: String!): Todo! +} +``` + +## Generate the bindings + +Now that we have defined the shape of our data, and what actions can be taken we can ask gqlgen to convert the schema into code: +```bash +gqlgen -out generated.go -package main +``` + +gqlgen should have created two new files `generated.go` and `models_gen.go`. If we take a peek in both we can see what the server has generated: + +```go +// generated.go +func MakeExecutableSchema(resolvers Resolvers) graphql.ExecutableSchema { + return &executableSchema{resolvers} +} + +type Resolvers interface { + Mutation_createTodo(ctx context.Context, text string) (Todo, error) + Query_todos(ctx context.Context) ([]Todo, error) + Todo_user(ctx context.Context, it *Todo) (User, error) +} + +// models_gen.go +type Todo struct { + ID string + Text string + Done bool + UserID string +} + +type User struct { + ID string + Name string +} + +``` + +**Note**: ctx here is the golang context.Context, its used to pass per-request context like url params, tracing +information, cancellation, and also the current selection set. This makes it more like the `info` argument in +`graphql-js`. Because the caller will create an object to satisfy the interface, they can inject any dependencies in +directly. + +## Write the resolvers + +Finally, we get to write some code! + +```go +// main.go +package main + +import ( + "context" + "fmt" + "log" + "math/rand" + "net/http" + + "github.com/vektah/gqlgen/handler" +) + +type MyApp struct { + todos []Todo +} + +func (a *MyApp) Query_todos(ctx context.Context) ([]Todo, error) { + return a.todos, nil +} + +func (a *MyApp) Mutation_createTodo(ctx context.Context, text string) (Todo, error) { + todo := Todo{ + Text: text, + ID: fmt.Sprintf("T%d", rand.Int()), + UserID: fmt.Sprintf("U%d", rand.Int()), + } + a.todos = append(a.todos, todo) + return todo, nil +} + + +func (a *MyApp) Todo_user(ctx context.Context, it *Todo) (User, error) { + return User{ID: it.UserID, Name: "user " + it.UserID}, nil +} + +func main() { + app := &MyApp{ + todos: []Todo{}, // this would normally be a reference to the db + } + http.Handle("/", handler.Playground("Todo", "/query")) + http.Handle("/query", handler.GraphQL(MakeExecutableSchema(app))) + + fmt.Println("Listening on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} +``` + +We now have a working server, to start it: +```bash +go run *.go +``` + +then open http://localhost:8080 in a browser. here are some queries to try: +```graphql +mutation createTodo { + createTodo(text:"test") { + user { + id + } + text + done + } +} + +query findTodos { + todos { + text + done + user { + name + } + } +} +``` + +## Customizing the models + +Generated models are nice to get moving quickly, but you probably want control over them at some point. To do that +create a types.json, eg: +```json +{ + "Todo": "github.com/vektah/gettingstarted.Todo" +} +``` + +and create the model yourself: +```go +type Todo struct { + ID string + Text string + done bool + userID string // I've made userID private now. +} + +// lets define a getter too. it could also return an error if we needed. +func (t Todo) Done() bool { + return t.done +} + +``` + +then regenerate, this time specifying the type map: + +```bash +gqlgen -out generated.go -package main -typemap types.json +``` + +gqlgen will look at the user defined types and match the fields up finding fields and functions by matching names. + + +## Finishing touches + +gqlgen is still unstable, and the APIs may change at any time. To prevent changes from ruining your day make sure +to lock your dependencies: + +*Note*: If you dont have dep installed yet, you can get it [here](https://github.com/golang/dep) + +```bash +dep init +dep ensure +go get github.com/vektah/gorunpkg +``` + +at the top of our main.go: +```go +//go:generate gorunpkg github.com/vektah/gqlgen -typemap types.json -out generated.go -package main + +package main +``` +**Note:** be careful formatting this, there must no space between the `//` and `go:generate`, and one empty line +between it and the `package main`. + + +This magic comment tells `go generate` what command to run when we want to regenerate our code. to do so run: +```go +go generate ./.. +``` + +*gorunpkg* will build and run the version of gqlgen we just installed into vendor with dep. This makes sure +that everyone working on your project generates code the same way regardless which binaries are installed in their gopath. + diff --git a/docs/layouts/404.html b/docs/layouts/404.html new file mode 100644 index 0000000000..6c298de5ea --- /dev/null +++ b/docs/layouts/404.html @@ -0,0 +1,5 @@ +{{ define "main" }} +

Page not found

+ + I'm sorry, but the requested page wasn’t found on the server. +{{ end }} \ No newline at end of file diff --git a/docs/layouts/_default/baseof.html b/docs/layouts/_default/baseof.html new file mode 100644 index 0000000000..e995088869 --- /dev/null +++ b/docs/layouts/_default/baseof.html @@ -0,0 +1,24 @@ + + + + + + {{ with .Site.Params.description -}} + + {{- end }} + + {{ if not .IsHome }}{{ .Title }} —{{ end }} {{ .Site.Title }} + + + + + + + + {{ partial "sidebar" . }} + {{ block "main" . }}{{ end }} + + + diff --git a/docs/layouts/_default/single.html b/docs/layouts/_default/single.html new file mode 100644 index 0000000000..752d2cbe58 --- /dev/null +++ b/docs/layouts/_default/single.html @@ -0,0 +1,10 @@ +{{ define "main" }} +
+

{{ .Title }}

+ {{ .Description }} +
+ +
+ {{ .Content }} +
+{{ end }} diff --git a/docs/layouts/index.html b/docs/layouts/index.html new file mode 100644 index 0000000000..c7b713ae6a --- /dev/null +++ b/docs/layouts/index.html @@ -0,0 +1,12 @@ +{{ define "main" }} + {{ range where .Site.Pages "Type" "homepage" }} +
+

{{ .Title }}

+ {{ .Description }} +
+ +
+ {{ .Content }} +
+ {{ end }} +{{ end }} diff --git a/docs/layouts/partials/sidebar.html b/docs/layouts/partials/sidebar.html new file mode 100644 index 0000000000..cd46452123 --- /dev/null +++ b/docs/layouts/partials/sidebar.html @@ -0,0 +1,29 @@ + diff --git a/docs/static/favicon.ico b/docs/static/favicon.ico new file mode 100644 index 0000000000..0bb7dae6d8 Binary files /dev/null and b/docs/static/favicon.ico differ diff --git a/docs/static/main.css b/docs/static/main.css new file mode 100644 index 0000000000..45552fdaf7 --- /dev/null +++ b/docs/static/main.css @@ -0,0 +1,175 @@ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +ol, ul { + margin-bottom: 1em; + list-style: disc; + margin-left: 1.5em; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +body { + font-family: 'Quicksand', sans-serif; + font-size: 15px; + line-height: 1.5em; + padding-bottom: -5px; + display: flex; + flex-direction: column; + min-height: 100vh; +} + +a { + color: #336699; + text-decoration: none; +} + +a:hover { + border-bottom: 2px solid; +} + +@media (min-width: 768px) { + body { + display: grid; + grid-template-columns: 220px 1fr; + grid-template-rows: min-content auto min-content; + } +} + +main { + flex: 1; + padding: 20px; + max-width: 760px; + margin: 1em auto; + color: #445; +} + +nav { + color: #eee; + background-color: #303538; + padding: 20px; + grid-row: span 3; +} + +header { + background: #e9ebed; + padding: 45px 20px; + overflow:hidden; +} + +footer { + padding: 2px; + text-align: center; + font-size: 0.7em; + color: #222; +} + +h1 { + font-size: 25px; + font-weight: bold; + width: 760px; + margin: .3em auto; + display: block; +} + +.description { + font-size: 18px; + color: #555; + width: 760px; + margin: auto; + display: block; +} + +h2 { + margin-bottom: 1em; + font-size: 19px; + font-weight: bold; +} + +h3 { + margin-bottom: 1em; + font-size: 17px; + font-weight: bold; +} + +p { + margin-bottom: 1em; +} + +ul.menu { + margin-left:0; + list-style: none; +} + +ul.submenu { + margin-left: 15px; + list-style: none; + margin-bottom: 0; +} + +.menu { + font-weight: 700; +} + +.menu a { + color: inherit +} + +.logo { + font-family: 'VT323', monospace; + font-size: 3em; + text-decoration: none; + color: #eee; + width: 100%; + text-align: center; + margin-top: .2em; + margin-bottom: 1em; + border-bottom: none; + display:block; +} + +.logo:hover { + border-bottom: none; +} + +code { + padding:2px; + font-family: 'Space Mono', monospace; + color: #996666; +} + +strong { + font-weight: 700; +} diff --git a/docs/static/syntax.css b/docs/static/syntax.css new file mode 100644 index 0000000000..2a9d172819 --- /dev/null +++ b/docs/static/syntax.css @@ -0,0 +1,68 @@ +.chroma .c { color: #aaaaaa; font-style: italic } /* Comment */ +.chroma .err { color: #F00000; background-color: #F0A0A0 } /* Error */ +.chroma .k { color: #0000aa } /* Keyword */ +.chroma .cm { color: #aaaaaa; font-style: italic } /* Comment.Multiline */ +.chroma .cp { color: #4c8317 } /* Comment.Preproc */ +.chroma .c1 { color: #aaaaaa; font-style: italic } /* Comment.Single */ +.chroma .cs { color: #0000aa; font-style: italic } /* Comment.Special */ +.chroma .gd { color: #aa0000 } /* Generic.Deleted */ +.chroma .ge { font-style: italic } /* Generic.Emph */ +.chroma .gr { color: #aa0000 } /* Generic.Error */ +.chroma .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.chroma .gi { color: #00aa00 } /* Generic.Inserted */ +.chroma .go { color: #888888 } /* Generic.Output */ +.chroma .gp { color: #555555 } /* Generic.Prompt */ +.chroma .gs { font-weight: bold } /* Generic.Strong */ +.chroma .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.chroma .gt { color: #aa0000 } /* Generic.Traceback */ +.chroma .kc { color: #0000aa } /* Keyword.Constant */ +.chroma .kd { color: #0000aa } /* Keyword.Declaration */ +.chroma .kn { color: #0000aa } /* Keyword.Namespace */ +.chroma .kp { color: #0000aa } /* Keyword.Pseudo */ +.chroma .kr { color: #0000aa } /* Keyword.Reserved */ +.chroma .kt { color: #00aaaa } /* Keyword.Type */ +.chroma .m { color: #009999 } /* Literal.Number */ +.chroma .s { color: #aa5500 } /* Literal.String */ +.chroma .na { color: #1e90ff } /* Name.Attribute */ +.chroma .nb { color: #00aaaa } /* Name.Builtin */ +.chroma .nc { color: #00aa00; text-decoration: underline } /* Name.Class */ +.chroma .no { color: #aa0000 } /* Name.Constant */ +.chroma .nd { color: #888888 } /* Name.Decorator */ +.chroma .ni { color: #800000; font-weight: bold } /* Name.Entity */ +.chroma .nf { color: #00aa00 } /* Name.Function */ +.chroma .nn { color: #00aaaa; text-decoration: underline } /* Name.Namespace */ +.chroma .nt { color: #1e90ff; font-weight: bold } /* Name.Tag */ +.chroma .nv { color: #aa0000 } /* Name.Variable */ +.chroma .ow { color: #0000aa } /* Operator.Word */ +.chroma .w { color: #bbbbbb } /* Text.Whitespace */ +.chroma .mf { color: #009999 } /* Literal.Number.Float */ +.chroma .mh { color: #009999 } /* Literal.Number.Hex */ +.chroma .mi { color: #009999 } /* Literal.Number.Integer */ +.chroma .mo { color: #009999 } /* Literal.Number.Oct */ +.chroma .sb { color: #aa5500 } /* Literal.String.Backtick */ +.chroma .sc { color: #aa5500 } /* Literal.String.Char */ +.chroma .sd { color: #aa5500 } /* Literal.String.Doc */ +.chroma .s2 { color: #aa5500 } /* Literal.String.Double */ +.chroma .se { color: #aa5500 } /* Literal.String.Escape */ +.chroma .sh { color: #aa5500 } /* Literal.String.Heredoc */ +.chroma .si { color: #aa5500 } /* Literal.String.Interpol */ +.chroma .sx { color: #aa5500 } /* Literal.String.Other */ +.chroma .sr { color: #009999 } /* Literal.String.Regex */ +.chroma .s1 { color: #aa5500 } /* Literal.String.Single */ +.chroma .ss { color: #0000aa } /* Literal.String.Symbol */ +.chroma .bp { color: #00aaaa } /* Name.Builtin.Pseudo */ +.chroma .vc { color: #aa0000 } /* Name.Variable.Class */ +.chroma .vg { color: #aa0000 } /* Name.Variable.Global */ +.chroma .vi { color: #aa0000 } /* Name.Variable.Instance */ +.chroma .il { color: #009999 } /* Literal.Number.Integer.Long */ + +.chroma code { + overflow: auto; + display: block; + background-color: #e7edef; + padding: 5px; + margin-bottom: 1em; + font-family: 'Space Mono', monospace; + font-size: 14px; + color: #445; +} diff --git a/example/scalars/advanced_scalars.md b/example/scalars/advanced_scalars.md deleted file mode 100644 index 25f7ade6ec..0000000000 --- a/example/scalars/advanced_scalars.md +++ /dev/null @@ -1,53 +0,0 @@ -### Custom scalars for types you don't control - -Sometimes you cant add methods to a type because its in another repo, part of the standard -library (eg string or time.Time). To do this we can build an external marshaler: - -```graphql schema -type MyCustomBooleanScalar -``` - -```go -package mypkg - -import ( - "fmt" - "io" - "strings" - - "github.com/vektah/gqlgen/graphql" -) - - -func MarshalMyCustomBooleanScalar(b bool) graphql.Marshaler { - return graphql.WriterFunc(func(w io.Writer) { - if b { - w.Write([]byte("true")) - } else { - w.Write([]byte("false")) - } - }) -} - -func UnmarshalMyCustomBooleanScalar(v interface{}) (bool, error) { - switch v := v.(type) { - case string: - return "true" == strings.ToLower(v), nil - case int: - return v != 0, nil - case bool: - return v, nil - default: - return false, fmt.Errorf("%T is not a bool", v) - } -} -``` - -and then in types.json point to the name without the Marshal|Unmarshal in front: -```json -{ - "MyCustomBooleanScalar": "github.com/me/mypkg.MyCustomBooleanScalar" -} -``` - -see the `graphql` package for more examples. diff --git a/example/scalars/readme.md b/example/scalars/readme.md deleted file mode 100644 index 5cc249fea9..0000000000 --- a/example/scalars/readme.md +++ /dev/null @@ -1,57 +0,0 @@ -### Custom scalars - -There are two different ways to implement scalars in gqlgen, depending on your need. - - -#### With user defined types -For user defined types you can implement the graphql.Marshal and graphql.Unmarshal interfaces and they will be called. - - -```graphql schema -type YesNo -``` - -```go -package mypkg - -import ( - "fmt" - "io" - "strings" -) - -type YesNo bool - -// UnmarshalGQL implements the graphql.Marshaler interface -func (y *YesNo) UnmarshalGQL(v interface{}) error { - yes, ok := v.(string) - if !ok { - return fmt.Errorf("points must be strings") - } - - if yes == "yes" { - *y = true - } else { - *y = false - } - return nil -} - -// MarshalGQL implements the graphql.Marshaler interface -func (y YesNo) MarshalGQL(w io.Writer) { - if y { - w.Write([]byte(`"yes"`)) - } else { - w.Write([]byte(`"no"`)) - } -} -``` - -and then in types.json point to the name without the Marshal|Unmarshal in front: -```json -{ - "YesNo": "github.com/me/mypkg.YesNo" -} -``` - -Occasionally you need to define scalars for types you dont own. [more details](./advanced_scalars.md)