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, mediatype identifier, and links
	var attributes []*gen.Attribute
	var identifier string
	var atts map[string]interface{}
	var links map[string]string
	if m, ok := res["media_type"].(map[string]interface{}); ok {
		// Compute attributes
		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}
		}

		// Compute mediatype identifier
		contentTypes := m["content_types"].([]interface{})
		if len(contentTypes) > 0 {
			identifier = contentTypes[0].(string)
		}

		// Compute links
		if l, ok := m["links"].(map[string]interface{}); ok {
			links = make(map[string]string, len(l))
			for n, d := range l {
				links[n] = d.(string)
			}
		}
	} 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 routes = meth["routes"].([]interface{})
		var strRoutes = make([]string, len(routes))
		for i, r := range routes {
			strRoutes[i] = r.(string)
		}
		var pathPatterns = ParseRoute(fmt.Sprintf("%s#%s", name, actionName), strRoutes)
		if len(pathPatterns) == 0 {
			// Custom action
			continue
		}
		// HACK: /api/right_scripts/:id/update_source is a unique snowflake. It has no
		// params, it accepts the script body as a text/plain type type!. We inject a
		// fake param here that acts as a multipart file upload does.
		if name == "RightScripts" && actionName == "update_source" {
			params["filename"] = map[string]interface{}{
				"class":       "SourceUpload",
				"mandatory":   true,
				"non_blank":   true,
				"description": "The file name to update the RightScript source with.",
			}
		}
		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(actionName, 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(actionName, 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),
		Identifier:  identifier,
		Links:       links,
	}
}
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, mediatype identifier, and links
	var attributes []*gen.Attribute
	var identifier string
	var atts map[string]interface{}
	var links map[string]string
	if m, ok := res["media_type"].(map[string]interface{}); ok {
		// Compute attributes
		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}
		}

		// Compute mediatype identifier
		contentTypes := m["content_types"].([]interface{})
		if len(contentTypes) > 0 {
			identifier = contentTypes[0].(string)
		}

		// Compute links
		if l, ok := m["links"].(map[string]interface{}); ok {
			links = make(map[string]string, len(l))
			for n, d := range l {
				links[n] = d.(string)
			}
		}
	} 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 routes = meth["routes"].([]interface{})
		var strRoutes = make([]string, len(routes))
		for i, r := range routes {
			strRoutes[i] = r.(string)
		}
		var pathPatterns = ParseRoute(fmt.Sprintf("%s#%s", name, actionName), strRoutes)
		if len(pathPatterns) == 0 {
			// Custom action
			continue
		}
		// HACK: /api/right_scripts/:id/update_source is a unique snowflake. It has no
		// params, it accepts the script body as a text/plain type type!. We inject a
		// fake param here that acts as a multipart file upload does.
		if name == "RightScripts" && actionName == "update_source" {
			params["filename"] = map[string]interface{}{
				"class":       "SourceUpload",
				"mandatory":   true,
				"non_blank":   true,
				"description": "The file name to update the RightScript source with.",
			}
		}
		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(actionName, 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(actionName, 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),
		Identifier:  identifier,
		Links:       links,
	}
}
// AnalyzeResource analyzes the given resource and updates the Resources and ParamTypes analyzer
// fields accordingly