github.com/goldeneggg/goa@v1.3.1/goagen/gen_client/cli_generator.go (about)

     1  package genclient
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"path"
     8  	"path/filepath"
     9  	"sort"
    10  	"strings"
    11  	"text/template"
    12  
    13  	"github.com/goadesign/goa/design"
    14  	"github.com/goadesign/goa/goagen/codegen"
    15  )
    16  
    17  func (g *Generator) generateMain(mainFile string, clientPkg, cliPkg string, funcs template.FuncMap) (err error) {
    18  	var file *codegen.SourceFile
    19  	file, err = codegen.SourceFileFor(mainFile)
    20  	if err != nil {
    21  		return err
    22  	}
    23  	defer func() {
    24  		file.Close()
    25  		if err == nil {
    26  			err = file.FormatCode()
    27  		}
    28  	}()
    29  	imports := []*codegen.ImportSpec{
    30  		codegen.SimpleImport("encoding/json"),
    31  		codegen.SimpleImport("fmt"),
    32  		codegen.SimpleImport("io/ioutil"),
    33  		codegen.SimpleImport("net/http"),
    34  		codegen.SimpleImport("os"),
    35  		codegen.SimpleImport("time"),
    36  		codegen.SimpleImport(clientPkg),
    37  		codegen.SimpleImport(cliPkg),
    38  		codegen.SimpleImport("github.com/spf13/cobra"),
    39  		codegen.NewImport("goaclient", "github.com/goadesign/goa/client"),
    40  		codegen.NewImport("uuid", "github.com/goadesign/goa/uuid"),
    41  	}
    42  	if err = file.WriteHeader("", "main", imports); err != nil {
    43  		return err
    44  	}
    45  
    46  	funcs["defaultRouteParams"] = defaultRouteParams
    47  	funcs["defaultRouteTemplate"] = defaultRouteTemplate
    48  	funcs["joinNames"] = joinNames
    49  	funcs["signerSignature"] = signerSignature
    50  	funcs["signerArgs"] = signerArgs
    51  
    52  	g.genfiles = append(g.genfiles, mainFile)
    53  	version := design.Design.Version
    54  	if version == "" {
    55  		version = "0"
    56  	}
    57  
    58  	hasSigners := false
    59  	hasBasicAuthSigners := false
    60  	hasAPIKeySigners := false
    61  	hasTokenSigners := false
    62  	for _, s := range g.API.SecuritySchemes {
    63  		if signerType(s) != "" {
    64  			hasSigners = true
    65  			switch s.Type {
    66  			case "basic":
    67  				hasBasicAuthSigners = true
    68  			case "apiKey":
    69  				hasAPIKeySigners = true
    70  			case "jwt", "oauth2":
    71  				hasTokenSigners = true
    72  			}
    73  		}
    74  	}
    75  
    76  	data := struct {
    77  		API                 *design.APIDefinition
    78  		Version             string
    79  		Package             string
    80  		HasSigners          bool
    81  		HasBasicAuthSigners bool
    82  		HasAPIKeySigners    bool
    83  		HasTokenSigners     bool
    84  	}{
    85  		API:                 g.API,
    86  		Version:             version,
    87  		Package:             g.Target,
    88  		HasSigners:          hasSigners,
    89  		HasBasicAuthSigners: hasBasicAuthSigners,
    90  		HasAPIKeySigners:    hasAPIKeySigners,
    91  		HasTokenSigners:     hasTokenSigners,
    92  	}
    93  	err = file.ExecuteTemplate("main", mainTmpl, funcs, data)
    94  	return
    95  }
    96  
    97  func (g *Generator) generateCommands(commandsFile string, clientPkg string, funcs template.FuncMap) (err error) {
    98  	var file *codegen.SourceFile
    99  	file, err = codegen.SourceFileFor(commandsFile)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	defer func() {
   104  		file.Close()
   105  		if err == nil {
   106  			err = file.FormatCode()
   107  		}
   108  	}()
   109  
   110  	funcs["defaultRouteParams"] = defaultRouteParams
   111  	funcs["defaultRouteTemplate"] = defaultRouteTemplate
   112  	funcs["joinNames"] = joinNames
   113  	funcs["joinRouteParams"] = joinRouteParams
   114  	funcs["routes"] = routes
   115  	funcs["flagType"] = flagType
   116  	funcs["cmdFieldType"] = cmdFieldTypeString
   117  	funcs["formatExample"] = formatExample
   118  	funcs["shouldAddExample"] = shouldAddExample
   119  	funcs["kebabCase"] = codegen.KebabCase
   120  
   121  	commandTypesTmpl := template.Must(template.New("commandTypes").Funcs(funcs).Parse(commandTypesTmpl))
   122  	commandsTmpl := template.Must(template.New("commands").Funcs(funcs).Parse(commandsTmpl))
   123  	commandsTmplWS := template.Must(template.New("commandsWS").Funcs(funcs).Parse(commandsTmplWS))
   124  	downloadCommandTmpl := template.Must(template.New("download").Funcs(funcs).Parse(downloadCommandTmpl))
   125  	registerTmpl := template.Must(template.New("register").Funcs(funcs).Parse(registerTmpl))
   126  
   127  	imports := []*codegen.ImportSpec{
   128  		codegen.SimpleImport("encoding/json"),
   129  		codegen.SimpleImport("fmt"),
   130  		codegen.SimpleImport("log"),
   131  		codegen.SimpleImport("net/url"),
   132  		codegen.SimpleImport("os"),
   133  		codegen.SimpleImport("path"),
   134  		codegen.SimpleImport("path/filepath"),
   135  		codegen.SimpleImport("strings"),
   136  		codegen.SimpleImport("strconv"),
   137  		codegen.SimpleImport("time"),
   138  		codegen.SimpleImport("github.com/goadesign/goa"),
   139  		codegen.SimpleImport("github.com/spf13/cobra"),
   140  		codegen.SimpleImport(clientPkg),
   141  		codegen.SimpleImport("context"),
   142  		codegen.SimpleImport("golang.org/x/net/websocket"),
   143  		codegen.NewImport("uuid", "github.com/goadesign/goa/uuid"),
   144  	}
   145  	if len(g.API.Resources) > 0 {
   146  		imports = append(imports, codegen.NewImport("goaclient", "github.com/goadesign/goa/client"))
   147  	}
   148  	title := fmt.Sprintf("%s: CLI Commands", g.API.Context())
   149  	if err = file.WriteHeader(title, "cli", imports); err != nil {
   150  		return err
   151  	}
   152  	g.genfiles = append(g.genfiles, commandsFile)
   153  
   154  	file.Write([]byte("type (\n"))
   155  	var fs []*design.FileServerDefinition
   156  	if err = g.API.IterateResources(func(res *design.ResourceDefinition) error {
   157  		fs = append(fs, res.FileServers...)
   158  		return res.IterateActions(func(action *design.ActionDefinition) error {
   159  			return commandTypesTmpl.Execute(file, action)
   160  		})
   161  	}); err != nil {
   162  		return err
   163  	}
   164  	if len(fs) > 0 {
   165  		file.Write([]byte(downloadCommandType))
   166  	}
   167  	file.Write([]byte(")\n\n"))
   168  
   169  	actions := make(map[string][]*design.ActionDefinition)
   170  	hasDownloads := false
   171  	g.API.IterateResources(func(res *design.ResourceDefinition) error {
   172  		if len(res.FileServers) > 0 {
   173  			hasDownloads = true
   174  		}
   175  		return res.IterateActions(func(action *design.ActionDefinition) error {
   176  			name := codegen.Goify(action.Name, false)
   177  			if as, ok := actions[name]; ok {
   178  				actions[name] = append(as, action)
   179  			} else {
   180  				actions[name] = []*design.ActionDefinition{action}
   181  			}
   182  			return nil
   183  		})
   184  	})
   185  	data := struct {
   186  		Actions      map[string][]*design.ActionDefinition
   187  		Package      string
   188  		HasDownloads bool
   189  	}{
   190  		Actions:      actions,
   191  		Package:      g.Target,
   192  		HasDownloads: hasDownloads,
   193  	}
   194  	if err = file.ExecuteTemplate("registerCmds", registerCmdsT, funcs, data); err != nil {
   195  		return err
   196  	}
   197  
   198  	var fsdata []map[string]interface{}
   199  	g.API.IterateResources(func(res *design.ResourceDefinition) error {
   200  		if res.FileServers != nil {
   201  			res.IterateFileServers(func(fs *design.FileServerDefinition) error {
   202  				wcs := design.ExtractWildcards(fs.RequestPath)
   203  				isDir := len(wcs) > 0
   204  				var reqDir, filename string
   205  				if isDir {
   206  					reqDir, _ = path.Split(fs.RequestPath)
   207  				} else {
   208  					_, filename = filepath.Split(fs.FilePath)
   209  				}
   210  				fsdata = append(fsdata, map[string]interface{}{
   211  					"IsDir":       isDir,
   212  					"RequestPath": fs.RequestPath,
   213  					"FilePath":    fs.FilePath,
   214  					"FileName":    filename,
   215  					"Name":        g.fileServerMethod(fs),
   216  					"RequestDir":  reqDir,
   217  				})
   218  				return nil
   219  			})
   220  		}
   221  		return nil
   222  	})
   223  	if fsdata != nil {
   224  		data := struct {
   225  			Package     string
   226  			FileServers []map[string]interface{}
   227  		}{
   228  			Package:     g.Target,
   229  			FileServers: fsdata,
   230  		}
   231  		if err = downloadCommandTmpl.Execute(file, data); err != nil {
   232  			return err
   233  		}
   234  	}
   235  	err = g.API.IterateResources(func(res *design.ResourceDefinition) error {
   236  		return res.IterateActions(func(action *design.ActionDefinition) error {
   237  			data := map[string]interface{}{
   238  				"Action":          action,
   239  				"Resource":        action.Parent,
   240  				"Package":         g.Target,
   241  				"HasMultiContent": len(g.API.Consumes) > 1,
   242  			}
   243  			var err error
   244  			if action.WebSocket() {
   245  				err = commandsTmplWS.Execute(file, data)
   246  			} else {
   247  				err = commandsTmpl.Execute(file, data)
   248  
   249  			}
   250  			if err != nil {
   251  				return err
   252  			}
   253  			err = registerTmpl.Execute(file, data)
   254  			return err
   255  		})
   256  	})
   257  	return
   258  }
   259  
   260  // defaultRouteParams returns the parameters needed to build the first route of the given action.
   261  func defaultRouteParams(a *design.ActionDefinition) *design.AttributeDefinition {
   262  	r := a.Routes[0]
   263  	params := r.Params()
   264  	o := make(design.Object, len(params))
   265  	nz := make(map[string]bool, len(params))
   266  	pparams := a.PathParams()
   267  	for _, p := range params {
   268  		o[p] = pparams.Type.ToObject()[p]
   269  		nz[p] = true
   270  	}
   271  	return &design.AttributeDefinition{Type: o, NonZeroAttributes: nz}
   272  }
   273  
   274  // produces a fmt template to render the first route of action.
   275  func defaultRouteTemplate(a *design.ActionDefinition) string {
   276  	return design.WildcardRegex.ReplaceAllLiteralString(a.Routes[0].FullPath(), "/%v")
   277  }
   278  
   279  // return a ',' joined list of Params as a reference to cmd.XFieldName
   280  // ordered by the required first rules.
   281  func joinRouteParams(action *design.ActionDefinition, att *design.AttributeDefinition) string {
   282  	var (
   283  		params = action.Routes[0].Params()
   284  		elems  = make([]string, len(params))
   285  	)
   286  	for i, p := range params {
   287  		patt, ok := att.Type.ToObject()[p]
   288  		if !ok {
   289  			continue
   290  		}
   291  		pf := "cmd.%s"
   292  		if patt.Type.Kind() == design.StringKind {
   293  			pf = "url.QueryEscape(cmd.%s)"
   294  		}
   295  		field := fmt.Sprintf(pf, codegen.Goify(p, true))
   296  		elems[i] = field
   297  	}
   298  	return strings.Join(elems, ", ")
   299  }
   300  
   301  // joinNames is a code generation helper function that generates a string built from concatenating
   302  // the keys of the given attribute type (assuming it's an object).
   303  func joinNames(useNil bool, atts ...*design.AttributeDefinition) string {
   304  	var elems []string
   305  	for _, att := range atts {
   306  		if att == nil {
   307  			continue
   308  		}
   309  		obj := att.Type.ToObject()
   310  		var names, optNames []string
   311  
   312  		keys := make([]string, len(obj))
   313  		i := 0
   314  		for n := range obj {
   315  			keys[i] = n
   316  			i++
   317  		}
   318  		sort.Strings(keys)
   319  
   320  		for _, n := range keys {
   321  			a := obj[n]
   322  			field := fmt.Sprintf("cmd.%s", codegen.Goify(n, true))
   323  			if !a.Type.IsArray() && !att.IsRequired(n) && !att.IsNonZero(n) {
   324  				if useNil {
   325  					field = flagTypeVal(a, n, field)
   326  				} else {
   327  					field = "&" + field
   328  				}
   329  			} else if a.Type.IsArray() {
   330  				field = flagTypeArrayVal(a, field)
   331  			} else {
   332  				field = flagRequiredTypeVal(a, field)
   333  			}
   334  			if att.IsRequired(n) {
   335  				names = append(names, field)
   336  			} else {
   337  				optNames = append(optNames, field)
   338  			}
   339  		}
   340  		elems = append(elems, names...)
   341  		elems = append(elems, optNames...)
   342  	}
   343  	return strings.Join(elems, ", ")
   344  }
   345  
   346  // resolve non required, non array Param/QueryParam for access via CII flags.
   347  // Some types need convertion from string to 'Type' before calling rich client Commands.
   348  func flagTypeVal(a *design.AttributeDefinition, key string, field string) string {
   349  	switch a.Type {
   350  	case design.Integer:
   351  		return `intFlagVal("` + key + `", ` + field + ")"
   352  	case design.String:
   353  		return `stringFlagVal("` + key + `", ` + field + ")"
   354  	case design.Number, design.Boolean, design.UUID, design.DateTime, design.Any:
   355  		return "%s"
   356  	default:
   357  		return "&" + field
   358  	}
   359  }
   360  
   361  // resolve required Param/QueryParam for access via CII flags.
   362  // Required Params are not generated as pointers
   363  // Special types like Number/UUID need to be converted from String
   364  // %s maps to specialTypeResult.Temps
   365  func flagRequiredTypeVal(a *design.AttributeDefinition, field string) string {
   366  	switch a.Type {
   367  	case design.Number, design.Boolean, design.UUID, design.DateTime, design.Any:
   368  		return "*%s"
   369  	default:
   370  		return field
   371  	}
   372  }
   373  
   374  // resolve required Param/QueryParam for access via CII flags.
   375  // Special types like Number/UUID need to be converted from String
   376  // %s maps to specialTypeResult.Temps
   377  func flagTypeArrayVal(a *design.AttributeDefinition, field string) string {
   378  	switch a.Type.ToArray().ElemType.Type {
   379  	case design.Number, design.Boolean, design.UUID, design.DateTime, design.Any:
   380  		return "%s"
   381  	}
   382  	return field
   383  }
   384  
   385  // format a stirng format("%s") with the given vars as argument
   386  func format(format string, vars []string) string {
   387  	new := make([]interface{}, len(vars))
   388  	for i, v := range vars {
   389  		new[i] = v
   390  	}
   391  	return fmt.Sprintf(format, new...)
   392  }
   393  
   394  // temp structure to describe the relationship between XParams
   395  // and their tmp var as generated in the Output. See handleSpecialTypes
   396  type specialTypeResult struct {
   397  	Temps  []string
   398  	Output string
   399  }
   400  
   401  // generate the relation and output of specially typed Params that need
   402  // custom convertion from String Flags to Rich objects in Client action
   403  //
   404  // tmp, err := uuidVal(cmd.X)
   405  // if err != nil {
   406  //        goa.LogError(ctx, "argument parse failed", "err", err)
   407  //        return err
   408  // }
   409  // resp, err := c.ShowX(ctx, path, tmp)
   410  //
   411  func handleSpecialTypes(atts ...*design.AttributeDefinition) specialTypeResult {
   412  	result := specialTypeResult{}
   413  	for _, att := range atts {
   414  		if att == nil {
   415  			continue
   416  		}
   417  		obj := att.Type.ToObject()
   418  		var names, optNames []string
   419  
   420  		keys := make([]string, len(obj))
   421  		i := 0
   422  		for n := range obj {
   423  			keys[i] = n
   424  			i++
   425  		}
   426  		sort.Strings(keys)
   427  		for _, n := range keys {
   428  			a := obj[n]
   429  			field := fmt.Sprintf("cmd.%s", codegen.Goify(n, true))
   430  			typ := cmdFieldType(a.Type, true)
   431  			var typeHandler, nilVal string
   432  			if !a.Type.IsArray() {
   433  				nilVal = `""`
   434  				switch a.Type {
   435  				case design.Number:
   436  					typeHandler = "float64Val"
   437  				case design.Boolean:
   438  					typeHandler = "boolVal"
   439  				case design.UUID:
   440  					typeHandler = "uuidVal"
   441  				case design.DateTime:
   442  					typeHandler = "timeVal"
   443  				case design.Any:
   444  					typeHandler = "jsonVal"
   445  				}
   446  
   447  			} else if a.Type.IsArray() {
   448  				nilVal = "nil"
   449  				switch a.Type.ToArray().ElemType.Type {
   450  				case design.Number:
   451  					typeHandler = "float64Array"
   452  				case design.Boolean:
   453  					typeHandler = "boolArray"
   454  				case design.UUID:
   455  					typeHandler = "uuidArray"
   456  				case design.DateTime:
   457  					typeHandler = "timeArray"
   458  				case design.Any:
   459  					typeHandler = "jsonArray"
   460  				}
   461  			}
   462  			if typeHandler != "" {
   463  				tmpVar := codegen.Tempvar()
   464  				if att.IsRequired(n) {
   465  					names = append(names, tmpVar)
   466  				} else {
   467  					optNames = append(optNames, tmpVar)
   468  				}
   469  
   470  				//result.Temps = append(result.Temps, tmpVar)
   471  				result.Output += fmt.Sprintf(`
   472  	var %s %s
   473  	if %s != %s {
   474  		var err error
   475  		%s, err = %s(%s)
   476  		if err != nil {
   477  			goa.LogError(ctx, "failed to parse flag into %s value", "flag", "--%s", "err", err)
   478  			return err
   479  		}
   480  	}`, tmpVar, typ, field, nilVal, tmpVar, typeHandler, field, typ, n)
   481  				if att.IsRequired(n) {
   482  					result.Output += fmt.Sprintf(`
   483  	if %s == nil {
   484  		goa.LogError(ctx, "required flag is missing", "flag", "--%s")
   485  		return fmt.Errorf("required flag %s is missing")
   486  	}`, tmpVar, n, n)
   487  				}
   488  			}
   489  		}
   490  		result.Temps = append(result.Temps, names...)
   491  		result.Temps = append(result.Temps, optNames...)
   492  	}
   493  	return result
   494  }
   495  
   496  // routes create the action command "Use" suffix.
   497  func routes(action *design.ActionDefinition) string {
   498  	var buf bytes.Buffer
   499  	routes := action.Routes
   500  	buf.WriteRune('[')
   501  	if len(routes) > 1 {
   502  		buf.WriteRune('(')
   503  	}
   504  	paths := make([]string, len(routes))
   505  	for i, r := range routes {
   506  		path := r.FullPath()
   507  		matches := design.WildcardRegex.FindAllStringSubmatch(path, -1)
   508  		for _, match := range matches {
   509  			paramName := match[1]
   510  			path = strings.Replace(path, ":"+paramName, strings.ToUpper(paramName), 1)
   511  		}
   512  		paths[i] = fmt.Sprintf("%q", path)
   513  	}
   514  	buf.WriteString(strings.Join(paths, "|"))
   515  	if len(routes) > 1 {
   516  		buf.WriteRune(')')
   517  	}
   518  	buf.WriteRune(']')
   519  	return buf.String()
   520  }
   521  
   522  // signerSignature returns the callee signature for the signer factory function for the given security
   523  // scheme.
   524  func signerSignature(sec *design.SecuritySchemeDefinition) string {
   525  	switch sec.Type {
   526  	case "basic":
   527  		return "user, pass string"
   528  	case "apiKey":
   529  		return "key, format string"
   530  	case "jwt":
   531  		return "source goaclient.TokenSource"
   532  	case "oauth2":
   533  		return "source goaclient.TokenSource"
   534  	default:
   535  		return ""
   536  	}
   537  }
   538  
   539  // signerArgs returns the caller signature for the signer factory function for the given security
   540  // scheme.
   541  func signerArgs(sec *design.SecuritySchemeDefinition) string {
   542  	switch sec.Type {
   543  	case "basic":
   544  		return "user, pass"
   545  	case "apiKey":
   546  		return "key, format"
   547  	case "jwt":
   548  		return "source"
   549  	case "oauth2":
   550  		return "source"
   551  	default:
   552  		return ""
   553  	}
   554  }
   555  
   556  // flagType returns the flag type for the given (basic type) attribute definition.
   557  func flagType(att *design.AttributeDefinition) string {
   558  	switch att.Type.Kind() {
   559  	case design.IntegerKind:
   560  		return "Int"
   561  	case design.NumberKind:
   562  		return "String"
   563  	case design.BooleanKind:
   564  		return "String"
   565  	case design.StringKind:
   566  		return "String"
   567  	case design.DateTimeKind:
   568  		return "String"
   569  	case design.UUIDKind:
   570  		return "String"
   571  	case design.AnyKind:
   572  		return "String"
   573  	case design.ArrayKind:
   574  		switch att.Type.ToArray().ElemType.Type.Kind() {
   575  		case design.NumberKind:
   576  			return "StringSlice"
   577  		case design.BooleanKind:
   578  			return "StringSlice"
   579  		default:
   580  			return flagType(att.Type.(*design.Array).ElemType) + "Slice"
   581  		}
   582  	case design.UserTypeKind:
   583  		return flagType(att.Type.(*design.UserTypeDefinition).AttributeDefinition)
   584  	case design.MediaTypeKind:
   585  		return flagType(att.Type.(*design.MediaTypeDefinition).AttributeDefinition)
   586  	default:
   587  		panic("invalid flag attribute type " + att.Type.Name())
   588  	}
   589  }
   590  
   591  func shouldAddExample(ut *design.UserTypeDefinition) bool {
   592  	if ut == nil {
   593  		return false
   594  	}
   595  	return ut.Example != nil
   596  }
   597  
   598  func formatExample(example interface{}) string {
   599  	if example == nil {
   600  		return ""
   601  	}
   602  	data, _ := json.MarshalIndent(example, "", "   ")
   603  	return string(data)
   604  }
   605  
   606  const mainTmpl = `
   607  func main() {
   608  	// Create command line parser
   609  	app := &cobra.Command{
   610  		Use: "{{ .API.Name }}-cli",
   611  		Short: ` + "`" + `CLI client for the {{ .API.Name }} service{{ if .API.Docs }} ({{ escapeBackticks .API.Docs.URL }}){{ end }}` + "`" + `,
   612  	}
   613  
   614  	// Create client struct
   615  	httpClient := newHTTPClient()
   616  	c := {{ .Package }}.New(goaclient.HTTPClientDoer(httpClient))
   617  
   618  	// Register global flags
   619  	app.PersistentFlags().StringVarP(&c.Scheme, "scheme", "s", "", "Set the requests scheme")
   620  	app.PersistentFlags().StringVarP(&c.Host, "host", "H", "{{ .API.Host }}", "API hostname")
   621  	app.PersistentFlags().DurationVarP(&httpClient.Timeout, "timeout", "t", time.Duration(20) * time.Second, "Set the request timeout")
   622  	app.PersistentFlags().BoolVar(&c.Dump, "dump", false, "Dump HTTP request and response.")
   623  
   624  {{ if .HasSigners }}	// Register signer flags
   625  {{ if .HasBasicAuthSigners }} var user, pass string
   626  	app.PersistentFlags().StringVar(&user, "user", "", "Username used for authentication")
   627  	app.PersistentFlags().StringVar(&pass, "pass", "", "Password used for authentication")
   628  {{ end }}{{ if .HasAPIKeySigners }} var key, format string
   629  	app.PersistentFlags().StringVar(&key, "key", "", "API key used for authentication")
   630  	app.PersistentFlags().StringVar(&format, "format", "Bearer %s", "Format used to create auth header or query from key")
   631  {{ end }}{{ if .HasTokenSigners }} var token, typ string
   632  	app.PersistentFlags().StringVar(&token, "token", "", "Token used for authentication")
   633  	app.PersistentFlags().StringVar(&typ, "token-type", "Bearer", "Token type used for authentication")
   634  {{ end }}
   635  	// Parse flags and setup signers
   636  	app.ParseFlags(os.Args)
   637  {{ if .HasTokenSigners }}	source := &goaclient.StaticTokenSource{
   638  		StaticToken: &goaclient.StaticToken{Type: typ, Value: token},
   639  	}
   640  {{ end }}{{ end }}{{ range $security := .API.SecuritySchemes }}{{ $signer := signerType $security }}{{ if $signer }}{{/*
   641  */}}	{{ goify $security.SchemeName false }}Signer := new{{ goify $security.SchemeName true }}Signer({{ signerArgs $security }}){{ end }}
   642  {{ end }}
   643  
   644  	// Initialize API client
   645  {{ range $security := .API.SecuritySchemes }}{{ $signer := signerType $security }}{{ if $signer }}{{/*
   646  */}}	c.Set{{ goify $security.SchemeName true }}Signer({{ goify $security.SchemeName false }}Signer)
   647  {{ end }}{{ end }} c.UserAgent = "{{ .API.Name }}-cli/{{ .Version }}"
   648  
   649  	// Register API commands
   650  	cli.RegisterCommands(app, c)
   651  
   652  	// Execute!
   653  	if err := app.Execute(); err != nil {
   654  		fmt.Fprintf(os.Stderr, err.Error())
   655  		os.Exit(-1)
   656  	}
   657  }
   658  
   659  // newHTTPClient returns the HTTP client used by the API client to make requests to the service.
   660  func newHTTPClient() *http.Client {
   661  	// TBD: Change as needed (e.g. to use a different transport to control redirection policy or
   662  	// disable cert validation or...)
   663  	return http.DefaultClient
   664  }
   665  
   666  {{ range $security := .API.SecuritySchemes }}{{ $signer := signerType $security }}{{ if $signer }}
   667  // new{{ goify $security.SchemeName true }}Signer returns the request signer used for authenticating
   668  // against the {{ $security.SchemeName }} security scheme.
   669  func new{{ goify $security.SchemeName true }}Signer({{ signerSignature $security }}) goaclient.Signer {
   670  {{ if eq .Type "basic" }}	return &goaclient.BasicSigner{
   671  		Username: user,
   672  		Password: pass,
   673  	}
   674  {{ else if eq .Type "apiKey" }}	return &goaclient.APIKeySigner{
   675  		SignQuery: {{ if eq $security.In "query" }}true{{ else }}false{{ end }},
   676  		KeyName: "{{ $security.Name }}",
   677  		KeyValue: key,
   678  		Format: {{ if eq $security.In "query" }}"%s"{{ else }}format{{ end }},
   679  	}
   680  {{ else if eq .Type "jwt" }}	return &goaclient.JWTSigner{
   681  		TokenSource: source,
   682  	}
   683  {{ else if eq .Type "oauth2" }}	return &goaclient.OAuth2Signer{
   684  		TokenSource: source,
   685  	}
   686  {{ end }}
   687  }
   688  {{ end }}{{ end }}
   689  `
   690  
   691  const commandTypesTmpl = `{{ $cmdName := goify (printf "%s%sCommand" .Name (title (kebabCase .Parent.Name))) true }}	// {{ $cmdName }} is the command line data structure for the {{ .Name }} action of {{ .Parent.Name }}
   692  	{{ $cmdName }} struct {
   693  {{ if .Payload }}		Payload string
   694  		ContentType string
   695  {{ end }}{{ $params := defaultRouteParams . }}{{ if $params }}{{ range $name, $att := $params.Type.ToObject }}{{ if $att.Description }}		{{ multiComment $att.Description }}
   696  {{ end }}		{{ goify $name true }} {{ cmdFieldType $att.Type false }}
   697  {{ end }}{{ end }}{{ $params := .QueryParams }}{{ if $params }}{{ range $name, $att := $params.Type.ToObject }}{{ if $att.Description }}		{{ multiComment $att.Description }}
   698  {{ end }}		{{ goify $name true }} {{ cmdFieldType $att.Type false}}
   699  {{ end }}{{ end }}{{ $headers := .Headers }}{{ if $headers }}{{ range $name, $att := $headers.Type.ToObject }}{{ if $att.Description }}		{{ multiComment $att.Description }}
   700  {{ end }}		{{ goify $name true }} {{ cmdFieldType $att.Type false}}
   701  {{ end }}{{ end }}		PrettyPrint bool
   702  	}
   703  
   704  `
   705  
   706  const downloadCommandType = `// DownloadCommand is the command line data structure for the download command.
   707  	DownloadCommand struct {
   708  		// OutFile is the path to the download output file.
   709  		OutFile string
   710  	}
   711  
   712  `
   713  
   714  const commandsTmplWS = `
   715  {{ $cmdName := goify (printf "%s%sCommand" .Action.Name (title (kebabCase .Resource.Name))) true }}// Run establishes a websocket connection for the {{ $cmdName }} command.
   716  func (cmd *{{ $cmdName }}) Run(c *{{ .Package }}.Client, args []string) error {
   717  	var path string
   718  	if len(args) > 0 {
   719  		path = args[0]
   720  	} else {
   721  {{ $default := defaultPath .Action }}{{ if $default }}	path = "{{ $default }}"
   722  {{ else }}{{ $pparams := defaultRouteParams .Action }}	path = fmt.Sprintf({{ printf "%q" (defaultRouteTemplate .Action)}}, {{ joinRouteParams .Action $pparams }})
   723  {{ end }}	}
   724  	logger := goa.NewLogger(log.New(os.Stderr, "", log.LstdFlags))
   725  	ctx := goa.WithLogger(context.Background(), logger){{ $specialTypeResult := handleSpecialTypes .Action.QueryParams .Action.Headers }}{{ $specialTypeResult.Output }}
   726  	ws, err := c.{{ goify (printf "%s%s" .Action.Name (title .Resource.Name)) true }}(ctx, path{{/*
   727  	*/}}{{ $params := joinNames true .Action.QueryParams .Action.Headers }}{{ if $params }}, {{ format $params $specialTypeResult.Temps }}{{ end }})
   728  	if err != nil {
   729  		goa.LogError(ctx, "failed", "err", err)
   730  		return err
   731  	}
   732  	go goaclient.WSWrite(ws)
   733  	goaclient.WSRead(ws)
   734  
   735  	return nil
   736  }
   737  `
   738  
   739  const downloadCommandTmpl = `
   740  // Run downloads files with given paths.
   741  func (cmd *DownloadCommand) Run(c *{{ .Package }}.Client, args []string) error {
   742  	var (
   743  		fnf func (context.Context, string) (int64, error)
   744  		fnd func (context.Context, string, string) (int64, error)
   745  
   746  		rpath = args[0]
   747  		outfile = cmd.OutFile
   748  		logger = goa.NewLogger(log.New(os.Stderr, "", log.LstdFlags))
   749  		ctx = goa.WithLogger(context.Background(), logger)
   750  		err error
   751  	)
   752  
   753  	if rpath[0] != '/' {
   754  		rpath = "/" + rpath
   755  	}
   756  {{ range .FileServers }}{{ if not .IsDir }}	if rpath == "{{ .RequestPath }}" {
   757  		fnf = c.{{ .Name }}
   758  		if outfile == "" {
   759  			outfile = "{{ .FileName }}"
   760  		}
   761  		goto found
   762  	}
   763  {{ end }}{{ end }}{{ range .FileServers }}{{ if .IsDir }}	if strings.HasPrefix(rpath, "{{ .RequestDir }}") {
   764  		fnd = c.{{ .Name }}
   765  		rpath = rpath[{{ len .RequestDir }}:]
   766  		if outfile == "" {
   767  			_, outfile = path.Split(rpath)
   768  		}
   769  		goto found
   770  	}
   771  {{ end }}{{ end }}	return fmt.Errorf("don't know how to download %s", rpath)
   772  found:
   773  	ctx = goa.WithLogContext(ctx, "file", outfile)
   774  	if fnf != nil {
   775  		_, err = fnf(ctx, outfile)
   776  	} else {
   777  		_, err = fnd(ctx, rpath, outfile)
   778  	}
   779  	if err != nil {
   780  		goa.LogError(ctx, "failed", "err", err)
   781  		return err
   782  	}
   783  
   784  	return nil
   785  }
   786  `
   787  
   788  const registerTmpl = `{{ $cmdName := goify (printf "%s%sCommand" .Action.Name (title (kebabCase .Resource.Name))) true }}// RegisterFlags registers the command flags with the command line.
   789  func (cmd *{{ $cmdName }}) RegisterFlags(cc *cobra.Command, c *{{ .Package }}.Client) {
   790  {{ if .Action.Payload }}	cc.Flags().StringVar(&cmd.Payload, "payload", "", "Request body encoded in JSON")
   791  	cc.Flags().StringVar(&cmd.ContentType, "content", "", "Request content type override, e.g. 'application/x-www-form-urlencoded'")
   792  {{ end }}{{ $pparams := defaultRouteParams .Action }}{{ if $pparams }}{{ range $pname, $pparam := $pparams.Type.ToObject }}{{ $tmp := goify $pname false }}{{/*
   793  */}}{{ if not $pparam.DefaultValue }}	var {{ $tmp }} {{ cmdFieldType $pparam.Type false }}
   794  {{ end }}	cc.Flags().{{ flagType $pparam }}Var(&cmd.{{ goify $pname true }}, "{{ $pname }}", {{/*
   795  */}}{{ if $pparam.DefaultValue }}{{ printf "%#v" $pparam.DefaultValue }}{{ else }}{{ $tmp }}{{ end }}, ` + "`" + `{{ escapeBackticks $pparam.Description }}` + "`" + `)
   796  {{ end }}{{ end }}{{ $params := .Action.QueryParams }}{{ if $params }}{{ range $name, $param := $params.Type.ToObject }}{{ $tmp := goify $name false }}{{/*
   797  */}}{{ if not $param.DefaultValue }}	var {{ $tmp }} {{ cmdFieldType $param.Type false }}
   798  {{ end }}	cc.Flags().{{ flagType $param }}Var(&cmd.{{ goify $name true }}, "{{ $name }}", {{/*
   799  */}}{{ if $param.DefaultValue }}{{ printf "%#v" $param.DefaultValue }}{{ else }}{{ $tmp }}{{ end }}, ` + "`" + `{{ escapeBackticks $param.Description }}` + "`" + `)
   800  {{ end }}{{ end }}{{ $headers := .Action.Headers }}{{ if $headers }}{{ range $name, $header := $headers.Type.ToObject }}{{/*
   801  */}} cc.Flags().StringVar(&cmd.{{ goify $name true }}, "{{ $name }}", {{/*
   802  */}}{{ if $header.DefaultValue }}{{ printf "%q" $header.DefaultValue }}{{ else }}""{{ end }}, ` + "`" + `{{ escapeBackticks $header.Description }}` + "`" + `)
   803  {{ end }}{{ end }}}`
   804  
   805  const commandsTmpl = `
   806  {{ $cmdName := goify (printf "%s%sCommand" .Action.Name (title (kebabCase .Resource.Name))) true }}// Run makes the HTTP request corresponding to the {{ $cmdName }} command.
   807  func (cmd *{{ $cmdName }}) Run(c *{{ .Package }}.Client, args []string) error {
   808  	var path string
   809  	if len(args) > 0 {
   810  		path = args[0]
   811  	} else {
   812  {{ $default := defaultPath .Action }}{{ if $default }}	path = "{{ $default }}"
   813  {{ else }}{{ $pparams := defaultRouteParams .Action }}	path = fmt.Sprintf({{ printf "%q" (defaultRouteTemplate .Action) }}, {{ joinRouteParams .Action $pparams }})
   814  {{ end }}	}
   815  {{ if .Action.Payload }}var payload {{ gotyperefext .Action.Payload 2 .Package }}
   816  	if cmd.Payload != "" {
   817  		err := json.Unmarshal([]byte(cmd.Payload), &payload)
   818  		if err != nil {
   819  {{ if eq .Action.Payload.Type.Kind 4 }}	payload = cmd.Payload
   820  {{ else }}			return fmt.Errorf("failed to deserialize payload: %s", err)
   821  {{ end }}		}
   822  	}
   823  {{ end }}	logger := goa.NewLogger(log.New(os.Stderr, "", log.LstdFlags))
   824  	ctx := goa.WithLogger(context.Background(), logger){{ $specialTypeResult := handleSpecialTypes .Action.QueryParams .Action.Headers }}{{ $specialTypeResult.Output }}
   825  	resp, err := c.{{ goify (printf "%s%s" .Action.Name (title .Resource.Name)) true }}(ctx, path{{ if .Action.Payload }}, {{/*
   826  	*/}}{{ if or .Action.Payload.Type.IsObject .Action.Payload.IsPrimitive }}&{{ end }}payload{{ else }}{{ end }}{{/*
   827  	*/}}{{ $params := joinNames true .Action.QueryParams .Action.Headers }}{{ if $params }}, {{ format $params $specialTypeResult.Temps }}{{ end }}{{/*
   828  	*/}}{{ if and .Action.Payload .HasMultiContent }}, cmd.ContentType{{ end }})
   829  	if err != nil {
   830  		goa.LogError(ctx, "failed", "err", err)
   831  		return err
   832  	}
   833  
   834  	goaclient.HandleResponse(c.Client, resp, cmd.PrettyPrint)
   835  	return nil
   836  }
   837  `
   838  
   839  // Takes map[string][]*design.ActionDefinition as input
   840  const registerCmdsT = `// RegisterCommands registers the resource action CLI commands.
   841  func RegisterCommands(app *cobra.Command, c *{{ .Package }}.Client) {
   842  {{ with .Actions }}{{ if gt (len .) 0 }}	var command, sub *cobra.Command
   843  {{ end }}{{ range $name, $actions := . }}	command = &cobra.Command{
   844  		Use:   "{{ kebabCase $name }}",
   845  		Short: ` + "`" + `{{ if eq (len $actions) 1 }}{{ $a := index $actions 0 }}{{ escapeBackticks $a.Description }}{{ else }}{{ $name }} action{{ end }}` + "`" + `,
   846  	}
   847  {{ range $action := $actions }}{{ $cmdName := goify (printf "%s%sCommand" $action.Name (title (kebabCase $action.Parent.Name))) true }}{{/*
   848  */}}{{ $tmp := tempvar }}	{{ $tmp }} := new({{ $cmdName }})
   849  	sub = &cobra.Command{
   850  		Use:   ` + "`" + `{{ kebabCase $action.Parent.Name }} {{ routes $action }}` + "`" + `,
   851  		Short: ` + "`" + `{{ escapeBackticks $action.Parent.Description }}` + "`" + `,{{ if shouldAddExample $action.Payload }}
   852  		Long:  ` + "`" + `{{ escapeBackticks $action.Parent.Description }}
   853  
   854  Payload example:
   855  
   856  {{ formatExample $action.Payload.Example }}` + "`" + `,{{ end }}
   857  		RunE:  func(cmd *cobra.Command, args []string) error { return {{ $tmp }}.Run(c, args) },
   858  	}
   859  	{{ $tmp }}.RegisterFlags(sub, c)
   860  	sub.PersistentFlags().BoolVar(&{{ $tmp }}.PrettyPrint, "pp", false, "Pretty print response body")
   861  	command.AddCommand(sub)
   862  {{ end }}app.AddCommand(command)
   863  {{ end }}{{ end }}{{ if .HasDownloads }}
   864  	dl := new(DownloadCommand)
   865  	dlc := &cobra.Command{
   866  		Use:	"download [PATH]",
   867  		Short: "Download file with given path",
   868  		RunE: func(cmd *cobra.Command, args []string) error {
   869  			return dl.Run(c, args)
   870  		},
   871  	}
   872  	dlc.Flags().StringVar(&dl.OutFile, "out", "", "Output file")
   873  	app.AddCommand(dlc)
   874  {{ end }}}
   875  
   876  func intFlagVal(name string, parsed int) *int {
   877  	if hasFlag(name) {
   878  		return &parsed
   879  	}
   880  	return nil
   881  }
   882  
   883  func float64FlagVal(name string, parsed float64) *float64 {
   884  	if hasFlag(name) {
   885  		return &parsed
   886  	}
   887  	return nil
   888  }
   889  
   890  func boolFlagVal(name string, parsed bool) *bool {
   891  	if hasFlag(name) {
   892  		return &parsed
   893  	}
   894  	return nil
   895  }
   896  
   897  func stringFlagVal(name string, parsed string) *string {
   898  	if hasFlag(name) {
   899  		return &parsed
   900  	}
   901  	return nil
   902  }
   903  
   904  func hasFlag(name string) bool {
   905  	for _, arg := range os.Args[1:] {
   906  		if strings.HasPrefix(arg, "--"+name) {
   907  			return true
   908  		}
   909  	}
   910  	return false
   911  }
   912  
   913  func jsonVal(val string) (*interface{}, error) {
   914  	var t interface{}
   915  	err := json.Unmarshal([]byte(val), &t)
   916  	if err != nil {
   917  		return nil, err
   918  	}
   919  	return &t, nil
   920  }
   921  
   922  func jsonArray(ins []string) ([]interface{}, error) {
   923  	if ins == nil {
   924  		return nil, nil
   925  	}
   926  	var vals []interface{}
   927  	for _, id := range ins {
   928  		val, err := jsonVal(id)
   929  		if err != nil {
   930  			return nil, err
   931  		}
   932  		vals = append(vals, val)
   933  	}
   934  	return vals, nil
   935  }
   936  
   937  func timeVal(val string) (*time.Time, error) {
   938  	t, err := time.Parse(time.RFC3339, val)
   939  	if err != nil {
   940  		return nil, err
   941  	}
   942  	return &t, nil
   943  }
   944  
   945  func timeArray(ins []string) ([]time.Time, error) {
   946  	if ins == nil {
   947  		return nil, nil
   948  	}
   949  	var vals []time.Time
   950  	for _, id := range ins {
   951  		val, err := timeVal(id)
   952  		if err != nil {
   953  			return nil, err
   954  		}
   955  		vals = append(vals, *val)
   956  	}
   957  	return vals, nil
   958  }
   959  
   960  func uuidVal(val string) (*uuid.UUID, error) {
   961  	t, err := uuid.FromString(val)
   962  	if err != nil {
   963  		return nil, err
   964  	}
   965  	return &t, nil
   966  }
   967  
   968  func uuidArray(ins []string) ([]uuid.UUID, error) {
   969  	if ins == nil {
   970  		return nil, nil
   971  	}
   972  	var vals []uuid.UUID
   973  	for _, id := range ins {
   974  		val, err := uuidVal(id)
   975  		if err != nil {
   976  			return nil, err
   977  		}
   978  		vals = append(vals, *val)
   979  	}
   980  	return vals, nil
   981  }
   982  
   983  func float64Val(val string) (*float64, error) {
   984  	t, err := strconv.ParseFloat(val, 64)
   985  	if err != nil {
   986  		return nil, err
   987  	}
   988  	return &t, nil
   989  }
   990  
   991  func float64Array(ins []string) ([]float64, error) {
   992  	if ins == nil {
   993  		return nil, nil
   994  	}
   995  	var vals []float64
   996  	for _, id := range ins {
   997  		val, err := float64Val(id)
   998  		if err != nil {
   999  			return nil, err
  1000  		}
  1001  		vals = append(vals, *val)
  1002  	}
  1003  	return vals, nil
  1004  }
  1005  
  1006  func boolVal(val string) (*bool, error) {
  1007  	t, err := strconv.ParseBool(val)
  1008  	if err != nil {
  1009  		return nil, err
  1010  	}
  1011  	return &t, nil
  1012  }
  1013  
  1014  func boolArray(ins []string) ([]bool, error) {
  1015  	if ins == nil {
  1016  		return nil, nil
  1017  	}
  1018  	var vals []bool
  1019  	for _, id := range ins {
  1020  		val, err := boolVal(id)
  1021  		if err != nil {
  1022  			return nil, err
  1023  		}
  1024  		vals = append(vals, *val)
  1025  	}
  1026  	return vals, nil
  1027  }`