github.com/AngusLu/go-swagger@v0.28.0/codescan/schema.go (about) 1 package codescan 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "go/ast" 7 "go/types" 8 "log" 9 "os" 10 "reflect" 11 "strconv" 12 "strings" 13 14 "golang.org/x/tools/go/ast/astutil" 15 16 "github.com/go-openapi/spec" 17 "github.com/pkg/errors" 18 ) 19 20 func addExtension(ve *spec.VendorExtensible, key string, value interface{}) { 21 if os.Getenv("SWAGGER_GENERATE_EXTENSION") == "false" { 22 return 23 } 24 25 ve.AddExtension(key, value) 26 } 27 28 type schemaTypable struct { 29 schema *spec.Schema 30 level int 31 } 32 33 func (st schemaTypable) Typed(tpe, format string) { 34 st.schema.Typed(tpe, format) 35 } 36 37 func (st schemaTypable) SetRef(ref spec.Ref) { 38 st.schema.Ref = ref 39 } 40 41 func (st schemaTypable) Schema() *spec.Schema { 42 return st.schema 43 } 44 45 func (st schemaTypable) Items() swaggerTypable { 46 if st.schema.Items == nil { 47 st.schema.Items = new(spec.SchemaOrArray) 48 } 49 if st.schema.Items.Schema == nil { 50 st.schema.Items.Schema = new(spec.Schema) 51 } 52 53 st.schema.Typed("array", "") 54 return schemaTypable{st.schema.Items.Schema, st.level + 1} 55 } 56 57 func (st schemaTypable) AdditionalProperties() swaggerTypable { 58 if st.schema.AdditionalProperties == nil { 59 st.schema.AdditionalProperties = new(spec.SchemaOrBool) 60 } 61 if st.schema.AdditionalProperties.Schema == nil { 62 st.schema.AdditionalProperties.Schema = new(spec.Schema) 63 } 64 65 st.schema.Typed("object", "") 66 return schemaTypable{st.schema.AdditionalProperties.Schema, st.level + 1} 67 } 68 69 func (st schemaTypable) Level() int { return st.level } 70 71 func (st schemaTypable) AddExtension(key string, value interface{}) { 72 addExtension(&st.schema.VendorExtensible, key, value) 73 } 74 75 func (st schemaTypable) WithEnum(values ...interface{}) { 76 st.schema.WithEnum(values...) 77 } 78 79 func (st schemaTypable) WithEnumDescription(desc string) { 80 if desc == "" { 81 return 82 } 83 st.AddExtension(extEnumDesc, desc) 84 } 85 86 type schemaValidations struct { 87 current *spec.Schema 88 } 89 90 func (sv schemaValidations) SetMaximum(val float64, exclusive bool) { 91 sv.current.Maximum = &val 92 sv.current.ExclusiveMaximum = exclusive 93 } 94 func (sv schemaValidations) SetMinimum(val float64, exclusive bool) { 95 sv.current.Minimum = &val 96 sv.current.ExclusiveMinimum = exclusive 97 } 98 func (sv schemaValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val } 99 func (sv schemaValidations) SetMinItems(val int64) { sv.current.MinItems = &val } 100 func (sv schemaValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val } 101 func (sv schemaValidations) SetMinLength(val int64) { sv.current.MinLength = &val } 102 func (sv schemaValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val } 103 func (sv schemaValidations) SetPattern(val string) { sv.current.Pattern = val } 104 func (sv schemaValidations) SetUnique(val bool) { sv.current.UniqueItems = val } 105 func (sv schemaValidations) SetDefault(val interface{}) { sv.current.Default = val } 106 func (sv schemaValidations) SetExample(val interface{}) { sv.current.Example = val } 107 func (sv schemaValidations) SetEnum(val string) { 108 sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Format: sv.current.Format, Type: sv.current.Type[0]}) 109 } 110 111 type schemaBuilder struct { 112 ctx *scanCtx 113 decl *entityDecl 114 GoName string 115 Name string 116 annotated bool 117 discovered []*entityDecl 118 postDecls []*entityDecl 119 } 120 121 func (s *schemaBuilder) inferNames() (goName string, name string) { 122 if s.GoName != "" { 123 goName, name = s.GoName, s.Name 124 return 125 } 126 127 goName = s.decl.Ident.Name 128 name = goName 129 defer func() { 130 s.GoName = goName 131 s.Name = name 132 }() 133 if s.decl.Comments == nil { 134 return 135 } 136 137 DECLS: 138 for _, cmt := range s.decl.Comments.List { 139 for _, ln := range strings.Split(cmt.Text, "\n") { 140 matches := rxModelOverride.FindStringSubmatch(ln) 141 if len(matches) > 0 { 142 s.annotated = true 143 } 144 if len(matches) > 1 && len(matches[1]) > 0 { 145 name = matches[1] 146 break DECLS 147 } 148 } 149 } 150 return 151 } 152 153 func (s *schemaBuilder) Build(definitions map[string]spec.Schema) error { 154 s.inferNames() 155 156 schema := definitions[s.Name] 157 err := s.buildFromDecl(s.decl, &schema) 158 if err != nil { 159 return err 160 } 161 definitions[s.Name] = schema 162 return nil 163 } 164 165 func (s *schemaBuilder) buildFromDecl(decl *entityDecl, schema *spec.Schema) error { 166 // analyze doc comment for the model 167 sp := new(sectionedParser) 168 sp.setTitle = func(lines []string) { schema.Title = joinDropLast(lines) } 169 sp.setDescription = func(lines []string) { 170 schema.Description = joinDropLast(lines) 171 enumDesc := getEnumDesc(schema.VendorExtensible.Extensions) 172 if enumDesc != "" { 173 schema.Description += "\n" + enumDesc 174 } 175 } 176 if err := sp.Parse(s.decl.Comments); err != nil { 177 return err 178 } 179 180 // if the type is marked to ignore, just return 181 if sp.ignored { 182 return nil 183 } 184 185 switch tpe := s.decl.Type.Obj().Type().(type) { 186 case *types.Basic: 187 debugLog("basic: %v", tpe.Name()) 188 case *types.Struct: 189 if err := s.buildFromStruct(s.decl, tpe, schema, make(map[string]string)); err != nil { 190 return err 191 } 192 case *types.Interface: 193 if err := s.buildFromInterface(s.decl, tpe, schema, make(map[string]string)); err != nil { 194 return err 195 } 196 case *types.Array: 197 debugLog("array: %v -> %v", s.decl.Ident.Name, tpe.Elem().String()) 198 case *types.Slice: 199 debugLog("slice: %v -> %v", s.decl.Ident.Name, tpe.Elem().String()) 200 case *types.Map: 201 debugLog("map: %v -> [%v]%v", s.decl.Ident.Name, tpe.Key().String(), tpe.Elem().String()) 202 case *types.Named: 203 o := tpe.Obj() 204 if o != nil { 205 debugLog("got the named type object: %s.%s | isAlias: %t | exported: %t", o.Pkg().Path(), o.Name(), o.IsAlias(), o.Exported()) 206 if o.Pkg().Name() == "time" && o.Name() == "Time" { 207 schema.Typed("string", "date-time") 208 return nil 209 } 210 211 ps := schemaTypable{schema, 0} 212 for { 213 ti := s.decl.Pkg.TypesInfo.Types[s.decl.Spec.Type] 214 if ti.IsBuiltin() { 215 break 216 } 217 if ti.IsType() { 218 if err := s.buildFromType(ti.Type, ps); err != nil { 219 return err 220 } 221 break 222 } 223 } 224 } 225 default: 226 log.Printf("WARNING: Missing parser for a %T, skipping model: %s\n", tpe, s.Name) 227 return nil 228 } 229 230 if schema.Ref.String() == "" { 231 if s.Name != s.GoName { 232 addExtension(&schema.VendorExtensible, "x-go-name", s.GoName) 233 } 234 addExtension(&schema.VendorExtensible, "x-go-package", s.decl.Type.Obj().Pkg().Path()) 235 } 236 return nil 237 } 238 239 func (s *schemaBuilder) buildFromType(tpe types.Type, tgt swaggerTypable) error { 240 switch titpe := tpe.(type) { 241 case *types.Basic: 242 return swaggerSchemaForType(titpe.String(), tgt) 243 case *types.Pointer: 244 return s.buildFromType(titpe.Elem(), tgt) 245 case *types.Struct: 246 return s.buildFromStruct(s.decl, titpe, tgt.Schema(), make(map[string]string)) 247 case *types.Interface: 248 return s.buildFromInterface(s.decl, titpe, tgt.Schema(), make(map[string]string)) 249 case *types.Slice: 250 return s.buildFromType(titpe.Elem(), tgt.Items()) 251 case *types.Array: 252 return s.buildFromType(titpe.Elem(), tgt.Items()) 253 case *types.Map: 254 // debugLog("map: %v -> [%v]%v", fld.Name(), ftpe.Key().String(), ftpe.Elem().String()) 255 // check if key is a string type, if not print a message 256 // and skip the map property. Only maps with string keys can go into additional properties 257 sch := tgt.Schema() 258 if sch == nil { 259 return errors.New("items doesn't support maps") 260 } 261 eleProp := schemaTypable{sch, tgt.Level()} 262 key := titpe.Key() 263 if key.Underlying().String() == "string" { 264 if err := s.buildFromType(titpe.Elem(), eleProp.AdditionalProperties()); err != nil { 265 return err 266 } 267 return nil 268 } 269 case *types.Named: 270 tio := titpe.Obj() 271 if tio.Pkg() == nil && tio.Name() == "error" { 272 return swaggerSchemaForType(tio.Name(), tgt) 273 } 274 debugLog("named refined type %s.%s", tio.Pkg().Path(), tio.Name()) 275 pkg, found := s.ctx.PkgForType(tpe) 276 if !found { 277 // this must be a builtin 278 debugLog("skipping because package is nil: %s", tpe.String()) 279 return nil 280 } 281 if pkg.Name == "time" && tio.Name() == "Time" { 282 tgt.Typed("string", "date-time") 283 return nil 284 } 285 if pkg.PkgPath == "encoding/json" && tio.Name() == "RawMessage" { 286 tgt.Typed("object", "") 287 return nil 288 } 289 cmt, hasComments := s.ctx.FindComments(pkg, tio.Name()) 290 if !hasComments { 291 cmt = new(ast.CommentGroup) 292 } 293 294 switch utitpe := tpe.Underlying().(type) { 295 case *types.Struct: 296 if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { 297 if decl.Type.Obj().Pkg().Path() == "time" && decl.Type.Obj().Name() == "Time" { 298 tgt.Typed("string", "date-time") 299 return nil 300 } 301 if sfnm, isf := strfmtName(cmt); isf { 302 tgt.Typed("string", sfnm) 303 return nil 304 } 305 if err := s.makeRef(decl, tgt); err != nil { 306 return err 307 } 308 return nil 309 } 310 case *types.Interface: 311 if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { 312 if err := s.makeRef(decl, tgt); err != nil { 313 return err 314 } 315 return nil 316 } 317 case *types.Basic: 318 if sfnm, isf := strfmtName(cmt); isf { 319 tgt.Typed("string", sfnm) 320 return nil 321 } 322 323 if enumName, ok := enumName(cmt); ok { 324 enumValues, enumDesces, _ := s.ctx.FindEnumValues(pkg, enumName) 325 if len(enumValues) > 0 { 326 tgt.WithEnum(enumValues...) 327 enumTypeName := reflect.TypeOf(enumValues[0]).String() 328 _ = swaggerSchemaForType(enumTypeName, tgt) 329 } 330 if len(enumDesces) > 0 { 331 tgt.WithEnumDescription(strings.Join(enumDesces, "\n")) 332 } 333 return nil 334 } 335 336 if defaultName, ok := defaultName(cmt); ok { 337 debugLog(defaultName) 338 return nil 339 } 340 341 if typeName, ok := typeName(cmt); ok { 342 _ = swaggerSchemaForType(typeName, tgt) 343 return nil 344 } 345 346 if isAliasParam(tgt) || aliasParam(cmt) { 347 err := swaggerSchemaForType(utitpe.Name(), tgt) 348 if err == nil { 349 return nil 350 } 351 } 352 if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { 353 if err := s.makeRef(decl, tgt); err != nil { 354 return err 355 } 356 return nil 357 } 358 return swaggerSchemaForType(utitpe.String(), tgt) 359 case *types.Array: 360 if sfnm, isf := strfmtName(cmt); isf { 361 if sfnm == "byte" { 362 tgt.Typed("string", sfnm) 363 return nil 364 } 365 tgt.Items().Typed("string", sfnm) 366 return nil 367 } 368 if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { 369 if err := s.makeRef(decl, tgt); err != nil { 370 return err 371 } 372 return nil 373 } 374 return s.buildFromType(utitpe.Elem(), tgt.Items()) 375 case *types.Slice: 376 if sfnm, isf := strfmtName(cmt); isf { 377 if sfnm == "byte" { 378 tgt.Typed("string", sfnm) 379 return nil 380 } 381 tgt.Items().Typed("string", sfnm) 382 return nil 383 } 384 if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { 385 if err := s.makeRef(decl, tgt); err != nil { 386 return err 387 } 388 return nil 389 } 390 return s.buildFromType(utitpe.Elem(), tgt.Items()) 391 case *types.Map: 392 if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { 393 if err := s.makeRef(decl, tgt); err != nil { 394 return err 395 } 396 return nil 397 } 398 return nil 399 400 default: 401 log.Printf("WARNING: can't figure out object type for named type (%T): %v [alias: %t]", tpe.Underlying(), tpe.Underlying(), titpe.Obj().IsAlias()) 402 403 return nil 404 } 405 default: 406 panic(fmt.Sprintf("WARNING: can't determine refined type %s (%T)", titpe.String(), titpe)) 407 } 408 409 return nil 410 } 411 412 func (s *schemaBuilder) buildFromInterface(decl *entityDecl, it *types.Interface, schema *spec.Schema, seen map[string]string) error { 413 if it.Empty() { 414 schema.Typed("object", "") 415 return nil 416 } 417 418 var ( 419 tgt *spec.Schema 420 hasAllOf bool 421 ) 422 423 flist := make([]*ast.Field, it.NumEmbeddeds()+it.NumExplicitMethods()) 424 for i := range decl.Spec.Type.(*ast.InterfaceType).Methods.List { 425 flist[i] = decl.Spec.Type.(*ast.InterfaceType).Methods.List[i] 426 } 427 428 // First collect the embedded interfaces 429 // create refs when the embedded interface is decorated with an allOf annotation 430 for i := 0; i < it.NumEmbeddeds(); i++ { 431 fld := it.EmbeddedType(i) 432 433 switch ftpe := fld.(type) { 434 case *types.Named: 435 o := ftpe.Obj() 436 var afld *ast.Field 437 for _, an := range flist { 438 if len(an.Names) != 0 { 439 continue 440 } 441 442 tpp := decl.Pkg.TypesInfo.Types[an.Type] 443 if tpp.Type.String() != o.Type().String() { 444 continue 445 } 446 447 // decl. 448 debugLog("maybe interface field %s: %s(%T)", o.Name(), o.Type().String(), o.Type()) 449 afld = an 450 break 451 } 452 453 if afld == nil { 454 debugLog("can't find source associated with %s for %s", fld.String(), it.String()) 455 continue 456 } 457 458 // if the field is annotated with swagger:ignore, ignore it 459 if ignored(afld.Doc) { 460 continue 461 } 462 463 if !allOfMember(afld.Doc) { 464 var newSch spec.Schema 465 if err := s.buildEmbedded(o.Type(), &newSch, seen); err != nil { 466 return err 467 } 468 schema.AllOf = append(schema.AllOf, newSch) 469 hasAllOf = true 470 continue 471 } 472 473 hasAllOf = true 474 if tgt == nil { 475 tgt = &spec.Schema{} 476 } 477 var newSch spec.Schema 478 // when the embedded struct is annotated with swagger:allOf it will be used as allOf property 479 // otherwise the fields will just be included as normal properties 480 if err := s.buildAllOf(o.Type(), &newSch); err != nil { 481 return err 482 } 483 if afld.Doc != nil { 484 for _, cmt := range afld.Doc.List { 485 for _, ln := range strings.Split(cmt.Text, "\n") { 486 matches := rxAllOf.FindStringSubmatch(ln) 487 ml := len(matches) 488 if ml > 1 { 489 mv := matches[ml-1] 490 if mv != "" { 491 schema.AddExtension("x-class", mv) 492 } 493 } 494 } 495 } 496 } 497 498 schema.AllOf = append(schema.AllOf, newSch) 499 default: 500 log.Printf("WARNING: can't figure out object type for allOf named type (%T): %v", ftpe, ftpe.Underlying()) 501 } 502 debugLog("got embedded interface: %s {%T}", fld.String(), fld) 503 } 504 505 if tgt == nil { 506 tgt = schema 507 } 508 // We can finally build the actual schema for the struct 509 if tgt.Properties == nil { 510 tgt.Properties = make(map[string]spec.Schema) 511 } 512 tgt.Typed("object", "") 513 514 for i := 0; i < it.NumExplicitMethods(); i++ { 515 fld := it.ExplicitMethod(i) 516 if !fld.Exported() { 517 continue 518 } 519 sig, isSignature := fld.Type().(*types.Signature) 520 if !isSignature { 521 continue 522 } 523 if sig.Params().Len() > 0 { 524 continue 525 } 526 if sig.Results() == nil || sig.Results().Len() != 1 { 527 continue 528 } 529 530 var afld *ast.Field 531 ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos()) 532 // debugLog("got %d nodes (exact: %t)", len(ans), isExact) 533 for _, an := range ans { 534 at, valid := an.(*ast.Field) 535 if !valid { 536 continue 537 } 538 539 debugLog("maybe interface field %s: %s(%T)", fld.Name(), fld.Type().String(), fld.Type()) 540 afld = at 541 break 542 } 543 544 if afld == nil { 545 debugLog("can't find source associated with %s for %s", fld.String(), it.String()) 546 continue 547 } 548 549 // if the field is annotated with swagger:ignore, ignore it 550 if ignored(afld.Doc) { 551 continue 552 } 553 554 name := fld.Name() 555 if afld.Doc != nil { 556 for _, cmt := range afld.Doc.List { 557 for _, ln := range strings.Split(cmt.Text, "\n") { 558 matches := rxName.FindStringSubmatch(ln) 559 ml := len(matches) 560 if ml > 1 { 561 name = matches[ml-1] 562 } 563 } 564 } 565 } 566 ps := tgt.Properties[name] 567 if err := s.buildFromType(sig.Results().At(0).Type(), schemaTypable{&ps, 0}); err != nil { 568 return err 569 } 570 if sfName, isStrfmt := strfmtName(afld.Doc); isStrfmt { 571 ps.Typed("string", sfName) 572 ps.Ref = spec.Ref{} 573 ps.Items = nil 574 } 575 576 if err := s.createParser(name, tgt, &ps, afld).Parse(afld.Doc); err != nil { 577 return err 578 } 579 580 if ps.Ref.String() == "" && name != fld.Name() { 581 ps.AddExtension("x-go-name", fld.Name()) 582 } 583 584 seen[name] = fld.Name() 585 tgt.Properties[name] = ps 586 } 587 588 if tgt == nil { 589 return nil 590 } 591 if hasAllOf && len(tgt.Properties) > 0 { 592 schema.AllOf = append(schema.AllOf, *tgt) 593 } 594 for k := range tgt.Properties { 595 if _, ok := seen[k]; !ok { 596 delete(tgt.Properties, k) 597 } 598 } 599 return nil 600 } 601 602 func (s *schemaBuilder) buildFromStruct(decl *entityDecl, st *types.Struct, schema *spec.Schema, seen map[string]string) error { 603 // First check for all of schemas 604 var tgt *spec.Schema 605 hasAllOf := false 606 607 for i := 0; i < st.NumFields(); i++ { 608 fld := st.Field(i) 609 if !fld.Anonymous() { 610 debugLog("skipping field %q for allOf scan because not anonymous", fld.Name()) 611 continue 612 } 613 tg := st.Tag(i) 614 615 debugLog("maybe allof field(%t) %s: %s (%T) [%q](anon: %t, embedded: %t)", fld.IsField(), fld.Name(), fld.Type().String(), fld.Type(), tg, fld.Anonymous(), fld.Embedded()) 616 var afld *ast.Field 617 ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos()) 618 // debugLog("got %d nodes (exact: %t)", len(ans), isExact) 619 for _, an := range ans { 620 at, valid := an.(*ast.Field) 621 if !valid { 622 continue 623 } 624 625 debugLog("maybe allof field %s: %s(%T) [%q]", fld.Name(), fld.Type().String(), fld.Type(), tg) 626 afld = at 627 break 628 } 629 630 if afld == nil { 631 debugLog("can't find source associated with %s for %s", fld.String(), st.String()) 632 continue 633 } 634 635 // if the field is annotated with swagger:ignore, ignore it 636 if ignored(afld.Doc) { 637 continue 638 } 639 640 _, ignore, _, err := parseJSONTag(afld) 641 if err != nil { 642 return err 643 } 644 if ignore { 645 continue 646 } 647 648 if !allOfMember(afld.Doc) { 649 if tgt == nil { 650 tgt = schema 651 } 652 if err := s.buildEmbedded(fld.Type(), tgt, seen); err != nil { 653 return err 654 } 655 continue 656 } 657 // if this created an allOf property then we have to rejig the schema var 658 // because all the fields collected that aren't from embedded structs should go in 659 // their own proper schema 660 // first process embedded structs in order of embedding 661 hasAllOf = true 662 if tgt == nil { 663 tgt = &spec.Schema{} 664 } 665 var newSch spec.Schema 666 // when the embedded struct is annotated with swagger:allOf it will be used as allOf property 667 // otherwise the fields will just be included as normal properties 668 if err := s.buildAllOf(fld.Type(), &newSch); err != nil { 669 return err 670 } 671 672 if afld.Doc != nil { 673 for _, cmt := range afld.Doc.List { 674 for _, ln := range strings.Split(cmt.Text, "\n") { 675 matches := rxAllOf.FindStringSubmatch(ln) 676 ml := len(matches) 677 if ml > 1 { 678 mv := matches[ml-1] 679 if mv != "" { 680 schema.AddExtension("x-class", mv) 681 } 682 } 683 } 684 } 685 } 686 687 schema.AllOf = append(schema.AllOf, newSch) 688 } 689 690 if tgt == nil { 691 tgt = schema 692 } 693 // We can finally build the actual schema for the struct 694 if tgt.Properties == nil { 695 tgt.Properties = make(map[string]spec.Schema) 696 } 697 tgt.Typed("object", "") 698 699 for i := 0; i < st.NumFields(); i++ { 700 fld := st.Field(i) 701 tg := st.Tag(i) 702 703 if fld.Embedded() { 704 continue 705 } 706 707 if !fld.Exported() { 708 debugLog("skipping field %s because it's not exported", fld.Name()) 709 continue 710 } 711 712 var afld *ast.Field 713 ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos()) 714 // debugLog("got %d nodes (exact: %t)", len(ans), isExact) 715 for _, an := range ans { 716 at, valid := an.(*ast.Field) 717 if !valid { 718 continue 719 } 720 721 debugLog("field %s: %s(%T) [%q] ==> %s", fld.Name(), fld.Type().String(), fld.Type(), tg, at.Doc.Text()) 722 afld = at 723 break 724 } 725 726 if afld == nil { 727 debugLog("can't find source associated with %s", fld.String()) 728 continue 729 } 730 731 // if the field is annotated with swagger:ignore, ignore it 732 if ignored(afld.Doc) { 733 continue 734 } 735 736 name, ignore, isString, err := parseJSONTag(afld) 737 if err != nil { 738 return err 739 } 740 if ignore { 741 for seenTagName, seenFieldName := range seen { 742 if seenFieldName == fld.Name() { 743 delete(tgt.Properties, seenTagName) 744 break 745 } 746 } 747 continue 748 } 749 750 ps := tgt.Properties[name] 751 if err = s.buildFromType(fld.Type(), schemaTypable{&ps, 0}); err != nil { 752 return err 753 } 754 if isString { 755 ps.Typed("string", ps.Format) 756 ps.Ref = spec.Ref{} 757 ps.Items = nil 758 } 759 if sfName, isStrfmt := strfmtName(afld.Doc); isStrfmt { 760 ps.Typed("string", sfName) 761 ps.Ref = spec.Ref{} 762 ps.Items = nil 763 } 764 765 if err = s.createParser(name, tgt, &ps, afld).Parse(afld.Doc); err != nil { 766 return err 767 } 768 769 if ps.Ref.String() == "" && name != fld.Name() { 770 addExtension(&ps.VendorExtensible, "x-go-name", fld.Name()) 771 } 772 773 // we have 2 cases: 774 // 1. field with different name override tag 775 // 2. field with different name removes tag 776 // so we need to save both tag&name 777 seen[name] = fld.Name() 778 tgt.Properties[name] = ps 779 } 780 781 if tgt == nil { 782 return nil 783 } 784 if hasAllOf && len(tgt.Properties) > 0 { 785 schema.AllOf = append(schema.AllOf, *tgt) 786 } 787 for k := range tgt.Properties { 788 if _, ok := seen[k]; !ok { 789 delete(tgt.Properties, k) 790 } 791 } 792 return nil 793 } 794 795 func (s *schemaBuilder) buildAllOf(tpe types.Type, schema *spec.Schema) error { 796 debugLog("allOf %s", tpe.Underlying()) 797 switch ftpe := tpe.(type) { 798 case *types.Pointer: 799 return s.buildAllOf(ftpe.Elem(), schema) 800 case *types.Named: 801 switch utpe := ftpe.Underlying().(type) { 802 case *types.Struct: 803 decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name()) 804 if found { 805 if ftpe.Obj().Pkg().Path() == "time" && ftpe.Obj().Name() == "Time" { 806 schema.Typed("string", "date-time") 807 return nil 808 } 809 if sfnm, isf := strfmtName(decl.Comments); isf { 810 schema.Typed("string", sfnm) 811 return nil 812 } 813 if decl.HasModelAnnotation() { 814 if err := s.makeRef(decl, schemaTypable{schema, 0}); err != nil { 815 return err 816 } 817 return nil 818 } 819 return s.buildFromStruct(decl, utpe, schema, make(map[string]string)) 820 } 821 return errors.Errorf("can't find source file for struct: %s", ftpe.String()) 822 case *types.Interface: 823 decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name()) 824 if found { 825 if sfnm, isf := strfmtName(decl.Comments); isf { 826 schema.Typed("string", sfnm) 827 return nil 828 } 829 if decl.HasModelAnnotation() { 830 if err := s.makeRef(decl, schemaTypable{schema, 0}); err != nil { 831 return err 832 } 833 return nil 834 } 835 return s.buildFromInterface(decl, utpe, schema, make(map[string]string)) 836 } 837 return errors.Errorf("can't find source file for interface: %s", ftpe.String()) 838 default: 839 log.Printf("WARNING: can't figure out object type for allOf named type (%T): %v", ftpe, ftpe.Underlying()) 840 return fmt.Errorf("unable to locate source file for allOf %s", utpe.String()) 841 } 842 default: 843 log.Printf("WARNING: Missing allOf parser for a %T, skipping field", ftpe) 844 return fmt.Errorf("unable to resolve allOf member for: %v", ftpe) 845 } 846 } 847 848 func (s *schemaBuilder) buildEmbedded(tpe types.Type, schema *spec.Schema, seen map[string]string) error { 849 debugLog("embedded %s", tpe.Underlying()) 850 switch ftpe := tpe.(type) { 851 case *types.Pointer: 852 return s.buildEmbedded(ftpe.Elem(), schema, seen) 853 case *types.Named: 854 debugLog("embedded named type: %T", ftpe.Underlying()) 855 switch utpe := ftpe.Underlying().(type) { 856 case *types.Struct: 857 decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name()) 858 if found { 859 return s.buildFromStruct(decl, utpe, schema, seen) 860 } 861 return errors.Errorf("can't find source file for struct: %s", ftpe.String()) 862 case *types.Interface: 863 decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name()) 864 if found { 865 return s.buildFromInterface(decl, utpe, schema, seen) 866 } 867 return errors.Errorf("can't find source file for struct: %s", ftpe.String()) 868 default: 869 log.Printf("WARNING: can't figure out object type for embedded named type (%T): %v", ftpe, ftpe.Underlying()) 870 } 871 default: 872 log.Printf("WARNING: Missing embedded parser for a %T, skipping model\n", ftpe) 873 return nil 874 } 875 return nil 876 } 877 878 func (s *schemaBuilder) makeRef(decl *entityDecl, prop swaggerTypable) error { 879 nm, _ := decl.Names() 880 ref, err := spec.NewRef("#/definitions/" + nm) 881 if err != nil { 882 return err 883 } 884 prop.SetRef(ref) 885 s.postDecls = append(s.postDecls, decl) 886 return nil 887 } 888 889 func (s *schemaBuilder) createParser(nm string, schema, ps *spec.Schema, fld *ast.Field) *sectionedParser { 890 sp := new(sectionedParser) 891 892 schemeType, err := ps.Type.MarshalJSON() 893 if err != nil { 894 return nil 895 } 896 897 if ps.Ref.String() == "" { 898 sp.setDescription = func(lines []string) { 899 ps.Description = joinDropLast(lines) 900 enumDesc := getEnumDesc(ps.VendorExtensible.Extensions) 901 if enumDesc != "" { 902 ps.Description += "\n" + enumDesc 903 } 904 } 905 sp.taggers = []tagParser{ 906 newSingleLineTagParser("maximum", &setMaximum{schemaValidations{ps}, rxf(rxMaximumFmt, "")}), 907 newSingleLineTagParser("minimum", &setMinimum{schemaValidations{ps}, rxf(rxMinimumFmt, "")}), 908 newSingleLineTagParser("multipleOf", &setMultipleOf{schemaValidations{ps}, rxf(rxMultipleOfFmt, "")}), 909 newSingleLineTagParser("minLength", &setMinLength{schemaValidations{ps}, rxf(rxMinLengthFmt, "")}), 910 newSingleLineTagParser("maxLength", &setMaxLength{schemaValidations{ps}, rxf(rxMaxLengthFmt, "")}), 911 newSingleLineTagParser("pattern", &setPattern{schemaValidations{ps}, rxf(rxPatternFmt, "")}), 912 newSingleLineTagParser("minItems", &setMinItems{schemaValidations{ps}, rxf(rxMinItemsFmt, "")}), 913 newSingleLineTagParser("maxItems", &setMaxItems{schemaValidations{ps}, rxf(rxMaxItemsFmt, "")}), 914 newSingleLineTagParser("unique", &setUnique{schemaValidations{ps}, rxf(rxUniqueFmt, "")}), 915 newSingleLineTagParser("enum", &setEnum{schemaValidations{ps}, rxf(rxEnumFmt, "")}), 916 newSingleLineTagParser("default", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}), 917 newSingleLineTagParser("type", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}), 918 newSingleLineTagParser("example", &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxExampleFmt, "")}), 919 newSingleLineTagParser("required", &setRequiredSchema{schema, nm}), 920 newSingleLineTagParser("readOnly", &setReadOnlySchema{ps}), 921 newSingleLineTagParser("discriminator", &setDiscriminator{schema, nm}), 922 newMultiLineTagParser("YAMLExtensionsBlock", newYamlParser(rxExtensions, schemaVendorExtensibleSetter(ps)), true), 923 } 924 925 itemsTaggers := func(items *spec.Schema, level int) []tagParser { 926 schemeType, err := items.Type.MarshalJSON() 927 if err != nil { 928 return nil 929 } 930 // the expression is 1-index based not 0-index 931 itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1) 932 return []tagParser{ 933 newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{schemaValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}), 934 newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{schemaValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}), 935 newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{schemaValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}), 936 newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{schemaValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}), 937 newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{schemaValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}), 938 newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{schemaValidations{items}, rxf(rxPatternFmt, itemsPrefix)}), 939 newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{schemaValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}), 940 newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{schemaValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}), 941 newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{schemaValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}), 942 newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{schemaValidations{items}, rxf(rxEnumFmt, itemsPrefix)}), 943 newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}), 944 newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxExampleFmt, itemsPrefix)}), 945 } 946 } 947 948 var parseArrayTypes func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error) 949 parseArrayTypes = func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error) { 950 if items == nil || items.Schema == nil { 951 return []tagParser{}, nil 952 } 953 switch iftpe := expr.(type) { 954 case *ast.ArrayType: 955 eleTaggers := itemsTaggers(items.Schema, level) 956 sp.taggers = append(eleTaggers, sp.taggers...) 957 otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Schema.Items, level+1) 958 if err != nil { 959 return nil, err 960 } 961 return otherTaggers, nil 962 case *ast.Ident: 963 taggers := []tagParser{} 964 if iftpe.Obj == nil { 965 taggers = itemsTaggers(items.Schema, level) 966 } 967 otherTaggers, err := parseArrayTypes(expr, items.Schema.Items, level+1) 968 if err != nil { 969 return nil, err 970 } 971 return append(taggers, otherTaggers...), nil 972 case *ast.StarExpr: 973 otherTaggers, err := parseArrayTypes(iftpe.X, items, level) 974 if err != nil { 975 return nil, err 976 } 977 return otherTaggers, nil 978 default: 979 return nil, fmt.Errorf("unknown field type ele for %q", nm) 980 } 981 } 982 // check if this is a primitive, if so parse the validations from the 983 // doc comments of the slice declaration. 984 if ftped, ok := fld.Type.(*ast.ArrayType); ok { 985 taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0) 986 if err != nil { 987 return sp 988 } 989 sp.taggers = append(taggers, sp.taggers...) 990 } 991 992 } else { 993 sp.taggers = []tagParser{ 994 newSingleLineTagParser("required", &setRequiredSchema{schema, nm}), 995 } 996 } 997 return sp 998 } 999 1000 func schemaVendorExtensibleSetter(meta *spec.Schema) func(json.RawMessage) error { 1001 return func(jsonValue json.RawMessage) error { 1002 var jsonData spec.Extensions 1003 err := json.Unmarshal(jsonValue, &jsonData) 1004 if err != nil { 1005 return err 1006 } 1007 for k := range jsonData { 1008 if !rxAllowedExtensions.MatchString(k) { 1009 return fmt.Errorf("invalid schema extension name, should start from `x-`: %s", k) 1010 } 1011 } 1012 meta.Extensions = jsonData 1013 return nil 1014 } 1015 } 1016 1017 type tagOptions []string 1018 1019 func (t tagOptions) Contain(option string) bool { 1020 for i := 1; i < len(t); i++ { 1021 if t[i] == option { 1022 return true 1023 } 1024 } 1025 return false 1026 } 1027 1028 func (t tagOptions) Name() string { 1029 return t[0] 1030 } 1031 1032 func parseJSONTag(field *ast.Field) (name string, ignore bool, isString bool, err error) { 1033 if len(field.Names) > 0 { 1034 name = field.Names[0].Name 1035 } 1036 if field.Tag == nil || len(strings.TrimSpace(field.Tag.Value)) == 0 { 1037 return name, false, false, nil 1038 } 1039 1040 tv, err := strconv.Unquote(field.Tag.Value) 1041 if err != nil { 1042 return name, false, false, err 1043 } 1044 1045 if strings.TrimSpace(tv) != "" { 1046 st := reflect.StructTag(tv) 1047 jsonParts := tagOptions(strings.Split(st.Get("json"), ",")) 1048 1049 if jsonParts.Contain("string") { 1050 // Need to check if the field type is a scalar. Otherwise, the 1051 // ",string" directive doesn't apply. 1052 isString = isFieldStringable(field.Type) 1053 } 1054 1055 switch jsonParts.Name() { 1056 case "-": 1057 return name, true, isString, nil 1058 case "": 1059 return name, false, isString, nil 1060 default: 1061 return jsonParts.Name(), false, isString, nil 1062 } 1063 } 1064 return name, false, false, nil 1065 } 1066 1067 // isFieldStringable check if the field type is a scalar. If the field type is 1068 // *ast.StarExpr and is pointer type, check if it refers to a scalar. 1069 // Otherwise, the ",string" directive doesn't apply. 1070 func isFieldStringable(tpe ast.Expr) bool { 1071 if ident, ok := tpe.(*ast.Ident); ok { 1072 switch ident.Name { 1073 case "int", "int8", "int16", "int32", "int64", 1074 "uint", "uint8", "uint16", "uint32", "uint64", 1075 "float64", "string", "bool": 1076 return true 1077 } 1078 } else if starExpr, ok := tpe.(*ast.StarExpr); ok { 1079 return isFieldStringable(starExpr.X) 1080 } else { 1081 return false 1082 } 1083 return false 1084 }