github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/goagen/codegen/types.go (about)

     1  package codegen
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  	"text/template"
     9  	"unicode"
    10  
    11  	"github.com/goadesign/goa/design"
    12  	"github.com/goadesign/goa/dslengine"
    13  )
    14  
    15  // TransformMapKey is the name of the metadata used to specify the key for mapping fields when
    16  // generating the code that transforms one data structure into another.
    17  const TransformMapKey = "transform:key"
    18  
    19  var (
    20  	// TempCount holds the value appended to variable names to make them unique.
    21  	TempCount int
    22  
    23  	// Templates used by GoTypeTransform
    24  	transformT       *template.Template
    25  	transformArrayT  *template.Template
    26  	transformHashT   *template.Template
    27  	transformObjectT *template.Template
    28  )
    29  
    30  // Initialize all templates
    31  func init() {
    32  	var err error
    33  	fn := template.FuncMap{
    34  		"tabs":               Tabs,
    35  		"add":                func(a, b int) int { return a + b },
    36  		"goify":              Goify,
    37  		"gotyperef":          GoTypeRef,
    38  		"gotypename":         GoTypeName,
    39  		"transformAttribute": transformAttribute,
    40  		"transformArray":     transformArray,
    41  		"transformHash":      transformHash,
    42  		"transformObject":    transformObject,
    43  		"typeName":           typeName,
    44  	}
    45  	if transformT, err = template.New("transform").Funcs(fn).Parse(transformTmpl); err != nil {
    46  		panic(err) // bug
    47  	}
    48  	if transformArrayT, err = template.New("transformArray").Funcs(fn).Parse(transformArrayTmpl); err != nil {
    49  		panic(err) // bug
    50  	}
    51  	if transformHashT, err = template.New("transformHash").Funcs(fn).Parse(transformHashTmpl); err != nil {
    52  		panic(err) // bug
    53  	}
    54  	if transformObjectT, err = template.New("transformObject").Funcs(fn).Parse(transformObjectTmpl); err != nil {
    55  		panic(err) // bug
    56  	}
    57  }
    58  
    59  // GoTypeDef returns the Go code that defines a Go type which matches the data structure
    60  // definition (the part that comes after `type foo`).
    61  // tabs is the number of tab character(s) used to tabulate the definition however the first
    62  // line is never indented.
    63  // jsonTags controls whether to produce json tags.
    64  // private controls whether the field is a pointer or not. All fields in the struct are
    65  //   pointers for a private struct.
    66  func GoTypeDef(ds design.DataStructure, tabs int, jsonTags, private bool) string {
    67  	def := ds.Definition()
    68  	t := def.Type
    69  	switch actual := t.(type) {
    70  	case design.Primitive:
    71  		return GoTypeName(t, nil, tabs, private)
    72  	case *design.Array:
    73  		d := GoTypeDef(actual.ElemType, tabs, jsonTags, private)
    74  		if actual.ElemType.Type.IsObject() {
    75  			d = "*" + d
    76  		}
    77  		return "[]" + d
    78  	case *design.Hash:
    79  		keyDef := GoTypeDef(actual.KeyType, tabs, jsonTags, private)
    80  		if actual.KeyType.Type.IsObject() {
    81  			keyDef = "*" + keyDef
    82  		}
    83  		elemDef := GoTypeDef(actual.ElemType, tabs, jsonTags, private)
    84  		if actual.ElemType.Type.IsObject() {
    85  			elemDef = "*" + elemDef
    86  		}
    87  		return fmt.Sprintf("map[%s]%s", keyDef, elemDef)
    88  	case design.Object:
    89  		return goTypeDefObject(actual, def, tabs, jsonTags, private)
    90  	case *design.UserTypeDefinition:
    91  		return GoTypeName(actual, actual.AllRequired(), tabs, private)
    92  	case *design.MediaTypeDefinition:
    93  		return GoTypeName(actual, actual.AllRequired(), tabs, private)
    94  	default:
    95  		panic("goa bug: unknown data structure type")
    96  	}
    97  }
    98  
    99  // goTypeDefObject returns the Go code that defines a Go struct.
   100  func goTypeDefObject(obj design.Object, def *design.AttributeDefinition, tabs int, jsonTags, private bool) string {
   101  	var buffer bytes.Buffer
   102  	buffer.WriteString("struct {\n")
   103  	keys := make([]string, len(obj))
   104  	i := 0
   105  	for n := range obj {
   106  		keys[i] = n
   107  		i++
   108  	}
   109  	sort.Strings(keys)
   110  	for _, name := range keys {
   111  		WriteTabs(&buffer, tabs+1)
   112  		field := obj[name]
   113  		typedef := ""
   114  		if tname, ok := field.Metadata["struct:field:type"]; ok {
   115  			if len(tname) > 0 {
   116  				typedef = tname[0]
   117  			}
   118  		}
   119  		if typedef == "" {
   120  			typedef = GoTypeDef(field, tabs+1, jsonTags, private)
   121  			if (field.Type.IsPrimitive() && private) || field.Type.IsObject() || def.IsPrimitivePointer(name) {
   122  				typedef = "*" + typedef
   123  			}
   124  		}
   125  		fname := GoifyAtt(field, name, true)
   126  		var tags string
   127  		if jsonTags {
   128  			tags = attributeTags(def, field, name, private)
   129  		}
   130  		desc := obj[name].Description
   131  		if desc != "" {
   132  			desc = strings.Replace(desc, "\n", "\n\t// ", -1)
   133  			desc = fmt.Sprintf("// %s\n\t", desc)
   134  		}
   135  		buffer.WriteString(fmt.Sprintf("%s%s %s%s\n", desc, fname, typedef, tags))
   136  	}
   137  	WriteTabs(&buffer, tabs)
   138  	buffer.WriteString("}")
   139  	return buffer.String()
   140  }
   141  
   142  // attributeTags computes the struct field tags.
   143  func attributeTags(parent, att *design.AttributeDefinition, name string, private bool) string {
   144  	var elems []string
   145  	keys := make([]string, len(att.Metadata))
   146  	i := 0
   147  	for k := range att.Metadata {
   148  		keys[i] = k
   149  		i++
   150  	}
   151  	sort.Strings(keys)
   152  	for _, key := range keys {
   153  		val := att.Metadata[key]
   154  		if strings.HasPrefix(key, "struct:tag:") {
   155  			name := key[11:]
   156  			value := strings.Join(val, ",")
   157  			elems = append(elems, fmt.Sprintf("%s:\"%s\"", name, value))
   158  		}
   159  	}
   160  	if len(elems) > 0 {
   161  		return " `" + strings.Join(elems, " ") + "`"
   162  	}
   163  	// Default algorithm
   164  	var omit string
   165  	if private || (!parent.IsRequired(name) && !parent.HasDefaultValue(name)) {
   166  		omit = ",omitempty"
   167  	}
   168  	return fmt.Sprintf(" `form:\"%s%s\" json:\"%s%s\" xml:\"%s%s\"`", name, omit, name, omit, name, omit)
   169  }
   170  
   171  // GoTypeRef returns the Go code that refers to the Go type which matches the given data type
   172  // (the part that comes after `var foo`)
   173  // required only applies when referring to a user type that is an object defined inline. In this
   174  // case the type (Object) does not carry the required field information defined in the parent
   175  // (anonymous) attribute.
   176  // tabs is used to properly tabulate the object struct fields and only applies to this case.
   177  // This function assumes the type is in the same package as the code accessing it.
   178  func GoTypeRef(t design.DataType, required []string, tabs int, private bool) string {
   179  	tname := GoTypeName(t, required, tabs, private)
   180  	if mt, ok := t.(*design.MediaTypeDefinition); ok {
   181  		if mt.IsError() {
   182  			return "error"
   183  		}
   184  	}
   185  	if t.IsObject() {
   186  		return "*" + tname
   187  	}
   188  	return tname
   189  }
   190  
   191  // GoTypeName returns the Go type name for a data type.
   192  // tabs is used to properly tabulate the object struct fields and only applies to this case.
   193  // This function assumes the type is in the same package as the code accessing it.
   194  // required only applies when referring to a user type that is an object defined inline. In this
   195  // case the type (Object) does not carry the required field information defined in the parent
   196  // (anonymous) attribute.
   197  func GoTypeName(t design.DataType, required []string, tabs int, private bool) string {
   198  	switch actual := t.(type) {
   199  	case design.Primitive:
   200  		return GoNativeType(t)
   201  	case *design.Array:
   202  		return "[]" + GoTypeRef(actual.ElemType.Type, actual.ElemType.AllRequired(), tabs+1, private)
   203  	case design.Object:
   204  		att := &design.AttributeDefinition{Type: actual}
   205  		if len(required) > 0 {
   206  			requiredVal := &dslengine.ValidationDefinition{Required: required}
   207  			att.Validation.Merge(requiredVal)
   208  		}
   209  		return GoTypeDef(att, tabs, false, private)
   210  	case *design.Hash:
   211  		return fmt.Sprintf(
   212  			"map[%s]%s",
   213  			GoTypeRef(actual.KeyType.Type, actual.KeyType.AllRequired(), tabs+1, private),
   214  			GoTypeRef(actual.ElemType.Type, actual.ElemType.AllRequired(), tabs+1, private),
   215  		)
   216  	case *design.UserTypeDefinition:
   217  		return Goify(actual.TypeName, !private)
   218  	case *design.MediaTypeDefinition:
   219  		if actual.IsError() {
   220  			return "error"
   221  		}
   222  		return Goify(actual.TypeName, !private)
   223  	default:
   224  		panic(fmt.Sprintf("goa bug: unknown type %#v", actual))
   225  	}
   226  }
   227  
   228  // GoNativeType returns the Go built-in type from which instances of t can be initialized.
   229  func GoNativeType(t design.DataType) string {
   230  	switch actual := t.(type) {
   231  	case design.Primitive:
   232  		switch actual.Kind() {
   233  		case design.BooleanKind:
   234  			return "bool"
   235  		case design.IntegerKind:
   236  			return "int"
   237  		case design.NumberKind:
   238  			return "float64"
   239  		case design.StringKind:
   240  			return "string"
   241  		case design.DateTimeKind:
   242  			return "time.Time"
   243  		case design.UUIDKind:
   244  			return "uuid.UUID"
   245  		case design.AnyKind:
   246  			return "interface{}"
   247  		default:
   248  			panic(fmt.Sprintf("goa bug: unknown primitive type %#v", actual))
   249  		}
   250  	case *design.Array:
   251  		return "[]" + GoNativeType(actual.ElemType.Type)
   252  	case design.Object:
   253  		return "map[string]interface{}"
   254  	case *design.Hash:
   255  		return fmt.Sprintf("map[%s]%s", GoNativeType(actual.KeyType.Type), GoNativeType(actual.ElemType.Type))
   256  	case *design.MediaTypeDefinition:
   257  		return GoNativeType(actual.Type)
   258  	case *design.UserTypeDefinition:
   259  		return GoNativeType(actual.Type)
   260  	default:
   261  		panic(fmt.Sprintf("goa bug: unknown type %#v", actual))
   262  	}
   263  }
   264  
   265  // GoTypeDesc returns the description of a type.  If no description is defined
   266  // for the type, one will be generated.
   267  func GoTypeDesc(t design.DataType, upper bool) string {
   268  	switch actual := t.(type) {
   269  	case *design.UserTypeDefinition:
   270  		if actual.Description != "" {
   271  			return strings.Replace(actual.Description, "\n", "\n// ", -1)
   272  		}
   273  
   274  		return Goify(actual.TypeName, upper) + " user type."
   275  	case *design.MediaTypeDefinition:
   276  		if actual.Description != "" {
   277  			return strings.Replace(actual.Description, "\n", "\n// ", -1)
   278  		}
   279  		name := Goify(actual.TypeName, upper)
   280  		if actual.View != "default" {
   281  			name += Goify(actual.View, true)
   282  		}
   283  
   284  		switch elem := actual.UserTypeDefinition.AttributeDefinition.Type.(type) {
   285  		case *design.Array:
   286  			elemName := GoTypeName(elem.ElemType.Type, nil, 0, !upper)
   287  			if actual.View != "default" {
   288  				elemName += Goify(actual.View, true)
   289  			}
   290  			return fmt.Sprintf("%s media type is a collection of %s.", name, elemName)
   291  		default:
   292  			return name + " media type."
   293  		}
   294  	default:
   295  		return ""
   296  	}
   297  }
   298  
   299  var commonInitialisms = map[string]bool{
   300  	"API":   true,
   301  	"ASCII": true,
   302  	"CPU":   true,
   303  	"CSS":   true,
   304  	"DNS":   true,
   305  	"EOF":   true,
   306  	"GUID":  true,
   307  	"HTML":  true,
   308  	"HTTP":  true,
   309  	"HTTPS": true,
   310  	"ID":    true,
   311  	"IP":    true,
   312  	"JMES":  true,
   313  	"JSON":  true,
   314  	"JWT":   true,
   315  	"LHS":   true,
   316  	"OK":    true,
   317  	"QPS":   true,
   318  	"RAM":   true,
   319  	"RHS":   true,
   320  	"RPC":   true,
   321  	"SLA":   true,
   322  	"SMTP":  true,
   323  	"SQL":   true,
   324  	"SSH":   true,
   325  	"TCP":   true,
   326  	"TLS":   true,
   327  	"TTL":   true,
   328  	"UDP":   true,
   329  	"UI":    true,
   330  	"UID":   true,
   331  	"UUID":  true,
   332  	"URI":   true,
   333  	"URL":   true,
   334  	"UTF8":  true,
   335  	"VM":    true,
   336  	"XML":   true,
   337  	"XSRF":  true,
   338  	"XSS":   true,
   339  }
   340  
   341  // removeTrailingInvalid removes trailing invalid identifiers from runes.
   342  func removeTrailingInvalid(runes []rune) []rune {
   343  	valid := len(runes) - 1
   344  	for ; valid >= 0 && !validIdentifier(runes[valid]); valid-- {
   345  	}
   346  
   347  	return runes[0 : valid+1]
   348  }
   349  
   350  // removeInvalidAtIndex removes consecutive invalid identifiers from runes starting at index i.
   351  func removeInvalidAtIndex(i int, runes []rune) []rune {
   352  	valid := i
   353  	for ; valid < len(runes) && !validIdentifier(runes[valid]); valid++ {
   354  	}
   355  
   356  	return append(runes[:i], runes[valid:]...)
   357  }
   358  
   359  // GoifyAtt honors any struct:field:name metadata set on the attribute and calls Goify with the tag
   360  // value if present or the given name otherwise.
   361  func GoifyAtt(att *design.AttributeDefinition, name string, firstUpper bool) string {
   362  	if tname, ok := att.Metadata["struct:field:name"]; ok {
   363  		if len(tname) > 0 {
   364  			name = tname[0]
   365  		}
   366  	}
   367  	return Goify(name, firstUpper)
   368  }
   369  
   370  // Goify makes a valid Go identifier out of any string.
   371  // It does that by removing any non letter and non digit character and by making sure the first
   372  // character is a letter or "_".
   373  // Goify produces a "CamelCase" version of the string, if firstUpper is true the first character
   374  // of the identifier is uppercase otherwise it's lowercase.
   375  func Goify(str string, firstUpper bool) string {
   376  	runes := []rune(str)
   377  
   378  	// remove trailing invalid identifiers (makes code below simpler)
   379  	runes = removeTrailingInvalid(runes)
   380  
   381  	w, i := 0, 0 // index of start of word, scan
   382  	for i+1 <= len(runes) {
   383  		eow := false // whether we hit the end of a word
   384  
   385  		// remove leading invalid identifiers
   386  		runes = removeInvalidAtIndex(i, runes)
   387  
   388  		if i+1 == len(runes) {
   389  			eow = true
   390  		} else if !validIdentifier(runes[i]) {
   391  			// get rid of it
   392  			runes = append(runes[:i], runes[i+1:]...)
   393  		} else if runes[i+1] == '_' {
   394  			// underscore; shift the remainder forward over any run of underscores
   395  			eow = true
   396  			n := 1
   397  			for i+n+1 < len(runes) && runes[i+n+1] == '_' {
   398  				n++
   399  			}
   400  			copy(runes[i+1:], runes[i+n+1:])
   401  			runes = runes[:len(runes)-n]
   402  		} else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) {
   403  			// lower->non-lower
   404  			eow = true
   405  		}
   406  		i++
   407  		if !eow {
   408  			continue
   409  		}
   410  
   411  		// [w,i] is a word.
   412  		word := string(runes[w:i])
   413  		// is it one of our initialisms?
   414  		if u := strings.ToUpper(word); commonInitialisms[u] {
   415  			if firstUpper {
   416  				u = strings.ToUpper(u)
   417  			} else if w == 0 {
   418  				u = strings.ToLower(u)
   419  			}
   420  
   421  			// All the common initialisms are ASCII,
   422  			// so we can replace the bytes exactly.
   423  			copy(runes[w:], []rune(u))
   424  		} else if w > 0 && strings.ToLower(word) == word {
   425  			// already all lowercase, and not the first word, so uppercase the first character.
   426  			runes[w] = unicode.ToUpper(runes[w])
   427  		} else if w == 0 && strings.ToLower(word) == word && firstUpper {
   428  			runes[w] = unicode.ToUpper(runes[w])
   429  		}
   430  		if w == 0 && !firstUpper {
   431  			runes[w] = unicode.ToLower(runes[w])
   432  		}
   433  		//advance to next word
   434  		w = i
   435  	}
   436  
   437  	return fixReserved(string(runes))
   438  }
   439  
   440  // Reserved golang keywords and package names
   441  var Reserved = map[string]bool{
   442  	"byte":       true,
   443  	"complex128": true,
   444  	"complex64":  true,
   445  	"float32":    true,
   446  	"float64":    true,
   447  	"int":        true,
   448  	"int16":      true,
   449  	"int32":      true,
   450  	"int64":      true,
   451  	"int8":       true,
   452  	"rune":       true,
   453  	"string":     true,
   454  	"uint16":     true,
   455  	"uint32":     true,
   456  	"uint64":     true,
   457  	"uint8":      true,
   458  
   459  	"break":       true,
   460  	"case":        true,
   461  	"chan":        true,
   462  	"const":       true,
   463  	"continue":    true,
   464  	"default":     true,
   465  	"defer":       true,
   466  	"else":        true,
   467  	"fallthrough": true,
   468  	"for":         true,
   469  	"func":        true,
   470  	"go":          true,
   471  	"goto":        true,
   472  	"if":          true,
   473  	"import":      true,
   474  	"interface":   true,
   475  	"map":         true,
   476  	"package":     true,
   477  	"range":       true,
   478  	"return":      true,
   479  	"select":      true,
   480  	"struct":      true,
   481  	"switch":      true,
   482  	"type":        true,
   483  	"var":         true,
   484  
   485  	// stdlib and goa packages used by generated code
   486  	"fmt":  true,
   487  	"http": true,
   488  	"json": true,
   489  	"os":   true,
   490  	"url":  true,
   491  	"time": true,
   492  }
   493  
   494  // validIdentifier returns true if the rune is a letter or number
   495  func validIdentifier(r rune) bool {
   496  	return unicode.IsLetter(r) || unicode.IsDigit(r)
   497  }
   498  
   499  // fixReserved appends an underscore on to Go reserved keywords.
   500  func fixReserved(w string) string {
   501  	if Reserved[w] {
   502  		w += "_"
   503  	}
   504  	return w
   505  }
   506  
   507  // GoTypeTransform produces Go code that initializes the data structure defined by target from an
   508  // instance of the data structure described by source. The algorithm matches object fields by name
   509  // or using the value of the "transform:key" attribute metadata when present.
   510  // The function returns an error if target is not compatible with source (different type, fields of
   511  // different type etc). It ignores fields in target that don't have a match in source.
   512  func GoTypeTransform(source, target *design.UserTypeDefinition, targetPkg, funcName string) (string, error) {
   513  	var impl string
   514  	var err error
   515  	switch {
   516  	case source.IsObject():
   517  		if !target.IsObject() {
   518  			return "", fmt.Errorf("source is an object but target type is %s", target.Type.Name())
   519  		}
   520  		impl, err = transformObject(source.ToObject(), target.ToObject(), targetPkg, target.TypeName, "source", "target", 1)
   521  	case source.IsArray():
   522  		if !target.IsArray() {
   523  			return "", fmt.Errorf("source is an array but target type is %s", target.Type.Name())
   524  		}
   525  		impl, err = transformArray(source.ToArray(), target.ToArray(), targetPkg, "source", "target", 1)
   526  	case source.IsHash():
   527  		if !target.IsHash() {
   528  			return "", fmt.Errorf("source is a hash but target type is %s", target.Type.Name())
   529  		}
   530  		impl, err = transformHash(source.ToHash(), target.ToHash(), targetPkg, "source", "target", 1)
   531  	default:
   532  		panic("cannot transform primitive types") // bug
   533  	}
   534  
   535  	if err != nil {
   536  		return "", err
   537  	}
   538  	t := GoTypeRef(target, nil, 0, false)
   539  	if strings.HasPrefix(t, "*") && len(targetPkg) > 0 {
   540  		t = fmt.Sprintf("*%s.%s", targetPkg, t[1:])
   541  	}
   542  	data := map[string]interface{}{
   543  		"Name":      funcName,
   544  		"Source":    source,
   545  		"Target":    target,
   546  		"TargetRef": t,
   547  		"TargetPkg": targetPkg,
   548  		"Impl":      impl,
   549  	}
   550  	return RunTemplate(transformT, data), nil
   551  }
   552  
   553  // GoTypeTransformName generates a valid Go identifer that is adequate for naming the type
   554  // transform function that creates an instance of the data structure described by target from an
   555  // instance of the data strucuture described by source.
   556  func GoTypeTransformName(source, target *design.UserTypeDefinition, suffix string) string {
   557  	return fmt.Sprintf("%sTo%s%s", Goify(source.TypeName, true), Goify(target.TypeName, true), Goify(suffix, true))
   558  }
   559  
   560  // WriteTabs is a helper function that writes count tabulation characters to buf.
   561  func WriteTabs(buf *bytes.Buffer, count int) {
   562  	for i := 0; i < count; i++ {
   563  		buf.WriteByte('\t')
   564  	}
   565  }
   566  
   567  // Tempvar generates a unique variable name.
   568  func Tempvar() string {
   569  	TempCount++
   570  	return fmt.Sprintf("tmp%d", TempCount)
   571  }
   572  
   573  // RunTemplate executs the given template with the given input and returns
   574  // the rendered string.
   575  func RunTemplate(tmpl *template.Template, data interface{}) string {
   576  	var b bytes.Buffer
   577  	err := tmpl.Execute(&b, data)
   578  	if err != nil {
   579  		panic(err) // should never happen, bug if it does.
   580  	}
   581  	return b.String()
   582  }
   583  
   584  func transformAttribute(source, target *design.AttributeDefinition, targetPkg, sctx, tctx string, depth int) (string, error) {
   585  	if source.Type.Kind() != target.Type.Kind() {
   586  		return "", fmt.Errorf("incompatible attribute types: %s is of type %s but %s is of type %s",
   587  			sctx, source.Type.Name(), tctx, target.Type.Name())
   588  	}
   589  	switch {
   590  	case source.Type.IsArray():
   591  		return transformArray(source.Type.ToArray(), target.Type.ToArray(), targetPkg, sctx, tctx, depth)
   592  	case source.Type.IsHash():
   593  		return transformHash(source.Type.ToHash(), target.Type.ToHash(), targetPkg, sctx, tctx, depth)
   594  	case source.Type.IsObject():
   595  		return transformObject(source.Type.ToObject(), target.Type.ToObject(), targetPkg, typeName(target), sctx, tctx, depth)
   596  	default:
   597  		return fmt.Sprintf("%s%s = %s\n", Tabs(depth), tctx, sctx), nil
   598  	}
   599  }
   600  
   601  func transformObject(source, target design.Object, targetPkg, targetType, sctx, tctx string, depth int) (string, error) {
   602  	attributeMap, err := computeMapping(source, target, sctx, tctx)
   603  	if err != nil {
   604  		return "", err
   605  	}
   606  
   607  	// First validate that all attributes are compatible - doing that in a template doesn't make
   608  	// sense.
   609  	for s, t := range attributeMap {
   610  		sourceAtt := source[s]
   611  		targetAtt := target[t]
   612  		if sourceAtt.Type.Kind() != targetAtt.Type.Kind() {
   613  			return "", fmt.Errorf("incompatible attribute types: %s.%s is of type %s but %s.%s is of type %s",
   614  				sctx, source.Name(), sourceAtt.Type.Name(), tctx, target.Name(), targetAtt.Type.Name())
   615  		}
   616  	}
   617  
   618  	// We're good - generate
   619  	data := map[string]interface{}{
   620  		"AttributeMap": attributeMap,
   621  		"Source":       source,
   622  		"Target":       target,
   623  		"TargetPkg":    targetPkg,
   624  		"TargetType":   targetType,
   625  		"SourceCtx":    sctx,
   626  		"TargetCtx":    tctx,
   627  		"Depth":        depth,
   628  	}
   629  	return RunTemplate(transformObjectT, data), nil
   630  }
   631  
   632  func transformArray(source, target *design.Array, targetPkg, sctx, tctx string, depth int) (string, error) {
   633  	if source.ElemType.Type.Kind() != target.ElemType.Type.Kind() {
   634  		return "", fmt.Errorf("incompatible attribute types: %s is an array with elements of type %s but %s is an array with elements of type %s",
   635  			sctx, source.ElemType.Type.Name(), tctx, target.ElemType.Type.Name())
   636  	}
   637  	data := map[string]interface{}{
   638  		"Source":    source,
   639  		"Target":    target,
   640  		"TargetPkg": targetPkg,
   641  		"SourceCtx": sctx,
   642  		"TargetCtx": tctx,
   643  		"Depth":     depth,
   644  	}
   645  	return RunTemplate(transformArrayT, data), nil
   646  }
   647  
   648  func transformHash(source, target *design.Hash, targetPkg, sctx, tctx string, depth int) (string, error) {
   649  	if source.ElemType.Type.Kind() != target.ElemType.Type.Kind() {
   650  		return "", fmt.Errorf("incompatible attribute types: %s is a hash with elements of type %s but %s is a hash with elements of type %s",
   651  			sctx, source.ElemType.Type.Name(), tctx, target.ElemType.Type.Name())
   652  	}
   653  	if source.KeyType.Type.Kind() != target.KeyType.Type.Kind() {
   654  		return "", fmt.Errorf("incompatible attribute types: %s is a hash with keys of type %s but %s is a hash with keys of type %s",
   655  			sctx, source.KeyType.Type.Name(), tctx, target.KeyType.Type.Name())
   656  	}
   657  	data := map[string]interface{}{
   658  		"Source":    source,
   659  		"Target":    target,
   660  		"TargetPkg": targetPkg,
   661  		"SourceCtx": sctx,
   662  		"TargetCtx": tctx,
   663  		"Depth":     depth,
   664  	}
   665  	return RunTemplate(transformHashT, data), nil
   666  }
   667  
   668  // computeMapping returns a map that indexes the target type definition object attributes with the
   669  // corresponding source type definition object attributes. An attribute is associated with another
   670  // attribute if their map key match. The map key of an attribute is the value of the TransformMapKey
   671  // metadata if present, the attribute name otherwise.
   672  // The function returns an error if the TransformMapKey metadata is malformed (has no value).
   673  func computeMapping(source, target design.Object, sctx, tctx string) (map[string]string, error) {
   674  	attributeMap := make(map[string]string)
   675  	sourceMap := make(map[string]string)
   676  	targetMap := make(map[string]string)
   677  	for name, att := range source {
   678  		key := name
   679  		if keys, ok := att.Metadata[TransformMapKey]; ok {
   680  			if len(keys) == 0 {
   681  				return nil, fmt.Errorf("invalid metadata transform key: missing value on attribte %s of %s", name, sctx)
   682  			}
   683  			key = keys[0]
   684  		}
   685  		sourceMap[key] = name
   686  	}
   687  	for name, att := range target {
   688  		key := name
   689  		if keys, ok := att.Metadata[TransformMapKey]; ok {
   690  			if len(keys) == 0 {
   691  				return nil, fmt.Errorf("invalid metadata transform key: missing value on attribute %s of %s", name, tctx)
   692  			}
   693  			key = keys[0]
   694  		}
   695  		targetMap[key] = name
   696  	}
   697  	for key, attName := range sourceMap {
   698  		if targetAtt, ok := targetMap[key]; ok {
   699  			attributeMap[attName] = targetAtt
   700  		}
   701  	}
   702  	return attributeMap, nil
   703  }
   704  
   705  // toSlice returns Go code that represents the given slice.
   706  func toSlice(val []interface{}) string {
   707  	elems := make([]string, len(val))
   708  	for i, v := range val {
   709  		elems[i] = fmt.Sprintf("%#v", v)
   710  	}
   711  	return fmt.Sprintf("[]interface{}{%s}", strings.Join(elems, ", "))
   712  }
   713  
   714  // typeName returns the type name of the given attribute if it is a named type, empty string otherwise.
   715  func typeName(att *design.AttributeDefinition) (name string) {
   716  	if ut, ok := att.Type.(*design.UserTypeDefinition); ok {
   717  		name = Goify(ut.TypeName, true)
   718  	} else if mt, ok := att.Type.(*design.MediaTypeDefinition); ok {
   719  		name = Goify(mt.TypeName, true)
   720  	}
   721  	return
   722  }
   723  
   724  const transformTmpl = `func {{ .Name }}(source {{ gotyperef .Source nil 0 false }}) (target {{ .TargetRef }}) {
   725  {{ .Impl }}	return
   726  }
   727  `
   728  
   729  const transformObjectTmpl = `{{ tabs .Depth }}{{ .TargetCtx }} = new({{ if .TargetPkg }}{{ .TargetPkg }}.{{ end }}{{ if .TargetType }}{{ .TargetType }}{{ else }}{{ gotyperef .Target.Type .Target.AllRequired 1 false }}{{ end }})
   730  {{ range $source, $target := .AttributeMap }}{{/*
   731  */}}{{ $sourceAtt := index $.Source $source }}{{ $targetAtt := index $.Target $target }}{{/*
   732  */}}{{ $source := goify $source true }}{{ $target := goify $target true }}{{/*
   733  */}}{{     if $sourceAtt.Type.IsArray }}{{ transformArray  $sourceAtt.Type.ToArray  $targetAtt.Type.ToArray  $.TargetPkg (printf "%s.%s" $.SourceCtx $source) (printf "%s.%s" $.TargetCtx $target) $.Depth }}{{/*
   734  */}}{{ else if $sourceAtt.Type.IsHash }}{{  transformHash   $sourceAtt.Type.ToHash   $targetAtt.Type.ToHash   $.TargetPkg (printf "%s.%s" $.SourceCtx $source) (printf "%s.%s" $.TargetCtx $target) $.Depth }}{{/*
   735  */}}{{ else if $sourceAtt.Type.IsObject }}{{ transformObject $sourceAtt.Type.ToObject $targetAtt.Type.ToObject $.TargetPkg (typeName $targetAtt) (printf "%s.%s" $.SourceCtx $source) (printf "%s.%s" $.TargetCtx $target) $.Depth }}{{/*
   736  */}}{{ else }}{{ tabs $.Depth }}{{ $.TargetCtx }}.{{ $target }} = {{ $.SourceCtx }}.{{ $source }}
   737  {{ end }}{{ end }}`
   738  
   739  const transformArrayTmpl = `{{ tabs .Depth }}{{ .TargetCtx}} = make([]{{ gotyperef .Target.ElemType.Type nil 0 false }}, len({{ .SourceCtx }}))
   740  {{ tabs .Depth }}for i, v := range {{ .SourceCtx }} {
   741  {{ transformAttribute .Source.ElemType .Target.ElemType .TargetPkg (printf "%s[i]" .SourceCtx) (printf "%s[i]" .TargetCtx) (add .Depth 1) }}{{/*
   742  */}}{{ tabs .Depth }}}
   743  `
   744  
   745  const transformHashTmpl = `{{ tabs .Depth }}{{ .TargetCtx }} = make(map[{{ gotyperef .Target.KeyType.Type nil 0 false }}]{{ gotyperef .Target.ElemType.Type nil 0 false }}, len({{ .SourceCtx }}))
   746  {{ tabs .Depth }}for k, v := range {{ .SourceCtx }} {
   747  {{ tabs .Depth }}	var tk {{ gotyperef .Target.KeyType.Type nil 0 false }}
   748  {{ transformAttribute .Source.KeyType .Target.KeyType .TargetPkg "k" "tk" (add .Depth 1) }}{{/*
   749  */}}{{ tabs .Depth }}	var tv {{ gotyperef .Target.ElemType.Type nil 0 false }}
   750  {{ transformAttribute .Source.ElemType .Target.ElemType .TargetPkg "v" "tv" (add .Depth 1) }}{{/*
   751  */}}{{ tabs .Depth }}	{{ .TargetCtx }}[tk] = tv
   752  {{ tabs .Depth }}}
   753  `