github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/openapi/v3/helper.go (about) 1 package v3 2 3 import ( 4 "encoding/json" 5 "github.com/getkin/kin-openapi/openapi2" 6 "github.com/getkin/kin-openapi/openapi2conv" 7 "github.com/getkin/kin-openapi/openapi3" 8 "github.com/go-resty/resty/v2" 9 "github.com/unionj-cloud/go-doudou/v2/toolkit/astutils" 10 "github.com/unionj-cloud/go-doudou/v2/toolkit/copier" 11 "github.com/unionj-cloud/go-doudou/v2/toolkit/sliceutils" 12 "github.com/unionj-cloud/go-doudou/v2/toolkit/stringutils" 13 "io/ioutil" 14 "os" 15 "path/filepath" 16 "regexp" 17 "strings" 18 "unicode" 19 ) 20 21 // Schemas from components of OpenAPI3.0 json document 22 var Schemas = make(map[string]Schema) 23 24 var Enums = make(map[string]astutils.EnumMeta) 25 26 // SchemaNames schema names from components of OpenAPI3.0 json document 27 // also struct names from vo package 28 var SchemaNames []string 29 30 // SchemaOf reference https://golang.org/pkg/builtin/ 31 // type bool 32 // type byte 33 // type complex128 34 // type complex64 35 // type error 36 // type float32 37 // type float64 38 // type int 39 // type int16 40 // type int32 41 // type int64 42 // type int8 43 // type rune 44 // type string 45 // type uint 46 // type uint16 47 // type uint32 48 // type uint64 49 // type uint8 50 // type uintptr 51 func SchemaOf(field astutils.FieldMeta) *Schema { 52 ft := field.Type 53 if IsVarargs(ft) { 54 ft = ToSlice(ft) 55 } 56 ft = strings.TrimLeft(ft, "*") 57 switch ft { 58 case "int", "int8", "int16", "int32", "uint", "uint8", "uint16", "uint32", "byte", "rune", "complex64", "complex128": 59 return Int 60 case "int64", "uint64", "uintptr": 61 return Int64 62 case "bool": 63 return Bool 64 case "string", "error", "[]rune", "[]byte": 65 return String 66 case "float32": 67 return Float32 68 case "float64": 69 return Float64 70 case "multipart.FileHeader", "v3.FileModel": 71 return File 72 case "time.Time", "customtypes.Time": 73 return Time 74 case "decimal.Decimal": // simple treat decimal.Decimal as string 75 return Decimal 76 default: 77 return handleDefaultCase(ft) 78 } 79 } 80 81 func handleDefaultCase(ft string) *Schema { 82 if strings.HasPrefix(ft, "map[") { 83 elem := ft[strings.Index(ft, "]")+1:] 84 return &Schema{ 85 Type: ObjectT, 86 AdditionalProperties: SchemaOf(astutils.FieldMeta{ 87 Type: elem, 88 }), 89 XMapType: ft, 90 } 91 } 92 if strings.HasPrefix(ft, "[") { 93 elem := ft[strings.Index(ft, "]")+1:] 94 return &Schema{ 95 Type: ArrayT, 96 Items: SchemaOf(astutils.FieldMeta{ 97 Type: elem, 98 }), 99 } 100 } 101 re := regexp.MustCompile(`anonystruct«(.*)»`) 102 if re.MatchString(ft) { 103 result := re.FindStringSubmatch(ft) 104 var structmeta astutils.StructMeta 105 json.Unmarshal([]byte(result[1]), &structmeta) 106 schema := NewSchema(structmeta) 107 return &schema 108 } 109 var title string 110 if !strings.Contains(ft, ".") { 111 title = ft 112 } 113 if stringutils.IsEmpty(title) { 114 title = ft[strings.LastIndex(ft, ".")+1:] 115 } 116 if stringutils.IsNotEmpty(title) { 117 if unicode.IsUpper(rune(title[0])) { 118 if sliceutils.StringContains(SchemaNames, title) { 119 return &Schema{ 120 Ref: "#/components/schemas/" + title, 121 } 122 } 123 } 124 if enumMeta, ok := Enums[title]; ok { 125 enumSchema := &Schema{ 126 Type: StringT, 127 Enum: sliceutils.StringSlice2InterfaceSlice(enumMeta.Values), 128 } 129 if len(enumMeta.Values) > 0 { 130 enumSchema.Default = enumMeta.Values[0] 131 } 132 return enumSchema 133 } 134 } 135 return Any 136 } 137 138 var castFuncMap = map[string]string{ 139 "bool": "ToBool", 140 "float64": "ToFloat64", 141 "float32": "ToFloat32", 142 "int64": "ToInt64", 143 "int32": "ToInt32", 144 "int16": "ToInt16", 145 "int8": "ToInt8", 146 "int": "ToInt", 147 "uint": "ToUint", 148 "uint8": "ToUint8", 149 "uint16": "ToUint16", 150 "uint32": "ToUint32", 151 "uint64": "ToUint64", 152 "error": "ToError", 153 "decimal.Decimal": "ToDecimal", 154 "[]byte": "ToByteSlice", 155 "[]rune": "ToRuneSlice", 156 "[]interface{}": "ToInterfaceSlice", 157 "[]bool": "ToBoolSlice", 158 "[]int": "ToIntSlice", 159 "[]float64": "ToFloat64Slice", 160 "[]float32": "ToFloat32Slice", 161 "[]int64": "ToInt64Slice", 162 "[]int32": "ToInt32Slice", 163 "[]int16": "ToInt16Slice", 164 "[]int8": "ToInt8Slice", 165 "[]uint": "ToUintSlice", 166 "[]uint8": "ToUint8Slice", 167 "[]uint16": "ToUint16Slice", 168 "[]uint32": "ToUint32Slice", 169 "[]uint64": "ToUint64Slice", 170 "[]error": "ToErrorSlice", 171 "[]decimal.Decimal": "ToDecimalSlice", 172 "[][]byte": "ToByteSliceSlice", 173 "[][]rune": "ToRuneSliceSlice", 174 } 175 176 func IsSupport(t string) bool { 177 if IsVarargs(t) { 178 t = ToSlice(t) 179 } 180 _, exists := castFuncMap[strings.TrimLeft(t, "*")] 181 return exists 182 } 183 184 func IsOptional(t string) bool { 185 return strings.HasPrefix(t, "*") || strings.HasPrefix(t, "...") 186 } 187 188 func IsSlice(t string) bool { 189 return strings.Contains(t, "[") || strings.HasPrefix(t, "...") 190 } 191 192 func IsVarargs(t string) bool { 193 return strings.HasPrefix(t, "...") 194 } 195 196 func ToSlice(t string) string { 197 return "[]" + strings.TrimPrefix(t, "...") 198 } 199 200 func CastFunc(t string) string { 201 if IsVarargs(t) { 202 t = ToSlice(t) 203 } 204 return castFuncMap[strings.TrimLeft(t, "*")] 205 } 206 207 // CopySchema as SchemaOf returns pointer, so deepcopy the schema the pointer points 208 func CopySchema(field astutils.FieldMeta) Schema { 209 var schema Schema 210 err := copier.DeepCopy(SchemaOf(field), &schema) 211 if err != nil { 212 panic(err) 213 } 214 return schema 215 } 216 217 func RefAddDoc(schema *Schema, doc string) { 218 if stringutils.IsNotEmpty(schema.Ref) { 219 title := strings.TrimPrefix(schema.Ref, "#/components/schemas/") 220 temp := Schemas[title] 221 temp.Description = strings.Join([]string{doc, temp.Description}, "\n") 222 Schemas[title] = temp 223 } else { 224 schema.Description = doc 225 } 226 } 227 228 // NewSchema new schema from astutils.StructMeta 229 func NewSchema(structmeta astutils.StructMeta) Schema { 230 properties := make(map[string]*Schema) 231 var required []string 232 for _, field := range structmeta.Fields { 233 fschema := CopySchema(field) 234 RefAddDoc(&fschema, strings.Join(field.Comments, "\n")) 235 properties[field.DocName] = &fschema 236 if !strings.HasPrefix(field.Type, "*") { 237 required = append(required, field.DocName) 238 } 239 } 240 return Schema{ 241 Title: structmeta.Name, 242 Type: ObjectT, 243 Properties: properties, 244 Description: strings.Join(structmeta.Comments, "\n"), 245 Required: required, 246 } 247 } 248 249 // IsBuiltin check whether field is built-in type https://pkg.go.dev/builtin or not 250 func IsBuiltin(field astutils.FieldMeta) bool { 251 simples := []interface{}{Int, Int64, Bool, String, Float32, Float64} 252 types := []interface{}{IntegerT, StringT, BooleanT, NumberT} 253 pschema := SchemaOf(field) 254 if sliceutils.Contains(simples, pschema) || (sliceutils.Contains(types, pschema.Type) && pschema.Format != BinaryF) { 255 return true 256 } 257 if pschema.Type == ArrayT && (sliceutils.Contains(simples, pschema.Items) || (sliceutils.Contains(types, pschema.Items.Type) && pschema.Items.Format != BinaryF)) { 258 return true 259 } 260 return false 261 } 262 263 // IsEnum check whether field is enum 264 func IsEnum(field astutils.FieldMeta) bool { 265 pschema := SchemaOf(field) 266 return len(pschema.Enum) > 0 || (pschema.Type == ArrayT && len(pschema.Items.Enum) > 0) 267 } 268 269 // IsStruct check whether field is struct type 270 func IsStruct(field astutils.FieldMeta) bool { 271 return stringutils.IsNotEmpty(SchemaOf(field).Ref) 272 } 273 274 // ElementType get element type string from slice 275 func ElementType(t string) string { 276 if IsVarargs(t) { 277 return strings.TrimPrefix(t, "...") 278 } 279 return t[strings.Index(t, "]")+1:] 280 } 281 282 func LoadAPI(file string) API { 283 var ( 284 docfile *os.File 285 err error 286 docraw []byte 287 api API 288 ) 289 if strings.HasPrefix(file, "http") { 290 link := file 291 client := resty.New() 292 client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15)) 293 root, _ := os.Getwd() 294 client.SetOutputDirectory(root) 295 filename := ".openapi3" 296 _, err := client.R(). 297 SetOutput(filename). 298 Get(link) 299 if err != nil { 300 panic(err) 301 } 302 file = filepath.Join(root, filename) 303 defer os.Remove(file) 304 } 305 if docfile, err = os.Open(file); err != nil { 306 panic(err) 307 } 308 defer docfile.Close() 309 if docraw, err = ioutil.ReadAll(docfile); err != nil { 310 panic(err) 311 } 312 if err = json.Unmarshal(docraw, &api); err != nil { 313 panic(err) 314 } 315 if stringutils.IsEmpty(api.Openapi) { 316 var doc openapi2.T 317 if err = json.Unmarshal(docraw, &doc); err != nil { 318 panic(err) 319 } 320 if stringutils.IsEmpty(doc.Swagger) { 321 panic("not support") 322 } 323 var doc1 *openapi3.T 324 doc1, err = openapi2conv.ToV3(&doc) 325 if err != nil { 326 panic(err) 327 } 328 doc1Json, _ := json.Marshal(doc1) 329 root, _ := os.Getwd() 330 spec := filepath.Join(root, "swaggerV2ToV3"+filepath.Ext(file)) 331 os.WriteFile(spec, doc1Json, os.ModePerm) 332 copier.DeepCopy(doc1, &api) 333 } 334 return api 335 } 336 337 func ToOptional(t string) string { 338 if !strings.HasPrefix(t, "*") { 339 return "*" + t 340 } 341 return t 342 } 343 344 func IsEnumType(methods []astutils.MethodMeta) bool { 345 methodMap := make(map[string]struct{}) 346 for _, item := range methods { 347 methodMap[item.String()] = struct{}{} 348 } 349 for _, item := range IEnumMethods { 350 if _, ok := methodMap[item]; !ok { 351 return false 352 } 353 } 354 return true 355 }