niki/vendor/github.com/swaggo/swag/v2/field_parserv3.go

597 lines
13 KiB
Go

package swag
import (
"fmt"
"go/ast"
"reflect"
"strconv"
"strings"
"github.com/sv-tools/openapi/spec"
)
type structFieldV3 struct {
schemaType string
arrayType string
formatType string
maximum *int
minimum *int
multipleOf *int
maxLength *int
minLength *int
maxItems *int
minItems *int
exampleValue interface{}
enums []interface{}
enumVarNames []interface{}
unique bool
pattern string
}
func (sf *structFieldV3) setOneOf(valValue string) {
if len(sf.enums) != 0 {
return
}
enumType := sf.schemaType
if sf.schemaType == ARRAY {
enumType = sf.arrayType
}
valValues := parseOneOfParam2(valValue)
for i := range valValues {
value, err := defineType(enumType, valValues[i])
if err != nil {
continue
}
sf.enums = append(sf.enums, value)
}
}
func (sf *structFieldV3) setMin(valValue string) {
value, err := strconv.Atoi(valValue)
if err != nil {
return
}
switch sf.schemaType {
case INTEGER, NUMBER:
sf.minimum = &value
case STRING:
sf.minLength = &value
case ARRAY:
sf.minItems = &value
}
}
func (sf *structFieldV3) setMax(valValue string) {
value, err := strconv.Atoi(valValue)
if err != nil {
return
}
switch sf.schemaType {
case INTEGER, NUMBER:
sf.maximum = &value
case STRING:
sf.maxLength = &value
case ARRAY:
sf.maxItems = &value
}
}
type tagBaseFieldParserV3 struct {
p *Parser
file *ast.File
field *ast.Field
tag reflect.StructTag
}
func newTagBaseFieldParserV3(p *Parser, file *ast.File, field *ast.Field) FieldParserV3 {
fieldParser := tagBaseFieldParserV3{
p: p,
file: file,
field: field,
tag: "",
}
if fieldParser.field.Tag != nil {
fieldParser.tag = reflect.StructTag(strings.ReplaceAll(field.Tag.Value, "`", ""))
}
return &fieldParser
}
func (ps *tagBaseFieldParserV3) CustomSchema() (*spec.RefOrSpec[spec.Schema], error) {
if ps.field.Tag == nil {
return nil, nil
}
typeTag := ps.tag.Get(swaggerTypeTag)
if typeTag != "" {
return BuildCustomSchemaV3(strings.Split(typeTag, ","))
}
return nil, nil
}
// ComplementSchema complement schema with field properties
func (ps *tagBaseFieldParserV3) ComplementSchema(schema *spec.RefOrSpec[spec.Schema]) error {
if schema.Spec == nil {
schema = ps.p.openAPI.Components.Spec.Schemas[strings.ReplaceAll(schema.Ref.Ref, "#/components/schemas/", "")]
if schema == nil {
return fmt.Errorf("could not resolve schema for ref %s", schema.Ref.Ref)
}
}
types := ps.p.GetSchemaTypePathV3(schema, 2)
if len(types) == 0 {
return fmt.Errorf("invalid type for field: %s", ps.field.Names[0])
}
if schema.Ref != nil { //IsRefSchema(schema)
// TODO fetch existing schema from components
var newSchema = spec.Schema{}
err := ps.complementSchema(&newSchema, types)
if err != nil {
return err
}
if !reflect.ValueOf(newSchema).IsZero() {
newSchema.AllOf = []*spec.RefOrSpec[spec.Schema]{{Spec: schema.Spec}}
*schema = spec.RefOrSpec[spec.Schema]{Spec: &newSchema}
}
return nil
}
return ps.complementSchema(schema.Spec, types)
}
// complementSchema complement schema with field properties
func (ps *tagBaseFieldParserV3) complementSchema(schema *spec.Schema, types []string) error {
if ps.field.Tag == nil {
if ps.field.Doc != nil {
schema.Description = strings.TrimSpace(ps.field.Doc.Text())
}
if schema.Description == "" && ps.field.Comment != nil {
schema.Description = strings.TrimSpace(ps.field.Comment.Text())
}
return nil
}
field := &structFieldV3{
schemaType: types[0],
formatType: ps.tag.Get(formatTag),
}
if len(types) > 1 && (types[0] == ARRAY || types[0] == OBJECT) {
field.arrayType = types[1]
}
jsonTagValue := ps.tag.Get(jsonTag)
bindingTagValue := ps.tag.Get(bindingTag)
if bindingTagValue != "" {
field.parseValidTags(bindingTagValue)
}
validateTagValue := ps.tag.Get(validateTag)
if validateTagValue != "" {
field.parseValidTags(validateTagValue)
}
enumsTagValue := ps.tag.Get(enumsTag)
if enumsTagValue != "" {
err := field.parseEnumTags(enumsTagValue)
if err != nil {
return err
}
}
if IsNumericType(field.schemaType) || IsNumericType(field.arrayType) {
maximum, err := getIntTagV3(ps.tag, maximumTag)
if err != nil {
return err
}
if maximum != nil {
field.maximum = maximum
}
minimum, err := getIntTagV3(ps.tag, minimumTag)
if err != nil {
return err
}
if minimum != nil {
field.minimum = minimum
}
multipleOf, err := getIntTagV3(ps.tag, multipleOfTag)
if err != nil {
return err
}
if multipleOf != nil {
field.multipleOf = multipleOf
}
}
if field.schemaType == STRING || field.arrayType == STRING {
maxLength, err := getIntTagV3(ps.tag, maxLengthTag)
if err != nil {
return err
}
if maxLength != nil {
field.maxLength = maxLength
}
minLength, err := getIntTagV3(ps.tag, minLengthTag)
if err != nil {
return err
}
if minLength != nil {
field.minLength = minLength
}
pattern, ok := ps.tag.Lookup(patternTag)
if ok {
field.pattern = pattern
}
}
// json:"name,string" or json:",string"
exampleTagValue, ok := ps.tag.Lookup(exampleTag)
if ok {
field.exampleValue = exampleTagValue
if !strings.Contains(jsonTagValue, ",string") {
example, err := defineTypeOfExample(field.schemaType, field.arrayType, exampleTagValue)
if err != nil {
return err
}
field.exampleValue = example
}
}
// perform this after setting everything else (min, max, etc...)
if strings.Contains(jsonTagValue, ",string") {
// @encoding/json: "It applies only to fields of string, floating point, integer, or boolean types."
defaultValues := map[string]string{
// Zero Values as string
STRING: "",
INTEGER: "0",
BOOLEAN: "false",
NUMBER: "0",
}
defaultValue, ok := defaultValues[field.schemaType]
if ok {
field.schemaType = STRING
*schema = *PrimitiveSchemaV3(field.schemaType).Spec
if field.exampleValue == nil {
// if exampleValue is not defined by the user,
// we will force an example with a correct value
// (eg: int->"0", bool:"false")
field.exampleValue = defaultValue
}
}
}
if ps.field.Doc != nil {
schema.Description = strings.TrimSpace(ps.field.Doc.Text())
}
if schema.Description == "" && ps.field.Comment != nil {
schema.Description = strings.TrimSpace(ps.field.Comment.Text())
}
schema.ReadOnly = ps.tag.Get(readOnlyTag) == "true"
defaultTagValue := ps.tag.Get(defaultTag)
if defaultTagValue != "" {
value, err := defineType(field.schemaType, defaultTagValue)
if err != nil {
return err
}
schema.Default = value
}
schema.Example = field.exampleValue
if field.schemaType != ARRAY {
schema.Format = field.formatType
}
extensionsTagValue := ps.tag.Get(extensionsTag)
if extensionsTagValue != "" {
schema.Extensions = setExtensionParam(extensionsTagValue)
}
varNamesTag := ps.tag.Get("x-enum-varnames")
if varNamesTag != "" {
varNames := strings.Split(varNamesTag, ",")
if len(varNames) != len(field.enums) {
return fmt.Errorf("invalid count of x-enum-varnames. expected %d, got %d", len(field.enums), len(varNames))
}
field.enumVarNames = nil
for _, v := range varNames {
field.enumVarNames = append(field.enumVarNames, v)
}
if field.schemaType == ARRAY {
// Add the var names in the items schema
if schema.Items.Schema.Spec.Extensions == nil {
schema.Items.Schema.Spec.Extensions = map[string]interface{}{}
}
schema.Items.Schema.Spec.Extensions[enumVarNamesExtension] = field.enumVarNames
} else {
// Add to top level schema
if schema.Extensions == nil {
schema.Extensions = map[string]interface{}{}
}
schema.Extensions[enumVarNamesExtension] = field.enumVarNames
}
}
var oneOfSchemas []*spec.RefOrSpec[spec.Schema]
oneOfTagValue := ps.tag.Get(oneOfTag)
if oneOfTagValue != "" {
oneOfTypes := strings.Split((oneOfTagValue), ",")
for _, oneOfType := range oneOfTypes {
oneOfSchema, err := ps.p.getTypeSchemaV3(oneOfType, ps.file, true)
if err != nil {
return fmt.Errorf("can't find oneOf type %q: %v", oneOfType, err)
}
oneOfSchemas = append(oneOfSchemas, oneOfSchema)
}
}
elemSchema := schema
if field.schemaType == ARRAY {
// For Array only
schema.MaxItems = field.maxItems
schema.MinItems = field.minItems
schema.UniqueItems = &field.unique
elemSchema = schema.Items.Schema.Spec
if elemSchema == nil {
elemSchema = ps.p.getSchemaByRef(schema.Items.Schema.Ref)
}
elemSchema.Format = field.formatType
}
elemSchema.Maximum = field.maximum
elemSchema.Minimum = field.minimum
elemSchema.MultipleOf = field.multipleOf
elemSchema.MaxLength = field.maxLength
elemSchema.MinLength = field.minLength
elemSchema.Enum = field.enums
elemSchema.Pattern = field.pattern
elemSchema.OneOf = oneOfSchemas
return nil
}
func getIntTagV3(structTag reflect.StructTag, tagName string) (*int, error) {
strValue := structTag.Get(tagName)
if strValue == "" {
return nil, nil
}
value, err := strconv.Atoi(strValue)
if err != nil {
return nil, fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err)
}
return &value, nil
}
func parseValidTagsV3(validTag string, sf *structFieldV3) {
// `validate:"required,max=10,min=1"`
// ps. required checked by IsRequired().
for _, val := range strings.Split(validTag, ",") {
var (
valValue string
keyVal = strings.Split(val, "=")
)
switch len(keyVal) {
case 1:
case 2:
valValue = strings.ReplaceAll(strings.ReplaceAll(keyVal[1], utf8HexComma, ","), utf8Pipe, "|")
default:
continue
}
switch keyVal[0] {
case "max", "lte":
sf.setMax(valValue)
case "min", "gte":
sf.setMin(valValue)
case "oneof":
if strings.Contains(validTag, "swaggerIgnore") {
continue
}
sf.setOneOf(valValue)
case "unique":
if sf.schemaType == ARRAY {
sf.unique = true
}
case "dive":
// ignore dive
return
default:
continue
}
}
}
func (sf *structFieldV3) parseValidTags(validTag string) {
// `validate:"required,max=10,min=1"`
// ps. required checked by IsRequired().
for _, val := range strings.Split(validTag, ",") {
var (
valValue string
keyVal = strings.Split(val, "=")
)
switch len(keyVal) {
case 1:
case 2:
valValue = strings.ReplaceAll(strings.ReplaceAll(keyVal[1], utf8HexComma, ","), utf8Pipe, "|")
default:
continue
}
switch keyVal[0] {
case "max", "lte":
sf.setMax(valValue)
case "min", "gte":
sf.setMin(valValue)
case "oneof":
if strings.Contains(validTag, "swaggerIgnore") {
continue
}
sf.setOneOf(valValue)
case "unique":
if sf.schemaType == ARRAY {
sf.unique = true
}
case "dive":
// ignore dive
return
default:
continue
}
}
}
func (sf *structFieldV3) parseEnumTags(enumTag string) error {
enumType := sf.schemaType
if sf.schemaType == ARRAY {
enumType = sf.arrayType
}
sf.enums = nil
for _, e := range strings.Split(enumTag, ",") {
value, err := defineType(enumType, e)
if err != nil {
return err
}
sf.enums = append(sf.enums, value)
}
return nil
}
func (ps *tagBaseFieldParserV3) ShouldSkip() bool {
// Skip non-exported fields.
if ps.field.Names != nil && !ast.IsExported(ps.field.Names[0].Name) {
return true
}
if ps.field.Tag == nil {
return false
}
ignoreTag := ps.tag.Get(swaggerIgnoreTag)
if strings.EqualFold(ignoreTag, "true") {
return true
}
// json:"tag,hoge"
name := strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0])
if name == "-" {
return true
}
return false
}
func (ps *tagBaseFieldParserV3) FieldName() (string, error) {
var name string
if ps.field.Tag != nil {
// json:"tag,hoge"
name = strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0])
if name != "" {
return name, nil
}
// use "form" tag over json tag
name = ps.FormName()
if name != "" {
return name, nil
}
}
if ps.field.Names == nil {
return "", nil
}
switch ps.p.PropNamingStrategy {
case SnakeCase:
return toSnakeCase(ps.field.Names[0].Name), nil
case PascalCase:
return ps.field.Names[0].Name, nil
default:
return toLowerCamelCase(ps.field.Names[0].Name), nil
}
}
func (ps *tagBaseFieldParserV3) FormName() string {
if ps.field.Tag != nil {
return strings.TrimSpace(strings.Split(ps.tag.Get(formTag), ",")[0])
}
return ""
}
func (ps *tagBaseFieldParserV3) IsRequired() (bool, error) {
if ps.field.Tag == nil {
return false, nil
}
bindingTag := ps.tag.Get(bindingTag)
if bindingTag != "" {
for _, val := range strings.Split(bindingTag, ",") {
switch val {
case requiredLabel:
return true, nil
case optionalLabel:
return false, nil
}
}
}
validateTag := ps.tag.Get(validateTag)
if validateTag != "" {
for _, val := range strings.Split(validateTag, ",") {
switch val {
case requiredLabel:
return true, nil
case optionalLabel:
return false, nil
}
}
}
return ps.p.RequiredByDefault, nil
}