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