github.com/easysoft/zendata@v0.0.0-20240513203326-705bd5a7fd67/internal/pkg/service/mock.go (about) 1 package service 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strings" 10 11 consts "github.com/easysoft/zendata/internal/pkg/const" 12 "github.com/easysoft/zendata/internal/pkg/domain" 13 fileUtils "github.com/easysoft/zendata/pkg/utils/file" 14 logUtils "github.com/easysoft/zendata/pkg/utils/log" 15 shellUtils "github.com/easysoft/zendata/pkg/utils/shell" 16 "github.com/easysoft/zendata/pkg/utils/vari" 17 "github.com/getkin/kin-openapi/openapi3" 18 "gopkg.in/yaml.v2" 19 ) 20 21 type MockService struct { 22 FileService *FileService `inject:""` 23 } 24 25 func (s *MockService) GenMockDef(input string) ( 26 name, mockDefPath, zendataDefPath string, err error) { // return the last ones for client spec uploading 27 28 var files []string 29 fileUtils.GetFilesByExtInDir(input, ".yaml,.json", &files) 30 31 ctx := context.Background() 32 loader := &openapi3.Loader{Context: ctx, IsExternalRefsAllowed: true} 33 34 for _, f := range files { 35 if filepath.Ext(f) == ".json" { 36 f = s.convertPostmanSpec(f) 37 } 38 39 doc3, err := loader.LoadFromFile(f) 40 if err != nil { 41 logUtils.PrintTo(fmt.Sprintf("skip file %s which is not a vaild openapi3, swagger and postman spec.", f)) 42 continue 43 } 44 45 fileName := filepath.Base(f) 46 dir := filepath.Dir(f) 47 if vari.GlobalVars.Output != "" { 48 dir = vari.GlobalVars.Output 49 } 50 mockDefPath, zendataDefPath = s.getFilePaths(fileName, dir) 51 52 zendataDef := domain.DefData{} 53 zendataDef.ClsInfo.Title = doc3.Info.Title 54 55 mockDef := domain.MockData{} 56 mockDef.Title = doc3.Info.Title 57 name = mockDef.Title 58 59 if mockDef.Paths == nil { 60 mockDef.Paths = map[string]map[string]map[string]map[string]*domain.EndPoint{} 61 } 62 63 for pathStr, pathItem := range doc3.Paths { 64 mp := map[string]map[string]map[string]*domain.EndPoint{} 65 66 if pathItem.Connect != nil { 67 s.setEndPoint(pathItem.Connect, &zendataDef, zendataDefPath, domain.Connect, &mp) 68 } 69 70 if pathItem.Delete != nil { 71 s.setEndPoint(pathItem.Delete, &zendataDef, zendataDefPath, domain.Delete, &mp) 72 } 73 74 if pathItem.Get != nil { 75 s.setEndPoint(pathItem.Get, &zendataDef, zendataDefPath, domain.Get, &mp) 76 } 77 78 if pathItem.Head != nil { 79 s.setEndPoint(pathItem.Head, &zendataDef, zendataDefPath, domain.Head, &mp) 80 } 81 82 if pathItem.Options != nil { 83 s.setEndPoint(pathItem.Options, &zendataDef, zendataDefPath, domain.Options, &mp) 84 } 85 86 if pathItem.Patch != nil { 87 s.setEndPoint(pathItem.Patch, &zendataDef, zendataDefPath, domain.Patch, &mp) 88 } 89 90 if pathItem.Post != nil { 91 s.setEndPoint(pathItem.Post, &zendataDef, zendataDefPath, domain.Post, &mp) 92 } 93 94 if pathItem.Put != nil { 95 s.setEndPoint(pathItem.Put, &zendataDef, zendataDefPath, domain.Put, &mp) 96 } 97 98 if pathItem.Trace != nil { 99 s.setEndPoint(pathItem.Trace, &zendataDef, zendataDefPath, domain.Trace, &mp) 100 } 101 102 mockDef.Paths[pathStr] = mp 103 } 104 105 s.saveFile(mockDef, mockDefPath) 106 s.saveFile(zendataDef, zendataDefPath) 107 } 108 109 return 110 } 111 112 func (s *MockService) convertPostmanSpec(input string) (ret string) { 113 // npm i postman-to-openapi -g 114 115 ret = input 116 117 content, _ := os.ReadFile(input) 118 119 if strings.Contains(string(content), "_postman_id") { 120 ret = ret + ".yaml" 121 cmd := fmt.Sprintf("p2o %s -f %s", input, ret) 122 123 shellUtils.ExecInDir(cmd, filepath.Dir(ret)) 124 } 125 126 return 127 } 128 129 func (s *MockService) setEndPoint(operation *openapi3.Operation, zendataDef *domain.DefData, 130 zendataDefPath string, method domain.HttpMethod, mp *map[string]map[string]map[string]*domain.EndPoint) { 131 132 codeToEndpointMap := s.createEndPoint(operation, zendataDef, zendataDefPath, method) 133 (*mp)[method.String()] = codeToEndpointMap 134 } 135 136 func (s *MockService) createEndPoint(operation *openapi3.Operation, zendataDef *domain.DefData, 137 zendataDefPath string, method domain.HttpMethod) ( 138 mockDef map[string]map[string]*domain.EndPoint) { 139 140 mockDef = map[string]map[string]*domain.EndPoint{} 141 142 for code, val := range operation.Responses { 143 // map[string]*ResponseRef 144 145 for mediaType, mediaItem := range val.Value.Content { 146 if mediaItem == nil { 147 continue 148 } 149 // mediaType is like "responses => 501 => content => application/json" 150 151 // zendata def 152 fields := s.genZendataDefFromMedia(mediaItem) 153 zendataDef.Fields = append(zendataDef.Fields, fields...) // maybe has no children from properties 154 155 // mock def 156 endpoint := s.genMockDefFromMedia(mediaItem, fields) 157 endpoint.Method = method 158 endpoint.MediaType = mediaType 159 endpoint.Config = filepath.Base(zendataDefPath) // set a relative path 160 endpoint.Lines = 10 161 162 if mockDef[code] == nil { 163 mockDef[code] = map[string]*domain.EndPoint{} 164 } 165 mockDef[code][mediaType] = &endpoint 166 } 167 } 168 169 return 170 } 171 172 func (s *MockService) genZendataDefFromMedia(item *openapi3.MediaType) (fields []domain.DefField) { 173 schemaNode := item.Schema 174 //exampleNode := item.Example 175 //examplesNode := item.Examples 176 //encodingNode := item.Encoding 177 178 if schemaNode != nil { 179 s.getFieldFromSchema(&fields, schemaNode) 180 } 181 182 return 183 } 184 185 func (s *MockService) genMockDefFromMedia(item *openapi3.MediaType, fields []domain.DefField) (endpoint domain.EndPoint) { 186 var fieldNames []string 187 for _, f := range fields { 188 fieldNames = append(fieldNames, f.Field) 189 } 190 endpoint.Fields = strings.Join(fieldNames, ",") 191 192 schemaNode := item.Schema 193 exampleNode := item.Example 194 examplesNode := item.Examples 195 //encodingNode := item.Encoding 196 197 if schemaNode != nil { 198 if schemaNode.Value.Type == string(consts.SchemaTypeArray) { 199 endpoint.Type = consts.SchemaTypeArray 200 } else if schemaNode.Value.Type == string(consts.SchemaTypeObject) { 201 endpoint.Type = consts.SchemaTypeObject 202 } else if schemaNode.Value.Type != "" { 203 endpoint.Type = consts.OpenApiSchemaType(schemaNode.Value.Type) 204 } else { 205 endpoint.Type = consts.SchemaTypeObject 206 } 207 } 208 209 endpoint.Samples = map[string]string{} 210 if schemaNode != nil && schemaNode.Value.Example != nil { // from schema's example 211 s.getExample(schemaNode.Value.Example, &endpoint.Samples) 212 } 213 214 if exampleNode != nil { 215 s.getExample(exampleNode, &endpoint.Samples) 216 } 217 if examplesNode != nil { 218 s.getExamples(examplesNode, &endpoint.Samples) 219 } 220 221 return 222 } 223 224 func (s *MockService) getFieldFromSchema(fields *[]domain.DefField, schemaNodes ...*openapi3.SchemaRef) { 225 propsMap := map[string]bool{} 226 227 for _, schemaNode := range schemaNodes { 228 if len(schemaNode.Value.Properties) > 0 { // properties based 229 for propName, prop := range schemaNode.Value.Properties { 230 if propsMap[propName] { 231 continue 232 } 233 234 field := domain.DefField{} 235 field.Field = propName // s.getSchemaNameFromRef(schemaNode.Ref) + "~" + propName 236 propsMap[propName] = true 237 238 if prop.Ref == "" { // leaf property 239 s.getRangeByTypeFormat(consts.OpenApiDataType(prop.Value.Type), 240 prop.Value.Enum, prop.Value.Default, prop.Value.Min, prop.Value.Max, &field) 241 } else { // refer to another schema 242 s.getFieldFromSchema(&field.Fields, prop) 243 } 244 245 *fields = append(*fields, field) 246 } 247 248 } else if schemaNode.Value.OneOf != nil { 249 s.getFieldFromSchema(fields, schemaNode.Value.OneOf[0]) 250 251 } else if schemaNode.Value.AllOf != nil { 252 s.getFieldFromSchema(fields, schemaNode.Value.AllOf...) 253 254 } else if schemaNode.Value.AnyOf != nil { 255 arr := openapi3.SchemaRefs{schemaNode.Value.AnyOf[0]} 256 if len(schemaNode.Value.AnyOf) > 1 { 257 arr = append(arr, schemaNode.Value.AnyOf[len(schemaNode.Value.AnyOf)-1]) 258 } 259 260 s.getFieldFromSchema(fields, arr...) 261 262 } 263 264 // items based 265 if schemaNode.Value.Items != nil { 266 s.getFieldFromItems(fields, schemaNode.Value.Items) 267 } 268 } 269 270 return 271 } 272 273 func (s *MockService) getSchemaNameFromRef(ref string) (ret string) { 274 arr := strings.Split(ref, "/") 275 ret = arr[len(arr)-1] 276 277 return 278 } 279 280 func (s *MockService) getFieldFromItems(fields *[]domain.DefField, itemsDef *openapi3.SchemaRef) { 281 s.getFieldFromSchema(fields, itemsDef) 282 283 return 284 } 285 286 func (s *MockService) getRangeByTypeFormat(typ consts.OpenApiDataType, 287 enums []interface{}, defaultVal interface{}, 288 min, max *float64, 289 field *domain.DefField) { 290 if enums != nil { 291 field.Range = s.getRangeFromEnum(enums) 292 return 293 } 294 295 if consts.OpenApiDataTypeInteger == typ { 296 start, end := s.getStartEnd(1, 99, min, max, typ) 297 field.Range = fmt.Sprintf("%d-%d", start, end) 298 299 } else if consts.OpenApiDataTypeLong == typ { 300 maxInt64 := ^uint64(0) 301 maxDefault := maxInt64 302 minDefault := maxInt64 - 10000 303 start, end := s.getStartEnd(minDefault, maxDefault, min, max, typ) 304 field.Range = fmt.Sprintf("%d-%d", start, end) 305 306 } else if consts.OpenApiDataTypeFloat == typ { 307 start, end := s.getStartEnd(1.01, 99, min, max, typ) 308 field.Range = fmt.Sprintf("%f-%f", start, end) 309 310 } else if consts.OpenApiDataTypeDouble == typ { 311 start, end := s.getStartEnd(1.000000000000009, 99, min, max, typ) 312 field.Range = fmt.Sprintf("%f-%f", start, end) 313 314 } else if consts.OpenApiDataTypeString == typ { 315 field.Range = "a-z" 316 field.Loop = "6-8" 317 318 } else if consts.OpenApiDataTypeByte == typ { 319 start, end := s.getStartEnd('a', 'z', min, max, typ) 320 field.Range = fmt.Sprintf("%c-%c", start, end) 321 322 } else if consts.OpenApiDataTypeBinary == typ { 323 field.Format = "binary" 324 325 } else if consts.OpenApiDataTypeBoolean == typ { 326 field.Range = "[true,false]" 327 328 } else if consts.OpenApiDataTypeDate == typ { 329 field.Range = "20230101 000000-20230101 235959:60" 330 field.Type = "timestamp" 331 field.Format = "YY/MM/DD" 332 333 } else if consts.OpenApiDataTypeDateTime == typ { 334 field.Range = "20230101 000000-20230101 235959:60" 335 field.Type = "timestamp" 336 field.Format = "YY/MM/DD hh:mm:ss" 337 338 } else if consts.OpenApiDataTypePassword == typ { 339 field.Format = "password(8)" 340 341 } 342 343 if defaultVal != nil && (consts.OpenApiDataTypeInteger == typ || consts.OpenApiDataTypeLong == typ || 344 consts.OpenApiDataTypeFloat == typ || consts.OpenApiDataTypeDouble == typ || 345 consts.OpenApiDataTypeString == typ || consts.OpenApiDataTypeByte == typ) { 346 347 field.Range = fmt.Sprintf("%v, ", defaultVal) + field.Range 348 } 349 } 350 351 func (s *MockService) getRangeFromEnum(enums []interface{}) (ret string) { 352 var arr []string 353 for _, e := range enums { 354 arr = append(arr, fmt.Sprintf("%v", e)) 355 } 356 357 ret = fmt.Sprintf("[%s]", strings.Join(arr, ",")) 358 359 return 360 } 361 362 func (s *MockService) getFilePaths(name string, dir string) (mockPath, zendataPath string) { 363 ext := filepath.Ext(name) 364 365 zendataPath = strings.ReplaceAll(name, ext, "-zd"+ext) 366 zendataPath = filepath.Join(dir, zendataPath) 367 368 mockPath = strings.ReplaceAll(name, ext, "-mock"+ext) 369 mockPath = filepath.Join(dir, mockPath) 370 371 return 372 } 373 374 func (s *MockService) saveFile(obj interface{}, pth string) { 375 fileUtils.MkDirIfNeeded(filepath.Dir(pth)) 376 377 bytes, err := yaml.Marshal(obj) 378 str := string(bytes) 379 if err != nil { 380 str = err.Error() 381 } 382 383 fileUtils.WriteFile(pth, str) 384 } 385 386 func (s *MockService) getStartEnd(startDefault, endDefault interface{}, min, max *float64, typ consts.OpenApiDataType) (startRet, endRet interface{}) { 387 startRet = startDefault 388 endRet = endDefault 389 390 if min != nil { 391 if typ == consts.OpenApiDataTypeInteger { 392 startRet = int(*min) 393 } else if typ == consts.OpenApiDataTypeLong { 394 startRet = int64(*min) 395 } else if typ == consts.OpenApiDataTypeFloat { 396 startRet = float32(*min) 397 } else if typ == consts.OpenApiDataTypeDouble { 398 startRet = *min 399 } else if typ == consts.OpenApiDataTypeByte { 400 startRet = int(*min) 401 } 402 } 403 404 if max != nil { 405 if typ == consts.OpenApiDataTypeInteger { 406 endRet = int(*max) 407 } else if typ == consts.OpenApiDataTypeLong { 408 endRet = int64(*max) 409 } else if typ == consts.OpenApiDataTypeFloat { 410 endRet = float32(*max) 411 } else if typ == consts.OpenApiDataTypeDouble { 412 endRet = *max 413 } else if typ == consts.OpenApiDataTypeByte { 414 endRet = int(*max) 415 } 416 } 417 418 return 419 } 420 421 func (s *MockService) getExample(exampleNode interface{}, sample *map[string]string) { 422 bytes, _ := json.Marshal(exampleNode) 423 (*sample)["example"] = string(bytes) 424 } 425 426 func (s *MockService) getExamples(exampleNodes openapi3.Examples, sample *map[string]string) { 427 for key, val := range exampleNodes { 428 bytes, _ := json.Marshal(val.Value.Value) 429 (*sample)[key] = string(bytes) 430 } 431 }