v1.16.5
This commit is contained in:
@@ -5,27 +5,72 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// extractTypeFromSchema extracts type from schema, handling oneOf cases
|
||||
// When oneOf contains a type and null, it returns the non-null type
|
||||
// platformParam describes one request parameter from platform swagger
|
||||
type platformParam struct {
|
||||
Name string
|
||||
// OpenAPI type: integer, string, boolean, number, array, object or empty if unknown
|
||||
Type string
|
||||
// OpenAPI type of array items, empty if unknown
|
||||
Items string
|
||||
Required bool
|
||||
// Value of "default" from swagger, nil if not set
|
||||
Default interface{}
|
||||
}
|
||||
|
||||
// extractTypeFromSchema extracts non-null type from "type" (string or array), "oneOf"/"anyOf" or "$ref" (object)
|
||||
func extractTypeFromSchema(schema map[string]interface{}) string {
|
||||
// Check for direct type
|
||||
if paramType, ok := schema["type"].(string); ok {
|
||||
return paramType
|
||||
switch typ := schema["type"].(type) {
|
||||
case string:
|
||||
return typ
|
||||
case []interface{}:
|
||||
for _, item := range typ {
|
||||
if s, ok := item.(string); ok && s != "null" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for oneOf
|
||||
if oneOf, ok := schema["oneOf"].([]interface{}); ok {
|
||||
for _, item := range oneOf {
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
if itemType, ok := itemMap["type"].(string); ok {
|
||||
// Skip null type, return the first non-null type found
|
||||
if itemType != "null" {
|
||||
return itemType
|
||||
}
|
||||
for _, key := range []string{"oneOf", "anyOf"} {
|
||||
variants, ok := schema[key].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, variant := range variants {
|
||||
if variantMap, ok := variant.(map[string]interface{}); ok {
|
||||
if variantType := extractTypeFromSchema(variantMap); variantType != "" && variantType != "null" {
|
||||
return variantType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := schema["$ref"]; ok {
|
||||
return "object"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractItemsType extracts type of array items from schema, including arrays declared inside oneOf/anyOf
|
||||
func extractItemsType(schema map[string]interface{}) string {
|
||||
if items, ok := schema["items"].(map[string]interface{}); ok {
|
||||
return extractTypeFromSchema(items)
|
||||
}
|
||||
|
||||
for _, key := range []string{"oneOf", "anyOf"} {
|
||||
variants, ok := schema[key].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, variant := range variants {
|
||||
if variantMap, ok := variant.(map[string]interface{}); ok && extractTypeFromSchema(variantMap) == "array" {
|
||||
if items := extractItemsType(variantMap); items != "" {
|
||||
return items
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,150 +79,84 @@ func extractTypeFromSchema(schema map[string]interface{}) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func getParameters(input map[string]interface{}) []interface{} {
|
||||
var emptySlice []interface{}
|
||||
methods := []string{"get", "post", "put", "delete", "patch", "head", "options"}
|
||||
|
||||
var methodData interface{}
|
||||
found := false
|
||||
|
||||
for _, method := range methods {
|
||||
if data, ok := input[method]; ok {
|
||||
methodData = data
|
||||
found = true
|
||||
break
|
||||
// getParameters returns request parameters of all methods, content types and query/path parameters of a path
|
||||
func getParameters(input map[string]interface{}) []platformParam {
|
||||
params := make(map[string]platformParam)
|
||||
add := func(name string, schema map[string]interface{}, required bool) {
|
||||
p := platformParam{Name: name, Required: required}
|
||||
if schema != nil {
|
||||
p.Type = extractTypeFromSchema(schema)
|
||||
if p.Type == "array" {
|
||||
p.Items = extractItemsType(schema)
|
||||
}
|
||||
p.Default = schema["default"]
|
||||
}
|
||||
if prev, ok := params[name]; ok {
|
||||
p.Required = p.Required || prev.Required
|
||||
if p.Type == "" {
|
||||
p.Type, p.Items = prev.Type, prev.Items
|
||||
}
|
||||
if p.Default == nil {
|
||||
p.Default = prev.Default
|
||||
}
|
||||
}
|
||||
params[name] = p
|
||||
}
|
||||
|
||||
if !found {
|
||||
return emptySlice
|
||||
}
|
||||
for _, method := range []string{"get", "post", "put", "delete", "patch", "head", "options"} {
|
||||
operation, ok := input[method].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
parameters, ok := methodData.(map[string]interface{})
|
||||
if !ok {
|
||||
return emptySlice
|
||||
}
|
||||
|
||||
var result []interface{}
|
||||
|
||||
if requestBody, ok := parameters["requestBody"].(map[string]interface{}); ok {
|
||||
if content, ok := requestBody["content"].(map[string]interface{}); ok {
|
||||
// Check for application/x-www-form-urlencoded, application/json, or multipart/form-data
|
||||
var schemaData map[string]interface{}
|
||||
var found bool
|
||||
|
||||
if formData, ok := content["application/x-www-form-urlencoded"].(map[string]interface{}); ok {
|
||||
if schema, ok := formData["schema"].(map[string]interface{}); ok {
|
||||
schemaData = schema
|
||||
found = true
|
||||
if requestBody, ok := operation["requestBody"].(map[string]interface{}); ok {
|
||||
content, _ := requestBody["content"].(map[string]interface{})
|
||||
for _, media := range content {
|
||||
mediaMap, _ := media.(map[string]interface{})
|
||||
schema, _ := mediaMap["schema"].(map[string]interface{})
|
||||
if schema == nil {
|
||||
continue
|
||||
}
|
||||
} else if jsonData, ok := content["application/json"].(map[string]interface{}); ok {
|
||||
if schema, ok := jsonData["schema"].(map[string]interface{}); ok {
|
||||
schemaData = schema
|
||||
found = true
|
||||
requiredFields := make(map[string]bool)
|
||||
if req, ok := schema["required"].([]interface{}); ok {
|
||||
for _, r := range req {
|
||||
if reqStr, ok := r.(string); ok {
|
||||
requiredFields[reqStr] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if multipartData, ok := content["multipart/form-data"].(map[string]interface{}); ok {
|
||||
if schema, ok := multipartData["schema"].(map[string]interface{}); ok {
|
||||
schemaData = schema
|
||||
found = true
|
||||
properties, _ := schema["properties"].(map[string]interface{})
|
||||
for name, prop := range properties {
|
||||
propMap, _ := prop.(map[string]interface{})
|
||||
add(name, propMap, requiredFields[name])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found && schemaData != nil {
|
||||
if properties, ok := schemaData["properties"].(map[string]interface{}); ok {
|
||||
requiredFields := make(map[string]bool)
|
||||
if req, ok := schemaData["required"].([]interface{}); ok {
|
||||
for _, r := range req {
|
||||
if reqStr, ok := r.(string); ok {
|
||||
requiredFields[reqStr] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for name, prop := range properties {
|
||||
propMap, ok := prop.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
newParam := make(map[string]interface{})
|
||||
newParam["name"] = name
|
||||
propType := extractTypeFromSchema(propMap)
|
||||
if propType != "" {
|
||||
newParam["type"] = propType
|
||||
}
|
||||
newParam["required"] = requiredFields[name]
|
||||
if propType == "array" {
|
||||
// Try to get items from propMap first
|
||||
if items, ok := propMap["items"].(map[string]interface{}); ok {
|
||||
newParam["items"] = items
|
||||
} else if oneOf, ok := propMap["oneOf"].([]interface{}); ok {
|
||||
// If items not in propMap, try to get from oneOf array element
|
||||
for _, item := range oneOf {
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
if itemType, ok := itemMap["type"].(string); ok && itemType == "array" {
|
||||
if items, ok := itemMap["items"].(map[string]interface{}); ok {
|
||||
newParam["items"] = items
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result = append(result, newParam)
|
||||
}
|
||||
if parameters, ok := operation["parameters"].([]interface{}); ok {
|
||||
for _, p := range parameters {
|
||||
param, ok := p.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name, ok := param["name"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
schema, _ := param["schema"].(map[string]interface{})
|
||||
required, _ := param["required"].(bool)
|
||||
add(name, schema, required)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if params, ok := parameters["parameters"].([]interface{}); ok {
|
||||
for _, p := range params {
|
||||
param, ok := p.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
newParam := make(map[string]interface{})
|
||||
if name, ok := param["name"].(string); ok {
|
||||
newParam["name"] = name
|
||||
}
|
||||
if schema, ok := param["schema"].(map[string]interface{}); ok {
|
||||
paramType := extractTypeFromSchema(schema)
|
||||
if paramType != "" {
|
||||
newParam["type"] = paramType
|
||||
}
|
||||
if paramType == "array" {
|
||||
if items, ok := schema["items"].(map[string]interface{}); ok {
|
||||
newParam["items"] = items
|
||||
} else if oneOf, ok := schema["oneOf"].([]interface{}); ok {
|
||||
for _, item := range oneOf {
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
if itemType, ok := itemMap["type"].(string); ok && itemType == "array" {
|
||||
if items, ok := itemMap["items"].(map[string]interface{}); ok {
|
||||
newParam["items"] = items
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle required field
|
||||
if required, ok := param["required"].(bool); ok {
|
||||
newParam["required"] = required
|
||||
} else {
|
||||
newParam["required"] = false
|
||||
}
|
||||
result = append(result, newParam)
|
||||
}
|
||||
result := make([]platformParam, 0, len(params))
|
||||
for _, p := range params {
|
||||
result = append(result, p)
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
|
||||
|
||||
if len(result) > 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
return emptySlice
|
||||
return result
|
||||
}
|
||||
|
||||
func getBytesFromJSON(fileName string, t *testing.T) []byte {
|
||||
@@ -195,6 +174,7 @@ func getBytesFromJSON(fileName string, t *testing.T) []byte {
|
||||
return bytes
|
||||
}
|
||||
|
||||
// getErrorsFromJSON compares platform requests from swagger with the values SDK passes to DecortApiCall*
|
||||
func getErrorsFromJSON(bytes []byte, t *testing.T, cloud string) {
|
||||
var requests map[string]interface{}
|
||||
|
||||
@@ -209,227 +189,272 @@ func getErrorsFromJSON(bytes []byte, t *testing.T, cloud string) {
|
||||
t.Fatalf("Wrong cloud provided, expected `cloudapi`, `cloudbroker` or `sdn`, got %s", cloud)
|
||||
}
|
||||
|
||||
var dataLogs []string
|
||||
index, err := getSDKIndex()
|
||||
if err != nil {
|
||||
t.Fatalf("can not parse SDK sources: %v", err)
|
||||
}
|
||||
|
||||
paths, err := getMapFromFile(bytes)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
i := 0
|
||||
for k, v := range paths {
|
||||
|
||||
requestPaths := make([]string, 0, len(requests))
|
||||
for k := range requests {
|
||||
requestPaths = append(requestPaths, k)
|
||||
}
|
||||
sort.Strings(requestPaths)
|
||||
|
||||
var absentOnPlatform, invalidKeys []string
|
||||
for _, k := range requestPaths {
|
||||
if !strings.HasPrefix(k, "/restmachine/"+cloud+"/") {
|
||||
invalidKeys = append(invalidKeys, k)
|
||||
continue
|
||||
}
|
||||
// exclude deprecated urls from analysis
|
||||
if !validateUrlFromJson(k) {
|
||||
continue
|
||||
}
|
||||
|
||||
params := getParameters(v.(map[string]interface{}))
|
||||
structure, ok := requests[k]
|
||||
v, ok := paths[k]
|
||||
if !ok {
|
||||
absentOnPlatform = append(absentOnPlatform, k)
|
||||
continue
|
||||
}
|
||||
i++
|
||||
typStruct := reflect.TypeOf(structure)
|
||||
|
||||
var errs []string
|
||||
// empty request case
|
||||
if len(params) == 0 && structure == nil {
|
||||
continue
|
||||
}
|
||||
if len(params) != typStruct.NumField() {
|
||||
errs = append(errs, fmt.Sprintf("Platform (%d) and golang structure (%d) have different amount of fields.", len(params), typStruct.NumField()))
|
||||
}
|
||||
paramMap := make(map[string]bool)
|
||||
paramRequiredMap := make(map[string]bool)
|
||||
for _, p := range params {
|
||||
param, ok := p.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name, ok := param["name"].(string)
|
||||
if ok {
|
||||
paramMap[name] = true
|
||||
required, ok := param["required"].(bool)
|
||||
if ok {
|
||||
paramRequiredMap[name] = required
|
||||
} else {
|
||||
paramRequiredMap[name] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range params {
|
||||
param, ok := p.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
name, ok := param["name"].(string)
|
||||
if !ok {
|
||||
name = ""
|
||||
}
|
||||
required, ok := param["required"].(bool)
|
||||
if !ok {
|
||||
required = false
|
||||
}
|
||||
typ, ok := p.(map[string]interface{})["type"].(string)
|
||||
if !ok {
|
||||
typ = ""
|
||||
}
|
||||
|
||||
var items string
|
||||
if p.(map[string]interface{})["items"] != nil {
|
||||
itemsTemp := p.(map[string]interface{})["items"]
|
||||
if itemsTemp != nil {
|
||||
itemsType := itemsTemp.(map[string]interface{})["type"]
|
||||
if itemsType != nil {
|
||||
items, ok = itemsType.(string)
|
||||
if !ok {
|
||||
items = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var found bool
|
||||
for i := 0; i < typStruct.NumField(); i++ {
|
||||
jsonTag := typStruct.Field(i).Tag.Get("json")
|
||||
validation, _ := typStruct.Field(i).Tag.Lookup("validate")
|
||||
|
||||
if checkName(name, jsonTag) {
|
||||
if !checkRequired(required, validation, typ) {
|
||||
errs = append(errs, fmt.Sprintf("Field %s has different required parameters on the platform and in golang structure", name))
|
||||
}
|
||||
if !checkKind(typ, items, typStruct.Field(i).Type) {
|
||||
errs = append(errs, fmt.Sprintf("Field %s has different type parameters on the platform and in golang structure", name))
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
errs = append(errs, fmt.Sprintf("Platform has field %s that golang structure doesn't", name))
|
||||
}
|
||||
}
|
||||
|
||||
// Check if required fields in Go structure are missing from platform JSON
|
||||
// or if they exist but are not required on platform
|
||||
for i := 0; i < typStruct.NumField(); i++ {
|
||||
jsonTag := typStruct.Field(i).Tag.Get("json")
|
||||
validation, _ := typStruct.Field(i).Tag.Lookup("validate")
|
||||
|
||||
fieldName := strings.Split(jsonTag, ",")[0]
|
||||
if fieldName == "" || fieldName == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(validation, "required") {
|
||||
if !paramMap[fieldName] {
|
||||
errs = append(errs, fmt.Sprintf("Golang structure has required field %s that platform doesn't", fieldName))
|
||||
} else {
|
||||
platformRequired := paramRequiredMap[fieldName]
|
||||
if !platformRequired {
|
||||
fieldType := typStruct.Field(i).Type
|
||||
if fieldType.Kind() == reflect.Bool {
|
||||
errs = append(errs, fmt.Sprintf("Golang structure has required field %s that platform doesn't", fieldName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errs := checkPath(k, getParameters(v.(map[string]interface{})), index)
|
||||
if len(errs) > 0 {
|
||||
msg := fmt.Sprintf("Path %s has following errors: %v", k, errs)
|
||||
t.Error(msg)
|
||||
dataLogs = append(dataLogs, msg)
|
||||
t.Errorf("Path %s has following errors: %v", k, errs)
|
||||
}
|
||||
}
|
||||
|
||||
if len(requests) != i {
|
||||
msg := fmt.Sprintf("Amount of structure checked (%d) is not the same as amount of platform requests available (%d), please check getRequests func in code.",
|
||||
i, len(requests))
|
||||
t.Error(msg)
|
||||
dataLogs = append(dataLogs, msg)
|
||||
if len(invalidKeys) > 0 {
|
||||
t.Errorf("Request map for %s has keys that don't start with /restmachine/%s/ and are never checked: %v", cloud, cloud, invalidKeys)
|
||||
}
|
||||
if len(absentOnPlatform) > 0 {
|
||||
t.Errorf("Request map has paths that platform doesn't (remove them from request map): %v", absentOnPlatform)
|
||||
}
|
||||
}
|
||||
|
||||
// checkName checks if name field from platform has the same value as json tag in golang structure (maybe including omitempty)
|
||||
func checkName(name, tag string) bool {
|
||||
return strings.Contains(tag, name)
|
||||
// requestVariant is one distinct request SDK sends to a path (e.g. sync and async methods send different wrappers)
|
||||
type requestVariant struct {
|
||||
FuncName string
|
||||
Transport string
|
||||
Fields []sentField
|
||||
}
|
||||
|
||||
// checkRequired checks if required field from platform has the same value as validate tag in golang structure
|
||||
func checkRequired(required bool, validation, fieldType string) bool {
|
||||
if required && strings.Contains(validation, "required") {
|
||||
return true
|
||||
}
|
||||
if required && (!strings.Contains(validation, "omitempty") && validation != "") {
|
||||
return true
|
||||
}
|
||||
if !required && (validation == "" || strings.Contains(validation, "omitempty")) {
|
||||
return true
|
||||
// checkPath compares platform parameters of a path with every distinct request SDK sends to this path
|
||||
func checkPath(path string, params []platformParam, index *sdkIndex) []string {
|
||||
calls := index.callsForPath(path)
|
||||
if len(calls) == 0 {
|
||||
return []string{"SDK call for this path is not found in sources, can not check it"}
|
||||
}
|
||||
|
||||
if fieldType == "boolean" {
|
||||
return true // otherwise we have issues with setting false/true
|
||||
type variantKey struct{ dir, typeName, transport string }
|
||||
seen := make(map[variantKey]bool)
|
||||
var variants []requestVariant
|
||||
var errs []string
|
||||
|
||||
for _, call := range calls {
|
||||
key := variantKey{call.Dir, call.TypeName, call.Transport}
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
|
||||
fields, ok := index.sentFields(call)
|
||||
if !ok {
|
||||
errs = append(errs, fmt.Sprintf("can not resolve type %s sent by %s", call.TypeName, call.FuncName))
|
||||
continue
|
||||
}
|
||||
variants = append(variants, requestVariant{FuncName: call.FuncName, Transport: call.Transport, Fields: fields})
|
||||
}
|
||||
|
||||
return append(errs, compareVariants(params, variants)...)
|
||||
}
|
||||
|
||||
// compareVariants returns differences between platform parameters and all requests SDK sends to the path
|
||||
func compareVariants(params []platformParam, variants []requestVariant) []string {
|
||||
var errs []string
|
||||
seenErr := make(map[string]bool)
|
||||
add := func(msg string) {
|
||||
if !seenErr[msg] {
|
||||
seenErr[msg] = true
|
||||
errs = append(errs, msg)
|
||||
}
|
||||
}
|
||||
|
||||
byName := make([]map[string]sentField, len(variants))
|
||||
for i, v := range variants {
|
||||
byName[i] = make(map[string]sentField, len(v.Fields))
|
||||
for _, f := range v.Fields {
|
||||
if _, ok := byName[i][f.Name]; ok && v.Transport == transportForm {
|
||||
add(fmt.Sprintf("Golang structure sends field %s more than once", f.Name))
|
||||
}
|
||||
byName[i][f.Name] = f
|
||||
}
|
||||
}
|
||||
|
||||
paramsByName := make(map[string]bool, len(params))
|
||||
for _, p := range params {
|
||||
paramsByName[p.Name] = true
|
||||
|
||||
var sentBy, notSentBy []string
|
||||
for i, v := range variants {
|
||||
f, ok := byName[i][p.Name]
|
||||
if !ok {
|
||||
notSentBy = append(notSentBy, v.FuncName)
|
||||
continue
|
||||
}
|
||||
sentBy = append(sentBy, v.FuncName)
|
||||
|
||||
if !checkKind(p.Type, p.Items, f, v.Transport) {
|
||||
add(fmt.Sprintf("Field %s has different type parameters on the platform (%s) and in golang structure (%s)",
|
||||
p.Name, platformTypeString(p), f.Type))
|
||||
}
|
||||
if msg := checkRequired(p, f); msg != "" {
|
||||
add(msg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(sentBy) == 0 {
|
||||
add(fmt.Sprintf("Platform has field %s that golang structure doesn't", p.Name))
|
||||
continue
|
||||
}
|
||||
for _, fn := range notSentBy {
|
||||
if p.Required {
|
||||
add(fmt.Sprintf("Required field %s is not sent by %s", p.Name, fn))
|
||||
} else if p.Type == "boolean" && p.Default == true {
|
||||
add(fmt.Sprintf("Field %s is not sent by %s, platform default is true", p.Name, fn))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range variants {
|
||||
for _, f := range v.Fields {
|
||||
if !paramsByName[f.Name] {
|
||||
add(fmt.Sprintf("Golang structure has field %q (%s) that platform doesn't", f.Name, f.GoName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func platformTypeString(p platformParam) string {
|
||||
if p.Type == "array" && p.Items != "" {
|
||||
return "array of " + p.Items
|
||||
}
|
||||
return p.Type
|
||||
}
|
||||
|
||||
// hasValidateTag checks if `validate` tag contains exactly the given tag
|
||||
func hasValidateTag(validate, tag string) bool {
|
||||
for _, v := range strings.Split(validate, ",") {
|
||||
if strings.TrimSpace(v) == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkKind checks if type field from platform has the same value as field type in golang structure
|
||||
func checkKind(platformType, items string, typ reflect.Type) bool {
|
||||
//nolint
|
||||
switch typ.Kind() {
|
||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int8, reflect.Int16, reflect.Uint, reflect.Uint64, reflect.Uint16, reflect.Uint32, reflect.Uint8:
|
||||
if platformType == "integer" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
func isIntKind(k reflect.Kind) bool {
|
||||
switch k {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
// checkRequired returns description of required mismatch between platform and golang field or empty string
|
||||
func checkRequired(p platformParam, f sentField) string {
|
||||
// validators without omitempty fail on zero value, so such field can not be omitted either
|
||||
goRequired := hasValidateTag(f.Validate, "required") ||
|
||||
(f.Validate != "" && !hasValidateTag(f.Validate, "omitempty"))
|
||||
|
||||
// numbers and booleans without omitempty are always sent and zero is a meaningful value for them
|
||||
alwaysSent := false
|
||||
if f.Type != nil && !f.OmitEmpty {
|
||||
kind := f.Type.Kind()
|
||||
alwaysSent = isIntKind(kind) || kind == reflect.Bool || kind == reflect.Float32 || kind == reflect.Float64
|
||||
}
|
||||
|
||||
if p.Required && !goRequired && !alwaysSent {
|
||||
return fmt.Sprintf("Field %s is required on the platform but is optional in golang structure", p.Name)
|
||||
}
|
||||
if !p.Required && hasValidateTag(f.Validate, "required") {
|
||||
return fmt.Sprintf("Field %s is optional on the platform but is required in golang structure", p.Name)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// checkKind checks if type field from platform corresponds to field type in golang structure
|
||||
func checkKind(platformType, items string, f sentField, transport string) bool {
|
||||
typ := f.Type
|
||||
// type can not be resolved on one of the sides
|
||||
if typ == nil || platformType == "" {
|
||||
return true
|
||||
}
|
||||
for typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
|
||||
kind := typ.Kind()
|
||||
switch {
|
||||
case isIntKind(kind):
|
||||
return platformType == "integer"
|
||||
|
||||
case kind == reflect.String:
|
||||
if platformType == "string" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// json-encoded object or array of objects
|
||||
return transport == transportForm &&
|
||||
(platformType == "object" || (platformType == "array" && items == "object"))
|
||||
|
||||
case reflect.Bool:
|
||||
if platformType == "boolean" {
|
||||
return true
|
||||
case kind == reflect.Bool:
|
||||
return platformType == "boolean"
|
||||
|
||||
case kind == reflect.Float32 || kind == reflect.Float64:
|
||||
return platformType == "number"
|
||||
|
||||
case kind == reflect.Interface:
|
||||
if hasValidateTag(f.Validate, "isBool") {
|
||||
return platformType == "boolean"
|
||||
}
|
||||
return false
|
||||
return true
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if platformType == "number" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case kind == reflect.Map || kind == reflect.Struct:
|
||||
return platformType == "object"
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
case kind == reflect.Array || kind == reflect.Slice:
|
||||
if platformType != "array" {
|
||||
return false
|
||||
}
|
||||
|
||||
elem := typ.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.String:
|
||||
if items == "string" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int8, reflect.Int16, reflect.Uint, reflect.Uint64, reflect.Uint16, reflect.Uint32, reflect.Uint8:
|
||||
if items == "integer" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
default: // for cases like dataDisks etc.
|
||||
return true
|
||||
|
||||
for elem.Kind() == reflect.Ptr {
|
||||
elem = elem.Elem()
|
||||
}
|
||||
// items type is not declared on the platform
|
||||
if items == "" {
|
||||
return true
|
||||
}
|
||||
switch {
|
||||
case elem.Kind() == reflect.String:
|
||||
// list of json-encoded objects
|
||||
return items == "string" || (transport == transportForm && items == "object")
|
||||
case isIntKind(elem.Kind()):
|
||||
return items == "integer"
|
||||
case elem.Kind() == reflect.Bool:
|
||||
return items == "boolean"
|
||||
case elem.Kind() == reflect.Float32 || elem.Kind() == reflect.Float64:
|
||||
return items == "number"
|
||||
case elem.Kind() == reflect.Struct || elem.Kind() == reflect.Map:
|
||||
return items == "object"
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
||||
default: // for cases like drivers etc
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user