func (g *Generator) createMainFile(mainFile string, funcs template.FuncMap) error { g.genfiles = append(g.genfiles, mainFile) file, err := codegen.SourceFileFor(mainFile) if err != nil { return err } funcs["getPort"] = func(hostport string) string { _, port, err := net.SplitHostPort(hostport) if err != nil { return "8080" } return port } outPkg, err := codegen.PackagePath(g.OutDir) if err != nil { return err } appPkg := path.Join(outPkg, "app") imports := []*codegen.ImportSpec{ codegen.SimpleImport("time"), codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("github.com/goadesign/goa/middleware"), codegen.SimpleImport(appPkg), } file.Write([]byte("//go:generate goagen bootstrap -d " + g.DesignPkg + "\n\n")) file.WriteHeader("", "main", imports) data := map[string]interface{}{ "Name": g.API.Name, "API": g.API, } if err = file.ExecuteTemplate("main", mainT, funcs, data); err != nil { return err } return file.FormatCode() }
func (g *Generator) generateClientResources(clientPkg string, funcs template.FuncMap, api *design.APIDefinition) error { clientsTmpl := template.Must(template.New("clients").Funcs(funcs).Parse(clientsTmpl)) imports := []*codegen.ImportSpec{ codegen.SimpleImport("bytes"), codegen.SimpleImport("encoding/json"), codegen.SimpleImport("fmt"), codegen.SimpleImport("io"), codegen.SimpleImport("net/http"), codegen.SimpleImport("net/url"), codegen.SimpleImport("strconv"), codegen.SimpleImport("strings"), } return api.IterateResources(func(res *design.ResourceDefinition) error { filename := filepath.Join(codegen.OutputDir, snakeCase(res.Name)+".go") file, err := codegen.SourceFileFor(filename) if err != nil { return err } if err := file.WriteHeader("", "client", imports); err != nil { return err } g.genfiles = append(g.genfiles, filename) if err := res.IterateActions(func(action *design.ActionDefinition) error { return clientsTmpl.Execute(file, action) }); err != nil { return err } return file.FormatCode() }) }
// NewMediaTypesWriter returns a contexts code writer. // Media types contain the data used to render response bodies. func NewMediaTypesWriter(filename string) (*MediaTypesWriter, error) { file, err := codegen.SourceFileFor(filename) if err != nil { return nil, err } return &MediaTypesWriter{SourceFile: file, Validator: codegen.NewValidator()}, nil }
// NewControllersWriter returns a handlers code writer. // Handlers provide the glue between the underlying request data and the user controller. func NewControllersWriter(filename string) (*ControllersWriter, error) { file, err := codegen.SourceFileFor(filename) if err != nil { return nil, err } return &ControllersWriter{SourceFile: file}, nil }
// NewSecurityWriter returns a security functionality code writer. // Those functionalities are there to support action-middleware related to security. func NewSecurityWriter(filename string) (*SecurityWriter, error) { file, err := codegen.SourceFileFor(filename) if err != nil { return nil, err } return &SecurityWriter{SourceFile: file}, nil }
// NewUserHelperWriter returns a contexts code writer. // User types contain custom data structured defined in the DSL with "Type". func NewUserHelperWriter(filename string) (*UserHelperWriter, error) { file, err := codegen.SourceFileFor(filename) if err != nil { return nil, err } return &UserHelperWriter{SourceFile: file}, nil }
// NewResourcesWriter returns a contexts code writer. // Resources provide the glue between the underlying request data and the user controller. func NewResourcesWriter(filename string) (*ResourcesWriter, error) { file, err := codegen.SourceFileFor(filename) if err != nil { return nil, err } return &ResourcesWriter{SourceFile: file}, nil }
func (g *Generator) generateJS(jsFile string, api *design.APIDefinition) (_ *design.ActionDefinition, err error) { file, err := codegen.SourceFileFor(jsFile) if err != nil { return } g.genfiles = append(g.genfiles, jsFile) if Scheme == "" && len(api.Schemes) > 0 { Scheme = api.Schemes[0] } data := map[string]interface{}{ "API": api, "Host": Host, "Scheme": Scheme, "Timeout": int64(Timeout / time.Millisecond), } if err = file.ExecuteTemplate("module", moduleT, nil, data); err != nil { return } actions := make(map[string][]*design.ActionDefinition) api.IterateResources(func(res *design.ResourceDefinition) error { return res.IterateActions(func(action *design.ActionDefinition) error { if as, ok := actions[action.Name]; ok { actions[action.Name] = append(as, action) } else { actions[action.Name] = []*design.ActionDefinition{action} } return nil }) }) var exampleAction *design.ActionDefinition keys := []string{} for n := range actions { keys = append(keys, n) } sort.Strings(keys) for _, n := range keys { for _, a := range actions[n] { if exampleAction == nil && a.Routes[0].Verb == "GET" { exampleAction = a } data := map[string]interface{}{ "Action": a, "Version": design.Design.APIVersionDefinition, } funcs := template.FuncMap{"params": params} if err = file.ExecuteTemplate("jsFuncs", jsFuncsT, funcs, data); err != nil { return } } } _, err = file.Write([]byte(moduleTend)) return exampleAction, err }
func (g *Generator) generateIndexHTML(htmlFile string, api *design.APIDefinition, exampleAction *design.ActionDefinition) error { file, err := codegen.SourceFileFor(htmlFile) if err != nil { return err } g.genfiles = append(g.genfiles, htmlFile) argNames := params(exampleAction) var args string if len(argNames) > 0 { query := exampleAction.QueryParams.Type.ToObject() argValues := make([]string, len(argNames)) for i, n := range argNames { q := query[n].Type.ToArray().ElemType // below works because we deal with simple types in query strings if q.Example != nil { argValues[i] = fmt.Sprintf("%v", q.Example) } else { argValues[i] = fmt.Sprintf("%v", q.GenerateExample(api.RandomGenerator())) } } args = strings.Join(argValues, ", ") } examplePath := exampleAction.Routes[0].FullPath(design.Design.APIVersionDefinition) pathParams := exampleAction.Routes[0].Params(design.Design.APIVersionDefinition) if len(pathParams) > 0 { pathVars := exampleAction.AllParams().Type.ToObject() pathValues := make([]interface{}, len(pathParams)) for i, n := range pathParams { if pathVars[n].Example != nil { pathValues[i] = fmt.Sprintf("%v", pathVars[n].Example) } else { pathValues[i] = pathVars[n].GenerateExample(api.RandomGenerator()) } } format := design.WildcardRegex.ReplaceAllLiteralString(examplePath, "/%v") examplePath = fmt.Sprintf(format, pathValues...) } if len(argNames) > 0 { args = ", " + args } exampleFunc := fmt.Sprintf( `%s%s ("%s"%s)`, exampleAction.Name, strings.Title(exampleAction.Parent.Name), examplePath, args, ) data := map[string]interface{}{ "API": api, "ExampleFunc": exampleFunc, } return file.ExecuteTemplate("exampleHTML", exampleT, nil, data) }
// Generate produces the skeleton main. func (g *Generator) Generate(api *design.APIDefinition) (_ []string, err error) { var genfiles []string cleanup := func() { for _, f := range genfiles { os.Remove(f) } } go utils.Catch(nil, cleanup) defer func() { if err != nil { cleanup() } }() s, err := New(api) if err != nil { return } b, err := json.Marshal(s) if err != nil { return } swaggerDir := filepath.Join(codegen.OutputDir, "swagger") os.RemoveAll(swaggerDir) if err = os.MkdirAll(swaggerDir, 0755); err != nil { return } genfiles = append(genfiles, swaggerDir) swaggerFile := filepath.Join(swaggerDir, "swagger.json") err = ioutil.WriteFile(swaggerFile, b, 0644) if err != nil { return } genfiles = append(genfiles, swaggerFile) controllerFile := filepath.Join(swaggerDir, "swagger.go") genfiles = append(genfiles, controllerFile) file, err := codegen.SourceFileFor(controllerFile) if err != nil { return } imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/goadesign/goa"), } file.WriteHeader(fmt.Sprintf("%s Swagger Spec", api.Name), "swagger", imports) file.Write([]byte(swagger)) if err = file.FormatCode(); err != nil { return } return genfiles, nil }
// NewUserTypesWriter returns a contexts code writer. // User types contain custom data structured defined in the DSL with "Type". func NewUserTypesWriter(filename string) (*UserTypesWriter, error) { file, err := codegen.SourceFileFor(filename) if err != nil { return nil, err } return &UserTypesWriter{ SourceFile: file, Finalizer: codegen.NewFinalizer(), Validator: codegen.NewValidator(), }, nil }
func (g *Generator) generateMain(mainFile string, clientPkg string, funcs template.FuncMap, api *design.APIDefinition) error { imports := []*codegen.ImportSpec{ codegen.SimpleImport("encoding/json"), codegen.SimpleImport("fmt"), codegen.SimpleImport("io/ioutil"), codegen.SimpleImport("net/http"), codegen.SimpleImport("os"), codegen.SimpleImport("time"), codegen.SimpleImport(clientPkg), codegen.SimpleImport("github.com/spf13/cobra"), } for _, pkg := range SignerPackages { imports = append(imports, codegen.SimpleImport(pkg)) } file, err := codegen.SourceFileFor(mainFile) if err != nil { return err } if err := file.WriteHeader("", "main", imports); err != nil { return err } g.genfiles = append(g.genfiles, mainFile) data := map[string]interface{}{ "API": api, "Signers": Signers, "Version": Version, } if err := file.ExecuteTemplate("main", mainTmpl, nil, data); err != nil { return err } actions := make(map[string][]*design.ActionDefinition) api.IterateResources(func(res *design.ResourceDefinition) error { return res.IterateActions(func(action *design.ActionDefinition) error { if as, ok := actions[action.Name]; ok { actions[action.Name] = append(as, action) } else { actions[action.Name] = []*design.ActionDefinition{action} } return nil }) }) if err := file.ExecuteTemplate("registerCmds", registerCmdsT, nil, actions); err != nil { return err } return file.FormatCode() }
func (g *Generator) generateIndexHTML(htmlFile string, exampleAction *design.ActionDefinition) error { file, err := codegen.SourceFileFor(htmlFile) if err != nil { return err } g.genfiles = append(g.genfiles, htmlFile) argNames := params(exampleAction) var args string if len(argNames) > 0 { query := exampleAction.QueryParams.Type.ToObject() argValues := make([]string, len(argNames)) for i, n := range argNames { ex := query[n].GenerateExample(g.API.RandomGenerator(), nil) argValues[i] = fmt.Sprintf("%v", ex) } args = strings.Join(argValues, ", ") } examplePath := exampleAction.Routes[0].FullPath() pathParams := exampleAction.Routes[0].Params() if len(pathParams) > 0 { pathVars := exampleAction.AllParams().Type.ToObject() pathValues := make([]interface{}, len(pathParams)) for i, n := range pathParams { ex := pathVars[n].GenerateExample(g.API.RandomGenerator(), nil) pathValues[i] = ex } format := design.WildcardRegex.ReplaceAllLiteralString(examplePath, "/%v") examplePath = fmt.Sprintf(format, pathValues...) } if len(argNames) > 0 { args = ", " + args } exampleFunc := fmt.Sprintf( `%s%s ("%s"%s)`, exampleAction.Name, strings.Title(exampleAction.Parent.Name), examplePath, args, ) data := map[string]interface{}{ "API": g.API, "ExampleFunc": exampleFunc, } return file.ExecuteTemplate("exampleHTML", exampleT, nil, data) }
func (g *Generator) generateCommands(commandsFile string, clientPkg string, funcs template.FuncMap, api *design.APIDefinition) error { file, err := codegen.SourceFileFor(commandsFile) if err != nil { return err } commandTypesTmpl := template.Must(template.New("commandTypes").Funcs(funcs).Parse(commandTypesTmpl)) commandsTmpl := template.Must(template.New("commands").Funcs(funcs).Parse(commandsTmpl)) imports := []*codegen.ImportSpec{ codegen.SimpleImport("encoding/json"), codegen.SimpleImport("fmt"), codegen.SimpleImport(clientPkg), codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("github.com/spf13/cobra"), codegen.NewImport("log", "gopkg.in/inconshreveable/log15.v2"), } if err := file.WriteHeader("", "main", imports); err != nil { return err } g.genfiles = append(g.genfiles, commandsFile) file.Write([]byte("type (\n")) if err := api.IterateResources(func(res *design.ResourceDefinition) error { return res.IterateActions(func(action *design.ActionDefinition) error { return commandTypesTmpl.Execute(file, action) }) }); err != nil { return err } file.Write([]byte(")\n\n")) if err := api.IterateResources(func(res *design.ResourceDefinition) error { return res.IterateActions(func(action *design.ActionDefinition) error { data := map[string]interface{}{ "Action": action, "Resource": action.Parent, "Version": design.Design.APIVersionDefinition, } return commandsTmpl.Execute(file, data) }) }); err != nil { return err } return file.FormatCode() }
// Generate produces the skeleton main. func (g *Generator) Generate(api *design.APIDefinition) (_ []string, err error) { go utils.Catch(nil, func() { g.Cleanup() }) defer func() { if err != nil { g.Cleanup() } }() os.RemoveAll(JSONSchemaDir()) os.MkdirAll(JSONSchemaDir(), 0755) g.genfiles = append(g.genfiles, JSONSchemaDir()) s := APISchema(api) js, err := s.JSON() if err != nil { return } schemaFile := filepath.Join(JSONSchemaDir(), "schema.json") if err = ioutil.WriteFile(schemaFile, js, 0644); err != nil { return } g.genfiles = append(g.genfiles, schemaFile) controllerFile := filepath.Join(JSONSchemaDir(), "schema.go") file, err := codegen.SourceFileFor(controllerFile) if err != nil { return } imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/julienschmidt/httprouter"), codegen.SimpleImport("github.com/goadesign/goa"), } g.genfiles = append(g.genfiles, controllerFile) file.WriteHeader(fmt.Sprintf("%s JSON Hyper-schema", api.Name), "schema", imports) file.Write([]byte(jsonSchemaCtrl)) if err = file.FormatCode(); err != nil { return } return g.genfiles, nil }
func (g *Generator) generateClient(clientFile string, clientPkg string, funcs template.FuncMap, api *design.APIDefinition) error { file, err := codegen.SourceFileFor(clientFile) if err != nil { return err } clientTmpl := template.Must(template.New("client").Funcs(funcs).Parse(clientTmpl)) imports := []*codegen.ImportSpec{ codegen.SimpleImport("net/http"), codegen.NewImport("goaclient", "github.com/goadesign/goa/client"), } if err := file.WriteHeader("", "client", imports); err != nil { return err } g.genfiles = append(g.genfiles, clientFile) if err := clientTmpl.Execute(file, api); err != nil { return err } return file.FormatCode() }
func (g *Generator) generateExample(api *design.APIDefinition) error { controllerFile := filepath.Join(codegen.OutputDir, "example.go") file, err := codegen.SourceFileFor(controllerFile) if err != nil { return err } imports := []*codegen.ImportSpec{ codegen.SimpleImport("net/http"), codegen.SimpleImport("github.com/julienschmidt/httprouter"), codegen.SimpleImport("github.com/goadesign/goa"), } if err := file.WriteHeader(fmt.Sprintf("%s JavaScript Client Example", api.Name), "js", imports); err != nil { return err } g.genfiles = append(g.genfiles, controllerFile) data := map[string]interface{}{"ServeDir": codegen.OutputDir} if err := file.ExecuteTemplate("examples", exampleCtrlT, nil, data); err != nil { return err } return file.FormatCode() }
func (g *Generator) generateResourceTest() error { if len(g.API.Resources) == 0 { return nil } funcs := template.FuncMap{"isSlice": isSlice} testTmpl := template.Must(template.New("test").Funcs(funcs).Parse(testTmpl)) outDir, err := makeTestDir(g, g.API.Name) if err != nil { return err } appPkg, err := codegen.PackagePath(g.OutDir) if err != nil { return err } imports := []*codegen.ImportSpec{ codegen.SimpleImport("bytes"), codegen.SimpleImport("fmt"), codegen.SimpleImport("io"), codegen.SimpleImport("log"), codegen.SimpleImport("net/http"), codegen.SimpleImport("net/http/httptest"), codegen.SimpleImport("net/url"), codegen.SimpleImport("strconv"), codegen.SimpleImport("strings"), codegen.SimpleImport("time"), codegen.SimpleImport(appPkg), codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("github.com/goadesign/goa/goatest"), codegen.SimpleImport("golang.org/x/net/context"), codegen.NewImport("uuid", "github.com/satori/go.uuid"), } return g.API.IterateResources(func(res *design.ResourceDefinition) error { filename := filepath.Join(outDir, codegen.SnakeCase(res.Name)+"_testing.go") file, err := codegen.SourceFileFor(filename) if err != nil { return err } title := fmt.Sprintf("%s: %s TestHelpers", g.API.Context(), res.Name) if err := file.WriteHeader(title, "test", imports); err != nil { return err } var methods []*TestMethod if err := res.IterateActions(func(action *design.ActionDefinition) error { if err := action.IterateResponses(func(response *design.ResponseDefinition) error { if response.Status == 101 { // SwitchingProtocols, Don't currently handle WebSocket endpoints return nil } for routeIndex, route := range action.Routes { mediaType := design.Design.MediaTypeWithIdentifier(response.MediaType) if mediaType == nil { methods = append(methods, g.createTestMethod(res, action, response, route, routeIndex, nil, nil)) } else { if err := mediaType.IterateViews(func(view *design.ViewDefinition) error { methods = append(methods, g.createTestMethod(res, action, response, route, routeIndex, mediaType, view)) return nil }); err != nil { return err } } } return nil }); err != nil { return err } return nil }); err != nil { return err } g.genfiles = append(g.genfiles, filename) err = testTmpl.Execute(file, methods) if err != nil { panic(err) } return file.FormatCode() }) }
func (g *Generator) generateClientResources(clientPkg string, funcs template.FuncMap, api *design.APIDefinition) error { userTypeTmpl := template.Must(template.New("userType").Funcs(funcs).Parse(userTypeTmpl)) typeDecodeTmpl := template.Must(template.New("typeDecode").Funcs(funcs).Parse(typeDecodeTmpl)) err := api.IterateResources(func(res *design.ResourceDefinition) error { return g.generateResourceClient(res, funcs) }) if err != nil { return err } types := make(map[string]*design.UserTypeDefinition) for _, res := range api.Resources { for n, ut := range res.UserTypes() { types[n] = ut } } filename := filepath.Join(codegen.OutputDir, typesFileName+".go") file, err := codegen.SourceFileFor(filename) if err != nil { return err } imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("fmt"), codegen.SimpleImport("io"), codegen.SimpleImport("time"), codegen.NewImport("uuid", "github.com/satori/go.uuid"), } if err := file.WriteHeader("User Types", "client", imports); err != nil { return err } g.genfiles = append(g.genfiles, filename) // Generate user and media types used by action payloads and parameters err = api.IterateUserTypes(func(userType *design.UserTypeDefinition) error { if _, ok := g.generatedTypes[userType.TypeName]; ok { return nil } if _, ok := types[userType.TypeName]; ok { g.generatedTypes[userType.TypeName] = true return userTypeTmpl.Execute(file, userType) } return nil }) if err != nil { return err } // Generate media types used by action responses and their load helpers err = api.IterateResources(func(res *design.ResourceDefinition) error { return res.IterateActions(func(a *design.ActionDefinition) error { return a.IterateResponses(func(r *design.ResponseDefinition) error { if mt := api.MediaTypeWithIdentifier(r.MediaType); mt != nil { if _, ok := g.generatedTypes[mt.TypeName]; !ok { g.generatedTypes[mt.TypeName] = true if !mt.IsBuiltIn() { if err := userTypeTmpl.Execute(file, mt); err != nil { return err } } typeName := mt.TypeName if mt.IsBuiltIn() { elems := strings.Split(typeName, ".") typeName = elems[len(elems)-1] } if err := typeDecodeTmpl.Execute(file, mt); err != nil { return err } } } return nil }) }) }) if err != nil { return err } // Generate media types used in payloads but not in responses err = api.IterateMediaTypes(func(mediaType *design.MediaTypeDefinition) error { if mediaType.IsBuiltIn() { return nil } if _, ok := g.generatedTypes[mediaType.TypeName]; ok { return nil } if _, ok := types[mediaType.TypeName]; ok { g.generatedTypes[mediaType.TypeName] = true return userTypeTmpl.Execute(file, mediaType) } return nil }) if err != nil { return err } return file.FormatCode() }
func (g *Generator) generateResourceClient(res *design.ResourceDefinition, funcs template.FuncMap) error { payloadTmpl := template.Must(template.New("payload").Funcs(funcs).Parse(payloadTmpl)) clientsTmpl := template.Must(template.New("clients").Funcs(funcs).Parse(clientsTmpl)) clientsWSTmpl := template.Must(template.New("clients").Funcs(funcs).Parse(clientsWSTmpl)) pathTmpl := template.Must(template.New("pathTemplate").Funcs(funcs).Parse(pathTmpl)) resFilename := codegen.SnakeCase(res.Name) if resFilename == typesFileName { // Avoid clash with datatypes.go resFilename += "_client" } filename := filepath.Join(codegen.OutputDir, resFilename+".go") file, err := codegen.SourceFileFor(filename) if err != nil { return err } imports := []*codegen.ImportSpec{ codegen.SimpleImport("bytes"), codegen.SimpleImport("encoding/json"), codegen.SimpleImport("fmt"), codegen.SimpleImport("io"), codegen.SimpleImport("net/http"), codegen.SimpleImport("net/url"), codegen.SimpleImport("strconv"), codegen.SimpleImport("strings"), codegen.SimpleImport("time"), codegen.SimpleImport("golang.org/x/net/context"), codegen.SimpleImport("golang.org/x/net/websocket"), codegen.NewImport("uuid", "github.com/satori/go.uuid"), } if err := file.WriteHeader("", "client", imports); err != nil { return err } g.genfiles = append(g.genfiles, filename) g.generatedTypes = make(map[string]bool) err = res.IterateActions(func(action *design.ActionDefinition) error { if action.Payload != nil { if err := payloadTmpl.Execute(file, action); err != nil { return err } g.generatedTypes[action.Payload.TypeName] = true } if action.Params != nil { params := make(design.Object, len(action.QueryParams.Type.ToObject())) for n, param := range action.QueryParams.Type.ToObject() { name := codegen.Goify(n, false) params[name] = param } action.QueryParams.Type = params } if action.Headers != nil { headers := make(design.Object, len(action.Headers.Type.ToObject())) for n, header := range action.Headers.Type.ToObject() { name := codegen.Goify(n, false) headers[name] = header } action.Headers.Type = headers } if action.WebSocket() { return clientsWSTmpl.Execute(file, action) } for i, r := range action.Routes { data := struct { Route *design.RouteDefinition Index int }{ Route: r, Index: i, } if err := pathTmpl.Execute(file, data); err != nil { return err } } return clientsTmpl.Execute(file, action) }) if err != nil { return err } return file.FormatCode() }
// Generate produces the skeleton main. func (g *Generator) Generate(api *design.APIDefinition) (_ []string, err error) { go utils.Catch(nil, func() { g.Cleanup() }) defer func() { if err != nil { g.Cleanup() } }() mainFile := filepath.Join(codegen.OutputDir, "main.go") if Force { os.Remove(mainFile) } g.genfiles = append(g.genfiles, mainFile) _, err = os.Stat(mainFile) funcs := template.FuncMap{ "tempvar": tempvar, "generateSwagger": generateSwagger, "okResp": okResp, "newControllerVersion": newControllerVersion, "targetPkg": func() string { return TargetPackage }, } if err != nil { file, err := codegen.SourceFileFor(mainFile) if err != nil { return nil, err } var outPkg string outPkg, err = codegen.PackagePath(codegen.OutputDir) if err != nil { return nil, err } outPkg = strings.TrimPrefix(filepath.ToSlash(outPkg), "src/") appPkg := path.Join(outPkg, "app") swaggerPkg := path.Join(outPkg, "swagger") imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("github.com/goadesign/middleware"), codegen.SimpleImport(appPkg), codegen.SimpleImport(swaggerPkg), } if generateSwagger() { jsonSchemaPkg := path.Join(outPkg, "schema") imports = append(imports, codegen.SimpleImport(jsonSchemaPkg)) } file.WriteHeader("", "main", imports) data := map[string]interface{}{ "Name": AppName, "API": api, } if err = file.ExecuteTemplate("main", mainT, funcs, data); err != nil { return nil, err } if err = file.FormatCode(); err != nil { return nil, err } } imp, err := codegen.PackagePath(codegen.OutputDir) if err != nil { return } imp = path.Join(filepath.ToSlash(imp), "app") imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport(imp), } api.IterateVersions(func(v *design.APIVersionDefinition) error { if v.IsDefault() { return nil } imports = append(imports, codegen.SimpleImport(imp+"/"+codegen.VersionPackage(v.Version))) return nil }) err = api.IterateResources(func(r *design.ResourceDefinition) error { filename := filepath.Join(codegen.OutputDir, snakeCase(r.Name)+".go") if Force { if err := os.Remove(filename); err != nil { return err } } g.genfiles = append(g.genfiles, filename) if _, err := os.Stat(filename); err != nil { file, err := codegen.SourceFileFor(filename) if err != nil { return err } file.WriteHeader("", "main", imports) err = file.ExecuteTemplate("controller", ctrlT, funcs, r) if err != nil { return err } if err := file.FormatCode(); err != nil { return err } } return nil }) if err != nil { return } return g.genfiles, nil }
// Generate produces the skeleton main. func (g *Generator) Generate(api *design.APIDefinition) (_ []string, err error) { var genfiles []string cleanup := func() { for _, f := range genfiles { os.Remove(f) } } go utils.Catch(nil, cleanup) defer func() { if err != nil { cleanup() } }() swaggerDir := filepath.Join(codegen.OutputDir, "swagger") os.RemoveAll(swaggerDir) if err = os.MkdirAll(swaggerDir, 0755); err != nil { return nil, err } genfiles = append(genfiles, swaggerDir) s, err := New(api) if err != nil { return nil, err } // JSON rawJSON, err := json.Marshal(s) if err != nil { return nil, err } swaggerFile := filepath.Join(swaggerDir, "swagger.json") if err := ioutil.WriteFile(swaggerFile, rawJSON, 0644); err != nil { return nil, err } genfiles = append(genfiles, swaggerFile) // YAML var yamlSource interface{} if err = json.Unmarshal(rawJSON, &yamlSource); err != nil { return nil, err } rawYAML, err := yaml.Marshal(yamlSource) if err != nil { return nil, err } swaggerFile = filepath.Join(swaggerDir, "swagger.yaml") if err := ioutil.WriteFile(swaggerFile, rawYAML, 0644); err != nil { return nil, err } genfiles = append(genfiles, swaggerFile) // Go endpoint controllerFile := filepath.Join(swaggerDir, "swagger.go") genfiles = append(genfiles, controllerFile) file, err := codegen.SourceFileFor(controllerFile) if err != nil { return nil, err } imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/goadesign/goa"), } file.WriteHeader(fmt.Sprintf("%s Swagger Spec", api.Name), "swagger", imports) err = file.ExecuteTemplate("swagger", swaggerT, nil, api) if err != nil { return nil, err } if err = file.FormatCode(); err != nil { return nil, err } return genfiles, nil }
func (g *Generator) generateCommands(commandsFile string, clientPkg string, funcs template.FuncMap) error { file, err := codegen.SourceFileFor(commandsFile) if err != nil { return err } funcs["defaultRouteParams"] = defaultRouteParams funcs["defaultRouteTemplate"] = defaultRouteTemplate funcs["joinNames"] = joinNames funcs["joinRouteParams"] = joinRouteParams funcs["routes"] = routes funcs["flagType"] = flagType funcs["cmdFieldType"] = cmdFieldTypeString funcs["formatExample"] = formatExample funcs["shouldAddExample"] = shouldAddExample commandTypesTmpl := template.Must(template.New("commandTypes").Funcs(funcs).Parse(commandTypesTmpl)) commandsTmpl := template.Must(template.New("commands").Funcs(funcs).Parse(commandsTmpl)) commandsTmplWS := template.Must(template.New("commandsWS").Funcs(funcs).Parse(commandsTmplWS)) downloadCommandTmpl := template.Must(template.New("download").Funcs(funcs).Parse(downloadCommandTmpl)) registerTmpl := template.Must(template.New("register").Funcs(funcs).Parse(registerTmpl)) imports := []*codegen.ImportSpec{ codegen.SimpleImport("encoding/json"), codegen.SimpleImport("fmt"), codegen.SimpleImport("log"), codegen.SimpleImport("net/url"), codegen.SimpleImport("os"), codegen.SimpleImport("path"), codegen.SimpleImport("path/filepath"), codegen.SimpleImport("strings"), codegen.SimpleImport("strconv"), codegen.SimpleImport("time"), codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("github.com/spf13/cobra"), codegen.SimpleImport(clientPkg), codegen.SimpleImport("golang.org/x/net/context"), codegen.SimpleImport("golang.org/x/net/websocket"), codegen.NewImport("uuid", "github.com/goadesign/goa/uuid"), } if len(g.API.Resources) > 0 { imports = append(imports, codegen.NewImport("goaclient", "github.com/goadesign/goa/client")) } if err := file.WriteHeader("", "cli", imports); err != nil { return err } g.genfiles = append(g.genfiles, commandsFile) file.Write([]byte("type (\n")) var fs []*design.FileServerDefinition if err := g.API.IterateResources(func(res *design.ResourceDefinition) error { fs = append(fs, res.FileServers...) return res.IterateActions(func(action *design.ActionDefinition) error { return commandTypesTmpl.Execute(file, action) }) }); err != nil { return err } if len(fs) > 0 { file.Write([]byte(downloadCommandType)) } file.Write([]byte(")\n\n")) actions := make(map[string][]*design.ActionDefinition) hasDownloads := false g.API.IterateResources(func(res *design.ResourceDefinition) error { if len(res.FileServers) > 0 { hasDownloads = true } return res.IterateActions(func(action *design.ActionDefinition) error { name := codegen.Goify(action.Name, false) if as, ok := actions[name]; ok { actions[name] = append(as, action) } else { actions[name] = []*design.ActionDefinition{action} } return nil }) }) data := struct { Actions map[string][]*design.ActionDefinition Package string HasDownloads bool }{ Actions: actions, Package: g.Target, HasDownloads: hasDownloads, } if err := file.ExecuteTemplate("registerCmds", registerCmdsT, funcs, data); err != nil { return err } var fsdata []map[string]interface{} g.API.IterateResources(func(res *design.ResourceDefinition) error { if res.FileServers != nil { res.IterateFileServers(func(fs *design.FileServerDefinition) error { wcs := design.ExtractWildcards(fs.RequestPath) isDir := len(wcs) > 0 var reqDir, filename string if isDir { reqDir, _ = path.Split(fs.RequestPath) } else { _, filename = filepath.Split(fs.FilePath) } fsdata = append(fsdata, map[string]interface{}{ "IsDir": isDir, "RequestPath": fs.RequestPath, "FilePath": fs.FilePath, "FileName": filename, "Name": g.fileServerMethod(fs), "RequestDir": reqDir, }) return nil }) } return nil }) if fsdata != nil { data := struct { Package string FileServers []map[string]interface{} }{ Package: g.Target, FileServers: fsdata, } if err := downloadCommandTmpl.Execute(file, data); err != nil { return err } } err = g.API.IterateResources(func(res *design.ResourceDefinition) error { return res.IterateActions(func(action *design.ActionDefinition) error { data := map[string]interface{}{ "Action": action, "Resource": action.Parent, "Package": g.Target, "HasMultiContent": len(g.API.Consumes) > 1, } var err error if action.WebSocket() { err = commandsTmplWS.Execute(file, data) } else { err = commandsTmpl.Execute(file, data) } if err != nil { return err } err = registerTmpl.Execute(file, data) return err }) }) if err != nil { return err } return file.FormatCode() }
func (g *Generator) generateResourceClient(pkgDir string, res *design.ResourceDefinition, funcs template.FuncMap) error { payloadTmpl := template.Must(template.New("payload").Funcs(funcs).Parse(payloadTmpl)) pathTmpl := template.Must(template.New("pathTemplate").Funcs(funcs).Parse(pathTmpl)) resFilename := codegen.SnakeCase(res.Name) if resFilename == typesFileName { // Avoid clash with datatypes.go resFilename += "_client" } filename := filepath.Join(pkgDir, resFilename+".go") file, err := codegen.SourceFileFor(filename) if err != nil { return err } imports := []*codegen.ImportSpec{ codegen.SimpleImport("bytes"), codegen.SimpleImport("encoding/json"), codegen.SimpleImport("fmt"), codegen.SimpleImport("io"), codegen.SimpleImport("io/ioutil"), codegen.SimpleImport("net/http"), codegen.SimpleImport("net/url"), codegen.SimpleImport("os"), codegen.SimpleImport("path"), codegen.SimpleImport("strconv"), codegen.SimpleImport("strings"), codegen.SimpleImport("time"), codegen.SimpleImport("golang.org/x/net/context"), codegen.SimpleImport("golang.org/x/net/websocket"), codegen.NewImport("uuid", "github.com/goadesign/goa/uuid"), } if err := file.WriteHeader("", g.Target, imports); err != nil { return err } g.genfiles = append(g.genfiles, filename) err = res.IterateFileServers(func(fs *design.FileServerDefinition) error { return g.generateFileServer(file, fs, funcs) }) err = res.IterateActions(func(action *design.ActionDefinition) error { if action.Payload != nil { found := false typeName := action.Payload.TypeName for _, t := range design.Design.Types { if t.TypeName == typeName { found = true break } } if !found { if err := payloadTmpl.Execute(file, action); err != nil { return err } } } for i, r := range action.Routes { data := struct { Route *design.RouteDefinition Index int }{ Route: r, Index: i, } if err := pathTmpl.Execute(file, data); err != nil { return err } } return g.generateActionClient(action, file, funcs) }) if err != nil { return err } return file.FormatCode() }
func (g *Generator) generateResourceTest(api *design.APIDefinition) error { if len(api.Resources) == 0 { return nil } testTmpl := template.Must(template.New("resources").Parse(testTmpl)) outDir, err := makeTestDir(g, api.Name) if err != nil { return err } appPkg, err := AppPackagePath() if err != nil { return err } imports := []*codegen.ImportSpec{ codegen.SimpleImport("bytes"), codegen.SimpleImport("fmt"), codegen.SimpleImport("net/http"), codegen.SimpleImport("net/http/httptest"), codegen.SimpleImport("testing"), codegen.SimpleImport(appPkg), codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("github.com/goadesign/goa/goatest"), codegen.SimpleImport("golang.org/x/net/context"), } return api.IterateResources(func(res *design.ResourceDefinition) error { filename := filepath.Join(outDir, res.Name+".go") file, err := codegen.SourceFileFor(filename) if err != nil { return err } if err := file.WriteHeader("", "test", imports); err != nil { return err } var methods = []TestMethod{} for _, action := range res.Actions { for _, response := range action.Responses { if response.Status == 101 { // SwitchingProtocols, Don't currently handle WebSocket endpoints continue } for routeIndex, route := range action.Routes { mediaType := design.Design.MediaTypeWithIdentifier(response.MediaType) if mediaType == nil { methods = append(methods, createTestMethod(res, action, response, route, routeIndex, nil, nil)) } else { for _, view := range mediaType.Views { methods = append(methods, createTestMethod(res, action, response, route, routeIndex, mediaType, view)) } } } } } g.genfiles = append(g.genfiles, filename) err = testTmpl.Execute(file, methods) if err != nil { panic(err) } return file.FormatCode() }) }
func (g *Generator) generateCommands(commandsFile string, clientPkg string, funcs template.FuncMap, api *design.APIDefinition) error { file, err := codegen.SourceFileFor(commandsFile) if err != nil { return err } commandTypesTmpl := template.Must(template.New("commandTypes").Funcs(funcs).Parse(commandTypesTmpl)) commandsTmpl := template.Must(template.New("commands").Funcs(funcs).Parse(commandsTmpl)) commandsTmplWS := template.Must(template.New("commandsWS").Funcs(funcs).Parse(commandsTmplWS)) registerTmpl := template.Must(template.New("register").Funcs(funcs).Parse(registerTmpl)) imports := []*codegen.ImportSpec{ codegen.SimpleImport("encoding/json"), codegen.SimpleImport("fmt"), codegen.SimpleImport("log"), codegen.SimpleImport("os"), codegen.SimpleImport("time"), codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("github.com/spf13/cobra"), codegen.SimpleImport(clientPkg), codegen.SimpleImport("golang.org/x/net/context"), codegen.SimpleImport("golang.org/x/net/websocket"), } if len(api.Resources) > 0 { imports = append(imports, codegen.NewImport("goaclient", "github.com/goadesign/goa/client")) } if err := file.WriteHeader("", "main", imports); err != nil { return err } g.genfiles = append(g.genfiles, commandsFile) file.Write([]byte("type (\n")) if err := api.IterateResources(func(res *design.ResourceDefinition) error { return res.IterateActions(func(action *design.ActionDefinition) error { return commandTypesTmpl.Execute(file, action) }) }); err != nil { return err } file.Write([]byte(")\n\n")) err = api.IterateResources(func(res *design.ResourceDefinition) error { return res.IterateActions(func(action *design.ActionDefinition) error { data := map[string]interface{}{ "Action": action, "Resource": action.Parent, } var err error if action.WebSocket() { err = commandsTmplWS.Execute(file, data) } else { err = commandsTmpl.Execute(file, data) } if err != nil { return err } err = registerTmpl.Execute(file, data) return err }) }) if err != nil { return err } return file.FormatCode() }
func (g *Generator) generateMain(mainFile string, clientPkg, cliPkg string, funcs template.FuncMap) error { imports := []*codegen.ImportSpec{ codegen.SimpleImport("encoding/json"), codegen.SimpleImport("fmt"), codegen.SimpleImport("io/ioutil"), codegen.SimpleImport("net/http"), codegen.SimpleImport("os"), codegen.SimpleImport("time"), codegen.SimpleImport(clientPkg), codegen.SimpleImport(cliPkg), codegen.SimpleImport("github.com/spf13/cobra"), codegen.NewImport("goaclient", "github.com/goadesign/goa/client"), codegen.NewImport("uuid", "github.com/goadesign/goa/uuid"), } funcs["defaultRouteParams"] = defaultRouteParams funcs["defaultRouteTemplate"] = defaultRouteTemplate funcs["joinNames"] = joinNames funcs["signerSignature"] = signerSignature funcs["signerArgs"] = signerArgs file, err := codegen.SourceFileFor(mainFile) if err != nil { return err } if err := file.WriteHeader("", "main", imports); err != nil { return err } g.genfiles = append(g.genfiles, mainFile) version := design.Design.Version if version == "" { version = "0" } hasSigners := false hasBasicAuthSigners := false hasAPIKeySigners := false hasTokenSigners := false for _, s := range g.API.SecuritySchemes { if signerType(s) != "" { hasSigners = true switch s.Type { case "basic": hasBasicAuthSigners = true case "apiKey": hasAPIKeySigners = true case "jwt", "oauth2": hasTokenSigners = true } } } data := struct { API *design.APIDefinition Version string Package string HasSigners bool HasBasicAuthSigners bool HasAPIKeySigners bool HasTokenSigners bool }{ API: g.API, Version: version, Package: g.Target, HasSigners: hasSigners, HasBasicAuthSigners: hasBasicAuthSigners, HasAPIKeySigners: hasAPIKeySigners, HasTokenSigners: hasTokenSigners, } if err := file.ExecuteTemplate("main", mainTmpl, funcs, data); err != nil { return err } return file.FormatCode() }
// Generate produces the skeleton main. func (g *Generator) Generate() (_ []string, err error) { go utils.Catch(nil, func() { g.Cleanup() }) defer func() { if err != nil { g.Cleanup() } }() if g.Target == "" { g.Target = "app" } codegen.Reserved[g.Target] = true mainFile := filepath.Join(g.OutDir, "main.go") if g.Force { os.Remove(mainFile) } funcs := template.FuncMap{ "tempvar": tempvar, "okResp": g.okResp, "targetPkg": func() string { return g.Target }, } imp, err := codegen.PackagePath(g.OutDir) if err != nil { return nil, err } imp = path.Join(filepath.ToSlash(imp), "app") _, err = os.Stat(mainFile) if err != nil { if err = g.createMainFile(mainFile, funcs); err != nil { return nil, err } } imports := []*codegen.ImportSpec{ codegen.SimpleImport("io"), codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport(imp), codegen.SimpleImport("golang.org/x/net/websocket"), } err = g.API.IterateResources(func(r *design.ResourceDefinition) error { filename := filepath.Join(g.OutDir, codegen.SnakeCase(r.Name)+".go") if g.Force { os.Remove(filename) } if _, e := os.Stat(filename); e != nil { g.genfiles = append(g.genfiles, filename) file, err2 := codegen.SourceFileFor(filename) if err2 != nil { return err } file.WriteHeader("", "main", imports) if err2 = file.ExecuteTemplate("controller", ctrlT, funcs, r); err2 != nil { return err } err2 = r.IterateActions(func(a *design.ActionDefinition) error { if a.WebSocket() { return file.ExecuteTemplate("actionWS", actionWST, funcs, a) } return file.ExecuteTemplate("action", actionT, funcs, a) }) if err2 != nil { return err } if err2 = file.FormatCode(); err2 != nil { return err2 } } return nil }) if err != nil { return } return g.genfiles, nil }
// Generate is the generator entry point called by the meta generator. func Generate(api *design.APIDefinition) (_ []string, err error) { var genfiles []string cleanup := func() { for _, f := range genfiles { os.Remove(f) } } go utils.Catch(nil, cleanup) defer func() { if err != nil { cleanup() } }() app := kingpin.New("Swagger generator", "Swagger spec generator") codegen.RegisterFlags(app) _, err = app.Parse(os.Args[1:]) if err != nil { return nil, fmt.Errorf(`invalid command line: %s. Command line was "%s"`, err, strings.Join(os.Args, " ")) } s, err := New(api) if err != nil { return } b, err := json.Marshal(s) if err != nil { return } swaggerDir := filepath.Join(codegen.OutputDir, "swagger") os.RemoveAll(swaggerDir) if err = os.MkdirAll(swaggerDir, 0755); err != nil { return } genfiles = append(genfiles, swaggerDir) swaggerFile := filepath.Join(swaggerDir, "swagger.json") err = ioutil.WriteFile(swaggerFile, b, 0644) if err != nil { return } genfiles = append(genfiles, swaggerFile) controllerFile := filepath.Join(swaggerDir, "swagger.go") genfiles = append(genfiles, controllerFile) file, err := codegen.SourceFileFor(controllerFile) if err != nil { return } imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/julienschmidt/httprouter"), codegen.SimpleImport("github.com/goadesign/goa"), } file.WriteHeader(fmt.Sprintf("%s Swagger Spec", api.Name), "swagger", imports) file.Write([]byte(swagger)) if err = file.FormatCode(); err != nil { return } return genfiles, nil }
func (g *Generator) generateClient(clientFile string, clientPkg string, funcs template.FuncMap) error { file, err := codegen.SourceFileFor(clientFile) if err != nil { return err } clientTmpl := template.Must(template.New("client").Funcs(funcs).Parse(clientTmpl)) // Compute list of encoders and decoders encoders, err := genapp.BuildEncoders(g.API.Produces, true) if err != nil { return err } decoders, err := genapp.BuildEncoders(g.API.Consumes, false) if err != nil { return err } im := make(map[string]bool) for _, data := range encoders { im[data.PackagePath] = true } for _, data := range decoders { im[data.PackagePath] = true } var packagePaths []string for packagePath := range im { if packagePath != "github.com/goadesign/goa" { packagePaths = append(packagePaths, packagePath) } } sort.Strings(packagePaths) // Setup codegen imports := []*codegen.ImportSpec{ codegen.SimpleImport("net/http"), codegen.SimpleImport("github.com/goadesign/goa"), codegen.NewImport("goaclient", "github.com/goadesign/goa/client"), codegen.NewImport("uuid", "github.com/goadesign/goa/uuid"), } for _, packagePath := range packagePaths { imports = append(imports, codegen.SimpleImport(packagePath)) } if err := file.WriteHeader("", g.Target, imports); err != nil { return err } g.genfiles = append(g.genfiles, clientFile) // Generate data := struct { API *design.APIDefinition Encoders []*genapp.EncoderTemplateData Decoders []*genapp.EncoderTemplateData }{ API: g.API, Encoders: encoders, Decoders: decoders, } if err := clientTmpl.Execute(file, data); err != nil { return err } return file.FormatCode() }