// APISchema produces the API JSON hyper schema. func APISchema(api *design.APIDefinition) *JSONSchema { api.IterateResources(func(r *design.ResourceDefinition) error { GenerateResourceDefinition(api, r) return nil }) links := []*JSONLink{ &JSONLink{ Href: ServiceURL, Rel: "self", }, &JSONLink{ Href: "/schema", Method: "GET", Rel: "self", TargetSchema: &JSONSchema{ Schema: SchemaRef, AdditionalProperties: true, }, }, } s := JSONSchema{ ID: fmt.Sprintf("%s/schema", ServiceURL), Title: api.Title, Description: api.Description, Type: JSONObject, Definitions: Definitions, Properties: propertiesFromDefs(Definitions, "#/definitions/"), Links: links, } return &s }
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() }) }
// generateHrefs iterates through the API resources and generates the href factory methods. func (g *Generator) generateHrefs(api *design.APIDefinition) error { hrefFile := filepath.Join(AppOutputDir(), "hrefs.go") resWr, err := NewResourcesWriter(hrefFile) if err != nil { panic(err) // bug } title := fmt.Sprintf("%s: Application Resource Href Factories", api.Context()) imports := []*codegen.ImportSpec{ codegen.SimpleImport("fmt"), } resWr.WriteHeader(title, TargetPackage, imports) err = api.IterateResources(func(r *design.ResourceDefinition) error { m := api.MediaTypeWithIdentifier(r.MediaType) var identifier string if m != nil { identifier = m.Identifier } else { identifier = "text/plain" } data := ResourceData{ Name: codegen.Goify(r.Name, true), Identifier: identifier, Description: r.Description, Type: m, CanonicalTemplate: codegen.CanonicalTemplate(r), CanonicalParams: codegen.CanonicalParams(r), } return resWr.Execute(&data) }) g.genfiles = append(g.genfiles, hrefFile) if err != nil { return err } return resWr.FormatCode() }
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 }
// generateContexts iterates through the API resources and actions and generates the action // contexts. func (g *Generator) generateContexts(api *design.APIDefinition) error { ctxFile := filepath.Join(AppOutputDir(), "contexts.go") ctxWr, err := NewContextsWriter(ctxFile) if err != nil { panic(err) // bug } title := fmt.Sprintf("%s: Application Contexts", api.Context()) imports := []*codegen.ImportSpec{ codegen.SimpleImport("fmt"), codegen.SimpleImport("golang.org/x/net/context"), codegen.SimpleImport("strconv"), codegen.SimpleImport("strings"), codegen.SimpleImport("time"), codegen.SimpleImport("github.com/goadesign/goa"), codegen.NewImport("uuid", "github.com/satori/go.uuid"), } ctxWr.WriteHeader(title, TargetPackage, imports) err = api.IterateResources(func(r *design.ResourceDefinition) error { return r.IterateActions(func(a *design.ActionDefinition) error { ctxName := codegen.Goify(a.Name, true) + codegen.Goify(a.Parent.Name, true) + "Context" headers := r.Headers.Merge(a.Headers) if headers != nil && len(headers.Type.ToObject()) == 0 { headers = nil // So that {{if .Headers}} returns false in templates } params := a.AllParams() if params != nil && len(params.Type.ToObject()) == 0 { params = nil // So that {{if .Params}} returns false in templates } ctxData := ContextTemplateData{ Name: ctxName, ResourceName: r.Name, ActionName: a.Name, Payload: a.Payload, Params: params, Headers: headers, Routes: a.Routes, Responses: BuildResponses(r.Responses, a.Responses), API: api, DefaultPkg: TargetPackage, Security: a.Security, } return ctxWr.Execute(&ctxData) }) }) g.genfiles = append(g.genfiles, ctxFile) if err != nil { return err } return ctxWr.FormatCode() }
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) 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() }
// APISchema produces the API JSON hyper schema. func APISchema(api *design.APIDefinition) *JSONSchema { api.IterateResources(func(r *design.ResourceDefinition) error { GenerateResourceDefinition(api, r) return nil }) scheme := "http" if len(api.Schemes) > 0 { scheme = api.Schemes[0] } u := url.URL{Scheme: scheme, Host: api.Host} href := u.String() links := []*JSONLink{ { Href: href, Rel: "self", }, { Href: "/schema", Method: "GET", Rel: "self", TargetSchema: &JSONSchema{ Schema: SchemaRef, AdditionalProperties: true, }, }, } s := JSONSchema{ ID: fmt.Sprintf("%s/schema", href), Title: api.Title, Description: api.Description, Type: JSONObject, Definitions: Definitions, Properties: propertiesFromDefs(Definitions, "#/definitions/"), Links: links, } return &s }
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() }
// 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 }
// New creates a Swagger spec from an API definition. func New(api *design.APIDefinition) (*Swagger, error) { if api == nil { return nil, nil } tags, err := tagsFromDefinition(api.Metadata) if err != nil { return nil, err } params, err := paramsFromDefinition(api.BaseParams, api.BasePath) if err != nil { return nil, err } var paramMap map[string]*Parameter if len(params) > 0 { paramMap = make(map[string]*Parameter, len(params)) for _, p := range params { paramMap[p.Name] = p } } var consumes []string for _, c := range api.Consumes { consumes = append(consumes, c.MIMETypes...) } var produces []string for _, p := range api.Produces { produces = append(produces, p.MIMETypes...) } s := &Swagger{ Swagger: "2.0", Info: &Info{ Title: api.Title, Description: api.Description, TermsOfService: api.TermsOfService, Contact: api.Contact, License: api.License, Version: "", }, Host: api.Host, BasePath: api.BasePath, Paths: make(map[string]*Path), Schemes: api.Schemes, Consumes: consumes, Produces: produces, Parameters: paramMap, Tags: tags, ExternalDocs: docsFromDefinition(api.Docs), } err = api.IterateResponses(func(r *design.ResponseDefinition) error { res, err := responseSpecFromDefinition(s, api, r) if err != nil { return err } if s.Responses == nil { s.Responses = make(map[string]*Response) } s.Responses[r.Name] = res return nil }) if err != nil { return nil, err } err = api.IterateResources(func(res *design.ResourceDefinition) error { return res.IterateActions(func(a *design.ActionDefinition) error { for _, route := range a.Routes { if err := buildPathFromDefinition(s, api, route); err != nil { return err } } return nil }) }) if err != nil { return nil, err } if len(genschema.Definitions) > 0 { s.Definitions = make(map[string]*genschema.JSONSchema) for n, d := range genschema.Definitions { // sad but swagger doesn't support these d.Media = nil d.Links = nil s.Definitions[n] = d } } return s, nil }
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() }) }
// generateControllers iterates through the API resources and generates the low level // controllers. func (g *Generator) generateControllers(api *design.APIDefinition) error { ctlFile := filepath.Join(AppOutputDir(), "controllers.go") ctlWr, err := NewControllersWriter(ctlFile) if err != nil { panic(err) // bug } title := fmt.Sprintf("%s: Application Controllers", api.Context()) imports := []*codegen.ImportSpec{ codegen.SimpleImport("net/http"), codegen.SimpleImport("fmt"), codegen.SimpleImport("golang.org/x/net/context"), codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("github.com/goadesign/goa/cors"), } encoders, err := BuildEncoders(api.Produces, true) if err != nil { return err } decoders, err := BuildEncoders(api.Consumes, false) if err != nil { return err } encoderImports := make(map[string]bool) for _, data := range encoders { encoderImports[data.PackagePath] = true } for _, data := range decoders { encoderImports[data.PackagePath] = true } for packagePath := range encoderImports { if packagePath != "github.com/goadesign/goa" { imports = append(imports, codegen.SimpleImport(packagePath)) } } ctlWr.WriteHeader(title, TargetPackage, imports) ctlWr.WriteInitService(encoders, decoders) var controllersData []*ControllerTemplateData err = api.IterateResources(func(r *design.ResourceDefinition) error { data := &ControllerTemplateData{ API: api, Resource: codegen.Goify(r.Name, true), PreflightPaths: r.PreflightPaths(), } ierr := r.IterateActions(func(a *design.ActionDefinition) error { context := fmt.Sprintf("%s%sContext", codegen.Goify(a.Name, true), codegen.Goify(r.Name, true)) unmarshal := fmt.Sprintf("unmarshal%s%sPayload", codegen.Goify(a.Name, true), codegen.Goify(r.Name, true)) action := map[string]interface{}{ "Name": codegen.Goify(a.Name, true), "Routes": a.Routes, "Context": context, "Unmarshal": unmarshal, "Payload": a.Payload, "Security": a.Security, } data.Actions = append(data.Actions, action) return nil }) if ierr != nil { return ierr } if len(data.Actions) > 0 { data.Encoders = encoders data.Decoders = decoders data.Origins = r.AllOrigins() controllersData = append(controllersData, data) } return nil }) if err != nil { return err } g.genfiles = append(g.genfiles, ctlFile) if err = ctlWr.Execute(controllersData); err != nil { return err } return ctlWr.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() }