func WritePhp(data *Data) { MakeLibraryDir("php") RunTemplate := ChooseTemplate("php") RunTemplate("gitignore", ".gitignore", data) RunTemplate("composer.json", "composer.json", data) RunTemplate("readme.md", "README.md", data) MakeDir("lib") MakeDir(inflect.Camelize(data.Pkg.Name)) RunTemplate("lib/Client.php", "Client.php", data) MakeDir("Exception") RunTemplate("lib/Exception/ExceptionInterface.php", "ExceptionInterface.php", data) RunTemplate("lib/Exception/ClientException.php", "ClientException.php", data) MoveDir("..") MakeDir("HttpClient") RunTemplate("lib/HttpClient/HttpClient.php", "HttpClient.php", data) RunTemplate("lib/HttpClient/AuthHandler.php", "AuthHandler.php", data) RunTemplate("lib/HttpClient/ErrorHandler.php", "ErrorHandler.php", data) RunTemplate("lib/HttpClient/RequestHandler.php", "RequestHandler.php", data) RunTemplate("lib/HttpClient/Response.php", "Response.php", data) RunTemplate("lib/HttpClient/ResponseHandler.php", "ResponseHandler.php", data) MoveDir("..") MakeDir("Api") for k, v := range data.Api["class"].(map[string]interface{}) { data.Api["active"] = ActiveClassInfo(k, v) RunTemplate("lib/Api/Api.php", inflect.Camelize(k)+".php", data) delete(data.Api, "active") } }
func WritePhp(data *Data) { MakeLibraryDir("php") RunTemplate := ChooseTemplate("php") RunTemplate("gitignore", ".gitignore", data) RunTemplate("composer.json", "composer.json", data) RunTemplate("readme.md", "README.md", data) MakeDir("lib") MakeDir(inflect.Camelize(data.Pkg.Name)) RunTemplate("lib/Client.php", "Client.php", data) MakeDir("Exception") RunTemplate("lib/Exception/ExceptionInterface.php", "ExceptionInterface.php", data) RunTemplate("lib/Exception/ClientException.php", "ClientException.php", data) MoveDir("..") MakeDir("HttpClient") RunTemplate("lib/HttpClient/HttpClient.php", "HttpClient.php", data) RunTemplate("lib/HttpClient/AuthHandler.php", "AuthHandler.php", data) RunTemplate("lib/HttpClient/ErrorHandler.php", "ErrorHandler.php", data) RunTemplate("lib/HttpClient/RequestHandler.php", "RequestHandler.php", data) RunTemplate("lib/HttpClient/Response.php", "Response.php", data) RunTemplate("lib/HttpClient/ResponseHandler.php", "ResponseHandler.php", data) MoveDir("..") MakeDir("Api") for _, v := range data.Api.Classes { data.Active = &v RunTemplate("lib/Api/Api.php", inflect.Camelize(v.Name)+".php", data) data.Active = nil } }
func parseReturn(kind, resName, contentType string) string { switch kind { case "show": return refType(resName) case "index": return fmt.Sprintf("[]%s", refType(resName)) case "create": if _, ok := noMediaTypeResources[resName]; ok { return "map[string]interface{}" } return "*" + inflect.Singularize(resName) + "Locator" case "update", "destroy": return "" case "current_instances": return "[]*Instance" default: switch { case len(contentType) == 0: return "" case strings.Index(contentType, "application/vnd.rightscale.") == 0: if contentType == "application/vnd.rightscale.text" { return "string" } elems := strings.SplitN(contentType[27:], ";", 2) name := refType(inflect.Camelize(elems[0])) if len(elems) > 1 && elems[1] == "type=collection" { name = "[]" + refType(inflect.Camelize(elems[0])) } return name default: // Shouldn't be here panic("api15gen: Unknown content type " + contentType) } } }
//NewBuilder creates a build instance using the JSON schema data from the given filename. If modelName and/or pkgName //are left empty the title-attribute of the JSON schema is used for: // => struct-name (singular, camelcase e.g contact => Contact) // => package name (pluralize, lowercased e.g. payment_reminder => paymentreminders) func NewBuilder(inputFile string, modelName string, pkgName string) (builder Builder) { builder = Builder{} // try to read input file raw, err := ioutil.ReadFile(inputFile) if err != nil { msg := fmt.Sprintf("File error: %s", err) builder.Errors = append(builder.Errors, msg) return } builder.InputFile = inputFile builder.SchemaRaw = raw // try parsing json if err := json.Unmarshal(builder.SchemaRaw, &builder.SchemaJSON); err != nil { msg := fmt.Sprintf("JSON error: %s", err) builder.Errors = append(builder.Errors, msg) return } // defer model name from schema.title if not given as argument, schema['title'] MUST be set if len(modelName) > 0 { builder.ModelName = modelName } else { builder.ModelName = inflect.Typeify(builder.SchemaJSON["title"].(string)) } // defer package name from schema.title if not given as argument if len(pkgName) > 0 { builder.PkgName = pkgName } else { //Pluralize no underscores builder.PkgName = strings.ToLower(inflect.Camelize(inflect.Pluralize(builder.SchemaJSON["title"].(string)))) } return }
// Payload implements the action payload DSL. An action payload describes the HTTP request body // data structure. The function accepts either a type or a DSL that describes the payload members // using the Member DSL which accepts the same syntax as the Attribute DSL. This function can be // called passing in a type, a DSL or both. Examples: // // Payload(BottlePayload) // Request payload is described by the BottlePayload type // // Payload(func() { // Request payload is an object and is described inline // Member("Name") // }) // // Payload(BottlePayload, func() { // Request payload is described by merging the inline // Required("Name") // definition into the BottlePayload type. // }) // func Payload(p interface{}, dsls ...func()) { if len(dsls) > 1 { ReportError("too many arguments given to Payload") return } if a, ok := actionDefinition(true); ok { var att *design.AttributeDefinition var dsl func() switch actual := p.(type) { case func(): dsl = actual att = newAttribute(a.Parent.MediaType) att.Type = design.Object{} case *design.AttributeDefinition: att = actual.Dup() case design.DataStructure: att = actual.Definition().Dup() case string: ut, ok := design.Design.Types[actual] if !ok { ReportError("unknown payload type %s", actual) } att = ut.AttributeDefinition.Dup() case *design.Array: att = &design.AttributeDefinition{Type: actual} case *design.Hash: att = &design.AttributeDefinition{Type: actual} case design.Primitive: att = &design.AttributeDefinition{Type: actual} } if len(dsls) == 1 { if dsl != nil { ReportError("invalid arguments in Payload call, must be (type), (dsl) or (type, dsl)") } dsl = dsls[0] } if dsl != nil { ExecuteDSL(dsl, att) } rn := inflect.Camelize(a.Parent.Name) an := inflect.Camelize(a.Name) a.Payload = &design.UserTypeDefinition{ AttributeDefinition: att, TypeName: fmt.Sprintf("%s%sPayload", an, rn), } } }
//NewProperty creates a Property from a json string func NewProperty(name string, values map[string]interface{}) Property { prop := Property{ Name: name, RawValues: values, FieldName: inflect.Camelize(name), } prop.setType() return prop }
// Create API descriptor from raw resources and types func (a *ApiAnalyzer) AnalyzeResource(name string, res map[string]interface{}, desc *gen.ApiDescriptor) error { name = inflect.Singularize(name) resource := gen.Resource{Name: name, ClientName: a.ClientName} // Description if d, ok := res["description"]; ok { resource.Description = removeBlankLines(d.(string)) } // Attributes hasHref := false attributes := []*gen.Attribute{} m, ok := res["media_type"].(string) if ok { t, ok := a.RawTypes[m] if ok { attrs, ok := t["attributes"].(map[string]interface{}) if ok { attributes = make([]*gen.Attribute, len(attrs)) for idx, n := range sortedKeys(attrs) { if n == "href" { hasHref = true } param, err := a.AnalyzeAttribute(n, n, attrs[n].(map[string]interface{})) if err != nil { return err } attributes[idx] = &gen.Attribute{n, inflect.Camelize(n), param.Signature()} } } } } resource.Attributes = attributes if hasHref { resource.LocatorFunc = locatorFunc(name) } // Actions actions, err := a.AnalyzeActions(name, res) if err != nil { return err } resource.Actions = actions // Name and done resName := toGoTypeName(name, false) desc.Resources[resName] = &resource desc.ResourceNames = append(desc.ResourceNames, resName) return nil }
// Helper method that creates or retrieve a object data type given its attributes. func (a *ApiAnalyzer) CreateType(query string, attributes map[string]interface{}) (*gen.ObjectDataType, error) { name := inflect.Camelize(bracketRegexp.ReplaceAllLiteralString(query, "_") + "_struct") obj := a.Registry.CreateInlineType(name) obj.Fields = make([]*gen.ActionParam, len(attributes)) for idx, an := range sortedKeys(attributes) { at := attributes[an] var childQ string if query == "payload" { childQ = an } else { childQ = fmt.Sprintf("%s[%s]", query, an) } att, err := a.AnalyzeAttribute(an, childQ, at.(map[string]interface{})) if err != nil { return nil, fmt.Errorf("Failed to compute type of attribute %s: %s", an, err) } obj.Fields[idx] = att } return obj, nil }
// MediaType implements the media type definition DSL. A media type definition describes the // representation of a resource used in a response body. This includes listing all the *potential* // resource attributes that can appear in the body. Views specify which of the attributes are // actually rendered so that the same media type definition may represent multiple rendering of a // given resource representation. // // All media types must define a view named "default". This view is used to render the media type in // response bodies when no other view is specified. // // A media type definition may also define links to other media types. This is done by first // defining an attribute for the linked-to media type and then referring to that attribute in the // Links DSL. Views may then elect to render one or the other or both. Links are rendered using the // special "link" view. Media types that are linked to must define that view. Here is an example // showing all the possible media type sub-definitions: // // MediaType("application/vnd.goa.example.bottle", func() { // Description("A bottle of wine") // Attributes(func() { // Attribute("id", Integer, "ID of bottle") // Attribute("href", String, "API href of bottle") // Attribute("account", Account, "Owner account") // Attribute("origin", Origin, "Details on wine origin") // Links(func() { // Link("account") // Defines a link to the Account media type // Link("origin", "tiny") // Overrides the default view used to render links // }) // Required("id", "href") // }) // View("default", func() { // Attribute("id") // Attribute("href") // Attribute("links") // Default view renders links // }) // View("extended", func() { // Attribute("id") // Attribute("href") // Attribute("account") // Extended view renders account inline // Attribute("origin") // Extended view renders origin inline // Attribute("links") // Extended view also renders links // }) // }) // // This function returns the media type definition so it can be referred to throughout the DSL. func MediaType(identifier string, dsl func()) *design.MediaTypeDefinition { if design.Design == nil { InitDesign() } if design.Design.MediaTypes == nil { design.Design.MediaTypes = make(map[string]*design.MediaTypeDefinition) } if topLevelDefinition(true) { identifier, params, err := mime.ParseMediaType(identifier) if err != nil { ReportError("invalid media type identifier %#v: %s", identifier, err) } slash := strings.Index(identifier, "/") if slash == -1 { identifier += "/json" } identifier = mime.FormatMediaType(identifier, params) elems := strings.Split(identifier, ".") elems = strings.Split(elems[len(elems)-1], "/") elems = strings.Split(elems[0], "+") typeName := inflect.Camelize(elems[0]) if typeName == "" { mediaTypeCount++ typeName = fmt.Sprintf("MediaType%d", mediaTypeCount) } if _, ok := design.Design.MediaTypes[identifier]; ok { ReportError("media type %#v is defined twice", identifier) return nil } mt := design.NewMediaTypeDefinition(typeName, identifier, dsl) design.Design.MediaTypes[identifier] = mt return mt } return nil }
// AnalyzeActions parses out a resource actions. // Resource actions in the JSON consist of an array of map. The map keys are: // "description", "name", "metadata", "urls", "headers", "params", "payload" and "responses". func (a *APIAnalyzer) AnalyzeActions(resourceName string, resource map[string]interface{}) ([]*gen.Action, error) { methods := resource["actions"].([]interface{}) actions := make([]*gen.Action, len(methods)) for i, m := range methods { meth := m.(map[string]interface{}) actionName := meth["name"].(string) description := fmt.Sprintf("No description provided for %s.", actionName) if d, _ := meth["description"]; d != nil { description = d.(string) } pathPatterns, err := a.ParseUrls(meth["urls"]) if err != nil { return nil, err } params := []*gen.ActionParam{} // Query, path and payload params pathParamNames := []string{} queryParamNames := []string{} payloadParamNames := []string{} // Query and path params analysis if p, ok := meth["params"]; ok { param := p.(map[string]interface{}) t, ok := param["type"] if !ok { return nil, fmt.Errorf("Missing type declaration in %s", prettify(p)) } attrs := t.(map[string]interface{})["attributes"].(map[string]interface{}) attrNames := sortedKeys(attrs) for _, pn := range attrNames { pt := attrs[pn] att, err := a.AnalyzeAttribute(pn, pn, pt.(map[string]interface{})) if err != nil { return nil, fmt.Errorf("Failed to compute type of param %s: %s", pn, err.Error()) } isPathParam := false for _, pat := range pathPatterns { for _, v := range pat.Variables { if v == pn { isPathParam = true break } } if isPathParam { break } } if isPathParam { pathParamNames = append(pathParamNames, pn) att.Location = gen.PathParam } else { queryParamNames = append(queryParamNames, pn) att.Location = gen.QueryParam params = append(params, att) } } } // Initialize leaf params, all path and query params are leaf params leafParams := make([]*gen.ActionParam, len(queryParamNames)) idx := 0 for _, p := range params { if p.Location == gen.QueryParam { leafParams[idx] = p idx++ } } paramNames := make([]string, len(queryParamNames)) for i, n := range queryParamNames { paramNames[i] = n } // Payload params analysis var payload gen.DataType if p, ok := meth["payload"]; ok { as, ok := p.(map[string]interface{})["type"] if ok { pd, err := a.AnalyzeType(as.(map[string]interface{}), "payload") if err != nil { return nil, err } if po, ok := pd.(*gen.ObjectDataType); ok { // Remove the type since we are "flattening" the attributes // as top level params. // This is a bit hacky and should be refactored // (it should be possible to get the type without having // it be registered). Works for now(TM). delete(a.Registry.InlineTypes, po.TypeName) for _, att := range po.Fields { payloadParamNames = append(payloadParamNames, att.Name) att.Location = gen.PayloadParam params = append(params, att) extracted := extractLeafParams(att, att.Name, make(map[string]*[]*gen.ActionParam)) for _, e := range extracted { e.Location = gen.PayloadParam } leafParams = append(leafParams, extracted...) } } else { // Raw payload (no attributes) payload = pd param := rawPayload(pd, p) params = append(params, param) leafParams = append(leafParams, param) } } } // Heuristic to check whether response returns a location header // Also extract response type from success response media type // TBD: support actions returning multiple success responses with media types? hasLocation := false var returnTypeName string responses, ok := meth["responses"] if ok { resps := responses.(map[string]interface{}) respNames := sortedKeys(resps) for _, rName := range respNames { r := resps[rName] resp := r.(map[string]interface{}) status := resp["status"] s := int(status.(float64)) if s < 200 || s > 299 { continue // Skip error responses } if s == 201 && actionName == "create" { hasLocation = true } else if headers, ok := resp["headers"]; ok { if hname, ok := headers.(string); ok { // TBD is there a better way? hasLocation = hname == "Location" && actionName == "create" } else { head := headers.(map[string]interface{}) keys, ok := head["keys"] if ok { headerKeys := keys.(map[string]interface{}) for _, k := range headerKeys { // TBD is there a better way? if k == "Location" && actionName == "create" { hasLocation = true } break } } } } if returnTypeName == "" { if media, ok := resp["media_type"]; ok { m := media.(map[string]interface{}) if name, ok := m["name"]; ok { returnTypeName = toGoTypeName(name.(string), true) a.descriptor.NeedJSON = true // Analyze return type to make sure it gets recorded _, err := a.AnalyzeType(a.RawTypes[name.(string)], "return") if err != nil { return nil, err } } else { // Default to string returnTypeName = "string" } } else if mime, ok := resp["mime_type"]; ok { // Resticle compat for n, r := range a.RawResources { if mt, ok := r["mime_type"]; ok { if mt == mime { if actionName == "index" { returnTypeName = "[]*" + n } else { returnTypeName = "*" + n } a.descriptor.NeedJSON = true break } } } } } } } if hasLocation { returnTypeName = fmt.Sprintf("*%sLocator", resourceName) } // Record action action := gen.Action{ Name: actionName, MethodName: inflect.Camelize(actionName), Description: removeBlankLines(description), ResourceName: resourceName, PathPatterns: pathPatterns, Payload: payload, Params: params, LeafParams: leafParams, Return: returnTypeName, ReturnLocation: hasLocation, QueryParamNames: queryParamNames, PayloadParamNames: payloadParamNames, PathParamNames: pathParamNames, } actions[i] = &action } return actions, 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), } }
// upperCamelCase returns camel case version of a word // with upper case first character func upperCamelCase(s string) string { return inflect.Camelize(s) }