github.com/operandinc/gqlgen@v0.16.1/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"html/template"
     8  	"io/fs"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"github.com/operandinc/gqlgen/api"
    15  	"github.com/operandinc/gqlgen/codegen/config"
    16  	"github.com/operandinc/gqlgen/graphql"
    17  	"github.com/operandinc/gqlgen/internal/code"
    18  	"github.com/operandinc/gqlgen/plugin/servergen"
    19  	"github.com/urfave/cli/v2"
    20  )
    21  
    22  var configTemplate = template.Must(template.New("name").Parse(
    23  	`# Where are all the schema files located? globs are supported eg  src/**/*.graphqls
    24  schema:
    25    - graph/*.graphqls
    26  
    27  # Where should the generated server code go?
    28  exec:
    29    filename: graph/generated/generated.go
    30    package: generated
    31  
    32  # Uncomment to enable federation
    33  # federation:
    34  #   filename: graph/generated/federation.go
    35  #   package: generated
    36  
    37  # Where should any generated models go?
    38  model:
    39    filename: graph/model/models_gen.go
    40    package: model
    41  
    42  # Where should the resolver implementations go?
    43  resolver:
    44    layout: follow-schema
    45    dir: graph
    46    package: graph
    47  
    48  # Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models
    49  # struct_tag: json
    50  
    51  # Optional: turn on to use []Thing instead of []*Thing
    52  # omit_slice_element_pointers: false
    53  
    54  # Optional: set to speed up generation time by not performing a final validation pass.
    55  # skip_validation: true
    56  
    57  # gqlgen will search for any type names in the schema in these go packages
    58  # if they match it will use them, otherwise it will generate them.
    59  autobind:
    60  #  - "{{.}}/graph/model"
    61  
    62  # This section declares type mapping between the GraphQL and go type systems
    63  #
    64  # The first line in each type will be used as defaults for resolver arguments and
    65  # modelgen, the others will be allowed when binding to fields. Configure them to
    66  # your liking
    67  models:
    68    ID:
    69      model:
    70        - github.com/operandinc/gqlgen/graphql.ID
    71        - github.com/operandinc/gqlgen/graphql.Int
    72        - github.com/operandinc/gqlgen/graphql.Int64
    73        - github.com/operandinc/gqlgen/graphql.Int32
    74    Int:
    75      model:
    76        - github.com/operandinc/gqlgen/graphql.Int
    77        - github.com/operandinc/gqlgen/graphql.Int64
    78        - github.com/operandinc/gqlgen/graphql.Int32
    79  `))
    80  
    81  var schemaDefault = `# GraphQL schema example
    82  #
    83  # https://gqlgen.com/getting-started/
    84  
    85  type Todo {
    86    id: ID!
    87    text: String!
    88    done: Boolean!
    89    user: User!
    90  }
    91  
    92  type User {
    93    id: ID!
    94    name: String!
    95  }
    96  
    97  type Query {
    98    todos: [Todo!]!
    99  }
   100  
   101  input NewTodo {
   102    text: String!
   103    userId: String!
   104  }
   105  
   106  type Mutation {
   107    createTodo(input: NewTodo!): Todo!
   108  }
   109  `
   110  
   111  func executeConfigTemplate(pkgName string) string {
   112  	var buf bytes.Buffer
   113  	if err := configTemplate.Execute(&buf, pkgName); err != nil {
   114  		panic(err)
   115  	}
   116  
   117  	return buf.String()
   118  }
   119  
   120  func fileExists(filename string) bool {
   121  	_, err := os.Stat(filename)
   122  	return !errors.Is(err, fs.ErrNotExist)
   123  }
   124  
   125  func initFile(filename, contents string) error {
   126  	if err := os.MkdirAll(filepath.Dir(filename), 0o755); err != nil {
   127  		return fmt.Errorf("unable to create directory for file '%s': %w\n", filename, err)
   128  	}
   129  	if err := ioutil.WriteFile(filename, []byte(contents), 0o644); err != nil {
   130  		return fmt.Errorf("unable to write file '%s': %w\n", filename, err)
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  var initCmd = &cli.Command{
   137  	Name:  "init",
   138  	Usage: "create a new gqlgen project",
   139  	Flags: []cli.Flag{
   140  		&cli.BoolFlag{Name: "verbose, v", Usage: "show logs"},
   141  		&cli.StringFlag{Name: "config, c", Usage: "the config filename", Value: "gqlgen.yml"},
   142  		&cli.StringFlag{Name: "server", Usage: "where to write the server stub to", Value: "server.go"},
   143  		&cli.StringFlag{Name: "schema", Usage: "where to write the schema stub to", Value: "graph/schema.graphqls"},
   144  	},
   145  	Action: func(ctx *cli.Context) error {
   146  		configFilename := ctx.String("config")
   147  		serverFilename := ctx.String("server")
   148  		schemaFilename := ctx.String("schema")
   149  
   150  		pkgName := code.ImportPathForDir(".")
   151  		if pkgName == "" {
   152  			return fmt.Errorf("unable to determine import path for current directory, you probably need to run go mod init first")
   153  		}
   154  
   155  		// check schema and config don't already exist
   156  		for _, filename := range []string{configFilename, schemaFilename, serverFilename} {
   157  			if fileExists(filename) {
   158  				return fmt.Errorf("%s already exists", filename)
   159  			}
   160  		}
   161  		_, err := config.LoadConfigFromDefaultLocations()
   162  		if err == nil {
   163  			return fmt.Errorf("gqlgen.yml already exists in a parent directory\n")
   164  		}
   165  
   166  		// create config
   167  		fmt.Println("Creating", configFilename)
   168  		if err := initFile(configFilename, executeConfigTemplate(pkgName)); err != nil {
   169  			return err
   170  		}
   171  
   172  		// create schema
   173  		fmt.Println("Creating", schemaFilename)
   174  		if err := initFile(schemaFilename, schemaDefault); err != nil {
   175  			return err
   176  		}
   177  
   178  		// create the package directory with a temporary file so that go recognises it as a package
   179  		// and autobinding doesn't error out
   180  		tmpPackageNameFile := "graph/model/_tmp_gqlgen_init.go"
   181  		if err := initFile(tmpPackageNameFile, "package model"); err != nil {
   182  			return err
   183  		}
   184  		defer os.Remove(tmpPackageNameFile)
   185  
   186  		var cfg *config.Config
   187  		if cfg, err = config.LoadConfig(configFilename); err != nil {
   188  			panic(err)
   189  		}
   190  
   191  		fmt.Println("Creating", serverFilename)
   192  		fmt.Println("Generating...")
   193  		if err := api.Generate(cfg, api.AddPlugin(servergen.New(serverFilename))); err != nil {
   194  			fmt.Fprintln(os.Stderr, err.Error())
   195  		}
   196  
   197  		fmt.Printf("\nExec \"go run ./%s\" to start GraphQL server\n", serverFilename)
   198  		return nil
   199  	},
   200  }
   201  
   202  var generateCmd = &cli.Command{
   203  	Name:  "generate",
   204  	Usage: "generate a graphql server based on schema",
   205  	Flags: []cli.Flag{
   206  		&cli.BoolFlag{Name: "verbose, v", Usage: "show logs"},
   207  		&cli.StringFlag{Name: "config, c", Usage: "the config filename"},
   208  	},
   209  	Action: func(ctx *cli.Context) error {
   210  		var cfg *config.Config
   211  		var err error
   212  		if configFilename := ctx.String("config"); configFilename != "" {
   213  			cfg, err = config.LoadConfig(configFilename)
   214  			if err != nil {
   215  				return err
   216  			}
   217  		} else {
   218  			cfg, err = config.LoadConfigFromDefaultLocations()
   219  			if errors.Is(err, fs.ErrNotExist) {
   220  				cfg, err = config.LoadDefaultConfig()
   221  			}
   222  
   223  			if err != nil {
   224  				return err
   225  			}
   226  		}
   227  
   228  		if err = api.Generate(cfg); err != nil {
   229  			return err
   230  		}
   231  		return nil
   232  	},
   233  }
   234  
   235  var versionCmd = &cli.Command{
   236  	Name:  "version",
   237  	Usage: "print the version string",
   238  	Action: func(ctx *cli.Context) error {
   239  		fmt.Println(graphql.Version)
   240  		return nil
   241  	},
   242  }
   243  
   244  func main() {
   245  	app := cli.NewApp()
   246  	app.Name = "gqlgen"
   247  	app.Usage = generateCmd.Usage
   248  	app.Description = "This is a library for quickly creating strictly typed graphql servers in golang. See https://gqlgen.com/ for a getting started guide."
   249  	app.HideVersion = true
   250  	app.Flags = generateCmd.Flags
   251  	app.Version = graphql.Version
   252  	app.Before = func(context *cli.Context) error {
   253  		if context.Bool("verbose") {
   254  			log.SetFlags(0)
   255  		} else {
   256  			log.SetOutput(ioutil.Discard)
   257  		}
   258  		return nil
   259  	}
   260  
   261  	app.Action = generateCmd.Action
   262  	app.Commands = []*cli.Command{
   263  		generateCmd,
   264  		initCmd,
   265  		versionCmd,
   266  	}
   267  
   268  	if err := app.Run(os.Args); err != nil {
   269  		fmt.Fprint(os.Stderr, err.Error()+"\n")
   270  		os.Exit(1)
   271  	}
   272  }