// Trait defines an API trait. A trait encapsulates arbitrary DSL that gets executed wherever the // trait is called via the UseTrait function. func Trait(name string, val ...func()) { var ver *design.APIVersionDefinition if a, ok := apiDefinition(false); ok { ver = a.APIVersionDefinition } else if v, ok := versionDefinition(true); ok { ver = v } if len(val) < 1 { dslengine.ReportError("missing trait DSL for %s", name) return } else if len(val) > 1 { dslengine.ReportError("too many arguments given to Trait") return } if ver == nil { return } if _, ok := ver.Traits[name]; ok { dslengine.ReportError("multiple definitions for trait %s%s", name, ver.Context()) return } trait := &dslengine.TraitDefinition{Name: name, DSLFunc: val[0]} if ver.Traits == nil { ver.Traits = make(map[string]*dslengine.TraitDefinition) } ver.Traits[name] = trait }
// ResponseTemplate defines a response template that action definitions can use to describe their // responses. The template may specify the HTTP response status, header specification and body media // type. The template consists of a name and an anonymous function. The function is called when an // action uses the template to define a response. Response template functions accept string // parameters they can use to define the response fields. Here is an example of a response template // definition that uses a function with one argument corresponding to the name of the response body // media type: // // ResponseTemplate(OK, func(mt string) { // Status(200) // OK response uses status code 200 // Media(mt) // Media type name set by action definition // Headers(func() { // Header("X-Request-Id", func() { // X-Request-Id header contains a string // Pattern("[0-9A-F]+") // Regexp used to validate the response header content // }) // Required("X-Request-Id") // Header is mandatory // }) // }) // // This template can the be used by actions to define the OK response as follows: // // Response(OK, "vnd.goa.example") // // goa comes with a set of predefined response templates (one per standard HTTP status code). The // OK template is the only one that accepts an argument. It is used as shown in the example above to // set the response media type. Other predefined templates do not use arguments. ResponseTemplate // makes it possible to define additional response templates specific to the API. func ResponseTemplate(name string, p interface{}) { var v *design.APIVersionDefinition if a, ok := apiDefinition(false); ok { v = a.APIVersionDefinition } else if ver, ok := versionDefinition(true); ok { v = ver } if v == nil { return } if v.Responses == nil { v.Responses = make(map[string]*design.ResponseDefinition) } if v.ResponseTemplates == nil { v.ResponseTemplates = make(map[string]*design.ResponseTemplateDefinition) } if _, ok := v.Responses[name]; ok { dslengine.ReportError("multiple definitions for response template %s", name) return } if _, ok := v.ResponseTemplates[name]; ok { dslengine.ReportError("multiple definitions for response template %s", name) return } setupResponseTemplate(v, name, p) }
// generateUserTypes iterates through the user types and generates the data structures and // marshaling code. func (g *Generator) generateUserTypes(verdir string, version *design.APIVersionDefinition) error { utFile := filepath.Join(verdir, "user_types.go") utWr, err := NewUserTypesWriter(utFile) if err != nil { panic(err) // bug } title := fmt.Sprintf("%s: Application User Types", version.Context()) imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("fmt"), } utWr.WriteHeader(title, packageName(version), imports) err = version.IterateUserTypes(func(t *design.UserTypeDefinition) error { data := &UserTypeTemplateData{ UserType: t, Versioned: version.Version != "", DefaultPkg: TargetPackage, } return utWr.Execute(data) }) g.genfiles = append(g.genfiles, utFile) if err != nil { return err } return utWr.FormatCode() }
// generateMediaTypes iterates through the media types and generate the data structures and // marshaling code. func (g *Generator) generateMediaTypes(verdir string, version *design.APIVersionDefinition) error { mtFile := filepath.Join(verdir, "media_types.go") mtWr, err := NewMediaTypesWriter(mtFile) if err != nil { panic(err) // bug } title := fmt.Sprintf("%s: Application Media Types", version.Context()) imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/goadesign/goa"), codegen.SimpleImport("fmt"), } mtWr.WriteHeader(title, packageName(version), imports) err = version.IterateMediaTypes(func(mt *design.MediaTypeDefinition) error { data := &MediaTypeTemplateData{ MediaType: mt, Versioned: version.Version != "", DefaultPkg: TargetPackage, } if mt.Type.IsObject() || mt.Type.IsArray() { return mtWr.Execute(data) } return nil }) g.genfiles = append(g.genfiles, mtFile) if err != nil { return err } return mtWr.FormatCode() }
// Produces adds a MIME type to the list of MIME types the APIs can encode responses with. // Produces may also specify the path of the encoding package. // The package must expose a EncoderFactory method that returns an object which implements // goa.EncoderFactory. func Produces(args ...interface{}) { var v *design.APIVersionDefinition if a, ok := apiDefinition(false); ok { v = a.APIVersionDefinition } else if ver, ok := versionDefinition(true); ok { v = ver } if v == nil { return } if def := buildEncodingDefinition(args...); def != nil { v.Produces = append(v.Produces, def) } }
// generateHrefs iterates through the version resources and generates the href factory methods. func (g *Generator) generateHrefs(verdir string, version *design.APIVersionDefinition) error { hrefFile := filepath.Join(verdir, "hrefs.go") resWr, err := NewResourcesWriter(hrefFile) if err != nil { panic(err) // bug } title := fmt.Sprintf("%s: Application Resource Href Factories", version.Context()) imports := []*codegen.ImportSpec{ codegen.SimpleImport("fmt"), } resWr.WriteHeader(title, packageName(version), imports) err = version.IterateResources(func(r *design.ResourceDefinition) error { if !r.SupportsVersion(version.Version) { return nil } m := design.Design.MediaTypeWithIdentifier(r.MediaType) var identifier string if m != nil { identifier = m.Identifier } else { identifier = "plain/text" } canoTemplate := r.URITemplate(version) canoTemplate = design.WildcardRegex.ReplaceAllLiteralString(canoTemplate, "/%v") var canoParams []string if ca := r.CanonicalAction(); ca != nil { if len(ca.Routes) > 0 { canoParams = ca.Routes[0].Params(version) } } data := ResourceData{ Name: codegen.Goify(r.Name, true), Identifier: identifier, Description: r.Description, Type: m, CanonicalTemplate: canoTemplate, CanonicalParams: canoParams, } return resWr.Execute(&data) }) g.genfiles = append(g.genfiles, hrefFile) if err != nil { return err } return resWr.FormatCode() }
// generateContexts iterates through the version resources and actions and generates the action // contexts. func (g *Generator) generateContexts(verdir string, api *design.APIDefinition, version *design.APIVersionDefinition) error { ctxFile := filepath.Join(verdir, "contexts.go") ctxWr, err := NewContextsWriter(ctxFile) if err != nil { panic(err) // bug } title := fmt.Sprintf("%s: Application Contexts", version.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"), } if !version.IsDefault() { appPkg, err := AppPackagePath() if err != nil { return err } imports = append(imports, codegen.SimpleImport(appPkg)) } ctxWr.WriteHeader(title, packageName(version), imports) err = version.IterateResources(func(r *design.ResourceDefinition) error { if !r.SupportsVersion(version.Version) { return nil } 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: MergeResponses(r.Responses, a.Responses), API: api, Version: version, DefaultPkg: TargetPackage, } return ctxWr.Execute(&ctxData) }) }) g.genfiles = append(g.genfiles, ctxFile) if err != nil { return err } return ctxWr.FormatCode() }
// generateControllers iterates through the version resources and generates the low level // controllers. func (g *Generator) generateControllers(verdir string, version *design.APIVersionDefinition) error { ctlFile := filepath.Join(verdir, "controllers.go") ctlWr, err := NewControllersWriter(ctlFile) if err != nil { panic(err) // bug } title := fmt.Sprintf("%s: Application Controllers", version.Context()) imports := []*codegen.ImportSpec{ codegen.SimpleImport("github.com/julienschmidt/httprouter"), codegen.SimpleImport("github.com/goadesign/goa"), } if !version.IsDefault() { appPkg, err := AppPackagePath() if err != nil { return err } imports = append(imports, codegen.SimpleImport(appPkg)) } ctlWr.WriteHeader(title, packageName(version), imports) var controllersData []*ControllerTemplateData version.IterateResources(func(r *design.ResourceDefinition) error { if !r.SupportsVersion(version.Version) { return nil } data := &ControllerTemplateData{Resource: codegen.Goify(r.Name, true)} err := 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, } data.Actions = append(data.Actions, action) return nil }) if err != nil { return err } if len(data.Actions) > 0 { data.Version = version controllersData = append(controllersData, data) } return nil }) g.genfiles = append(g.genfiles, ctlFile) if err = ctlWr.Execute(controllersData); err != nil { return err } return ctlWr.FormatCode() }
// generateControllers iterates through the version resources and generates the low level // controllers. func (g *Generator) generateControllers(verdir string, version *design.APIVersionDefinition) error { ctlFile := filepath.Join(verdir, "controllers.go") ctlWr, err := NewControllersWriter(ctlFile) if err != nil { panic(err) // bug } title := fmt.Sprintf("%s: Application Controllers", version.Context()) imports := []*codegen.ImportSpec{ codegen.SimpleImport("net/http"), codegen.SimpleImport("golang.org/x/net/context"), codegen.SimpleImport("github.com/goadesign/goa"), } if !version.IsDefault() { appPkg, err := AppPackagePath() if err != nil { return err } imports = append(imports, codegen.SimpleImport(appPkg)) } encoderMap, err := BuildEncoderMap(version.Produces, true) if err != nil { return err } decoderMap, err := BuildEncoderMap(version.Consumes, false) if err != nil { return err } encoderImports := make(map[string]bool) for _, data := range encoderMap { encoderImports[data.PackagePath] = true } for _, data := range decoderMap { encoderImports[data.PackagePath] = true } for packagePath := range encoderImports { if !design.IsGoaEncoder(packagePath) { imports = append(imports, codegen.SimpleImport(packagePath)) } } ctlWr.WriteHeader(title, packageName(version), imports) var controllersData []*ControllerTemplateData version.IterateResources(func(r *design.ResourceDefinition) error { if !r.SupportsVersion(version.Version) { return nil } data := &ControllerTemplateData{API: design.Design, Resource: codegen.Goify(r.Name, true)} err := 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, } data.Actions = append(data.Actions, action) return nil }) if err != nil { return err } if len(data.Actions) > 0 { data.EncoderMap = encoderMap data.DecoderMap = decoderMap data.Version = version controllersData = append(controllersData, data) } return nil }) g.genfiles = append(g.genfiles, ctlFile) if err = ctlWr.Execute(controllersData); err != nil { return err } return ctlWr.FormatCode() }