code.vegaprotocol.io/vega@v0.79.0/wallet/api/openrpc_go_helper_for_test.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package api_test 17 18 import ( 19 "fmt" 20 "reflect" 21 "strings" 22 "testing" 23 ) 24 25 func parseASTFromGo(t *testing.T, i interface{}) (astNode, error) { 26 t.Helper() 27 28 if i == nil { 29 return astNode{}, nil 30 } 31 32 rootType := reflect.TypeOf(i) 33 nestedProperties, err := resolveNestedProperties(rootType) 34 if err != nil { 35 return astNode{}, fmt.Errorf("could not reflect the AST for struct %q: %w", rootType.String(), err) 36 } 37 38 return astNode{ 39 nodeType: nodeTypeObject, 40 nestedProperties: nestedProperties, 41 }, nil 42 } 43 44 func resolveNestedProperties(rootType reflect.Type) ([]astNode, error) { 45 unwrappedType := unwrapType(rootType) 46 47 if unwrappedType.Kind() == reflect.Interface { 48 return nil, nil 49 } 50 51 if unwrappedType.Kind() == reflect.Array || unwrappedType.Kind() == reflect.Slice { 52 return resolveNestedPropertiesForArray(unwrappedType) 53 } 54 55 if unwrappedType.Kind() == reflect.Struct && !structShouldBeString(unwrappedType) { 56 return resolveNestedPropertiesForStruct(unwrappedType) 57 } 58 59 if unwrappedType.Kind() == reflect.Map { 60 return resolveNestedPropertiesForMap(unwrappedType) 61 } 62 63 return nil, nil 64 } 65 66 func structShouldBeString(unwrappedType reflect.Type) bool { 67 return unwrappedType.String() == "time.Time" 68 } 69 70 func resolveNestedPropertiesForMap(unwrappedType reflect.Type) ([]astNode, error) { 71 elemType := unwrappedType.Elem() 72 unwrappedElemType := unwrapType(elemType) 73 74 jsonTypeForElem, err := goTypeToJSONType(unwrappedElemType) 75 if err != nil { 76 return nil, fmt.Errorf("could not figure out the type for element field %q: %w", elemType.String(), err) 77 } 78 79 nestedPropertiesForField, err := resolveNestedProperties(unwrappedElemType) 80 if err != nil { 81 return nil, fmt.Errorf("could not resolve nested properties for field %q: %w", unwrappedElemType.String(), err) 82 } 83 84 return []astNode{{ 85 nodeType: jsonTypeForElem, 86 nestedProperties: nestedPropertiesForField, 87 }}, nil 88 } 89 90 func resolveNestedPropertiesForArray(unwrappedType reflect.Type) ([]astNode, error) { 91 elemType := unwrappedType.Elem() 92 unwrappedElemType := unwrapType(elemType) 93 94 if unwrappedElemType.Kind() == reflect.Struct && !structShouldBeString(unwrappedType) { 95 itemsProperty, err := resolveNestedPropertiesForStruct(unwrappedElemType) 96 if err != nil { 97 return nil, fmt.Errorf("could not reflect on the item properties %q: %w", unwrappedType.String(), err) 98 } 99 return []astNode{{ 100 nodeType: nodeTypeObject, 101 nestedProperties: itemsProperty, 102 }}, nil 103 } 104 105 jsonTypeForElem, err := goTypeToJSONType(unwrappedElemType) 106 if err != nil { 107 return nil, fmt.Errorf("could not figure out the type for element field %q: %w", elemType.String(), err) 108 } 109 110 // No name, nor nested properties. 111 return []astNode{{nodeType: jsonTypeForElem}}, nil 112 } 113 114 func resolveNestedPropertiesForStruct(rootType reflect.Type) ([]astNode, error) { 115 numField := rootType.NumField() 116 nestedProperties := make([]astNode, 0, numField) 117 numNodesToAccountFor := numField 118 119 for fieldIdx := 0; fieldIdx < numField; fieldIdx++ { 120 field := rootType.Field(fieldIdx) 121 122 jsonName, shouldBeAccountedFor, err := retrieveFieldName(field) 123 if err != nil { 124 return nil, fmt.Errorf("could not retrieve the JSON name for field %q at index %d: %w", field.Name, fieldIdx, err) 125 } 126 if !shouldBeAccountedFor { 127 numNodesToAccountFor-- 128 continue 129 } 130 131 unwrappedType := unwrapType(field.Type) 132 133 jsonType, err := goTypeToJSONType(unwrappedType) 134 if err != nil { 135 return nil, fmt.Errorf("could not figure out the type for field %q at index %d: %w", field.Name, fieldIdx, err) 136 } 137 138 var nestedPropertiesForField []astNode 139 140 if jsonType == nodeTypeArray || jsonType == nodeTypeObject { 141 nestedPropertiesForField, err = resolveNestedProperties(unwrappedType) 142 if err != nil { 143 return nil, fmt.Errorf("could not resolve nested properties for field %q at index %d: %w", field.Name, fieldIdx, err) 144 } 145 } 146 147 nestedProperties = append(nestedProperties, astNode{ 148 name: jsonName, 149 nodeType: jsonType, 150 nestedProperties: nestedPropertiesForField, 151 }) 152 } 153 154 return deterministNestedProperties(nestedProperties[:numNodesToAccountFor]), nil 155 } 156 157 func unwrapType(field reflect.Type) reflect.Type { 158 if field.Kind() == reflect.Pointer { 159 return field.Elem() 160 } 161 return field 162 } 163 164 func retrieveFieldName(field reflect.StructField) (string, bool, error) { 165 protobufValue, exist := field.Tag.Lookup("protobuf") 166 if exist { 167 names := strings.Split(protobufValue, ",") 168 for _, name := range names { 169 if strings.HasPrefix(name, "json=") { 170 return name[5:], true, nil 171 } 172 } 173 } 174 175 protobufOneofValue, exist := field.Tag.Lookup("protobuf_oneof") 176 if exist { 177 return protobufOneofValue, true, nil 178 } 179 180 jsonValue, exist := field.Tag.Lookup("json") 181 if !exist { 182 if field.IsExported() { 183 return "", false, fmt.Errorf("field is exported but does not have a JSON tag") 184 } 185 186 // No json tag, so it is not meant to be used in the API. 187 return "", false, nil 188 } 189 190 // the first value is the name in the JSON tag 191 jsonName := strings.Split(jsonValue, ",")[0] 192 193 if strings.ToLower(field.Name) != strings.ToLower(jsonName) { //nolint:staticcheck 194 return "", false, fmt.Errorf("field name %q does not match JSON name %q", field.Name, jsonName) 195 } 196 197 return jsonName, true, nil 198 } 199 200 func goTypeToJSONType(fieldType reflect.Type) (nodeType, error) { 201 switch fieldType.Kind() { 202 case reflect.String: 203 return nodeTypeString, nil 204 case reflect.Int, 205 reflect.Int8, 206 reflect.Int16, 207 reflect.Int32, 208 reflect.Int64, 209 reflect.Uint, 210 reflect.Uint8, 211 reflect.Uint16, 212 reflect.Uint32, 213 reflect.Uint64, 214 reflect.Uintptr, 215 reflect.Float32, 216 reflect.Float64, 217 reflect.Complex64, 218 reflect.Complex128: 219 return nodeTypeNumber, nil 220 case reflect.Bool: 221 return nodeTypeBoolean, nil 222 case reflect.Struct, reflect.Map, reflect.Interface: 223 if structShouldBeString(fieldType) { 224 return nodeTypeString, nil 225 } 226 return nodeTypeObject, nil 227 case reflect.Array, reflect.Slice: 228 if fieldType.Elem().Kind() == reflect.Uint8 { 229 return nodeTypeString, nil 230 } 231 return nodeTypeArray, nil 232 default: 233 return nodeTypeUnknown, fmt.Errorf("struct type %q cannot be converted to node type", fieldType.Kind().String()) 234 } 235 }