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 `