func (entry *ExchangeEntry) generateAPICode(exchangeEntry string) string { content := "" if entry.Description != "" { content = utils.Indent(entry.Description, "// ") } if len(content) >= 1 && content[len(content)-1:] != "\n" { content += "\n" } content += "//\n" content += fmt.Sprintf("// See %v/#%v\n", entry.Parent.apiDef.DocRoot, entry.Name) content += "type " + exchangeEntry + " struct {\n" keyNames := make(map[string]bool, len(entry.RoutingKey)) for _, rk := range entry.RoutingKey { mwch := "*" if rk.MultipleWords { mwch = "#" } content += "\t" + utils.Normalise(rk.Name, keyNames) + " string `mwords:\"" + mwch + "\"`\n" } content += "}\n" content += "func (binding " + exchangeEntry + ") RoutingKey() string {\n" content += "\treturn generateRoutingKey(&binding)\n" content += "}\n" content += "\n" content += "func (binding " + exchangeEntry + ") ExchangeName() string {\n" content += "\treturn \"" + entry.Parent.ExchangePrefix + entry.Exchange + "\"\n" content += "}\n" content += "\n" content += "func (binding " + exchangeEntry + ") NewPayloadObject() interface{} {\n" content += "\treturn new(" + entry.Payload.TypeName + ")\n" content += "}\n" content += "\n" return content }
func (api *API) postPopulate(apiDef *APIDefinition) { // make sure each entry defined for this API has a unique generated method name methods := make(map[string]bool) for i := range api.Entries { api.Entries[i].Parent = api api.Entries[i].MethodName = utils.Normalise(api.Entries[i].Name, methods) api.Entries[i].postPopulate(apiDef) } }
// LoadAPIs takes care of reading all json files and performing elementary // processing of the data, such as assigning unique type names to entities // which will be translated to go types. // // Data is unmarshaled into objects (or instances of go types) and then // postPopulate is called on the objects. This in turn triggers further reading // of json files and unmarshalling where schemas refer to other schemas. // // When LoadAPIs returns, all json schemas and sub schemas should have been // read and unmarhsalled into go objects. func LoadAPIs(apiManifestUrl, supplementaryDataFile string) []APIDefinition { resp, err := http.Get(apiManifestUrl) if err != nil { fmt.Printf("Could not download api manifest from url: '%v'!\n", apiManifestUrl) } utils.ExitOnFail(err) supDataReader, err := os.Open(supplementaryDataFile) if err != nil { fmt.Printf("Could not load supplementary data json file: '%v'!\n", supplementaryDataFile) } utils.ExitOnFail(err) apiManifestDecoder := json.NewDecoder(resp.Body) apiMan := make(map[string]string) err = apiManifestDecoder.Decode(&apiMan) utils.ExitOnFail(err) supDataDecoder := json.NewDecoder(supDataReader) err = supDataDecoder.Decode(&apiDefs) utils.ExitOnFail(err) sort.Sort(SortedAPIDefs(apiDefs)) // build up apis based on data in *both* data sources for i := range apiMan { // seach for apiMan[i] in apis k := sort.Search(len(apiDefs), func(j int) bool { return apiDefs[j].URL >= apiMan[i] }) if k < len(apiDefs) && apiDefs[k].URL == apiMan[i] { // url is present in supplementary data apiDefs[k].Name = i } else { fmt.Printf( "\nFATAL: Manifest from url '%v' contains key '%v' with url '%v', but this url does not exist in supplementary data file '%v', therefore exiting...\n\n", apiManifestUrl, i, apiMan[i], supplementaryDataFile) os.Exit(64) } } for i := range apiDefs { if apiDefs[i].Name == "" { fmt.Printf( "\nFATAL: Manifest from url '%v' does not contain url '%v' which does exist in supplementary data file '%v', therefore exiting...\n\n", apiManifestUrl, apiDefs[i].URL, supplementaryDataFile) os.Exit(65) } } for i := range apiDefs { apiDefs[i].schemas = make(map[string]*JsonSubSchema) var resp *http.Response resp, err = http.Get(apiDefs[i].URL) utils.ExitOnFail(err) defer resp.Body.Close() apiDefs[i].loadJson(resp.Body) // check that the json schema is valid! validateJson(apiDefs[i].SchemaURL, apiDefs[i].URL) // now all data should be loaded, let's sort the schemas apiDefs[i].schemaURLs = make([]string, 0, len(apiDefs[i].schemas)) for url := range apiDefs[i].schemas { apiDefs[i].schemaURLs = append(apiDefs[i].schemaURLs, url) } sort.Strings(apiDefs[i].schemaURLs) // finally, now we can generate normalised names // for schemas // keep a record of generated type names, so that we don't reuse old names // map[string]bool acts like a set of strings TypeName := make(map[string]bool) for _, j := range apiDefs[i].schemaURLs { apiDefs[i].schemas[j].TypeName = utils.Normalise(*apiDefs[i].schemas[j].Title, TypeName) } } return apiDefs }
func (jsonSubSchema *JsonSubSchema) TypeDefinition(withComments bool, extraPackages map[string]bool, rawMessageTypes map[string]bool) (string, map[string]bool, map[string]bool) { content := "" comment := "" if withComments { content += "\n" if d := jsonSubSchema.Description; d != nil { if desc := *d; desc != "" { comment = utils.Indent(desc, "// ") } if len(comment) >= 1 && comment[len(comment)-1:] != "\n" { comment += "\n" } } if url := jsonSubSchema.SourceURL; url != "" { comment += "//\n// See " + url + "\n" } content += comment content += jsonSubSchema.TypeName + " " } typ := "json.RawMessage" if p := jsonSubSchema.Type; p != nil { typ = *p } if p := jsonSubSchema.RefSubSchema; p != nil { typ = p.TypeName } switch typ { case "array": if jsonType := jsonSubSchema.Items.Type; jsonType != nil { var newType string newType, extraPackages, rawMessageTypes = jsonSubSchema.Items.TypeDefinition(false, extraPackages, rawMessageTypes) typ = "[]" + newType } else { if refSubSchema := jsonSubSchema.Items.RefSubSchema; refSubSchema != nil { typ = "[]" + refSubSchema.TypeName } } case "object": if s := jsonSubSchema.Properties; s != nil { typ = fmt.Sprintf("struct {\n") members := make(map[string]bool, len(s.SortedPropertyNames)) for _, j := range s.SortedPropertyNames { memberName := utils.Normalise(j, members) // recursive call to build structs inside structs var subType string subType, extraPackages, rawMessageTypes = s.Properties[j].TypeDefinition(false, extraPackages, rawMessageTypes) // comment the struct member with the description from the json comment = "" if d := s.Properties[j].Description; d != nil { comment = utils.Indent(*d, "\t// ") } if len(comment) >= 1 && comment[len(comment)-1:] != "\n" { comment += "\n" } if enum := s.Properties[j].Enum; enum != nil { comment += "//\n// Possible values:\n" for _, i := range enum { switch i.(type) { case float64: comment += fmt.Sprintf("// * %v\n", i) default: comment += fmt.Sprintf("// * %q\n", i) } } } if regex := s.Properties[j].Pattern; regex != nil { comment += "//\n// Syntax: " + *regex + "\n" } typ += comment // struct member name and type, as part of struct definition typ += fmt.Sprintf("\t%v %v `json:\"%v\"`\n", memberName, subType, j) } typ += "}" } else { typ = "json.RawMessage" } case "number": typ = "int" case "integer": typ = "int" case "boolean": typ = "bool" // json type string maps to go type string, so only need to test case of when // string is a json date-time, so we can convert to go type Time... case "string": if f := jsonSubSchema.Format; f != nil { if *f == "date-time" { typ = "Time" } } } switch typ { case "json.RawMessage": extraPackages["encoding/json"] = true if withComments { // Special case: we have here a top level RawMessage such as // queue.PostArtifactRequest - therefore need to implement // Marhsal and Unmarshal methods. See: // http://play.golang.org/p/FKHSUmWVFD vs // http://play.golang.org/p/erjM6ptIYI extraPackages["errors"] = true rawMessageTypes[jsonSubSchema.TypeName] = true } } content += typ if withComments { content += "\n" } return content, extraPackages, rawMessageTypes }
func (exchange *Exchange) generateAPICode(exchangeName string) string { comment := "" if exchange.Description != "" { comment = utils.Indent(exchange.Description, "// ") } if len(comment) >= 1 && comment[len(comment)-1:] != "\n" { comment += "\n" } comment += "//\n" comment += fmt.Sprintf("// See: %v\n", exchange.apiDef.DocRoot) comment += "//\n" comment += "// How to use this package\n" comment += "//\n" comment += "// This package is designed to sit on top of http://godoc.org/github.com/taskcluster/pulse-go/pulse. Please read\n" comment += "// the pulse package overview to get an understanding of how the pulse client is implemented in go.\n" comment += "//\n" comment += "// This package provides two things in addition to the basic pulse package: structured types for unmarshaling\n" comment += "// pulse message bodies into, and custom Binding interfaces, for defining the fixed strings for task cluster\n" comment += "// exchange names, and routing keys as structured types.\n" comment += "//\n" comment += "// For example, when specifying a binding, rather than using:\n" comment += "// \n" comment += "// pulse.Bind(\n" comment += "// \t\"*.*.*.*.*.*.gaia.#\",\n" comment += "// \t\"exchange/taskcluster-queue/v1/task-defined\")\n" comment += "// \n" comment += "// You can rather use:\n" comment += "// \n" comment += "// queueevents.TaskDefined{WorkerType: \"gaia\"}\n" comment += "// \n" comment += "// In addition, this means that you will also get objects in your callback method like *queueevents.TaskDefinedMessage\n" comment += "// rather than just interface{}.\n" content := comment content += "package " + exchange.apiDef.PackageName + "\n" content += ` import ( "reflect" "strings" "time" %%{imports} ) ` entryTypeNames := make(map[string]bool, len(exchange.Entries)) for _, entry := range exchange.Entries { content += entry.generateAPICode(utils.Normalise(entry.Name, entryTypeNames)) } content += ` func generateRoutingKey(x interface{}) string { val := reflect.ValueOf(x).Elem() p := make([]string, 0, val.NumField()) for i := 0; i < val.NumField(); i++ { valueField := val.Field(i) typeField := val.Type().Field(i) tag := typeField.Tag if t := tag.Get("mwords"); t != "" { if v := valueField.Interface(); v == "" { p = append(p, t) } else { p = append(p, v.(string)) } } } return strings.Join(p, ".") } ` return content }