// Analyze an attribute, create corresponding ActionParam func (a *ApiAnalyzer) AnalyzeAttribute(name, query string, attr map[string]interface{}) (*gen.ActionParam, error) { param := gen.ActionParam{Name: name, QueryName: query, VarName: toVarName(name)} if d, ok := attr["description"]; ok { param.Description = removeBlankLines(d.(string)) } if r, ok := attr["required"]; ok { if r.(bool) { param.Mandatory = true } } if options, ok := attr["options"]; ok { opts, ok := options.(map[string]interface{}) if ok { for n, o := range opts { switch n { case "max": param.Max = int(o.(float64)) case "min": param.Min = int(o.(float64)) case "regexp": param.Regexp = o.(string) } } } } if values, ok := attr["values"]; ok { param.ValidValues = values.([]interface{}) } t := attr["type"].(map[string]interface{}) dataType, err := a.AnalyzeType(t, query) if err != nil { return nil, err } param.Type = dataType switch dataType.(type) { case *gen.ArrayDataType: param.QueryName += "[]" } return ¶m, nil }
// AnalyzeResource analyzes the given resource and updates the Resources and ParamTypes analyzer // fields accordingly func (a *APIAnalyzer) AnalyzeResource(name string, resource interface{}, descriptor *gen.APIDescriptor) { var res = resource.(map[string]interface{}) // Compute description var description string if d, ok := res["description"].(string); ok { description = d } // Compute attributes var attributes []*gen.Attribute var atts map[string]interface{} if m, ok := res["media_type"].(map[string]interface{}); ok { atts = m["attributes"].(map[string]interface{}) attributes = make([]*gen.Attribute, len(atts)) for idx, n := range sortedKeys(atts) { at, ok := a.attributeTypes[n+"#"+name] if !ok { at = a.attributeTypes[n] } attributes[idx] = &gen.Attribute{n, inflect.Camelize(n), at} } } else { attributes = []*gen.Attribute{} } // Compute actions var methods = res["methods"].(map[string]interface{}) var actionNames = sortedKeys(methods) var actions = []*gen.Action{} for _, actionName := range actionNames { var m = methods[actionName] var meth = m.(map[string]interface{}) var params map[string]interface{} if p, ok := meth["parameters"]; ok { params = p.(map[string]interface{}) } var description = "No description provided for " + actionName + "." if d, _ := meth["description"]; d != nil { description = d.(string) } var pathPatterns = ParseRoute(fmt.Sprintf("%s#%s", name, actionName), meth["route"].(string)) if len(pathPatterns) == 0 { // Custom action continue } var allParamNames = make([]string, len(params)) var i = 0 for n := range params { allParamNames[i] = n i++ } sort.Strings(allParamNames) var contentType string if c, ok := meth["content_type"].(string); ok { contentType = c } var paramAnalyzer = NewAnalyzer(params) paramAnalyzer.Analyze() // Record new parameter types var paramTypeNames = make([]string, len(paramAnalyzer.ParamTypes)) var idx = 0 for n := range paramAnalyzer.ParamTypes { paramTypeNames[idx] = n idx++ } sort.Strings(paramTypeNames) for _, name := range paramTypeNames { var pType = paramAnalyzer.ParamTypes[name] if _, ok := a.rawTypes[name]; ok { a.rawTypes[name] = append(a.rawTypes[name], pType) } else { a.rawTypes[name] = []*gen.ObjectDataType{pType} } } // Update description with parameter descriptions var mandatory []string var optional []string for _, p := range paramAnalyzer.Params { if p.Mandatory { desc := p.Name if p.Description != "" { desc += ": " + strings.TrimSpace(p.Description) } mandatory = append(mandatory, desc) } else { desc := p.Name if p.Description != "" { desc += ": " + strings.TrimSpace(p.Description) } optional = append(optional, desc) } } if len(mandatory) > 0 { sort.Strings(mandatory) if !strings.HasSuffix(description, "\n") { description += "\n" } description += "Required parameters:\n\t" + strings.Join(mandatory, "\n\t") } if len(optional) > 0 { sort.Strings(optional) if !strings.HasSuffix(description, "\n") { description += "\n" } description += "Optional parameters:\n\t" + strings.Join(optional, "\n\t") } // Sort parameters by location actionParams := paramAnalyzer.Params leafParams := paramAnalyzer.LeafParams var pathParamNames []string var queryParamNames []string var payloadParamNames []string for _, p := range leafParams { n := p.Name if isQueryParam(n) { queryParamNames = append(queryParamNames, n) p.Location = gen.QueryParam } else if isPathParam(n, pathPatterns) { pathParamNames = append(pathParamNames, n) p.Location = gen.PathParam } else { payloadParamNames = append(payloadParamNames, n) p.Location = gen.PayloadParam } } for _, p := range actionParams { done := false for _, ap := range leafParams { if ap == p { done = true break } } if done { continue } n := p.Name if isQueryParam(n) { p.Location = gen.QueryParam } else if isPathParam(n, pathPatterns) { p.Location = gen.PathParam } else { p.Location = gen.PayloadParam } } // Mix in filters information if filters, ok := meth["filters"]; ok { var filterParam *gen.ActionParam for _, p := range actionParams { if p.Name == "filter" { filterParam = p break } } if filterParam != nil { values := sortedKeys(filters.(map[string]interface{})) ivalues := make([]interface{}, len(values)) for i, v := range values { ivalues[i] = v } filterParam.ValidValues = ivalues } } // Record action action := gen.Action{ Name: actionName, MethodName: inflect.Camelize(actionName), Description: removeBlankLines(description), ResourceName: inflect.Singularize(name), PathPatterns: pathPatterns, Params: actionParams, LeafParams: paramAnalyzer.LeafParams, Return: parseReturn(actionName, name, contentType), ReturnLocation: actionName == "create" && name != "Oauth2", PathParamNames: pathParamNames, QueryParamNames: queryParamNames, PayloadParamNames: payloadParamNames, } actions = append(actions, &action) } // We're done! name = inflect.Singularize(name) descriptor.Resources[name] = &gen.Resource{ Name: name, ClientName: "API", Description: removeBlankLines(description), Actions: actions, Attributes: attributes, LocatorFunc: LocatorFunc(attributes, name), } }
// Analyze all parameters and categorize them // Initialize all fields of ParamAnalyzer struct func (p *ParamAnalyzer) Analyze() { // Order params using their length so "foo[bar]" is analyzed before "foo" params := p.rawParams paths := make([]string, len(params)) i := 0 for n := range params { paths[i] = n i++ } sort.Strings(paths) sort.Sort(ByReverseLength(paths)) rawLeafParams := []string{} for _, p := range paths { hasLeaf := false for _, r := range rawLeafParams { if strings.HasSuffix(r, "]") && strings.HasPrefix(r, p) { hasLeaf = true break } } if hasLeaf { continue } rawLeafParams = append(rawLeafParams, p) } sort.Strings(rawLeafParams) p.leafParamNames = rawLeafParams // Iterate through all params and build corresponding ActionParam structs p.parsed = map[string]*gen.ActionParam{} top := map[string]*gen.ActionParam{} for _, path := range paths { if strings.HasSuffix(path, "[*]") { // Cheat a little bit - there a couple of cases where parent type is // Hash instead of Enumerable, make that enumerable everywhere // There are also cases where there's no parent path, fix that up also matches := parentPathRegexp.FindStringSubmatch(path) if hashParam, ok := params[matches[1]].(map[string]interface{}); ok { hashParam["class"] = "Enumerable" } else { // Create parent rawParams := map[string]interface{}{} parentPath := matches[1] p.parsed[parentPath] = p.newParam(parentPath, rawParams, new(gen.EnumerableDataType)) if parentPathRegexp.FindStringSubmatch(parentPath) == nil { top[parentPath] = p.parsed[parentPath] } } continue } var child *gen.ActionParam origPath := path origParam := params[path].(map[string]interface{}) matches := parentPathRegexp.FindStringSubmatch(path) isTop := (matches == nil) if prev, ok := p.parsed[path]; ok { if isTop { top[path] = prev } continue } var branch []*gen.ActionParam for matches != nil { param := params[path].(map[string]interface{}) parentPath := matches[1] var isArrayChild bool if strings.HasSuffix(parentPath, "[]") { isArrayChild = true } if parent, ok := p.parsed[parentPath]; ok { a, ok := parent.Type.(*gen.ArrayDataType) if ok { parent = a.ElemType } child = p.parseParam(path, param, child) if !parent.Mandatory { // Make required fields of optional hashes optional. child.Mandatory = false } branch = append(branch, child) if _, ok = parent.Type.(*gen.EnumerableDataType); !ok { o := parent.Type.(*gen.ObjectDataType) o.Fields = appendSorted(o.Fields, child) p.parsed[path] = child } break // No need to keep going back, we already have a parent } else { child = p.parseParam(path, param, child) branch = append(branch, child) p.parsed[path] = child if isArrayChild { // Generate array item as it's not listed explicitly in JSON itemPath := matches[1] + "[item]" typeName := p.typeName(matches[1]) parent = p.newParam(itemPath, map[string]interface{}{}, &gen.ObjectDataType{typeName, []*gen.ActionParam{child}}) p.parsed[parentPath] = parent child = parent branch = append(branch, child) parentPath = parentPath[:len(parentPath)-2] } } path = parentPath matches = parentPathRegexp.FindStringSubmatch(path) } if isTop { if _, ok := p.parsed[path]; !ok { actionParam := p.parseParam(path, origParam, nil) p.parsed[path] = actionParam } top[path] = p.parsed[path] } else { matches := rootRegexp.FindStringSubmatch(origPath) rootPath := matches[1] if _, ok := p.parsed[rootPath]; !ok { p.parsed[rootPath] = p.parseParam(rootPath, params[rootPath].(map[string]interface{}), child) } actionParam, _ := p.parsed[rootPath] mandatory := actionParam.Mandatory if len(branch) > 0 { for i := len(branch) - 1; i >= 0; i-- { p := branch[i] if mandatory { if !p.Mandatory { mandatory = false } } else { p.Mandatory = false } } } } } // Now do a second pass on parsed params to generate their declarations p.ParamTypes = make(map[string]*gen.ObjectDataType) for _, param := range top { p.recordTypes(param.Type) } i = 0 res := make([]*gen.ActionParam, len(top)) for _, param := range top { res[i] = param i++ } sort.Sort(gen.ByName(res)) p.Params = res }