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