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