github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/cmd/internal/openapi/v3/helper.go (about)

     1  package v3
     2  
     3  import (
     4  	"encoding/json"
     5  	"github.com/unionj-cloud/go-doudou/cmd/internal/astutils"
     6  	"github.com/unionj-cloud/go-doudou/toolkit/copier"
     7  	. "github.com/unionj-cloud/go-doudou/toolkit/openapi/v3"
     8  	"github.com/unionj-cloud/go-doudou/toolkit/sliceutils"
     9  	"github.com/unionj-cloud/go-doudou/toolkit/stringutils"
    10  	"regexp"
    11  	"strings"
    12  	"unicode"
    13  )
    14  
    15  // Schemas from components of OpenAPI3.0 json document
    16  var Schemas = make(map[string]Schema)
    17  
    18  var Enums = make(map[string]astutils.EnumMeta)
    19  
    20  // SchemaNames schema names from components of OpenAPI3.0 json document
    21  // also struct names from vo package
    22  var SchemaNames []string
    23  
    24  // SchemaOf reference https://golang.org/pkg/builtin/
    25  // type bool
    26  // type byte
    27  // type complex128
    28  // type complex64
    29  // type error
    30  // type float32
    31  // type float64
    32  // type int
    33  // type int16
    34  // type int32
    35  // type int64
    36  // type int8
    37  // type rune
    38  // type string
    39  // type uint
    40  // type uint16
    41  // type uint32
    42  // type uint64
    43  // type uint8
    44  // type uintptr
    45  func SchemaOf(field astutils.FieldMeta) *Schema {
    46  	ft := field.Type
    47  	if IsVarargs(ft) {
    48  		ft = ToSlice(ft)
    49  	}
    50  	ft = strings.TrimLeft(ft, "*")
    51  	switch ft {
    52  	case "int", "int8", "int16", "int32", "uint", "uint8", "uint16", "uint32", "byte", "rune", "complex64", "complex128":
    53  		return Int
    54  	case "int64", "uint64", "uintptr":
    55  		return Int64
    56  	case "bool":
    57  		return Bool
    58  	case "string", "error", "[]rune", "[]byte":
    59  		return String
    60  	case "float32":
    61  		return Float32
    62  	case "float64":
    63  		return Float64
    64  	case "multipart.FileHeader", "v3.FileModel":
    65  		return File
    66  	default:
    67  		return handleDefaultCase(ft)
    68  	}
    69  }
    70  
    71  func handleDefaultCase(ft string) *Schema {
    72  	if strings.HasPrefix(ft, "map[") {
    73  		elem := ft[strings.Index(ft, "]")+1:]
    74  		return &Schema{
    75  			Type: ObjectT,
    76  			AdditionalProperties: SchemaOf(astutils.FieldMeta{
    77  				Type: elem,
    78  			}),
    79  		}
    80  	}
    81  	if strings.HasPrefix(ft, "[") {
    82  		elem := ft[strings.Index(ft, "]")+1:]
    83  		return &Schema{
    84  			Type: ArrayT,
    85  			Items: SchemaOf(astutils.FieldMeta{
    86  				Type: elem,
    87  			}),
    88  		}
    89  	}
    90  	re := regexp.MustCompile(`anonystruct«(.*)»`)
    91  	if re.MatchString(ft) {
    92  		result := re.FindStringSubmatch(ft)
    93  		var structmeta astutils.StructMeta
    94  		json.Unmarshal([]byte(result[1]), &structmeta)
    95  		schema := NewSchema(structmeta)
    96  		return &schema
    97  	}
    98  	var title string
    99  	if !strings.Contains(ft, ".") {
   100  		title = ft
   101  	}
   102  	if stringutils.IsEmpty(title) {
   103  		title = ft[strings.LastIndex(ft, ".")+1:]
   104  	}
   105  	if stringutils.IsNotEmpty(title) {
   106  		if unicode.IsUpper(rune(title[0])) {
   107  			if sliceutils.StringContains(SchemaNames, title) {
   108  				return &Schema{
   109  					Ref: "#/components/schemas/" + title,
   110  				}
   111  			}
   112  		}
   113  		if enumMeta, ok := Enums[title]; ok {
   114  			enumSchema := &Schema{
   115  				Type: StringT,
   116  				Enum: sliceutils.StringSlice2InterfaceSlice(enumMeta.Values),
   117  			}
   118  			if len(enumMeta.Values) > 0 {
   119  				enumSchema.Default = enumMeta.Values[0]
   120  			}
   121  			return enumSchema
   122  		}
   123  	}
   124  	return Any
   125  }
   126  
   127  var castFuncMap = map[string]string{
   128  	"bool":          "ToBool",
   129  	"float64":       "ToFloat64",
   130  	"float32":       "ToFloat32",
   131  	"int64":         "ToInt64",
   132  	"int32":         "ToInt32",
   133  	"int16":         "ToInt16",
   134  	"int8":          "ToInt8",
   135  	"int":           "ToInt",
   136  	"uint":          "ToUint",
   137  	"uint8":         "ToUint8",
   138  	"uint16":        "ToUint16",
   139  	"uint32":        "ToUint32",
   140  	"uint64":        "ToUint64",
   141  	"error":         "ToError",
   142  	"[]byte":        "ToByteSlice",
   143  	"[]rune":        "ToRuneSlice",
   144  	"[]interface{}": "ToInterfaceSlice",
   145  	"[]bool":        "ToBoolSlice",
   146  	"[]int":         "ToIntSlice",
   147  	"[]float64":     "ToFloat64Slice",
   148  	"[]float32":     "ToFloat32Slice",
   149  	"[]int64":       "ToInt64Slice",
   150  	"[]int32":       "ToInt32Slice",
   151  	"[]int16":       "ToInt16Slice",
   152  	"[]int8":        "ToInt8Slice",
   153  	"[]uint":        "ToUintSlice",
   154  	"[]uint8":       "ToUint8Slice",
   155  	"[]uint16":      "ToUint16Slice",
   156  	"[]uint32":      "ToUint32Slice",
   157  	"[]uint64":      "ToUint64Slice",
   158  	"[]error":       "ToErrorSlice",
   159  	"[][]byte":      "ToByteSliceSlice",
   160  	"[][]rune":      "ToRuneSliceSlice",
   161  }
   162  
   163  func IsSupport(t string) bool {
   164  	if IsVarargs(t) {
   165  		t = ToSlice(t)
   166  	}
   167  	_, exists := castFuncMap[strings.TrimLeft(t, "*")]
   168  	return exists
   169  }
   170  
   171  func IsOptional(t string) bool {
   172  	return strings.HasPrefix(t, "*") || strings.HasPrefix(t, "...")
   173  }
   174  
   175  func IsSlice(t string) bool {
   176  	return strings.Contains(t, "[") || strings.HasPrefix(t, "...")
   177  }
   178  
   179  func IsVarargs(t string) bool {
   180  	return strings.HasPrefix(t, "...")
   181  }
   182  
   183  func ToSlice(t string) string {
   184  	return "[]" + strings.TrimPrefix(t, "...")
   185  }
   186  
   187  func CastFunc(t string) string {
   188  	if IsVarargs(t) {
   189  		t = ToSlice(t)
   190  	}
   191  	return castFuncMap[strings.TrimLeft(t, "*")]
   192  }
   193  
   194  // CopySchema as SchemaOf returns pointer, so deepcopy the schema the pointer points
   195  func CopySchema(field astutils.FieldMeta) Schema {
   196  	var schema Schema
   197  	err := copier.DeepCopy(SchemaOf(field), &schema)
   198  	if err != nil {
   199  		panic(err)
   200  	}
   201  	return schema
   202  }
   203  
   204  func RefAddDoc(schema *Schema, doc string) {
   205  	if stringutils.IsNotEmpty(schema.Ref) {
   206  		title := strings.TrimPrefix(schema.Ref, "#/components/schemas/")
   207  		temp := Schemas[title]
   208  		temp.Description = strings.Join([]string{doc, temp.Description}, "\n")
   209  		Schemas[title] = temp
   210  	} else {
   211  		schema.Description = doc
   212  	}
   213  }
   214  
   215  // NewSchema new schema from astutils.StructMeta
   216  func NewSchema(structmeta astutils.StructMeta) Schema {
   217  	properties := make(map[string]*Schema)
   218  	var required []string
   219  	for _, field := range structmeta.Fields {
   220  		fschema := CopySchema(field)
   221  		RefAddDoc(&fschema, strings.Join(field.Comments, "\n"))
   222  		properties[field.DocName] = &fschema
   223  		if !strings.HasPrefix(field.Type, "*") {
   224  			required = append(required, field.DocName)
   225  		}
   226  	}
   227  	return Schema{
   228  		Title:       structmeta.Name,
   229  		Type:        ObjectT,
   230  		Properties:  properties,
   231  		Description: strings.Join(structmeta.Comments, "\n"),
   232  		Required:    required,
   233  	}
   234  }
   235  
   236  // IsBuiltin check whether field is built-in type https://pkg.go.dev/builtin or not
   237  func IsBuiltin(field astutils.FieldMeta) bool {
   238  	simples := []interface{}{Int, Int64, Bool, String, Float32, Float64}
   239  	types := []interface{}{IntegerT, StringT, BooleanT, NumberT}
   240  	pschema := SchemaOf(field)
   241  	if sliceutils.Contains(simples, pschema) || (sliceutils.Contains(types, pschema.Type) && pschema.Format != BinaryF) {
   242  		return true
   243  	}
   244  	if pschema.Type == ArrayT && (sliceutils.Contains(simples, pschema.Items) || (sliceutils.Contains(types, pschema.Items.Type) && pschema.Items.Format != BinaryF)) {
   245  		return true
   246  	}
   247  	return false
   248  }
   249  
   250  // IsEnum check whether field is enum
   251  func IsEnum(field astutils.FieldMeta) bool {
   252  	pschema := SchemaOf(field)
   253  	return len(pschema.Enum) > 0 || (pschema.Type == ArrayT && len(pschema.Items.Enum) > 0)
   254  }
   255  
   256  // IsStruct check whether field is struct type
   257  func IsStruct(field astutils.FieldMeta) bool {
   258  	return stringutils.IsNotEmpty(SchemaOf(field).Ref)
   259  }
   260  
   261  // ElementType get element type string from slice
   262  func ElementType(t string) string {
   263  	if IsVarargs(t) {
   264  		return strings.TrimPrefix(t, "...")
   265  	}
   266  	return t[strings.Index(t, "]")+1:]
   267  }