diff options
Diffstat (limited to 'vendor/github.com/go-swagger/go-swagger/codescan/schema.go')
| -rw-r--r-- | vendor/github.com/go-swagger/go-swagger/codescan/schema.go | 1692 |
1 files changed, 0 insertions, 1692 deletions
diff --git a/vendor/github.com/go-swagger/go-swagger/codescan/schema.go b/vendor/github.com/go-swagger/go-swagger/codescan/schema.go deleted file mode 100644 index 670b2ece1..000000000 --- a/vendor/github.com/go-swagger/go-swagger/codescan/schema.go +++ /dev/null @@ -1,1692 +0,0 @@ -package codescan - -import ( - "encoding/json" - "errors" - "fmt" - "go/ast" - "go/importer" - "go/types" - "log" - "os" - "reflect" - "strconv" - "strings" - - "golang.org/x/tools/go/ast/astutil" - - "github.com/go-openapi/spec" -) - -func addExtension(ve *spec.VendorExtensible, key string, value interface{}) { - if os.Getenv("SWAGGER_GENERATE_EXTENSION") == "false" { - return - } - - ve.AddExtension(key, value) -} - -type schemaTypable struct { - schema *spec.Schema - level int -} - -func (st schemaTypable) In() string { return "body" } - -func (st schemaTypable) Typed(tpe, format string) { - st.schema.Typed(tpe, format) -} - -func (st schemaTypable) SetRef(ref spec.Ref) { - st.schema.Ref = ref -} - -func (st schemaTypable) Schema() *spec.Schema { - return st.schema -} - -func (st schemaTypable) Items() swaggerTypable { - if st.schema.Items == nil { - st.schema.Items = new(spec.SchemaOrArray) - } - if st.schema.Items.Schema == nil { - st.schema.Items.Schema = new(spec.Schema) - } - - st.schema.Typed("array", "") - return schemaTypable{st.schema.Items.Schema, st.level + 1} -} - -func (st schemaTypable) AdditionalProperties() swaggerTypable { - if st.schema.AdditionalProperties == nil { - st.schema.AdditionalProperties = new(spec.SchemaOrBool) - } - if st.schema.AdditionalProperties.Schema == nil { - st.schema.AdditionalProperties.Schema = new(spec.Schema) - } - - st.schema.Typed("object", "") - return schemaTypable{st.schema.AdditionalProperties.Schema, st.level + 1} -} - -func (st schemaTypable) Level() int { return st.level } - -func (st schemaTypable) AddExtension(key string, value interface{}) { - addExtension(&st.schema.VendorExtensible, key, value) -} - -func (st schemaTypable) WithEnum(values ...interface{}) { - st.schema.WithEnum(values...) -} - -func (st schemaTypable) WithEnumDescription(desc string) { - if desc == "" { - return - } - st.AddExtension(extEnumDesc, desc) -} - -type schemaValidations struct { - current *spec.Schema -} - -func (sv schemaValidations) SetMaximum(val float64, exclusive bool) { - sv.current.Maximum = &val - sv.current.ExclusiveMaximum = exclusive -} - -func (sv schemaValidations) SetMinimum(val float64, exclusive bool) { - sv.current.Minimum = &val - sv.current.ExclusiveMinimum = exclusive -} -func (sv schemaValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val } -func (sv schemaValidations) SetMinItems(val int64) { sv.current.MinItems = &val } -func (sv schemaValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val } -func (sv schemaValidations) SetMinLength(val int64) { sv.current.MinLength = &val } -func (sv schemaValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val } -func (sv schemaValidations) SetPattern(val string) { sv.current.Pattern = val } -func (sv schemaValidations) SetUnique(val bool) { sv.current.UniqueItems = val } -func (sv schemaValidations) SetDefault(val interface{}) { sv.current.Default = val } -func (sv schemaValidations) SetExample(val interface{}) { sv.current.Example = val } -func (sv schemaValidations) SetEnum(val string) { - sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Format: sv.current.Format, Type: sv.current.Type[0]}) -} - -type schemaBuilder struct { - ctx *scanCtx - decl *entityDecl - GoName string - Name string - annotated bool - discovered []*entityDecl - postDecls []*entityDecl -} - -func (s *schemaBuilder) inferNames() (goName string, name string) { - if s.GoName != "" { - goName, name = s.GoName, s.Name - return - } - - goName = s.decl.Ident.Name - name = goName - defer func() { - s.GoName = goName - s.Name = name - }() - if s.decl.Comments == nil { - return - } - -DECLS: - for _, cmt := range s.decl.Comments.List { - for _, ln := range strings.Split(cmt.Text, "\n") { - matches := rxModelOverride.FindStringSubmatch(ln) - if len(matches) > 0 { - s.annotated = true - } - if len(matches) > 1 && len(matches[1]) > 0 { - name = matches[1] - break DECLS - } - } - } - return -} - -func (s *schemaBuilder) Build(definitions map[string]spec.Schema) error { - s.inferNames() - - schema := definitions[s.Name] - err := s.buildFromDecl(s.decl, &schema) - if err != nil { - return err - } - definitions[s.Name] = schema - return nil -} - -func (s *schemaBuilder) buildFromDecl(_ *entityDecl, schema *spec.Schema) error { - // analyze doc comment for the model - sp := new(sectionedParser) - sp.setTitle = func(lines []string) { schema.Title = joinDropLast(lines) } - sp.setDescription = func(lines []string) { - schema.Description = joinDropLast(lines) - enumDesc := getEnumDesc(schema.Extensions) - if enumDesc != "" { - schema.Description += "\n" + enumDesc - } - } - if err := sp.Parse(s.decl.Comments); err != nil { - return err - } - - // if the type is marked to ignore, just return - if sp.ignored { - return nil - } - - defer func() { - if schema.Ref.String() == "" { - // unless this is a $ref, we add traceability of the origin of this schema in source - if s.Name != s.GoName { - addExtension(&schema.VendorExtensible, "x-go-name", s.GoName) - } - addExtension(&schema.VendorExtensible, "x-go-package", s.decl.Obj().Pkg().Path()) - } - }() - - switch tpe := s.decl.ObjType().(type) { - // TODO(fredbi): we may safely remove all the cases here that are not Named or Alias - case *types.Basic: - debugLog("basic: %v", tpe.Name()) - return nil - case *types.Struct: - return s.buildFromStruct(s.decl, tpe, schema, make(map[string]string)) - case *types.Interface: - return s.buildFromInterface(s.decl, tpe, schema, make(map[string]string)) - case *types.Array: - debugLog("array: %v -> %v", s.decl.Ident.Name, tpe.Elem().String()) - return nil - case *types.Slice: - debugLog("slice: %v -> %v", s.decl.Ident.Name, tpe.Elem().String()) - return nil - case *types.Map: - debugLog("map: %v -> [%v]%v", s.decl.Ident.Name, tpe.Key().String(), tpe.Elem().String()) - return nil - case *types.Named: - debugLog("named: %v", tpe) - return s.buildDeclNamed(tpe, schema) - case *types.Alias: - debugLog("alias: %v -> %v", tpe, tpe.Rhs()) - tgt := schemaTypable{schema, 0} - - return s.buildDeclAlias(tpe, tgt) - case *types.TypeParam: - log.Printf("WARNING: generic type parameters are not supported yet %[1]v (%[1]T). Skipped", tpe) - return nil - case *types.Chan: - log.Printf("WARNING: channels are not supported %[1]v (%[1]T). Skipped", tpe) - return nil - case *types.Signature: - log.Printf("WARNING: functions are not supported %[1]v (%[1]T). Skipped", tpe) - return nil - default: - log.Printf("WARNING: missing parser for type %T, skipping model: %s\n", tpe, s.Name) - return nil - } -} - -func (s *schemaBuilder) buildDeclNamed(tpe *types.Named, schema *spec.Schema) error { - if unsupportedBuiltin(tpe) { - log.Printf("WARNING: skipped unsupported builtin type: %v", tpe) - - return nil - } - o := tpe.Obj() - - mustNotBeABuiltinType(o) - - debugLog("got the named type object: %s.%s | isAlias: %t | exported: %t", o.Pkg().Path(), o.Name(), o.IsAlias(), o.Exported()) - if isStdTime(o) { - schema.Typed("string", "date-time") - return nil - } - - ps := schemaTypable{schema, 0} - ti := s.decl.Pkg.TypesInfo.Types[s.decl.Spec.Type] - if !ti.IsType() { - return fmt.Errorf("declaration is not a type: %v", o) - } - - return s.buildFromType(ti.Type, ps) -} - -// buildFromTextMarshal renders a type that marshals as text as a string -func (s *schemaBuilder) buildFromTextMarshal(tpe types.Type, tgt swaggerTypable) error { - if typePtr, ok := tpe.(*types.Pointer); ok { - return s.buildFromTextMarshal(typePtr.Elem(), tgt) - } - - typeNamed, ok := tpe.(*types.Named) - if !ok { - tgt.Typed("string", "") - return nil - } - - tio := typeNamed.Obj() - if isStdError(tio) { - tgt.AddExtension("x-go-type", tio.Name()) - return swaggerSchemaForType(tio.Name(), tgt) - } - - debugLog("named refined type %s.%s", tio.Pkg().Path(), tio.Name()) - pkg, found := s.ctx.PkgForType(tpe) - - if strings.ToLower(tio.Name()) == "uuid" { - tgt.Typed("string", "uuid") - return nil - } - - if !found { - // this must be a builtin - debugLog("skipping because package is nil: %v", tpe) - return nil - } - - if isStdTime(tio) { - tgt.Typed("string", "date-time") - return nil - } - - if isStdJSONRawMessage(tio) { - tgt.Typed("object", "") // TODO: this should actually be any type - return nil - } - - cmt, hasComments := s.ctx.FindComments(pkg, tio.Name()) - if !hasComments { - cmt = new(ast.CommentGroup) - } - - if sfnm, isf := strfmtName(cmt); isf { - tgt.Typed("string", sfnm) - return nil - } - - tgt.Typed("string", "") - tgt.AddExtension("x-go-type", tio.Pkg().Path()+"."+tio.Name()) - - return nil -} - -func (s *schemaBuilder) buildFromType(tpe types.Type, tgt swaggerTypable) error { - // check if the type implements encoding.TextMarshaler interface - // if so, the type is rendered as a string. - debugLog("schema buildFromType %v (%T)", tpe, tpe) - - if isTextMarshaler(tpe) { - return s.buildFromTextMarshal(tpe, tgt) - } - - switch titpe := tpe.(type) { - case *types.Basic: - if unsupportedBuiltinType(titpe) { - log.Printf("WARNING: skipped unsupported builtin type: %v", tpe) - return nil - } - return swaggerSchemaForType(titpe.String(), tgt) - case *types.Pointer: - return s.buildFromType(titpe.Elem(), tgt) - case *types.Struct: - return s.buildFromStruct(s.decl, titpe, tgt.Schema(), make(map[string]string)) - case *types.Interface: - return s.buildFromInterface(s.decl, titpe, tgt.Schema(), make(map[string]string)) - case *types.Slice: - // anonymous slice - return s.buildFromType(titpe.Elem(), tgt.Items()) - case *types.Array: - // anonymous array - return s.buildFromType(titpe.Elem(), tgt.Items()) - case *types.Map: - return s.buildFromMap(titpe, tgt) - case *types.Named: - // a named type, e.g. type X struct {} - return s.buildNamedType(titpe, tgt) - case *types.Alias: - // a named alias, e.g. type X = {RHS type}. - debugLog("alias(schema.buildFromType): got alias %v to %v", titpe, titpe.Rhs()) - return s.buildAlias(titpe, tgt) - case *types.TypeParam: - log.Printf("WARNING: generic type parameters are not supported yet %[1]v (%[1]T). Skipped", titpe) - return nil - case *types.Chan: - log.Printf("WARNING: channels are not supported %[1]v (%[1]T). Skipped", tpe) - return nil - case *types.Signature: - log.Printf("WARNING: functions are not supported %[1]v (%[1]T). Skipped", tpe) - return nil - default: - panic(fmt.Errorf("ERROR: can't determine refined type %[1]v (%[1]T): %w", titpe, ErrInternal)) - } -} - -func (s *schemaBuilder) buildNamedType(titpe *types.Named, tgt swaggerTypable) error { - tio := titpe.Obj() - if unsupportedBuiltin(titpe) { - log.Printf("WARNING: skipped unsupported builtin type: %v", titpe) - return nil - } - if isAny(tio) { - // e.g type X any or type X interface{} - _ = tgt.Schema() - - return nil - } - - // special case of the "error" interface. - if isStdError(tio) { - tgt.AddExtension("x-go-type", tio.Name()) - return swaggerSchemaForType(tio.Name(), tgt) - } - - // special case of the "time.Time" type - if isStdTime(tio) { - tgt.Typed("string", "date-time") - return nil - } - - // special case of the "json.RawMessage" type - if isStdJSONRawMessage(tio) { - tgt.Typed("object", "") // TODO: this should actually be any type - return nil - } - - pkg, found := s.ctx.PkgForType(titpe) - debugLog("named refined type %s.%s", pkg, tio.Name()) - if !found { - // this must be a builtin - // - // This could happen for example when using unsupported types such as complex64, complex128, uintptr, - // or type constraints such as comparable. - debugLog("skipping because package is nil (builtin type): %v", tio) - - return nil - } - - cmt, hasComments := s.ctx.FindComments(pkg, tio.Name()) - if !hasComments { - cmt = new(ast.CommentGroup) - } - - if typeName, ok := typeName(cmt); ok { - _ = swaggerSchemaForType(typeName, tgt) - - return nil - } - - if s.decl.Spec.Assign.IsValid() { - debugLog("found assignment: %s.%s", tio.Pkg().Path(), tio.Name()) - return s.buildFromType(titpe.Underlying(), tgt) - } - - if titpe.TypeArgs() != nil && titpe.TypeArgs().Len() > 0 { - return s.buildFromType(titpe.Underlying(), tgt) - } - - // invariant: the Underlying cannot be an alias or named type - switch utitpe := titpe.Underlying().(type) { - case *types.Struct: - debugLog("found struct: %s.%s", tio.Pkg().Path(), tio.Name()) - - decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()) - if !ok { - debugLog("could not find model in index: %s.%s", tio.Pkg().Path(), tio.Name()) - return nil - } - - o := decl.Obj() - if isStdTime(o) { - tgt.Typed("string", "date-time") - return nil - } - - if sfnm, isf := strfmtName(cmt); isf { - tgt.Typed("string", sfnm) - return nil - } - - if typeName, ok := typeName(cmt); ok { - _ = swaggerSchemaForType(typeName, tgt) - return nil - } - - return s.makeRef(decl, tgt) - case *types.Interface: - debugLog("found interface: %s.%s", tio.Pkg().Path(), tio.Name()) - - decl, found := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()) - if !found { - return fmt.Errorf("can't find source file for type: %v", utitpe) - } - - return s.makeRef(decl, tgt) - case *types.Basic: - if unsupportedBuiltinType(utitpe) { - log.Printf("WARNING: skipped unsupported builtin type: %v", utitpe) - return nil - } - - debugLog("found primitive type: %s.%s", tio.Pkg().Path(), tio.Name()) - - if sfnm, isf := strfmtName(cmt); isf { - tgt.Typed("string", sfnm) - return nil - } - - if enumName, ok := enumName(cmt); ok { - enumValues, enumDesces, _ := s.ctx.FindEnumValues(pkg, enumName) - if len(enumValues) > 0 { - tgt.WithEnum(enumValues...) - enumTypeName := reflect.TypeOf(enumValues[0]).String() - _ = swaggerSchemaForType(enumTypeName, tgt) - } - if len(enumDesces) > 0 { - tgt.WithEnumDescription(strings.Join(enumDesces, "\n")) - } - return nil - } - - if defaultName, ok := defaultName(cmt); ok { - debugLog("default name: %s", defaultName) - return nil - } - - if typeName, ok := typeName(cmt); ok { - _ = swaggerSchemaForType(typeName, tgt) - return nil - - } - - if isAliasParam(tgt) || aliasParam(cmt) { - err := swaggerSchemaForType(utitpe.Name(), tgt) - if err == nil { - return nil - } - } - - if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { - return s.makeRef(decl, tgt) - } - - return swaggerSchemaForType(utitpe.String(), tgt) - case *types.Array: - debugLog("found array type: %s.%s", tio.Pkg().Path(), tio.Name()) - - if sfnm, isf := strfmtName(cmt); isf { - if sfnm == "byte" { - tgt.Typed("string", sfnm) - return nil - } - if sfnm == "bsonobjectid" { - tgt.Typed("string", sfnm) - return nil - } - - tgt.Items().Typed("string", sfnm) - return nil - } - if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { - return s.makeRef(decl, tgt) - } - return s.buildFromType(utitpe.Elem(), tgt.Items()) - case *types.Slice: - debugLog("found slice type: %s.%s", tio.Pkg().Path(), tio.Name()) - - if sfnm, isf := strfmtName(cmt); isf { - if sfnm == "byte" { - tgt.Typed("string", sfnm) - return nil - } - tgt.Items().Typed("string", sfnm) - return nil - } - if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { - return s.makeRef(decl, tgt) - } - return s.buildFromType(utitpe.Elem(), tgt.Items()) - case *types.Map: - debugLog("found map type: %s.%s", tio.Pkg().Path(), tio.Name()) - - if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok { - return s.makeRef(decl, tgt) - } - return nil - case *types.TypeParam: - log.Printf("WARNING: generic type parameters are not supported yet %[1]v (%[1]T). Skipped", utitpe) - return nil - case *types.Chan: - log.Printf("WARNING: channels are not supported %[1]v (%[1]T). Skipped", utitpe) - return nil - case *types.Signature: - log.Printf("WARNING: functions are not supported %[1]v (%[1]T). Skipped", utitpe) - return nil - default: - log.Printf( - "WARNING: can't figure out object type for named type (%T): %v [alias: %t]", - titpe.Underlying(), titpe.Underlying(), titpe.Obj().IsAlias(), - ) - - return nil - } -} - -// buildDeclAlias builds a top-level alias declaration. -func (s *schemaBuilder) buildDeclAlias(tpe *types.Alias, tgt swaggerTypable) error { - if unsupportedBuiltinType(tpe) { - log.Printf("WARNING: skipped unsupported builtin type: %v", tpe) - return nil - } - - o := tpe.Obj() - if isAny(o) { - _ = tgt.Schema() // this is mutating tgt to create an empty schema - return nil - } - if isStdError(o) { - tgt.AddExtension("x-go-type", o.Name()) - return swaggerSchemaForType(o.Name(), tgt) - } - mustNotBeABuiltinType(o) - - if isStdTime(o) { - tgt.Typed("string", "date-time") - return nil - } - - mustHaveRightHandSide(tpe) - rhs := tpe.Rhs() - - decl, ok := s.ctx.FindModel(o.Pkg().Path(), o.Name()) - if !ok { - return fmt.Errorf("can't find source file for aliased type: %v -> %v", tpe, rhs) - } - - s.postDecls = append(s.postDecls, decl) // mark the left-hand side as discovered - - if !s.ctx.app.refAliases { - // expand alias - return s.buildFromType(tpe.Underlying(), tgt) - } - - // resolve alias to named type as $ref - switch rtpe := rhs.(type) { - // named declarations: we construct a $ref to the right-hand side target of the alias - case *types.Named: - ro := rtpe.Obj() - rdecl, found := s.ctx.FindModel(ro.Pkg().Path(), ro.Name()) - if !found { - return fmt.Errorf("can't find source file for target type of alias: %v -> %v", tpe, rtpe) - } - - return s.makeRef(rdecl, tgt) - case *types.Alias: - ro := rtpe.Obj() - if unsupportedBuiltin(rtpe) { - log.Printf("WARNING: skipped unsupported builtin type: %v", rtpe) - return nil - } - if isAny(ro) { - // e.g. type X = any - _ = tgt.Schema() // this is mutating tgt to create an empty schema - return nil - } - if isStdError(ro) { - // e.g. type X = error - tgt.AddExtension("x-go-type", o.Name()) - return swaggerSchemaForType(o.Name(), tgt) - } - mustNotBeABuiltinType(ro) // TODO(fred): there are a few other cases - - rdecl, found := s.ctx.FindModel(ro.Pkg().Path(), ro.Name()) - if !found { - return fmt.Errorf("can't find source file for target type of alias: %v -> %v", tpe, rtpe) - } - - return s.makeRef(rdecl, tgt) - } - - // alias to anonymous type - return s.buildFromType(rhs, tgt) -} - -func (s *schemaBuilder) buildAnonymousInterface(it *types.Interface, tgt swaggerTypable, decl *entityDecl) error { - tgt.Typed("object", "") - - for i := range it.NumExplicitMethods() { - fld := it.ExplicitMethod(i) - if !fld.Exported() { - continue - } - sig, isSignature := fld.Type().(*types.Signature) - if !isSignature { - continue - } - if sig.Params().Len() > 0 { - continue - } - if sig.Results() == nil || sig.Results().Len() != 1 { - continue - } - - var afld *ast.Field - ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos()) - // debugLog("got %d nodes (exact: %t)", len(ans), isExact) - for _, an := range ans { - at, valid := an.(*ast.Field) - if !valid { - continue - } - - debugLog("maybe interface field %s: %s(%T)", fld.Name(), fld.Type().String(), fld.Type()) - afld = at - break - } - - if afld == nil { - debugLog("can't find source associated with %s for %s", fld.String(), it.String()) - continue - } - - // if the field is annotated with swagger:ignore, ignore it - if ignored(afld.Doc) { - continue - } - - name := fld.Name() - if afld.Doc != nil { - for _, cmt := range afld.Doc.List { - for _, ln := range strings.Split(cmt.Text, "\n") { - matches := rxName.FindStringSubmatch(ln) - ml := len(matches) - if ml > 1 { - name = matches[ml-1] - } - } - } - } - - if tgt.Schema().Properties == nil { - tgt.Schema().Properties = make(map[string]spec.Schema) - } - ps := tgt.Schema().Properties[name] - if err := s.buildFromType(sig.Results().At(0).Type(), schemaTypable{&ps, 0}); err != nil { - return err - } - if sfName, isStrfmt := strfmtName(afld.Doc); isStrfmt { - ps.Typed("string", sfName) - ps.Ref = spec.Ref{} - ps.Items = nil - } - - if err := s.createParser(name, tgt.Schema(), &ps, afld).Parse(afld.Doc); err != nil { - return err - } - - if ps.Ref.String() == "" && name != fld.Name() { - ps.AddExtension("x-go-name", fld.Name()) - } - - if s.ctx.app.setXNullableForPointers { - if _, isPointer := fld.Type().(*types.Signature).Results().At(0).Type().(*types.Pointer); isPointer && (ps.Extensions == nil || (ps.Extensions["x-nullable"] == nil && ps.Extensions["x-isnullable"] == nil)) { - ps.AddExtension("x-nullable", true) - } - } - - // seen[name] = fld.Name() - tgt.Schema().Properties[name] = ps - } - - return nil -} - -// buildAlias builds a reference to an alias from another type. -func (s *schemaBuilder) buildAlias(tpe *types.Alias, tgt swaggerTypable) error { - if unsupportedBuiltinType(tpe) { - log.Printf("WARNING: skipped unsupported builtin type: %v", tpe) - - return nil - } - - o := tpe.Obj() - if isAny(o) { - _ = tgt.Schema() - return nil - } - mustNotBeABuiltinType(o) - - decl, ok := s.ctx.FindModel(o.Pkg().Path(), o.Name()) - if !ok { - return fmt.Errorf("can't find source file for aliased type: %v", tpe) - } - - return s.makeRef(decl, tgt) -} - -func (s *schemaBuilder) buildFromMap(titpe *types.Map, tgt swaggerTypable) error { - // check if key is a string type, or knows how to marshall to text. - // If not, print a message and skip the map property. - // - // Only maps with string keys can go into additional properties - - sch := tgt.Schema() - if sch == nil { - return errors.New("items doesn't support maps") - } - - eleProp := schemaTypable{sch, tgt.Level()} - key := titpe.Key() - if key.Underlying().String() == "string" || isTextMarshaler(key) { - return s.buildFromType(titpe.Elem(), eleProp.AdditionalProperties()) - } - - return nil -} - -func (s *schemaBuilder) buildFromInterface(decl *entityDecl, it *types.Interface, schema *spec.Schema, seen map[string]string) error { - if it.Empty() { - // return an empty schema for empty interfaces - return nil - } - - var ( - tgt *spec.Schema - hasAllOf bool - ) - - var flist []*ast.Field - if specType, ok := decl.Spec.Type.(*ast.InterfaceType); ok { - flist = make([]*ast.Field, it.NumEmbeddeds()+it.NumExplicitMethods()) - copy(flist, specType.Methods.List) - } - - // First collect the embedded interfaces - // create refs when: - // - // 1. the embedded interface is decorated with an allOf annotation - // 2. the embedded interface is an alias - for i := range it.NumEmbeddeds() { - fld := it.EmbeddedType(i) - debugLog("inspecting embedded type in interface: %v", fld) - var ( - fieldHasAllOf bool - err error - ) - - if tgt == nil { - tgt = &spec.Schema{} - } - - switch ftpe := fld.(type) { - case *types.Named: - debugLog("embedded named type (buildInterface): %v", ftpe) - o := ftpe.Obj() - if isAny(o) || isStdError(o) { - // ignore bultin interfaces - continue - } - - if fieldHasAllOf, err = s.buildNamedInterface(ftpe, flist, decl, schema, seen); err != nil { - return err - } - case *types.Interface: - debugLog("embedded anonymous interface type (buildInterface): %v", ftpe) // e.g. type X interface{ interface{Error() string}} - var aliasedSchema spec.Schema - ps := schemaTypable{schema: &aliasedSchema} - if err = s.buildAnonymousInterface(ftpe, ps, decl); err != nil { - return err - } - - if aliasedSchema.Ref.String() != "" || len(aliasedSchema.Properties) > 0 || len(aliasedSchema.AllOf) > 0 { - schema.AddToAllOf(aliasedSchema) - fieldHasAllOf = true - } - case *types.Alias: - debugLog("embedded alias (buildInterface): %v -> %v", ftpe, ftpe.Rhs()) - var aliasedSchema spec.Schema - ps := schemaTypable{schema: &aliasedSchema} - if err = s.buildAlias(ftpe, ps); err != nil { - return err - } - - if aliasedSchema.Ref.String() != "" || len(aliasedSchema.Properties) > 0 || len(aliasedSchema.AllOf) > 0 { - schema.AddToAllOf(aliasedSchema) - fieldHasAllOf = true - } - case *types.Union: // e.g. type X interface{ ~uint16 | ~float32 } - log.Printf("WARNING: union type constraints are not supported yet %[1]v (%[1]T). Skipped", ftpe) - case *types.TypeParam: - log.Printf("WARNING: generic type parameters are not supported yet %[1]v (%[1]T). Skipped", ftpe) - case *types.Chan: - log.Printf("WARNING: channels are not supported %[1]v (%[1]T). Skipped", ftpe) - case *types.Signature: - log.Printf("WARNING: functions are not supported %[1]v (%[1]T). Skipped", ftpe) - default: - log.Printf( - "WARNING: can't figure out object type for allOf named type (%T): %v", - ftpe, ftpe.Underlying(), - ) - } - - debugLog("got embedded interface: %v {%T}, fieldHasAllOf: %t", fld, fld, fieldHasAllOf) - hasAllOf = hasAllOf || fieldHasAllOf - } - - if tgt == nil { - tgt = schema - } - - // We can finally build the actual schema for the struct - if tgt.Properties == nil { - tgt.Properties = make(map[string]spec.Schema) - } - tgt.Typed("object", "") - - for i := range it.NumExplicitMethods() { - fld := it.ExplicitMethod(i) - if !fld.Exported() { - continue - } - sig, isSignature := fld.Type().(*types.Signature) - if !isSignature { - continue - } - if sig.Params().Len() > 0 { - continue - } - if sig.Results() == nil || sig.Results().Len() != 1 { - continue - } - - var afld *ast.Field - ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos()) - // debugLog("got %d nodes (exact: %t)", len(ans), isExact) - for _, an := range ans { - at, valid := an.(*ast.Field) - if !valid { - continue - } - - debugLog("maybe interface field %s: %s(%T)", fld.Name(), fld.Type().String(), fld.Type()) - afld = at - break - } - - if afld == nil { - debugLog("can't find source associated with %s for %s", fld.String(), it.String()) - continue - } - - // if the field is annotated with swagger:ignore, ignore it - if ignored(afld.Doc) { - continue - } - - name := fld.Name() - if afld.Doc != nil { - for _, cmt := range afld.Doc.List { - for _, ln := range strings.Split(cmt.Text, "\n") { - matches := rxName.FindStringSubmatch(ln) - ml := len(matches) - if ml > 1 { - name = matches[ml-1] - } - } - } - } - ps := tgt.Properties[name] - if err := s.buildFromType(sig.Results().At(0).Type(), schemaTypable{&ps, 0}); err != nil { - return err - } - if sfName, isStrfmt := strfmtName(afld.Doc); isStrfmt { - ps.Typed("string", sfName) - ps.Ref = spec.Ref{} - ps.Items = nil - } - - if err := s.createParser(name, tgt, &ps, afld).Parse(afld.Doc); err != nil { - return err - } - - if ps.Ref.String() == "" && name != fld.Name() { - ps.AddExtension("x-go-name", fld.Name()) - } - - if s.ctx.app.setXNullableForPointers { - if _, isPointer := fld.Type().(*types.Signature).Results().At(0).Type().(*types.Pointer); isPointer && (ps.Extensions == nil || (ps.Extensions["x-nullable"] == nil && ps.Extensions["x-isnullable"] == nil)) { - ps.AddExtension("x-nullable", true) - } - } - - seen[name] = fld.Name() - tgt.Properties[name] = ps - } - - if tgt == nil { - return nil - } - if hasAllOf && len(tgt.Properties) > 0 { - schema.AllOf = append(schema.AllOf, *tgt) - } - - for k := range tgt.Properties { - if _, ok := seen[k]; !ok { - delete(tgt.Properties, k) - } - } - - return nil -} - -func (s *schemaBuilder) buildNamedInterface(ftpe *types.Named, flist []*ast.Field, decl *entityDecl, schema *spec.Schema, seen map[string]string) (hasAllOf bool, err error) { - o := ftpe.Obj() - var afld *ast.Field - - for _, an := range flist { - if len(an.Names) != 0 { - continue - } - - tpp := decl.Pkg.TypesInfo.Types[an.Type] - if tpp.Type.String() != o.Type().String() { - continue - } - - // decl. - debugLog("maybe interface field %s: %s(%T)", o.Name(), o.Type().String(), o.Type()) - afld = an - break - } - - if afld == nil { - debugLog("can't find source associated with %s", ftpe.String()) - return hasAllOf, nil - } - - // if the field is annotated with swagger:ignore, ignore it - if ignored(afld.Doc) { - return hasAllOf, nil - } - - if !allOfMember(afld.Doc) { - var newSch spec.Schema - if err = s.buildEmbedded(o.Type(), &newSch, seen); err != nil { - return hasAllOf, err - } - schema.AllOf = append(schema.AllOf, newSch) - hasAllOf = true - - return hasAllOf, nil - } - - hasAllOf = true - - var newSch spec.Schema - // when the embedded struct is annotated with swagger:allOf it will be used as allOf property - // otherwise the fields will just be included as normal properties - if err = s.buildAllOf(o.Type(), &newSch); err != nil { - return hasAllOf, err - } - - if afld.Doc != nil { - for _, cmt := range afld.Doc.List { - for _, ln := range strings.Split(cmt.Text, "\n") { - matches := rxAllOf.FindStringSubmatch(ln) - ml := len(matches) - if ml <= 1 { - continue - } - - mv := matches[ml-1] - if mv != "" { - schema.AddExtension("x-class", mv) - } - } - } - } - - schema.AllOf = append(schema.AllOf, newSch) - - return hasAllOf, nil -} - -func (s *schemaBuilder) buildFromStruct(decl *entityDecl, st *types.Struct, schema *spec.Schema, seen map[string]string) error { - s.ctx.FindComments(decl.Pkg, decl.Obj().Name()) - cmt, hasComments := s.ctx.FindComments(decl.Pkg, decl.Obj().Name()) - if !hasComments { - cmt = new(ast.CommentGroup) - } - if typeName, ok := typeName(cmt); ok { - _ = swaggerSchemaForType(typeName, schemaTypable{schema: schema}) - return nil - } - // First check for all of schemas - var tgt *spec.Schema - hasAllOf := false - - for i := range st.NumFields() { - fld := st.Field(i) - if !fld.Anonymous() { - // e.g. struct { _ struct{} } - debugLog("skipping field %q for allOf scan because not anonymous", fld.Name()) - continue - } - tg := st.Tag(i) - - 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(), - ) - var afld *ast.Field - ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos()) - // debugLog("got %d nodes (exact: %t)", len(ans), isExact) - for _, an := range ans { - at, valid := an.(*ast.Field) - if !valid { - continue - } - - debugLog("maybe allof field %s: %s(%T) [%q]", fld.Name(), fld.Type().String(), fld.Type(), tg) - afld = at - break - } - - if afld == nil { - debugLog("can't find source associated with %s for %s", fld.String(), st.String()) - continue - } - - // if the field is annotated with swagger:ignore, ignore it - if ignored(afld.Doc) { - continue - } - - _, ignore, _, _, err := parseJSONTag(afld) - if err != nil { - return err - } - if ignore { - continue - } - - _, isAliased := fld.Type().(*types.Alias) - - if !allOfMember(afld.Doc) && !isAliased { - if tgt == nil { - tgt = schema - } - if err := s.buildEmbedded(fld.Type(), tgt, seen); err != nil { - return err - } - continue - } - - if isAliased { - debugLog("alias member in struct: %v", fld) - } - - // if this created an allOf property then we have to rejig the schema var - // because all the fields collected that aren't from embedded structs should go in - // their own proper schema - // first process embedded structs in order of embedding - hasAllOf = true - if tgt == nil { - tgt = &spec.Schema{} - } - var newSch spec.Schema - // when the embedded struct is annotated with swagger:allOf it will be used as allOf property - // otherwise the fields will just be included as normal properties - if err := s.buildAllOf(fld.Type(), &newSch); err != nil { - return err - } - - if afld.Doc != nil { - for _, cmt := range afld.Doc.List { - for _, ln := range strings.Split(cmt.Text, "\n") { - matches := rxAllOf.FindStringSubmatch(ln) - ml := len(matches) - if ml > 1 { - mv := matches[ml-1] - if mv != "" { - schema.AddExtension("x-class", mv) - } - } - } - } - } - - schema.AllOf = append(schema.AllOf, newSch) - } - - if tgt == nil { - if schema != nil { - tgt = schema - } else { - tgt = &spec.Schema{} - } - } - // We can finally build the actual schema for the struct - if tgt.Properties == nil { - tgt.Properties = make(map[string]spec.Schema) - } - tgt.Typed("object", "") - - for i := range st.NumFields() { - fld := st.Field(i) - tg := st.Tag(i) - - if fld.Embedded() { - continue - } - - if !fld.Exported() { - debugLog("skipping field %s because it's not exported", fld.Name()) - continue - } - - var afld *ast.Field - ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos()) - for _, an := range ans { - at, valid := an.(*ast.Field) - if !valid { - continue - } - - debugLog("field %s: %s(%T) [%q] ==> %s", fld.Name(), fld.Type().String(), fld.Type(), tg, at.Doc.Text()) - afld = at - break - } - - if afld == nil { - debugLog("can't find source associated with %s", fld.String()) - continue - } - - // if the field is annotated with swagger:ignore, ignore it - if ignored(afld.Doc) { - continue - } - - name, ignore, isString, omitEmpty, err := parseJSONTag(afld) - if err != nil { - return err - } - if ignore { - for seenTagName, seenFieldName := range seen { - if seenFieldName == fld.Name() { - delete(tgt.Properties, seenTagName) - break - } - } - continue - } - - ps := tgt.Properties[name] - if err = s.buildFromType(fld.Type(), schemaTypable{&ps, 0}); err != nil { - return err - } - if isString { - ps.Typed("string", ps.Format) - ps.Ref = spec.Ref{} - ps.Items = nil - } - if sfName, isStrfmt := strfmtName(afld.Doc); isStrfmt { - ps.Typed("string", sfName) - ps.Ref = spec.Ref{} - ps.Items = nil - } - - if err = s.createParser(name, tgt, &ps, afld).Parse(afld.Doc); err != nil { - return err - } - - if ps.Ref.String() == "" && name != fld.Name() { - addExtension(&ps.VendorExtensible, "x-go-name", fld.Name()) - } - - if s.ctx.app.setXNullableForPointers { - if _, isPointer := fld.Type().(*types.Pointer); isPointer && !omitEmpty && - (ps.Extensions == nil || (ps.Extensions["x-nullable"] == nil && ps.Extensions["x-isnullable"] == nil)) { - ps.AddExtension("x-nullable", true) - } - } - - // we have 2 cases: - // 1. field with different name override tag - // 2. field with different name removes tag - // so we need to save both tag&name - seen[name] = fld.Name() - tgt.Properties[name] = ps - } - - if tgt == nil { - return nil - } - if hasAllOf && len(tgt.Properties) > 0 { - schema.AllOf = append(schema.AllOf, *tgt) - } - for k := range tgt.Properties { - if _, ok := seen[k]; !ok { - delete(tgt.Properties, k) - } - } - return nil -} - -func (s *schemaBuilder) buildAllOf(tpe types.Type, schema *spec.Schema) error { - debugLog("allOf %s", tpe.Underlying()) - - switch ftpe := tpe.(type) { - case *types.Pointer: - return s.buildAllOf(ftpe.Elem(), schema) - case *types.Named: - return s.buildNamedAllOf(ftpe, schema) - case *types.Alias: - debugLog("allOf member is alias %v => %v", ftpe, ftpe.Rhs()) - tgt := schemaTypable{schema: schema} - return s.buildAlias(ftpe, tgt) - case *types.TypeParam: - log.Printf("WARNING: generic type parameters are not supported yet %[1]v (%[1]T). Skipped", ftpe) - return nil - case *types.Chan: - log.Printf("WARNING: channels are not supported %[1]v (%[1]T). Skipped", ftpe) - return nil - case *types.Signature: - log.Printf("WARNING: functions are not supported %[1]v (%[1]T). Skipped", ftpe) - return nil - default: - log.Printf("WARNING: missing allOf parser for a %T, skipping field", ftpe) - return fmt.Errorf("unable to resolve allOf member for: %v", ftpe) - } -} - -func (s *schemaBuilder) buildNamedAllOf(ftpe *types.Named, schema *spec.Schema) error { - switch utpe := ftpe.Underlying().(type) { - case *types.Struct: - decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name()) - if !found { - return fmt.Errorf("can't find source file for struct: %s", ftpe.String()) - } - - if isStdTime(ftpe.Obj()) { - schema.Typed("string", "date-time") - return nil - } - - if sfnm, isf := strfmtName(decl.Comments); isf { - schema.Typed("string", sfnm) - return nil - } - - if decl.HasModelAnnotation() { - return s.makeRef(decl, schemaTypable{schema, 0}) - } - - return s.buildFromStruct(decl, utpe, schema, make(map[string]string)) - case *types.Interface: - decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name()) - if !found { - return fmt.Errorf("can't find source file for interface: %s", ftpe.String()) - } - - if sfnm, isf := strfmtName(decl.Comments); isf { - schema.Typed("string", sfnm) - return nil - } - - if decl.HasModelAnnotation() { - return s.makeRef(decl, schemaTypable{schema, 0}) - } - - return s.buildFromInterface(decl, utpe, schema, make(map[string]string)) - case *types.TypeParam: - log.Printf("WARNING: generic type parameters are not supported yet %[1]v (%[1]T). Skipped", ftpe) - return nil - case *types.Chan: - log.Printf("WARNING: channels are not supported %[1]v (%[1]T). Skipped", ftpe) - return nil - case *types.Signature: - log.Printf("WARNING: functions are not supported %[1]v (%[1]T). Skipped", ftpe) - return nil - default: - log.Printf( - "WARNING: can't figure out object type for allOf named type (%T): %v", - ftpe, utpe, - ) - return fmt.Errorf("unable to locate source file for allOf (%T): %v", - ftpe, utpe, - ) - } -} - -func (s *schemaBuilder) buildEmbedded(tpe types.Type, schema *spec.Schema, seen map[string]string) error { - debugLog("embedded %v", tpe.Underlying()) - - switch ftpe := tpe.(type) { - case *types.Pointer: - return s.buildEmbedded(ftpe.Elem(), schema, seen) - case *types.Named: - return s.buildNamedEmbedded(ftpe, schema, seen) - case *types.Alias: - debugLog("embedded alias %v => %v", ftpe, ftpe.Rhs()) - tgt := schemaTypable{schema, 0} - return s.buildAlias(ftpe, tgt) - case *types.Union: // e.g. type X interface{ ~uint16 | ~float32 } - log.Printf("WARNING: union type constraints are not supported yet %[1]v (%[1]T). Skipped", ftpe) - return nil - case *types.TypeParam: - log.Printf("WARNING: generic type parameters are not supported yet %[1]v (%[1]T). Skipped", ftpe) - return nil - case *types.Chan: - log.Printf("WARNING: channels are not supported %[1]v (%[1]T). Skipped", ftpe) - return nil - case *types.Signature: - log.Printf("WARNING: functions are not supported %[1]v (%[1]T). Skipped", ftpe) - return nil - default: - log.Printf("WARNING: Missing embedded parser for a %T, skipping model\n", ftpe) - return nil - } -} - -func (s *schemaBuilder) buildNamedEmbedded(ftpe *types.Named, schema *spec.Schema, seen map[string]string) error { - debugLog("embedded named type: %T", ftpe.Underlying()) - if unsupportedBuiltin(ftpe) { - log.Printf("WARNING: skipped unsupported builtin type: %v", ftpe) - - return nil - } - - switch utpe := ftpe.Underlying().(type) { - case *types.Struct: - decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name()) - if !found { - return fmt.Errorf("can't find source file for struct: %s", ftpe.String()) - } - - return s.buildFromStruct(decl, utpe, schema, seen) - case *types.Interface: - if utpe.Empty() { - return nil - } - o := ftpe.Obj() - if isAny(o) { - return nil - } - if isStdError(o) { - tgt := schemaTypable{schema: schema} - tgt.AddExtension("x-go-type", o.Name()) - return swaggerSchemaForType(o.Name(), tgt) - } - mustNotBeABuiltinType(o) - - decl, found := s.ctx.FindModel(o.Pkg().Path(), o.Name()) - if !found { - return fmt.Errorf("can't find source file for struct: %s", ftpe.String()) - } - return s.buildFromInterface(decl, utpe, schema, seen) - case *types.Union: // e.g. type X interface{ ~uint16 | ~float32 } - log.Printf("WARNING: union type constraints are not supported yet %[1]v (%[1]T). Skipped", utpe) - return nil - case *types.TypeParam: - log.Printf("WARNING: generic type parameters are not supported yet %[1]v (%[1]T). Skipped", utpe) - return nil - case *types.Chan: - log.Printf("WARNING: channels are not supported %[1]v (%[1]T). Skipped", utpe) - return nil - case *types.Signature: - log.Printf("WARNING: functions are not supported %[1]v (%[1]T). Skipped", utpe) - return nil - default: - log.Printf("WARNING: can't figure out object type for embedded named type (%T): %v", - ftpe, utpe, - ) - return nil - } -} - -func (s *schemaBuilder) makeRef(decl *entityDecl, prop swaggerTypable) error { - nm, _ := decl.Names() - ref, err := spec.NewRef("#/definitions/" + nm) - if err != nil { - return err - } - prop.SetRef(ref) - s.postDecls = append(s.postDecls, decl) - return nil -} - -func (s *schemaBuilder) createParser(nm string, schema, ps *spec.Schema, fld *ast.Field) *sectionedParser { - sp := new(sectionedParser) - - schemeType, err := ps.Type.MarshalJSON() - if err != nil { - return nil - } - - if ps.Ref.String() == "" { - sp.setDescription = func(lines []string) { - ps.Description = joinDropLast(lines) - enumDesc := getEnumDesc(ps.Extensions) - if enumDesc != "" { - ps.Description += "\n" + enumDesc - } - } - sp.taggers = []tagParser{ - newSingleLineTagParser("maximum", &setMaximum{schemaValidations{ps}, rxf(rxMaximumFmt, "")}), - newSingleLineTagParser("minimum", &setMinimum{schemaValidations{ps}, rxf(rxMinimumFmt, "")}), - newSingleLineTagParser("multipleOf", &setMultipleOf{schemaValidations{ps}, rxf(rxMultipleOfFmt, "")}), - newSingleLineTagParser("minLength", &setMinLength{schemaValidations{ps}, rxf(rxMinLengthFmt, "")}), - newSingleLineTagParser("maxLength", &setMaxLength{schemaValidations{ps}, rxf(rxMaxLengthFmt, "")}), - newSingleLineTagParser("pattern", &setPattern{schemaValidations{ps}, rxf(rxPatternFmt, "")}), - newSingleLineTagParser("minItems", &setMinItems{schemaValidations{ps}, rxf(rxMinItemsFmt, "")}), - newSingleLineTagParser("maxItems", &setMaxItems{schemaValidations{ps}, rxf(rxMaxItemsFmt, "")}), - newSingleLineTagParser("unique", &setUnique{schemaValidations{ps}, rxf(rxUniqueFmt, "")}), - newSingleLineTagParser("enum", &setEnum{schemaValidations{ps}, rxf(rxEnumFmt, "")}), - newSingleLineTagParser("default", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}), - newSingleLineTagParser("type", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}), - newSingleLineTagParser("example", &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxExampleFmt, "")}), - newSingleLineTagParser("required", &setRequiredSchema{schema, nm}), - newSingleLineTagParser("readOnly", &setReadOnlySchema{ps}), - newSingleLineTagParser("discriminator", &setDiscriminator{schema, nm}), - newMultiLineTagParser("YAMLExtensionsBlock", newYamlParser(rxExtensions, schemaVendorExtensibleSetter(ps)), true), - } - - itemsTaggers := func(items *spec.Schema, level int) []tagParser { - schemeType, err := items.Type.MarshalJSON() - if err != nil { - return nil - } - // the expression is 1-index based not 0-index - itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1) - return []tagParser{ - newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{schemaValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{schemaValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{schemaValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{schemaValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{schemaValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{schemaValidations{items}, rxf(rxPatternFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{schemaValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{schemaValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{schemaValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{schemaValidations{items}, rxf(rxEnumFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}), - newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxExampleFmt, itemsPrefix)}), - } - } - - var parseArrayTypes func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error) - parseArrayTypes = func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error) { - if items == nil || items.Schema == nil { - return []tagParser{}, nil - } - switch iftpe := expr.(type) { - case *ast.ArrayType: - eleTaggers := itemsTaggers(items.Schema, level) - sp.taggers = append(eleTaggers, sp.taggers...) - otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Schema.Items, level+1) - if err != nil { - return nil, err - } - return otherTaggers, nil - case *ast.Ident: - taggers := []tagParser{} - if iftpe.Obj == nil { - taggers = itemsTaggers(items.Schema, level) - } - otherTaggers, err := parseArrayTypes(expr, items.Schema.Items, level+1) - if err != nil { - return nil, err - } - return append(taggers, otherTaggers...), nil - case *ast.StarExpr: - otherTaggers, err := parseArrayTypes(iftpe.X, items, level) - if err != nil { - return nil, err - } - return otherTaggers, nil - default: - return nil, fmt.Errorf("unknown field type element for %q", nm) - } - } - // check if this is a primitive, if so parse the validations from the - // doc comments of the slice declaration. - if ftped, ok := fld.Type.(*ast.ArrayType); ok { - taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0) - if err != nil { - return sp - } - sp.taggers = append(taggers, sp.taggers...) - } - - } else { - sp.taggers = []tagParser{ - newSingleLineTagParser("required", &setRequiredSchema{schema, nm}), - } - } - return sp -} - -func schemaVendorExtensibleSetter(meta *spec.Schema) func(json.RawMessage) error { - return func(jsonValue json.RawMessage) error { - var jsonData spec.Extensions - err := json.Unmarshal(jsonValue, &jsonData) - if err != nil { - return err - } - for k := range jsonData { - if !rxAllowedExtensions.MatchString(k) { - return fmt.Errorf("invalid schema extension name, should start from `x-`: %s", k) - } - } - meta.Extensions = jsonData - return nil - } -} - -type tagOptions []string - -func (t tagOptions) Contain(option string) bool { - for i := 1; i < len(t); i++ { - if t[i] == option { - return true - } - } - return false -} - -func (t tagOptions) Name() string { - return t[0] -} - -func parseJSONTag(field *ast.Field) (name string, ignore, isString, omitEmpty bool, err error) { - if len(field.Names) > 0 { - name = field.Names[0].Name - } - if field.Tag == nil || len(strings.TrimSpace(field.Tag.Value)) == 0 { - return name, false, false, false, nil - } - - tv, err := strconv.Unquote(field.Tag.Value) - if err != nil { - return name, false, false, false, err - } - - if strings.TrimSpace(tv) != "" { - st := reflect.StructTag(tv) - jsonParts := tagOptions(strings.Split(st.Get("json"), ",")) - - if jsonParts.Contain("string") { - // Need to check if the field type is a scalar. Otherwise, the - // ",string" directive doesn't apply. - isString = isFieldStringable(field.Type) - } - - omitEmpty = jsonParts.Contain("omitempty") - - switch jsonParts.Name() { - case "-": - return name, true, isString, omitEmpty, nil - case "": - return name, false, isString, omitEmpty, nil - default: - return jsonParts.Name(), false, isString, omitEmpty, nil - } - } - return name, false, false, false, nil -} - -// isFieldStringable check if the field type is a scalar. If the field type is -// *ast.StarExpr and is pointer type, check if it refers to a scalar. -// Otherwise, the ",string" directive doesn't apply. -func isFieldStringable(tpe ast.Expr) bool { - if ident, ok := tpe.(*ast.Ident); ok { - switch ident.Name { - case "int", "int8", "int16", "int32", "int64", - "uint", "uint8", "uint16", "uint32", "uint64", - "float64", "string", "bool": - return true - } - } else if starExpr, ok := tpe.(*ast.StarExpr); ok { - return isFieldStringable(starExpr.X) - } else { - return false - } - return false -} - -func isTextMarshaler(tpe types.Type) bool { - encodingPkg, err := importer.Default().Import("encoding") - if err != nil { - return false - } - ifc := encodingPkg.Scope().Lookup("TextMarshaler").Type().Underlying().(*types.Interface) // TODO: there is a better way to check this - - return types.Implements(tpe, ifc) -} - -func isStdTime(o *types.TypeName) bool { - return o.Pkg() != nil && o.Pkg().Name() == "time" && o.Name() == "Time" -} - -func isStdError(o *types.TypeName) bool { - return o.Pkg() == nil && o.Name() == "error" -} - -func isStdJSONRawMessage(o *types.TypeName) bool { - return o.Pkg() != nil && o.Pkg().Path() == "encoding/json" && o.Name() == "RawMessage" -} - -func isAny(o *types.TypeName) bool { - return o.Pkg() == nil && o.Name() == "any" -} |
