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  }