Skip to content

Commit

Permalink
Add an enum example (graph-gophers#587)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelnikolov authored and KNiepok committed Feb 28, 2023
1 parent 25e8656 commit 04340de
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 32 deletions.
33 changes: 33 additions & 0 deletions example/enum/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>GraphiQL</title>
<style>
body {
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}
#graphiql {
height: 100vh;
}
</style>
<script src="https://unpkg.com/react@17/umd/react.development.js" integrity="sha512-Vf2xGDzpqUOEIKO+X2rgTLWPY+65++WPwCHkX2nFMu9IcstumPsf/uKKRd5prX3wOu8Q0GBylRpsDB26R6ExOg==" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" integrity="sha512-Wr9OKCTtq1anK0hq5bY3X/AvDI5EflDSAh0mE9gma+4hl+kXdTJPKZ3TwLMBcrgUeoY0s3dq9JjhCQc7vddtFg==" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/graphiql.min.css" />
</head>
<body>
<div id="graphiql">Loading...</div>
<script src="https://unpkg.com/[email protected]/graphiql.min.js" type="application/javascript"></script>
<script>
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: GraphiQL.createFetcher({url: '/query'}),
defaultEditorToolsVisibility: true,
}),
document.getElementById('graphiql'),
);
</script>
</body>
</html>
57 changes: 57 additions & 0 deletions example/enum/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Package main demonstrates a simple web app that uses type-safe enums in a GraphQL resolver.
package main

import (
"context"
_ "embed"
"log"
"net/http"

"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)

//go:embed index.html
var page []byte

//go:embed schema.graphql
var sdl string

type resolver struct {
state State
}

func (r *resolver) Query() *queryResolver {
return &queryResolver{state: &r.state}
}

func (r *resolver) Mutation() *mutationResolver {
return &mutationResolver{state: &r.state}
}

type queryResolver struct {
state *State
}

func (q *queryResolver) State(ctx context.Context) State {
return *q.state
}

type mutationResolver struct {
state *State
}

func (m *mutationResolver) State(ctx context.Context, args *struct{ State State }) State {
*m.state = args.State
return *m.state
}

func main() {
opts := []graphql.SchemaOpt{graphql.UseStringDescriptions()}
schema := graphql.MustParseSchema(sdl, &resolver{}, opts...)

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write(page) })
http.Handle("/query", &relay.Handler{Schema: schema})

log.Fatal(http.ListenAndServe(":8080", nil))
}
27 changes: 27 additions & 0 deletions example/enum/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
type Query {
"""
Returns the current state.
"""
state: State!
}

type Mutation {
"""
Changes the state and returns the new state.
"""
state(state: State!): State!
}

"""
State holds the possible task states.
"""
enum State {
"Backlog indicates that a task is in the backlog and needs to be triaged."
BACKLOG
"Todo means that a task is ready to be worked on."
TODO
"INPROG means that a task is currently in progress."
INPROG
"Done means that a task is finished."
DONE
}
52 changes: 52 additions & 0 deletions example/enum/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"fmt"
)

// State represents a type-safe enum in Go which corresponds to the State enum in the GraphQL schema.
type State int

const (
Backlog State = iota // default value
TODO
InProg
Done
)

// the items in this array must have the exact same order as the corresponding constants above
var states = [...]string{"BACKLOG", "TODO", "INPROG", "DONE"}

func (s State) String() string { return states[s] }

func (s *State) Deserialize(str string) {
var found bool
for i, st := range states {
if st == str {
found = true
(*s) = State(i)
}
}
if !found {
panic("invalid value for enum State: " + str)
}
}

// ImplementsGraphQLType instructs the GraphQL server that the State enum can be represented by the State type in Golang.
// If this method is missing we would get a runtime error that the State type can't be assigned a string. However, since
// this type implements the State enum, the server will try to call its [State.UnmarshalGraphQL] method to set a value.
func (State) ImplementsGraphQLType(name string) bool {
return name == "State"
}

// UnmarshalGraphQL tries to unmarshal a type from a given GraphQL value.
func (s *State) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
s.Deserialize(input)
default:
err = fmt.Errorf("wrong type for State: %T", input)
}
return err
}
100 changes: 71 additions & 29 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,6 @@ func ExampleSchema_AST() {

func ExampleSchema_AST_generateEnum() {
s := `
schema {
query: Query
}
type Query {
currentSeason: Season!
}
Expand All @@ -282,18 +278,48 @@ const (
{{- end }}
)
func (s Season) String() string {
switch s {
{{ range $i, $e := $enum.EnumValuesDefinition }}{{ if ne $i 0 }}{{ printf "\n\t" }}{{ end -}}
case {{ $e.EnumValue | toVar }}:
return "{{ $e.EnumValue }}"
{{- end }}
var {{ $enum.Name | toLower }}Items = [...]string{
{{- range $i, $e := $enum.EnumValuesDefinition }}{{ if ne $i 0 }}{{ printf ", " }}{{ end }}
{{- $e.EnumValue | quote }}
{{- end -}}
}
func (s {{ $enum.Name }}) String() string { return {{ $enum.Name | toLower }}Items[s] }
func (s *{{ $enum.Name }}) Deserialize(str string) {
var found bool
for i, st := range {{ $enum.Name | toLower }}Items {
if st == str {
found = true
(*s) = {{ $enum.Name }}(i)
}
}
if !found {
panic("invalid value for enum {{ $enum.Name }}: " + str)
}
}
func ({{ $enum.Name }}) ImplementsGraphQLType(name string) bool {
return name == {{ $enum.Name | quote }}
}
func (s *{{ $enum.Name }}) UnmarshalGraphQL(input interface{}) error {
var err error
switch input := input.(type) {
case string:
s.Deserialize(input)
default:
err = fmt.Errorf("wrong type for {{ $enum.Name }}: %T", input)
}
panic("unreachable")
return err
}
`

funcs := template.FuncMap{
"quote": func(s string) string {
return `"` + s + `"`
},
"toLower": strings.ToLower,
"toVar": func(s string) string {
if len(s) == 0 {
return s
Expand Down Expand Up @@ -323,24 +349,42 @@ func (s Season) String() string {
// type Season int
//
// const (
// Spring Season = iota
// Summer
// Autumn
// Winter
// Spring Season = iota
// Summer
// Autumn
// Winter
// )
//
// func (s Season) String() string {
// switch s {
// case Spring:
// return "SPRING"
// case Summer:
// return "SUMMER"
// case Autumn:
// return "AUTUMN"
// case Winter:
// return "WINTER"
// var seasonItems = [...]string{"SPRING", "SUMMER", "AUTUMN", "WINTER"}
//
// func (s Season) String() string { return seasonItems[s] }
//
// func (s *Season) Deserialize(str string) {
// var found bool
// for i, st := range seasonItems {
// if st == str {
// found = true
// (*s) = Season(i)
// }
// }
// if !found {
// panic("invalid value for enum Season: " + str)
// }
// panic("unreachable")
// }
//
// func (Season) ImplementsGraphQLType(name string) bool {
// return name == "Season"
// }
//
// func (s *Season) UnmarshalGraphQL(input interface{}) error {
// var err error
// switch input := input.(type) {
// case string:
// s.Deserialize(input)
// default:
// err = fmt.Errorf("wrong type for Season: %T", input)
// }
// return err
// }
}

Expand All @@ -358,9 +402,7 @@ func ExampleUseStringDescriptions() {
Post represents a blog post.
"""
type Post {
"""
Unique identifier of the post.
"""
"Unique identifier of the post."
id: ID!
# The title field has no description.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/graph-gophers/graphql-go

go 1.13
go 1.16

require (
github.com/opentracing/opentracing-go v1.2.0
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ go.opentelemetry.io/otel v1.6.3 h1:FLOfo8f9JzFVFVyU+MSRJc2HdEAXQgm7pIv2uFKRSZE=
go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
go.opentelemetry.io/otel/trace v1.6.3 h1:IqN4L+5b0mPNjdXIiZ90Ni4Bl5BRkDQywePLWemd9bc=
go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit 04340de

Please sign in to comment.