2024-04-16 14:26:06 +03:00
|
|
|
package test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"reflect"
|
2026-09-25 16:32:09 +03:00
|
|
|
"sort"
|
2024-04-16 14:26:06 +03:00
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
// 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)
|
2025-12-29 14:16:28 +03:00
|
|
|
func extractTypeFromSchema(schema map[string]interface{}) string {
|
2026-09-25 16:32:09 +03:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2025-12-29 14:16:28 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
if _, ok := schema["$ref"]; ok {
|
|
|
|
|
return "object"
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 14:16:28 +03:00
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
// 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)
|
|
|
|
|
}
|
2025-08-29 12:51:25 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-29 12:51:25 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
return ""
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
// 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
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
for _, method := range []string{"get", "post", "put", "delete", "patch", "head", "options"} {
|
|
|
|
|
operation, ok := input[method].(map[string]interface{})
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2025-12-23 17:39:58 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
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
|
2025-12-23 17:39:58 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
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
|
2025-12-23 17:39:58 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
properties, _ := schema["properties"].(map[string]interface{})
|
|
|
|
|
for name, prop := range properties {
|
|
|
|
|
propMap, _ := prop.(map[string]interface{})
|
|
|
|
|
add(name, propMap, requiredFields[name])
|
|
|
|
|
}
|
2025-12-23 17:39:58 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
if parameters, ok := operation["parameters"].([]interface{}); ok {
|
|
|
|
|
for _, p := range parameters {
|
|
|
|
|
param, ok := p.(map[string]interface{})
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
2025-08-29 12:51:25 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
name, ok := param["name"].(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
2025-08-29 12:51:25 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
schema, _ := param["schema"].(map[string]interface{})
|
|
|
|
|
required, _ := param["required"].(bool)
|
|
|
|
|
add(name, schema, required)
|
2025-08-29 12:51:25 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-23 17:39:58 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
result := make([]platformParam, 0, len(params))
|
|
|
|
|
for _, p := range params {
|
|
|
|
|
result = append(result, p)
|
2025-12-23 17:39:58 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
|
2025-12-23 17:39:58 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
return result
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getBytesFromJSON(fileName string, t *testing.T) []byte {
|
|
|
|
|
jsonFile, err := os.Open(fileName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
}
|
|
|
|
|
defer jsonFile.Close()
|
|
|
|
|
|
|
|
|
|
bytes, err := io.ReadAll(jsonFile)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bytes
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
// getErrorsFromJSON compares platform requests from swagger with the values SDK passes to DecortApiCall*
|
2024-04-16 14:26:06 +03:00
|
|
|
func getErrorsFromJSON(bytes []byte, t *testing.T, cloud string) {
|
|
|
|
|
var requests map[string]interface{}
|
|
|
|
|
|
|
|
|
|
switch cloud {
|
|
|
|
|
case "cloudapi":
|
|
|
|
|
requests = getRequestsMapCloudAPI()
|
|
|
|
|
case "cloudbroker":
|
|
|
|
|
requests = getRequestsMapCloudbroker()
|
2025-08-29 12:51:25 +03:00
|
|
|
case "sdn":
|
|
|
|
|
requests = getRequestsMapSDN()
|
2024-04-16 14:26:06 +03:00
|
|
|
default:
|
2025-08-29 12:51:25 +03:00
|
|
|
t.Fatalf("Wrong cloud provided, expected `cloudapi`, `cloudbroker` or `sdn`, got %s", cloud)
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
index, err := getSDKIndex()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("can not parse SDK sources: %v", err)
|
|
|
|
|
}
|
2024-05-31 13:35:39 +03:00
|
|
|
|
2024-04-16 14:26:06 +03:00
|
|
|
paths, err := getMapFromFile(bytes)
|
|
|
|
|
if err != nil {
|
2026-09-25 16:32:09 +03:00
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
requestPaths := make([]string, 0, len(requests))
|
|
|
|
|
for k := range requests {
|
|
|
|
|
requestPaths = append(requestPaths, k)
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
sort.Strings(requestPaths)
|
|
|
|
|
|
|
|
|
|
var absentOnPlatform, invalidKeys []string
|
|
|
|
|
for _, k := range requestPaths {
|
|
|
|
|
if !strings.HasPrefix(k, "/restmachine/"+cloud+"/") {
|
|
|
|
|
invalidKeys = append(invalidKeys, k)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
// exclude deprecated urls from analysis
|
|
|
|
|
if !validateUrlFromJson(k) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
v, ok := paths[k]
|
2024-04-16 14:26:06 +03:00
|
|
|
if !ok {
|
2026-09-25 16:32:09 +03:00
|
|
|
absentOnPlatform = append(absentOnPlatform, k)
|
2024-04-16 14:26:06 +03:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
errs := checkPath(k, getParameters(v.(map[string]interface{})), index)
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
t.Errorf("Path %s has following errors: %v", k, errs)
|
2026-01-16 16:50:40 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
}
|
2026-01-16 16:50:40 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
// 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
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
// 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"}
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
type variantKey struct{ dir, typeName, transport string }
|
|
|
|
|
seen := make(map[variantKey]bool)
|
|
|
|
|
var variants []requestVariant
|
|
|
|
|
var errs []string
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
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})
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
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))
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
byName[i][f.Name] = f
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
}
|
2026-01-16 16:50:40 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
paramsByName := make(map[string]bool, len(params))
|
|
|
|
|
for _, p := range params {
|
|
|
|
|
paramsByName[p.Name] = true
|
2026-01-16 16:50:40 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
var sentBy, notSentBy []string
|
|
|
|
|
for i, v := range variants {
|
|
|
|
|
f, ok := byName[i][p.Name]
|
|
|
|
|
if !ok {
|
|
|
|
|
notSentBy = append(notSentBy, v.FuncName)
|
2026-01-16 16:50:40 +03:00
|
|
|
continue
|
|
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
sentBy = append(sentBy, v.FuncName)
|
2026-01-16 16:50:40 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
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)
|
2026-01-16 16:50:40 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
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))
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
return errs
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
func platformTypeString(p platformParam) string {
|
|
|
|
|
if p.Type == "array" && p.Items != "" {
|
|
|
|
|
return "array of " + p.Items
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
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
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
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:
|
2024-04-16 14:26:06 +03:00
|
|
|
return true
|
|
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
return false
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
// 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
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
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 ""
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
// 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()
|
|
|
|
|
}
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
kind := typ.Kind()
|
|
|
|
|
switch {
|
|
|
|
|
case isIntKind(kind):
|
|
|
|
|
return platformType == "integer"
|
|
|
|
|
|
|
|
|
|
case kind == reflect.String:
|
2024-04-16 14:26:06 +03:00
|
|
|
if platformType == "string" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
// json-encoded object or array of objects
|
|
|
|
|
return transport == transportForm &&
|
|
|
|
|
(platformType == "object" || (platformType == "array" && items == "object"))
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
case kind == reflect.Bool:
|
|
|
|
|
return platformType == "boolean"
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
case kind == reflect.Float32 || kind == reflect.Float64:
|
|
|
|
|
return platformType == "number"
|
|
|
|
|
|
|
|
|
|
case kind == reflect.Interface:
|
|
|
|
|
if hasValidateTag(f.Validate, "isBool") {
|
|
|
|
|
return platformType == "boolean"
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
case kind == reflect.Map || kind == reflect.Struct:
|
|
|
|
|
return platformType == "object"
|
2024-04-16 14:26:06 +03:00
|
|
|
|
2026-09-25 16:32:09 +03:00
|
|
|
case kind == reflect.Array || kind == reflect.Slice:
|
2024-04-16 14:26:06 +03:00
|
|
|
if platformType != "array" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
elem := typ.Elem()
|
2026-09-25 16:32:09 +03:00
|
|
|
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:
|
2024-05-31 13:35:39 +03:00
|
|
|
return true
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|
2024-05-31 13:35:39 +03:00
|
|
|
}
|
2026-09-25 16:32:09 +03:00
|
|
|
|
|
|
|
|
return true
|
2024-04-16 14:26:06 +03:00
|
|
|
}
|