Example #1
0
// 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 &param, nil
}
Example #2
0
// 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),
	}
}
Example #3
0
// 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
}