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 `