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
}
Example #2
0
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)
	}
}
Example #3
0
// 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
}