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