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 }`