func paramsFromDefinition(params *design.AttributeDefinition, path string) ([]*Parameter, error) { if params == nil { return nil, nil } obj := params.Type.ToObject() if obj == nil { return nil, fmt.Errorf("invalid parameters definition, not an object") } res := make([]*Parameter, len(obj)) i := 0 wildcards := design.ExtractWildcards(path) obj.IterateAttributes(func(n string, at *design.AttributeDefinition) error { in := "query" required := params.IsRequired(n) for _, w := range wildcards { if n == w { in = "path" required = true break } } param := paramFor(at, n, in, required) res[i] = param i++ return nil }) return res, nil }
func buildPathFromFileServer(s *Swagger, api *design.APIDefinition, fs *design.FileServerDefinition) error { wcs := design.ExtractWildcards(fs.RequestPath) var param []*Parameter if len(wcs) > 0 { param = []*Parameter{{ In: "path", Name: wcs[0], Description: "Relative file path", Required: true, Type: "string", }} } responses := map[string]*Response{ "200": { Description: "File downloaded", Schema: &genschema.JSONSchema{Type: genschema.JSONFile}, }, } if len(wcs) > 0 { schema := genschema.TypeSchema(api, design.ErrorMedia) responses["404"] = &Response{Description: "File not found", Schema: schema} } operationID := fmt.Sprintf("%s#%s", fs.Parent.Name, fs.RequestPath) schemes := api.Schemes operation := &Operation{ Description: fs.Description, Summary: summaryFromDefinition(fmt.Sprintf("Download %s", fs.FilePath), fs.Metadata), ExternalDocs: docsFromDefinition(fs.Docs), OperationID: operationID, Parameters: param, Responses: responses, Schemes: schemes, } applySecurity(operation, fs.Security) key := design.WildcardRegex.ReplaceAllStringFunc( fs.RequestPath, func(w string) string { return fmt.Sprintf("/{%s}", w[2:]) }, ) if key == "" { key = "/" } var path interface{} var ok bool if path, ok = s.Paths[key]; !ok { path = new(Path) s.Paths[key] = path } p := path.(*Path) p.Get = operation p.Extensions = extensionsFromDefinition(fs.Metadata) return nil }
// BasePath defines the API base path, i.e. the common path prefix to all the API actions. // The path may define wildcards (see Routing for a description of the wildcard syntax). // The corresponding parameters must be described using BaseParams. func BasePath(val string) { if a, ok := apiDefinition(false); ok { a.BasePath = val } else if v, ok := versionDefinition(false); ok { v.BasePath = val } else if r, ok := resourceDefinition(true); ok { r.BasePath = val awcs := design.ExtractWildcards(design.Design.BasePath) wcs := design.ExtractWildcards(val) for _, awc := range awcs { for _, wc := range wcs { if awc == wc { dslengine.ReportError(`duplicate wildcard "%s" in API and resource base paths`, wc) } } } } }
// BasePath defines the API base path, i.e. the common path prefix to all the API actions. // The path may define wildcards (see Routing for a description of the wildcard syntax). // The corresponding parameters must be described using BaseParams. func BasePath(val string) { switch def := dslengine.CurrentDefinition().(type) { case *design.APIDefinition: def.BasePath = val case *design.ResourceDefinition: def.BasePath = val awcs := design.ExtractWildcards(design.Design.BasePath) wcs := design.ExtractWildcards(val) for _, awc := range awcs { for _, wc := range wcs { if awc == wc { dslengine.ReportError(`duplicate wildcard "%s" in API and resource base paths`, wc) } } } default: dslengine.IncompatibleDSL() } }
func paramsFromDefinition(params *design.AttributeDefinition, path string) ([]*Parameter, error) { if params == nil { return nil, nil } obj := params.Type.ToObject() if obj == nil { return nil, fmt.Errorf("invalid parameters definition, not an object") } res := make([]*Parameter, len(obj)) i := 0 wildcards := design.ExtractWildcards(path) obj.IterateAttributes(func(n string, at *design.AttributeDefinition) error { in := "query" required := params.IsRequired(n) for _, w := range wildcards { if n == w { in = "path" required = true break } } param := &Parameter{ Name: n, Default: at.DefaultValue, Description: at.Description, Required: required, In: in, Type: at.Type.Name(), } var items *Items if at.Type.IsArray() { items = itemsFromDefinition(at) } param.Items = items initValidations(at, param) res[i] = param i++ return nil }) return res, nil }
func (g *Generator) generateFileServer(file *codegen.SourceFile, fs *design.FileServerDefinition, funcs template.FuncMap) error { var ( dir string fsTmpl = template.Must(template.New("fileserver").Funcs(funcs).Parse(fsTmpl)) name = g.fileServerMethod(fs) wcs = design.ExtractWildcards(fs.RequestPath) scheme = "http" ) if len(wcs) > 0 { dir = "/" fileElems := filepath.SplitList(fs.FilePath) if len(fileElems) > 1 { dir = fileElems[len(fileElems)-2] } } if len(design.Design.Schemes) > 0 { scheme = design.Design.Schemes[0] } requestDir, _ := path.Split(fs.RequestPath) data := struct { Name string // Download functionn name RequestPath string // File server request path FilePath string // File server file path FileName string // Filename being download if request path has no wildcard DirName string // Parent directory name if request path has wildcard RequestDir string // Request path without wildcard suffix CanonicalScheme string // HTTP scheme }{ Name: name, RequestPath: fs.RequestPath, FilePath: fs.FilePath, FileName: filepath.Base(fs.FilePath), DirName: dir, RequestDir: requestDir, CanonicalScheme: scheme, } return fsTmpl.Execute(file, data) }
// PathSelectVersionFunc returns a SelectVersionFunc that uses the given path pattern and param to // extract the version from the request path. Use the same path pattern given in the DSL to define // the API base path, e.g. "/api/:api_version". func PathSelectVersionFunc(pattern, param string) (SelectVersionFunc, error) { params := design.ExtractWildcards(pattern) index := -1 for i, p := range params { if p == param { index = i break } } if index == -1 { return nil, fmt.Errorf("Mux versioning setup: no param %s in pattern %s", param, pattern) } rgs := strings.Replace(pattern, ":"+param, `([^/]+)`, 1) rg := regexp.MustCompile("^" + rgs) return func(req *http.Request) (version string) { match := rg.FindStringSubmatch(req.URL.Path) if len(match) > 1 { version = match[1] } return }, nil }
// fileServerMethod returns the name of the client method for downloading assets served by the given // file server. // Note: the implementation opts for generating good names rather than names that are guaranteed to // be unique. This means that the generated code could be potentially incorrect in the rare cases // where it produces the same names for two different file servers. This should be addressed later // (when it comes up?) using metadata to let users override the default. func (g *Generator) fileServerMethod(fs *design.FileServerDefinition) string { var ( suffix string wcs = design.ExtractWildcards(fs.RequestPath) reqElems = strings.Split(fs.RequestPath, "/") ) if len(wcs) == 0 { suffix = path.Base(fs.RequestPath) ext := filepath.Ext(suffix) suffix = strings.TrimSuffix(suffix, ext) suffix += codegen.Goify(ext, true) } else { if len(reqElems) == 1 { suffix = filepath.Base(fs.RequestPath) suffix = suffix[1:] // remove "*" prefix } else { suffix = reqElems[len(reqElems)-2] // should work most of the time } } return "Download" + codegen.Goify(suffix, true) }
BeforeEach(func() { id = "application/json+xml; foo=bar" }) It("canonicalizes it", func() { Ω(canonical).Should(Equal("application/json; foo=bar")) }) }) }) var _ = Describe("ExtractWildcards", func() { var path string var wcs []string JustBeforeEach(func() { wcs = design.ExtractWildcards(path) }) Context("with a path with no wildcard", func() { BeforeEach(func() { path = "/foo" }) It("returns the empty slice", func() { Ω(wcs).Should(HaveLen(0)) }) }) Context("with a path with wildcards", func() { BeforeEach(func() { path = "/a/:foo/:bar/b/:baz/c"
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() }