github.com/kaisawind/go-swagger@v0.19.0/scan/schema.go (about) 1 // Copyright 2015 go-swagger maintainers 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package scan 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "go/ast" 21 "log" 22 "os" 23 "path/filepath" 24 "reflect" 25 "runtime" 26 "strconv" 27 "strings" 28 29 "golang.org/x/tools/go/loader" 30 31 "github.com/go-openapi/spec" 32 ) 33 34 func addExtension(ve *spec.VendorExtensible, key string, value interface{}) { 35 if os.Getenv("SWAGGER_GENERATE_EXTENSION") == "false" { 36 return 37 } 38 39 ve.AddExtension(key, value) 40 } 41 42 type schemaTypable struct { 43 schema *spec.Schema 44 level int 45 } 46 47 func (st schemaTypable) Typed(tpe, format string) { 48 st.schema.Typed(tpe, format) 49 } 50 51 func (st schemaTypable) SetRef(ref spec.Ref) { 52 st.schema.Ref = ref 53 } 54 55 func (st schemaTypable) Schema() *spec.Schema { 56 return st.schema 57 } 58 59 func (st schemaTypable) Items() swaggerTypable { 60 if st.schema.Items == nil { 61 st.schema.Items = new(spec.SchemaOrArray) 62 } 63 if st.schema.Items.Schema == nil { 64 st.schema.Items.Schema = new(spec.Schema) 65 } 66 67 st.schema.Typed("array", "") 68 return schemaTypable{st.schema.Items.Schema, st.level + 1} 69 } 70 71 func (st schemaTypable) AdditionalProperties() swaggerTypable { 72 if st.schema.AdditionalProperties == nil { 73 st.schema.AdditionalProperties = new(spec.SchemaOrBool) 74 } 75 if st.schema.AdditionalProperties.Schema == nil { 76 st.schema.AdditionalProperties.Schema = new(spec.Schema) 77 } 78 79 st.schema.Typed("object", "") 80 return schemaTypable{st.schema.AdditionalProperties.Schema, st.level + 1} 81 } 82 func (st schemaTypable) Level() int { return st.level } 83 84 type schemaValidations struct { 85 current *spec.Schema 86 } 87 88 func (sv schemaValidations) SetMaximum(val float64, exclusive bool) { 89 sv.current.Maximum = &val 90 sv.current.ExclusiveMaximum = exclusive 91 } 92 func (sv schemaValidations) SetMinimum(val float64, exclusive bool) { 93 sv.current.Minimum = &val 94 sv.current.ExclusiveMinimum = exclusive 95 } 96 func (sv schemaValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val } 97 func (sv schemaValidations) SetMinItems(val int64) { sv.current.MinItems = &val } 98 func (sv schemaValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val } 99 func (sv schemaValidations) SetMinLength(val int64) { sv.current.MinLength = &val } 100 func (sv schemaValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val } 101 func (sv schemaValidations) SetPattern(val string) { sv.current.Pattern = val } 102 func (sv schemaValidations) SetUnique(val bool) { sv.current.UniqueItems = val } 103 func (sv schemaValidations) SetDefault(val interface{}) { sv.current.Default = val } 104 func (sv schemaValidations) SetExample(val interface{}) { sv.current.Example = val } 105 func (sv schemaValidations) SetEnum(val string) { 106 list := strings.Split(val, ",") 107 interfaceSlice := make([]interface{}, len(list)) 108 for i, d := range list { 109 interfaceSlice[i] = d 110 } 111 sv.current.Enum = interfaceSlice 112 } 113 114 type schemaDecl struct { 115 File *ast.File 116 Decl *ast.GenDecl 117 TypeSpec *ast.TypeSpec 118 GoName string 119 Name string 120 annotated bool 121 } 122 123 func newSchemaDecl(file *ast.File, decl *ast.GenDecl, ts *ast.TypeSpec) *schemaDecl { 124 sd := &schemaDecl{ 125 File: file, 126 Decl: decl, 127 TypeSpec: ts, 128 } 129 sd.inferNames() 130 return sd 131 } 132 133 func (sd *schemaDecl) hasAnnotation() bool { 134 sd.inferNames() 135 return sd.annotated 136 } 137 138 func (sd *schemaDecl) inferNames() (goName string, name string) { 139 if sd.GoName != "" { 140 goName, name = sd.GoName, sd.Name 141 return 142 } 143 goName = sd.TypeSpec.Name.Name 144 name = goName 145 if sd.Decl.Doc != nil { 146 DECLS: 147 for _, cmt := range sd.Decl.Doc.List { 148 for _, ln := range strings.Split(cmt.Text, "\n") { 149 matches := rxModelOverride.FindStringSubmatch(ln) 150 if len(matches) > 0 { 151 sd.annotated = true 152 } 153 if len(matches) > 1 && len(matches[1]) > 0 { 154 name = matches[1] 155 break DECLS 156 } 157 } 158 } 159 } 160 sd.GoName = goName 161 sd.Name = name 162 return 163 } 164 165 type schemaParser struct { 166 program *loader.Program 167 postDecls []schemaDecl 168 known map[string]spec.Schema 169 discovered *schemaDecl 170 } 171 172 func newSchemaParser(prog *loader.Program) *schemaParser { 173 scp := new(schemaParser) 174 scp.program = prog 175 scp.known = make(map[string]spec.Schema) 176 return scp 177 } 178 179 func (scp *schemaParser) Parse(gofile *ast.File, target interface{}) error { 180 tgt := target.(map[string]spec.Schema) 181 for _, decl := range gofile.Decls { 182 gd, ok := decl.(*ast.GenDecl) 183 if !ok { 184 continue 185 } 186 for _, spc := range gd.Specs { 187 if ts, ok := spc.(*ast.TypeSpec); ok { 188 sd := newSchemaDecl(gofile, gd, ts) 189 if err := scp.parseDecl(tgt, sd); err != nil { 190 return err 191 } 192 } 193 } 194 } 195 return nil 196 } 197 198 func (scp *schemaParser) parseDecl(definitions map[string]spec.Schema, decl *schemaDecl) error { 199 // check if there is a swagger:model tag that is followed by a word, 200 // this word is the type name for swagger 201 // the package and type are recorded in the extensions 202 // once type name is found convert it to a schema, by looking up the schema in the 203 // definitions dictionary that got passed into this parse method 204 205 // if our schemaParser is parsing a discovered schemaDecl and it does not match 206 // the current schemaDecl we can skip parsing. 207 if scp.discovered != nil && scp.discovered.Name != decl.Name { 208 return nil 209 } 210 211 decl.inferNames() 212 schema := definitions[decl.Name] 213 schPtr := &schema 214 215 // analyze doc comment for the model 216 sp := new(sectionedParser) 217 sp.setTitle = func(lines []string) { schema.Title = joinDropLast(lines) } 218 sp.setDescription = func(lines []string) { schema.Description = joinDropLast(lines) } 219 if err := sp.Parse(decl.Decl.Doc); err != nil { 220 return err 221 } 222 223 // if the type is marked to ignore, just return 224 if sp.ignored { 225 return nil 226 } 227 228 // analyze struct body for fields etc 229 // each exported struct field: 230 // * gets a type mapped to a go primitive 231 // * perhaps gets a format 232 // * has to document the validations that apply for the type and the field 233 // * when the struct field points to a model it becomes a ref: #/definitions/ModelName 234 // * the first line of the comment is the title 235 // * the following lines are the description 236 switch tpe := decl.TypeSpec.Type.(type) { 237 case *ast.StructType: 238 if err := scp.parseStructType(decl.File, schPtr, tpe, make(map[string]string)); err != nil { 239 return err 240 } 241 case *ast.InterfaceType: 242 if err := scp.parseInterfaceType(decl.File, schPtr, tpe, make(map[string]string)); err != nil { 243 return err 244 } 245 case *ast.Ident: 246 prop := &schemaTypable{schPtr, 0} 247 if strfmtName, ok := strfmtName(decl.Decl.Doc); ok { 248 prop.Typed("string", strfmtName) 249 } else { 250 if err := scp.parseNamedType(decl.File, tpe, prop); err != nil { 251 return err 252 } 253 } 254 case *ast.SelectorExpr: 255 prop := &schemaTypable{schPtr, 0} 256 if strfmtName, ok := strfmtName(decl.Decl.Doc); ok { 257 prop.Typed("string", strfmtName) 258 } else { 259 if err := scp.parseNamedType(decl.File, tpe, prop); err != nil { 260 return err 261 } 262 } 263 264 case *ast.ArrayType: 265 prop := &schemaTypable{schPtr, 0} 266 if strfmtName, ok := strfmtName(decl.Decl.Doc); ok { 267 prop.Items().Typed("string", strfmtName) 268 } else { 269 if err := scp.parseNamedType(decl.File, tpe, &schemaTypable{schPtr, 0}); err != nil { 270 return err 271 } 272 } 273 274 case *ast.MapType: 275 prop := &schemaTypable{schPtr, 0} 276 if strfmtName, ok := strfmtName(decl.Decl.Doc); ok { 277 prop.AdditionalProperties().Typed("string", strfmtName) 278 } else { 279 if err := scp.parseNamedType(decl.File, tpe, &schemaTypable{schPtr, 0}); err != nil { 280 return err 281 } 282 } 283 default: 284 log.Printf("WARNING: Missing parser for a %T, skipping model: %s\n", tpe, decl.Name) 285 return nil 286 } 287 288 if schPtr.Ref.String() == "" { 289 if decl.Name != decl.GoName { 290 addExtension(&schPtr.VendorExtensible, "x-go-name", decl.GoName) 291 } 292 for _, pkgInfo := range scp.program.AllPackages { 293 if pkgInfo.Importable { 294 for _, fil := range pkgInfo.Files { 295 if fil.Pos() == decl.File.Pos() { 296 addExtension(&schPtr.VendorExtensible, "x-go-package", pkgInfo.Pkg.Path()) 297 } 298 } 299 } 300 } 301 } 302 definitions[decl.Name] = schema 303 return nil 304 } 305 306 func (scp *schemaParser) parseNamedType(gofile *ast.File, expr ast.Expr, prop swaggerTypable) error { 307 switch ftpe := expr.(type) { 308 case *ast.Ident: // simple value 309 pkg, err := scp.packageForFile(gofile, ftpe) 310 if err != nil { 311 return err 312 } 313 return scp.parseIdentProperty(pkg, ftpe, prop) 314 315 case *ast.StarExpr: // pointer to something, optional by default 316 if err := scp.parseNamedType(gofile, ftpe.X, prop); err != nil { 317 return err 318 } 319 320 case *ast.ArrayType: // slice type 321 if err := scp.parseNamedType(gofile, ftpe.Elt, prop.Items()); err != nil { 322 return err 323 } 324 325 case *ast.StructType: 326 schema := prop.Schema() 327 if schema == nil { 328 return fmt.Errorf("items doesn't support embedded structs") 329 } 330 return scp.parseStructType(gofile, prop.Schema(), ftpe, make(map[string]string)) 331 332 case *ast.SelectorExpr: 333 err := scp.typeForSelector(gofile, ftpe, prop) 334 return err 335 336 case *ast.MapType: 337 // check if key is a string type, if not print a message 338 // and skip the map property. Only maps with string keys can go into additional properties 339 sch := prop.Schema() 340 if sch == nil { 341 return fmt.Errorf("items doesn't support maps") 342 } 343 if keyIdent, ok := ftpe.Key.(*ast.Ident); sch != nil && ok { 344 if keyIdent.Name == "string" { 345 if sch.AdditionalProperties == nil { 346 sch.AdditionalProperties = new(spec.SchemaOrBool) 347 } 348 sch.AdditionalProperties.Allows = false 349 if sch.AdditionalProperties.Schema == nil { 350 sch.AdditionalProperties.Schema = new(spec.Schema) 351 } 352 if err := scp.parseNamedType(gofile, ftpe.Value, schemaTypable{sch.AdditionalProperties.Schema, 0}); err != nil { 353 return err 354 } 355 sch.Typed("object", "") 356 } 357 } 358 359 case *ast.InterfaceType: 360 prop.Schema().Typed("object", "") 361 default: 362 pos := "unknown file:unknown position" 363 if scp != nil { 364 if scp.program != nil { 365 if scp.program.Fset != nil { 366 pos = scp.program.Fset.Position(expr.Pos()).String() 367 } 368 } 369 } 370 return fmt.Errorf("expr (%s) is unsupported for a schema", pos) 371 } 372 return nil 373 } 374 375 func (scp *schemaParser) parseEmbeddedType(gofile *ast.File, schema *spec.Schema, expr ast.Expr, seenPreviously map[string]string) error { 376 switch tpe := expr.(type) { 377 case *ast.Ident: 378 // do lookup of type 379 // take primitives into account, they should result in an error for swagger 380 pkg, err := scp.packageForFile(gofile, tpe) 381 if err != nil { 382 return err 383 } 384 file, _, ts, err := findSourceFile(pkg, tpe.Name) 385 if err != nil { 386 return err 387 } 388 389 switch st := ts.Type.(type) { 390 case *ast.StructType: 391 return scp.parseStructType(file, schema, st, seenPreviously) 392 case *ast.InterfaceType: 393 return scp.parseInterfaceType(file, schema, st, seenPreviously) 394 default: 395 prop := &schemaTypable{schema, 0} 396 return scp.parseNamedType(gofile, st, prop) 397 } 398 399 case *ast.SelectorExpr: 400 // look up package, file and then type 401 pkg, err := scp.packageForSelector(gofile, tpe.X) 402 if err != nil { 403 return fmt.Errorf("embedded struct: %v", err) 404 } 405 file, _, ts, err := findSourceFile(pkg, tpe.Sel.Name) 406 if err != nil { 407 return fmt.Errorf("embedded struct: %v", err) 408 } 409 if st, ok := ts.Type.(*ast.StructType); ok { 410 return scp.parseStructType(file, schema, st, seenPreviously) 411 } 412 if st, ok := ts.Type.(*ast.InterfaceType); ok { 413 return scp.parseInterfaceType(file, schema, st, seenPreviously) 414 } 415 case *ast.StarExpr: 416 return scp.parseEmbeddedType(gofile, schema, tpe.X, seenPreviously) 417 default: 418 return fmt.Errorf( 419 "parseEmbeddedType: unsupported type %v at position %#v", 420 expr, 421 scp.program.Fset.Position(tpe.Pos()), 422 ) 423 } 424 return fmt.Errorf("unable to resolve embedded struct for: %v", expr) 425 } 426 427 func (scp *schemaParser) parseAllOfMember(gofile *ast.File, schema *spec.Schema, expr ast.Expr, seenPreviously map[string]string) error { 428 // TODO: check if struct is annotated with swagger:model or known in the definitions otherwise 429 var pkg *loader.PackageInfo 430 var file *ast.File 431 var gd *ast.GenDecl 432 var ts *ast.TypeSpec 433 var err error 434 435 switch tpe := expr.(type) { 436 case *ast.Ident: 437 // do lookup of type 438 // take primitives into account, they should result in an error for swagger 439 pkg, err = scp.packageForFile(gofile, tpe) 440 if err != nil { 441 return err 442 } 443 file, gd, ts, err = findSourceFile(pkg, tpe.Name) 444 if err != nil { 445 return err 446 } 447 448 case *ast.SelectorExpr: 449 // look up package, file and then type 450 pkg, err = scp.packageForSelector(gofile, tpe.X) 451 if err != nil { 452 return fmt.Errorf("embedded struct: %v", err) 453 } 454 file, gd, ts, err = findSourceFile(pkg, tpe.Sel.Name) 455 if err != nil { 456 return fmt.Errorf("embedded struct: %v", err) 457 } 458 default: 459 return fmt.Errorf("unable to resolve allOf member for: %v", expr) 460 } 461 462 sd := newSchemaDecl(file, gd, ts) 463 if sd.hasAnnotation() && pkg.String() != "time" && ts.Name.Name != "Time" { 464 ref, err := spec.NewRef("#/definitions/" + sd.Name) 465 if err != nil { 466 return err 467 } 468 schema.Ref = ref 469 scp.postDecls = append(scp.postDecls, *sd) 470 } else { 471 switch st := ts.Type.(type) { 472 case *ast.StructType: 473 return scp.parseStructType(file, schema, st, seenPreviously) 474 case *ast.InterfaceType: 475 return scp.parseInterfaceType(file, schema, st, seenPreviously) 476 } 477 } 478 479 return nil 480 } 481 func (scp *schemaParser) parseInterfaceType(gofile *ast.File, bschema *spec.Schema, tpe *ast.InterfaceType, seenPreviously map[string]string) error { 482 if tpe.Methods == nil { 483 return nil 484 } 485 486 // first check if this has embedded interfaces, if so make sure to refer to those by ref 487 // when they are decorated with an allOf annotation 488 // go over the method list again and this time collect the nullary methods and parse the comments 489 // as if they are properties on a struct 490 var schema *spec.Schema 491 seenProperties := seenPreviously 492 hasAllOf := false 493 494 for _, fld := range tpe.Methods.List { 495 if len(fld.Names) == 0 { 496 // if this created an allOf property then we have to rejig the schema var 497 // because all the fields collected that aren't from embedded structs should go in 498 // their own proper schema 499 // first process embedded structs in order of embedding 500 if allOfMember(fld.Doc) { 501 hasAllOf = true 502 if schema == nil { 503 schema = new(spec.Schema) 504 } 505 var newSch spec.Schema 506 // when the embedded struct is annotated with swagger:allOf it will be used as allOf property 507 // otherwise the fields will just be included as normal properties 508 if err := scp.parseAllOfMember(gofile, &newSch, fld.Type, seenProperties); err != nil { 509 return err 510 } 511 512 if fld.Doc != nil { 513 for _, cmt := range fld.Doc.List { 514 for _, ln := range strings.Split(cmt.Text, "\n") { 515 matches := rxAllOf.FindStringSubmatch(ln) 516 ml := len(matches) 517 if ml > 1 { 518 mv := matches[ml-1] 519 if mv != "" { 520 addExtension(&bschema.VendorExtensible, "x-class", mv) 521 } 522 } 523 } 524 } 525 } 526 527 bschema.AllOf = append(bschema.AllOf, newSch) 528 continue 529 } 530 531 var newSch spec.Schema 532 // when the embedded struct is annotated with swagger:allOf it will be used as allOf property 533 // otherwise the fields will just be included as normal properties 534 if err := scp.parseEmbeddedType(gofile, &newSch, fld.Type, seenProperties); err != nil { 535 return err 536 } 537 bschema.AllOf = append(bschema.AllOf, newSch) 538 hasAllOf = true 539 } 540 } 541 542 if schema == nil { 543 schema = bschema 544 } 545 // then add and possibly override values 546 if schema.Properties == nil { 547 schema.Properties = make(map[string]spec.Schema) 548 } 549 schema.Typed("object", "") 550 for _, fld := range tpe.Methods.List { 551 if mtpe, ok := fld.Type.(*ast.FuncType); ok && mtpe.Params.NumFields() == 0 && mtpe.Results.NumFields() == 1 { 552 gnm := fld.Names[0].Name 553 nm := gnm 554 if fld.Doc != nil { 555 for _, cmt := range fld.Doc.List { 556 for _, ln := range strings.Split(cmt.Text, "\n") { 557 matches := rxName.FindStringSubmatch(ln) 558 ml := len(matches) 559 if ml > 1 { 560 nm = matches[ml-1] 561 } 562 } 563 } 564 } 565 566 ps := schema.Properties[nm] 567 if err := parseProperty(scp, gofile, mtpe.Results.List[0].Type, schemaTypable{&ps, 0}); err != nil { 568 return err 569 } 570 571 if err := scp.createParser(nm, schema, &ps, fld).Parse(fld.Doc); err != nil { 572 return err 573 } 574 575 if ps.Ref.String() == "" && nm != gnm { 576 addExtension(&ps.VendorExtensible, "x-go-name", gnm) 577 } 578 seenProperties[nm] = gnm 579 schema.Properties[nm] = ps 580 } 581 582 } 583 if schema != nil && hasAllOf && len(schema.Properties) > 0 { 584 bschema.AllOf = append(bschema.AllOf, *schema) 585 } 586 for k := range schema.Properties { 587 if _, ok := seenProperties[k]; !ok { 588 delete(schema.Properties, k) 589 } 590 } 591 return nil 592 } 593 594 func (scp *schemaParser) parseStructType(gofile *ast.File, bschema *spec.Schema, tpe *ast.StructType, seenPreviously map[string]string) error { 595 if tpe.Fields == nil { 596 return nil 597 } 598 var schema *spec.Schema 599 seenProperties := seenPreviously 600 hasAllOf := false 601 602 for _, fld := range tpe.Fields.List { 603 if len(fld.Names) == 0 { 604 // if the field is annotated with swagger:ignore, ignore it 605 if ignored(fld.Doc) { 606 continue 607 } 608 609 _, ignore, _, err := parseJSONTag(fld) 610 if err != nil { 611 return err 612 } 613 if ignore { 614 continue 615 } 616 617 // if this created an allOf property then we have to rejig the schema var 618 // because all the fields collected that aren't from embedded structs should go in 619 // their own proper schema 620 // first process embedded structs in order of embedding 621 if allOfMember(fld.Doc) { 622 hasAllOf = true 623 if schema == nil { 624 schema = new(spec.Schema) 625 } 626 var newSch spec.Schema 627 // when the embedded struct is annotated with swagger:allOf it will be used as allOf property 628 // otherwise the fields will just be included as normal properties 629 if err := scp.parseAllOfMember(gofile, &newSch, fld.Type, seenProperties); err != nil { 630 return err 631 } 632 633 if fld.Doc != nil { 634 for _, cmt := range fld.Doc.List { 635 for _, ln := range strings.Split(cmt.Text, "\n") { 636 matches := rxAllOf.FindStringSubmatch(ln) 637 ml := len(matches) 638 if ml > 1 { 639 mv := matches[ml-1] 640 if mv != "" { 641 addExtension(&bschema.VendorExtensible, "x-class", mv) 642 } 643 } 644 } 645 } 646 } 647 648 bschema.AllOf = append(bschema.AllOf, newSch) 649 continue 650 } 651 if schema == nil { 652 schema = bschema 653 } 654 655 // when the embedded struct is annotated with swagger:allOf it will be used as allOf property 656 // otherwise the fields will just be included as normal properties 657 if err := scp.parseEmbeddedType(gofile, schema, fld.Type, seenProperties); err != nil { 658 return err 659 } 660 } 661 } 662 if schema == nil { 663 schema = bschema 664 } 665 666 // then add and possibly override values 667 if schema.Properties == nil { 668 schema.Properties = make(map[string]spec.Schema) 669 } 670 schema.Typed("object", "") 671 for _, fld := range tpe.Fields.List { 672 if len(fld.Names) > 0 && fld.Names[0] != nil && fld.Names[0].IsExported() { 673 // if the field is annotated with swagger:ignore, ignore it 674 if ignored(fld.Doc) { 675 continue 676 } 677 678 gnm := fld.Names[0].Name 679 nm, ignore, isString, err := parseJSONTag(fld) 680 if err != nil { 681 return err 682 } 683 if ignore { 684 for seenTagName, seenFieldName := range seenPreviously { 685 if seenFieldName == gnm { 686 delete(schema.Properties, seenTagName) 687 break 688 } 689 } 690 continue 691 } 692 693 ps := schema.Properties[nm] 694 if err := parseProperty(scp, gofile, fld.Type, schemaTypable{&ps, 0}); err != nil { 695 return err 696 } 697 if isString { 698 ps.Typed("string", ps.Format) 699 ps.Ref = spec.Ref{} 700 } 701 if strfmtName, ok := strfmtName(fld.Doc); ok { 702 ps.Typed("string", strfmtName) 703 ps.Ref = spec.Ref{} 704 } 705 706 if err := scp.createParser(nm, schema, &ps, fld).Parse(fld.Doc); err != nil { 707 return err 708 } 709 710 if ps.Ref.String() == "" && nm != gnm { 711 addExtension(&ps.VendorExtensible, "x-go-name", gnm) 712 } 713 // we have 2 cases: 714 // 1. field with different name override tag 715 // 2. field with different name removes tag 716 // so we need to save both tag&name 717 seenProperties[nm] = gnm 718 schema.Properties[nm] = ps 719 } 720 } 721 if schema != nil && hasAllOf && len(schema.Properties) > 0 { 722 bschema.AllOf = append(bschema.AllOf, *schema) 723 } 724 for k := range schema.Properties { 725 if _, ok := seenProperties[k]; !ok { 726 delete(schema.Properties, k) 727 } 728 } 729 return nil 730 } 731 732 func schemaVendorExtensibleSetter(meta *spec.Schema) func(json.RawMessage) error { 733 return func(jsonValue json.RawMessage) error { 734 var jsonData spec.Extensions 735 err := json.Unmarshal(jsonValue, &jsonData) 736 if err != nil { 737 return err 738 } 739 for k := range jsonData { 740 if !rxAllowedExtensions.MatchString(k) { 741 return fmt.Errorf("invalid schema extension name, should start from `x-`: %s", k) 742 } 743 } 744 meta.Extensions = jsonData 745 return nil 746 } 747 } 748 749 func (scp *schemaParser) createParser(nm string, schema, ps *spec.Schema, fld *ast.Field) *sectionedParser { 750 sp := new(sectionedParser) 751 752 schemeType, err := ps.Type.MarshalJSON() 753 if err != nil { 754 return nil 755 } 756 757 if ps.Ref.String() == "" { 758 sp.setDescription = func(lines []string) { ps.Description = joinDropLast(lines) } 759 sp.taggers = []tagParser{ 760 newSingleLineTagParser("maximum", &setMaximum{schemaValidations{ps}, rxf(rxMaximumFmt, "")}), 761 newSingleLineTagParser("minimum", &setMinimum{schemaValidations{ps}, rxf(rxMinimumFmt, "")}), 762 newSingleLineTagParser("multipleOf", &setMultipleOf{schemaValidations{ps}, rxf(rxMultipleOfFmt, "")}), 763 newSingleLineTagParser("minLength", &setMinLength{schemaValidations{ps}, rxf(rxMinLengthFmt, "")}), 764 newSingleLineTagParser("maxLength", &setMaxLength{schemaValidations{ps}, rxf(rxMaxLengthFmt, "")}), 765 newSingleLineTagParser("pattern", &setPattern{schemaValidations{ps}, rxf(rxPatternFmt, "")}), 766 newSingleLineTagParser("minItems", &setMinItems{schemaValidations{ps}, rxf(rxMinItemsFmt, "")}), 767 newSingleLineTagParser("maxItems", &setMaxItems{schemaValidations{ps}, rxf(rxMaxItemsFmt, "")}), 768 newSingleLineTagParser("unique", &setUnique{schemaValidations{ps}, rxf(rxUniqueFmt, "")}), 769 newSingleLineTagParser("enum", &setEnum{schemaValidations{ps}, rxf(rxEnumFmt, "")}), 770 newSingleLineTagParser("default", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}), 771 newSingleLineTagParser("type", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}), 772 newSingleLineTagParser("example", &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxExampleFmt, "")}), 773 newSingleLineTagParser("required", &setRequiredSchema{schema, nm}), 774 newSingleLineTagParser("readOnly", &setReadOnlySchema{ps}), 775 newSingleLineTagParser("discriminator", &setDiscriminator{schema, nm}), 776 newMultiLineTagParser("YAMLExtensionsBlock", newYamlParser(rxExtensions, schemaVendorExtensibleSetter(ps)), true), 777 } 778 779 itemsTaggers := func(items *spec.Schema, level int) []tagParser { 780 schemeType, err := items.Type.MarshalJSON() 781 if err != nil { 782 return nil 783 } 784 // the expression is 1-index based not 0-index 785 itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1) 786 return []tagParser{ 787 newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{schemaValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}), 788 newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{schemaValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}), 789 newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{schemaValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}), 790 newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{schemaValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}), 791 newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{schemaValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}), 792 newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{schemaValidations{items}, rxf(rxPatternFmt, itemsPrefix)}), 793 newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{schemaValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}), 794 newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{schemaValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}), 795 newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{schemaValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}), 796 newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{schemaValidations{items}, rxf(rxEnumFmt, itemsPrefix)}), 797 newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}), 798 newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxExampleFmt, itemsPrefix)}), 799 } 800 } 801 802 var parseArrayTypes func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error) 803 parseArrayTypes = func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error) { 804 if items == nil || items.Schema == nil { 805 return []tagParser{}, nil 806 } 807 switch iftpe := expr.(type) { 808 case *ast.ArrayType: 809 eleTaggers := itemsTaggers(items.Schema, level) 810 sp.taggers = append(eleTaggers, sp.taggers...) 811 otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Schema.Items, level+1) 812 if err != nil { 813 return nil, err 814 } 815 return otherTaggers, nil 816 case *ast.Ident: 817 taggers := []tagParser{} 818 if iftpe.Obj == nil { 819 taggers = itemsTaggers(items.Schema, level) 820 } 821 otherTaggers, err := parseArrayTypes(expr, items.Schema.Items, level+1) 822 if err != nil { 823 return nil, err 824 } 825 return append(taggers, otherTaggers...), nil 826 case *ast.StarExpr: 827 otherTaggers, err := parseArrayTypes(iftpe.X, items, level) 828 if err != nil { 829 return nil, err 830 } 831 return otherTaggers, nil 832 default: 833 return nil, fmt.Errorf("unknown field type ele for %q", nm) 834 } 835 } 836 // check if this is a primitive, if so parse the validations from the 837 // doc comments of the slice declaration. 838 if ftped, ok := fld.Type.(*ast.ArrayType); ok { 839 taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0) 840 if err != nil { 841 return sp 842 } 843 sp.taggers = append(taggers, sp.taggers...) 844 } 845 846 } else { 847 sp.taggers = []tagParser{ 848 newSingleLineTagParser("required", &setRequiredSchema{schema, nm}), 849 } 850 } 851 return sp 852 } 853 854 // hasFilePathPrefix reports whether the filesystem path s begins with the 855 // elements in prefix. 856 // 857 // taken from: https://github.com/golang/go/blob/c87520c5981ecdeaa99e7ba636a6088f900c0c75/src/cmd/go/internal/load/path.go#L60-L80 858 func hasFilePathPrefix(s, prefix string) bool { 859 sv := strings.ToUpper(filepath.VolumeName(s)) 860 pv := strings.ToUpper(filepath.VolumeName(prefix)) 861 s = s[len(sv):] 862 prefix = prefix[len(pv):] 863 switch { 864 default: 865 return false 866 case sv != pv: 867 return false 868 case len(s) == len(prefix): 869 return s == prefix 870 case len(s) > len(prefix): 871 if prefix != "" && prefix[len(prefix)-1] == filepath.Separator { 872 return strings.HasPrefix(s, prefix) 873 } 874 return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix 875 } 876 } 877 878 func (scp *schemaParser) packageForFile(gofile *ast.File, tpe *ast.Ident) (*loader.PackageInfo, error) { 879 fn := scp.program.Fset.File(gofile.Pos()).Name() 880 if Debug { 881 log.Println("trying for", fn, tpe.Name, tpe.String()) 882 } 883 fa, err := filepath.Abs(fn) 884 if err != nil { 885 return nil, err 886 } 887 if Debug { 888 log.Println("absolute path", fa) 889 } 890 var fgp string 891 gopath := os.Getenv("GOPATH") 892 if gopath == "" { 893 gopath = filepath.Join(os.Getenv("HOME"), "go") 894 } 895 for _, p := range append(filepath.SplitList(gopath), runtime.GOROOT()) { 896 pref := filepath.Join(p, "src") 897 if hasFilePathPrefix(fa, pref) { 898 fgp = filepath.Dir(strings.TrimPrefix(fa, pref))[1:] 899 break 900 } 901 } 902 if Debug { 903 log.Println("package in gopath", fgp) 904 } 905 for pkg, pkgInfo := range scp.program.AllPackages { 906 if Debug { 907 log.Println("inferring for", tpe.Name, "with", gofile.Name.Name, "at", pkg.Path(), "against", filepath.ToSlash(fgp)) 908 } 909 if pkg.Name() == gofile.Name.Name && filepath.ToSlash(fgp) == pkg.Path() { 910 return pkgInfo, nil 911 } 912 } 913 914 return nil, fmt.Errorf("unable to determine package for %s", fn) 915 } 916 917 func (scp *schemaParser) packageForSelector(gofile *ast.File, expr ast.Expr) (*loader.PackageInfo, error) { 918 919 if pth, ok := expr.(*ast.Ident); ok { 920 // lookup import 921 var selPath string 922 for _, imp := range gofile.Imports { 923 pv, err := strconv.Unquote(imp.Path.Value) 924 if err != nil { 925 pv = imp.Path.Value 926 } 927 if imp.Name != nil { 928 if imp.Name.Name == pth.Name { 929 selPath = pv 930 break 931 } 932 } else { 933 pkg := scp.program.Package(pv) 934 if pkg != nil && pth.Name == pkg.Pkg.Name() { 935 selPath = pv 936 break 937 } else { 938 parts := strings.Split(pv, "/") 939 if len(parts) > 0 && parts[len(parts)-1] == pth.Name { 940 selPath = pv 941 break 942 } 943 } 944 } 945 } 946 // find actual struct 947 if selPath == "" { 948 return nil, fmt.Errorf("no import found for %s", pth.Name) 949 } 950 951 pkg := scp.program.Package(selPath) 952 if pkg != nil { 953 return pkg, nil 954 } 955 // TODO: I must admit this made me cry, it's not even a great solution. 956 pkg = scp.program.Package("github.com/go-swagger/go-swagger/vendor/" + selPath) 957 if pkg != nil { 958 return pkg, nil 959 } 960 for _, info := range scp.program.AllPackages { 961 n := info.String() 962 path := "/vendor/" + selPath 963 if strings.HasSuffix(n, path) { 964 pkg = scp.program.Package(n) 965 return pkg, nil 966 } 967 } 968 } 969 return nil, fmt.Errorf("can't determine selector path from %v", expr) 970 } 971 972 func (scp *schemaParser) makeRef(file *ast.File, pkg *loader.PackageInfo, gd *ast.GenDecl, ts *ast.TypeSpec, prop swaggerTypable) error { 973 sd := newSchemaDecl(file, gd, ts) 974 sd.inferNames() 975 // make an exception for time.Time because this is a well-known string format 976 if sd.Name == "Time" && pkg.String() == "time" { 977 return nil 978 } 979 ref, err := spec.NewRef("#/definitions/" + sd.Name) 980 if err != nil { 981 return err 982 } 983 prop.SetRef(ref) 984 scp.postDecls = append(scp.postDecls, *sd) 985 return nil 986 } 987 988 func (scp *schemaParser) parseIdentProperty(pkg *loader.PackageInfo, expr *ast.Ident, prop swaggerTypable) error { 989 // before proceeding make an exception to time.Time because it is a well known string format 990 if pkg.String() == "time" && expr.String() == "Time" { 991 prop.Typed("string", "date-time") 992 return nil 993 } 994 995 // find the file this selector points to 996 file, gd, ts, err := findSourceFile(pkg, expr.Name) 997 998 if err != nil { 999 err := swaggerSchemaForType(expr.Name, prop) 1000 if err != nil { 1001 return fmt.Errorf("package %s, error is: %v", pkg.String(), err) 1002 } 1003 return nil 1004 } 1005 1006 if at, ok := ts.Type.(*ast.ArrayType); ok { 1007 // the swagger spec defines strfmt base64 as []byte. 1008 // in that case we don't actually want to turn it into an array 1009 // but we want to turn it into a string 1010 if _, ok := at.Elt.(*ast.Ident); ok { 1011 if strfmtName, ok := strfmtName(gd.Doc); ok { 1012 prop.Typed("string", strfmtName) 1013 return nil 1014 } 1015 } 1016 // this is a selector, so most likely not base64 1017 if strfmtName, ok := strfmtName(gd.Doc); ok { 1018 prop.Items().Typed("string", strfmtName) 1019 return nil 1020 } 1021 } 1022 1023 // look at doc comments for swagger:strfmt [name] 1024 // when found this is the format name, create a schema with that name 1025 if strfmtName, ok := strfmtName(gd.Doc); ok { 1026 prop.Typed("string", strfmtName) 1027 return nil 1028 } 1029 1030 if enumName, ok := enumName(gd.Doc); ok { 1031 log.Println(enumName) 1032 return nil 1033 } 1034 1035 if defaultName, ok := defaultName(gd.Doc); ok { 1036 log.Println(defaultName) 1037 return nil 1038 } 1039 1040 if typeName, ok := typeName(gd.Doc); ok { 1041 _ = swaggerSchemaForType(typeName, prop) 1042 return nil 1043 } 1044 1045 if isAliasParam(prop) || aliasParam(gd.Doc) { 1046 itype, ok := ts.Type.(*ast.Ident) 1047 if ok { 1048 err := swaggerSchemaForType(itype.Name, prop) 1049 if err == nil { 1050 return nil 1051 } 1052 } 1053 } 1054 switch tpe := ts.Type.(type) { 1055 case *ast.ArrayType: 1056 return scp.makeRef(file, pkg, gd, ts, prop) 1057 case *ast.StructType: 1058 return scp.makeRef(file, pkg, gd, ts, prop) 1059 1060 case *ast.Ident: 1061 return scp.makeRef(file, pkg, gd, ts, prop) 1062 1063 case *ast.StarExpr: 1064 return parseProperty(scp, file, tpe.X, prop) 1065 1066 case *ast.SelectorExpr: 1067 // return scp.refForSelector(file, gd, tpe, ts, prop) 1068 return scp.makeRef(file, pkg, gd, ts, prop) 1069 1070 case *ast.InterfaceType: 1071 return scp.makeRef(file, pkg, gd, ts, prop) 1072 1073 case *ast.MapType: 1074 return scp.makeRef(file, pkg, gd, ts, prop) 1075 1076 default: 1077 err := swaggerSchemaForType(expr.Name, prop) 1078 if err != nil { 1079 return fmt.Errorf("package %s, error is: %v", pkg.String(), err) 1080 } 1081 return nil 1082 } 1083 1084 } 1085 1086 func (scp *schemaParser) typeForSelector(gofile *ast.File, expr *ast.SelectorExpr, prop swaggerTypable) error { 1087 pkg, err := scp.packageForSelector(gofile, expr.X) 1088 if err != nil { 1089 return err 1090 } 1091 1092 return scp.parseIdentProperty(pkg, expr.Sel, prop) 1093 } 1094 1095 func findSourceFile(pkg *loader.PackageInfo, typeName string) (*ast.File, *ast.GenDecl, *ast.TypeSpec, error) { 1096 for _, file := range pkg.Files { 1097 for _, decl := range file.Decls { 1098 if gd, ok := decl.(*ast.GenDecl); ok { 1099 for _, gs := range gd.Specs { 1100 if ts, ok := gs.(*ast.TypeSpec); ok { 1101 strfmtNme, isStrfmt := strfmtName(gd.Doc) 1102 if (isStrfmt && strfmtNme == typeName) || ts.Name != nil && ts.Name.Name == typeName { 1103 return file, gd, ts, nil 1104 } 1105 } 1106 } 1107 } 1108 } 1109 } 1110 return nil, nil, nil, fmt.Errorf("unable to find %s in %s", typeName, pkg.String()) 1111 } 1112 1113 func allOfMember(comments *ast.CommentGroup) bool { 1114 if comments != nil { 1115 for _, cmt := range comments.List { 1116 for _, ln := range strings.Split(cmt.Text, "\n") { 1117 if rxAllOf.MatchString(ln) { 1118 return true 1119 } 1120 } 1121 } 1122 } 1123 return false 1124 } 1125 1126 func fileParam(comments *ast.CommentGroup) bool { 1127 if comments != nil { 1128 for _, cmt := range comments.List { 1129 for _, ln := range strings.Split(cmt.Text, "\n") { 1130 if rxFileUpload.MatchString(ln) { 1131 return true 1132 } 1133 } 1134 } 1135 } 1136 return false 1137 } 1138 1139 func strfmtName(comments *ast.CommentGroup) (string, bool) { 1140 if comments != nil { 1141 for _, cmt := range comments.List { 1142 for _, ln := range strings.Split(cmt.Text, "\n") { 1143 matches := rxStrFmt.FindStringSubmatch(ln) 1144 if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 { 1145 return strings.TrimSpace(matches[1]), true 1146 } 1147 } 1148 } 1149 } 1150 return "", false 1151 } 1152 1153 func ignored(comments *ast.CommentGroup) bool { 1154 if comments != nil { 1155 for _, cmt := range comments.List { 1156 for _, ln := range strings.Split(cmt.Text, "\n") { 1157 if rxIgnoreOverride.MatchString(ln) { 1158 return true 1159 } 1160 } 1161 } 1162 } 1163 return false 1164 } 1165 1166 func enumName(comments *ast.CommentGroup) (string, bool) { 1167 if comments != nil { 1168 for _, cmt := range comments.List { 1169 for _, ln := range strings.Split(cmt.Text, "\n") { 1170 matches := rxEnum.FindStringSubmatch(ln) 1171 if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 { 1172 return strings.TrimSpace(matches[1]), true 1173 } 1174 } 1175 } 1176 } 1177 return "", false 1178 } 1179 1180 func aliasParam(comments *ast.CommentGroup) bool { 1181 if comments != nil { 1182 for _, cmt := range comments.List { 1183 for _, ln := range strings.Split(cmt.Text, "\n") { 1184 if rxAlias.MatchString(ln) { 1185 return true 1186 } 1187 } 1188 } 1189 } 1190 return false 1191 } 1192 1193 func defaultName(comments *ast.CommentGroup) (string, bool) { 1194 if comments != nil { 1195 for _, cmt := range comments.List { 1196 for _, ln := range strings.Split(cmt.Text, "\n") { 1197 matches := rxDefault.FindStringSubmatch(ln) 1198 if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 { 1199 return strings.TrimSpace(matches[1]), true 1200 } 1201 } 1202 } 1203 } 1204 return "", false 1205 } 1206 1207 func typeName(comments *ast.CommentGroup) (string, bool) { 1208 1209 var typ string 1210 if comments != nil { 1211 for _, cmt := range comments.List { 1212 for _, ln := range strings.Split(cmt.Text, "\n") { 1213 matches := rxType.FindStringSubmatch(ln) 1214 if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 { 1215 typ = strings.TrimSpace(matches[1]) 1216 return typ, true 1217 } 1218 } 1219 } 1220 } 1221 return "", false 1222 } 1223 1224 func parseProperty(scp *schemaParser, gofile *ast.File, fld ast.Expr, prop swaggerTypable) error { 1225 switch ftpe := fld.(type) { 1226 case *ast.Ident: // simple value 1227 pkg, err := scp.packageForFile(gofile, ftpe) 1228 if err != nil { 1229 return err 1230 } 1231 return scp.parseIdentProperty(pkg, ftpe, prop) 1232 1233 case *ast.StarExpr: // pointer to something, optional by default 1234 if err := parseProperty(scp, gofile, ftpe.X, prop); err != nil { 1235 return err 1236 } 1237 1238 case *ast.ArrayType: // slice type 1239 if err := parseProperty(scp, gofile, ftpe.Elt, prop.Items()); err != nil { 1240 return err 1241 } 1242 1243 case *ast.StructType: 1244 schema := prop.Schema() 1245 if schema == nil { 1246 return fmt.Errorf("items doesn't support embedded structs") 1247 } 1248 return scp.parseStructType(gofile, prop.Schema(), ftpe, make(map[string]string)) 1249 1250 case *ast.SelectorExpr: 1251 err := scp.typeForSelector(gofile, ftpe, prop) 1252 return err 1253 1254 case *ast.MapType: 1255 // check if key is a string type, if not print a message 1256 // and skip the map property. Only maps with string keys can go into additional properties 1257 sch := prop.Schema() 1258 if sch == nil { 1259 return fmt.Errorf("items doesn't support maps") 1260 } 1261 if keyIdent, ok := ftpe.Key.(*ast.Ident); sch != nil && ok { 1262 if keyIdent.Name == "string" { 1263 if sch.AdditionalProperties == nil { 1264 sch.AdditionalProperties = new(spec.SchemaOrBool) 1265 } 1266 sch.AdditionalProperties.Allows = false 1267 if sch.AdditionalProperties.Schema == nil { 1268 sch.AdditionalProperties.Schema = new(spec.Schema) 1269 } 1270 if err := parseProperty(scp, gofile, ftpe.Value, schemaTypable{sch.AdditionalProperties.Schema, 0}); err != nil { 1271 return err 1272 } 1273 sch.Typed("object", "") 1274 } 1275 } 1276 1277 case *ast.InterfaceType: 1278 prop.Schema().Typed("object", "") 1279 default: 1280 pos := "unknown file:unknown position" 1281 if scp != nil { 1282 if scp.program != nil { 1283 if scp.program.Fset != nil { 1284 pos = scp.program.Fset.Position(fld.Pos()).String() 1285 } 1286 } 1287 } 1288 return fmt.Errorf("Expr (%s) is unsupported for a schema", pos) 1289 } 1290 return nil 1291 } 1292 1293 func parseJSONTag(field *ast.Field) (name string, ignore bool, isString bool, err error) { 1294 if len(field.Names) > 0 { 1295 name = field.Names[0].Name 1296 } 1297 if field.Tag != nil && len(strings.TrimSpace(field.Tag.Value)) > 0 { 1298 tv, err := strconv.Unquote(field.Tag.Value) 1299 if err != nil { 1300 return name, false, false, err 1301 } 1302 1303 if strings.TrimSpace(tv) != "" { 1304 st := reflect.StructTag(tv) 1305 jsonParts := strings.Split(st.Get("json"), ",") 1306 jsonName := jsonParts[0] 1307 1308 if len(jsonParts) > 1 && jsonParts[1] == "string" { 1309 // Need to check if the field type is a scalar. Otherwise, the 1310 // ",string" directive doesn't apply. 1311 ident, ok := field.Type.(*ast.Ident) 1312 if ok { 1313 switch ident.Name { 1314 case "int", "int8", "int16", "int32", "int64", 1315 "uint", "uint8", "uint16", "uint32", "uint64", 1316 "float64", "string", "bool": 1317 isString = true 1318 } 1319 } 1320 } 1321 1322 if jsonName == "-" { 1323 return name, true, isString, nil 1324 } else if jsonName != "" { 1325 return jsonName, false, isString, nil 1326 } 1327 } 1328 } 1329 return name, false, false, nil 1330 }