github.com/tada-team/tdproto@v1.51.57/codegen/openapi/converters.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "github.com/tada-team/tdproto/codegen" 9 "github.com/tada-team/tdproto/codegen/api_paths" 10 ) 11 12 var golangTypeToOpenApiType = map[string]openApiType{ 13 "string": openApiString, 14 "int": openApiInteger, 15 "int32": openApiInteger, 16 "int64": openApiInteger, 17 "uint16": openApiInteger, 18 "uint": openApiInteger, 19 "bool": openApiBoolean, 20 "ISODateTimeString": openApiString, 21 "time.Time": openApiString, 22 "Struct": openApiObject, 23 "Slice": openApiArray, 24 "interface{}": openApiObject, 25 } 26 27 var golangTypeToOpenApiFormat = map[string]openApiFormat{ 28 "int32": openApiInt32, 29 "int64": openApiInt64, 30 "ISODateTimeString": openApiDateTime, 31 } 32 33 func schemaRef(name string) openApiRef { 34 return openApiRef{ 35 Ref: "#/components/schemas/" + name, 36 } 37 } 38 39 func schemaCreate(typeStr string, help string, isList bool) (newSchema openApiSchema) { 40 if help != "" { 41 newSchema.Description = help 42 } 43 44 primtiveType, isPrimitive := golangTypeToOpenApiType[typeStr] 45 if isPrimitive { 46 if isList { 47 newSchema.Type = openApiArray 48 newSchema.Items = &openApiSchema{Type: primtiveType} 49 return 50 } 51 52 newSchema.Type = primtiveType 53 return 54 } 55 56 newSchema.openApiRef = schemaRef(typeStr) 57 58 return 59 } 60 61 func schemaFromTdField(tdField codegen.TdStructField) (res openApiSchema) { 62 res.Description = tdField.Help 63 64 primtiveType, isPrimitive := golangTypeToOpenApiType[tdField.TypeStr] 65 if isPrimitive { 66 format, hasFormat := golangTypeToOpenApiFormat[tdField.TypeStr] 67 68 if tdField.IsList { 69 res.Type = openApiArray 70 res.Items = &openApiSchema{Type: primtiveType} 71 if hasFormat { 72 res.Items.Format = format 73 } 74 75 return 76 } 77 78 res.Type = primtiveType 79 if hasFormat { 80 res.Format = format 81 } 82 return 83 } 84 85 if tdField.IsList { 86 res.Type = openApiArray 87 res.Items = &openApiSchema{ 88 openApiRef: schemaRef(tdField.TypeStr), 89 } 90 } else { 91 res.openApiRef = schemaRef(tdField.TypeStr) 92 } 93 94 return 95 } 96 97 func addTypeSchema(components map[string]openApiSchema, name string, tdInfo *codegen.TdPackage) error { 98 tdTypeInfo, found := tdInfo.TdTypes[name] 99 if !found { 100 return fmt.Errorf("type alias not found: %s", name) 101 } 102 103 schema := openApiSchema{ 104 Type: golangTypeToOpenApiType[tdTypeInfo.BaseType], 105 Description: tdTypeInfo.Help, 106 } 107 108 format, hasFormat := golangTypeToOpenApiFormat[name] 109 if hasFormat { 110 schema.Format = format 111 } 112 113 components[name] = schema 114 return nil 115 } 116 117 func addStructSchema(components map[string]openApiSchema, name string, tdInfo *codegen.TdPackage) error { 118 tdStructInfo, found := tdInfo.TdStructs[name] 119 if !found { 120 return fmt.Errorf("struct type not found: %s", name) 121 } 122 123 schema := openApiSchema{ 124 Type: openApiObject, 125 Properties: make(map[string]openApiSchema), 126 Description: tdStructInfo.Help, 127 } 128 129 for _, tdField := range tdStructInfo.GetAllJsonFields(tdInfo) { 130 if tdField.IsNotSerialized { 131 continue 132 } 133 134 property := schemaFromTdField(tdField) 135 136 // The field can either be: 137 // NOT omitempty AND pointer -> nullable 138 // NOT omitemty -> required 139 if !tdField.IsOmitEmpty && tdField.IsPointer { 140 property.IsNullable = true 141 } else if !tdField.IsOmitEmpty { 142 schema.Required = append(schema.Required, tdField.JsonName) 143 } 144 145 schema.Properties[tdField.JsonName] = property 146 } 147 148 components[name] = schema 149 return nil 150 } 151 152 func addMapType(components map[string]openApiSchema, name string, tdInfo *codegen.TdPackage) error { 153 mapInfo, found := tdInfo.TdMapTypes[name] 154 if !found { 155 return fmt.Errorf("map type not found: %s", name) 156 } 157 158 valueSchema := schemaCreate( 159 mapInfo.ValueTypeStr, 160 mapInfo.Help, 161 false, 162 ) 163 164 mapSchema := openApiSchema{ 165 Type: openApiObject, 166 AdditionalProperties: &valueSchema, 167 } 168 169 components[name] = mapSchema 170 171 return nil 172 } 173 174 func interfaceToOaContents(someData interface{}, newContents *openApiContents, wrapInResult bool) error { 175 176 resultSchema := openApiSchema{} 177 178 if someData != nil { 179 dataType := reflect.TypeOf(someData) 180 switch dataType.Kind() { 181 case reflect.Struct: 182 resultSchema.openApiRef = schemaRef(dataType.Name()) 183 case reflect.Slice: 184 resultSchema.Type = openApiArray 185 resultSchema.Items = &openApiSchema{ 186 openApiRef: schemaRef(dataType.Elem().Name()), 187 } 188 case reflect.String: 189 resultSchema.Type = openApiString 190 case reflect.Int: 191 resultSchema.Type = openApiInteger 192 default: 193 return fmt.Errorf("cannot convert data to OpenApi %#v", someData) 194 } 195 } 196 197 if wrapInResult { 198 newContents.ApplicationJSON = &openApiContent{ 199 Schema: openApiSchema{ 200 Type: openApiObject, 201 Properties: map[string]openApiSchema{ 202 "ok": { 203 Type: openApiBoolean, 204 }, 205 "result": resultSchema, 206 }, 207 }, 208 } 209 } else { 210 newContents.ApplicationJSON = &openApiContent{ 211 Schema: resultSchema, 212 } 213 } 214 return nil 215 } 216 217 func getDescription(method api_paths.OperationSpec) string { 218 if method.Description == nil { 219 return "" 220 } 221 222 switch d := reflect.TypeOf(method.Description).Kind(); d { 223 case reflect.String: 224 return method.Description.(string) 225 case reflect.Slice: 226 if reflect.TypeOf(method.Description).Elem().Kind() != reflect.String { 227 return "" 228 } 229 return strings.Join(method.Description.([]string), "\n") 230 } 231 232 return "" 233 } 234 235 func addQueryParameters(operation *openApiOperation, queryStruct interface{}) error { 236 237 structureInfo := reflect.TypeOf(queryStruct) 238 if structureInfo.Kind() != reflect.Struct { 239 return fmt.Errorf("expected struct as query structure got %v", structureInfo) 240 } 241 242 operation.Parameters = make([]openApiParameter, 0) 243 244 for i := 0; i < structureInfo.NumField(); i++ { 245 field := structureInfo.Field(i) 246 if field.Anonymous { 247 // TODO 248 continue 249 } 250 paramName := field.Tag.Get("schema") 251 252 operation.Parameters = append(operation.Parameters, openApiParameter{ 253 Name: paramName, 254 In: InQuery, 255 Required: false, 256 Schema: openApiSchema{ 257 Type: openApiString, 258 }, 259 }) 260 } 261 262 return nil 263 } 264 265 func convertPathSpecMethod(method api_paths.OperationSpec, operation **openApiOperation) error { 266 getRepsonce := openApiResponse{} 267 err := interfaceToOaContents(method.Response, &getRepsonce.Content, true) 268 if err != nil { 269 return err 270 } 271 272 *operation = &openApiOperation{ 273 Responses: map[string]openApiResponse{ 274 "200": getRepsonce, 275 }, 276 Description: getDescription(method), 277 } 278 279 if method.Request != nil { 280 requestContents := openApiContents{} 281 interfaceToOaContents(method.Request, &requestContents, false) 282 283 requestBody := openApiRequestBody{ 284 Content: requestContents, 285 } 286 287 (*operation).RequestBody = &requestBody 288 } 289 290 if method.SecurityIsOptional { 291 (*operation).Security = []map[string][]string{{}} 292 } 293 294 if method.QueryStruct != nil { 295 err = addQueryParameters(*operation, method.QueryStruct) 296 if err != nil { 297 return err 298 } 299 } 300 301 return nil 302 } 303 304 func pathSpecToOpenApiPath(path api_paths.PathSpec, newPath *openApiPath) error { 305 306 if path.Get != nil { 307 err := convertPathSpecMethod(*path.Get, &newPath.Get) 308 if err != nil { 309 return err 310 } 311 } 312 313 if path.Put != nil { 314 err := convertPathSpecMethod(*path.Put, &newPath.Put) 315 if err != nil { 316 return err 317 } 318 } 319 320 if path.Delete != nil { 321 err := convertPathSpecMethod(*path.Delete, &newPath.Delete) 322 if err != nil { 323 return err 324 } 325 } 326 327 if path.Post != nil { 328 err := convertPathSpecMethod(*path.Post, &newPath.Post) 329 if err != nil { 330 return err 331 } 332 } 333 334 return nil 335 } 336 337 func addPathParameters(path string, newPath *openApiPath) error { 338 339 newParameters := make([]openApiParameter, 0) 340 341 possibleParameters := []string{ 342 "team_id", "contact_id", 343 "chat_id", "message_id", 344 "group_id", "task_id"} 345 346 for _, paramString := range possibleParameters { 347 if strings.Contains(path, fmt.Sprintf("{%s}", paramString)) { 348 newParameters = append(newParameters, openApiParameter{ 349 Name: paramString, 350 In: InPath, 351 Required: true, 352 Schema: openApiSchema{ 353 Type: openApiString, 354 }, 355 }) 356 } 357 } 358 359 if len(newParameters) > 0 { 360 newPath.Parameters = newParameters 361 } 362 363 return nil 364 }