code.vegaprotocol.io/vega@v0.79.0/wallet/api/openrpc_doc_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 "encoding/json" 20 "fmt" 21 "strings" 22 "testing" 23 24 vgfs "code.vegaprotocol.io/vega/libs/fs" 25 ) 26 27 type openrpc struct { 28 Methods []method `json:"methods"` 29 Components components `json:"components"` 30 } 31 32 func (o openrpc) ASTForParams(methodName string) (astNode, error) { 33 m := o.method(methodName) 34 35 numParams := len(m.Params) 36 37 // No params means nil. 38 if numParams == 0 { 39 return astNode{}, nil 40 } 41 42 rootNode := astNode{ 43 nodeType: nodeTypeObject, 44 nestedProperties: make([]astNode, 0, numParams), 45 } 46 47 for _, p := range m.Params { 48 paramNode, err := o.parseParamSchema(p) 49 if err != nil { 50 return astNode{}, fmt.Errorf("could not parse params %q: %w", p.Name, err) 51 } 52 53 rootNode.nestedProperties = append(rootNode.nestedProperties, paramNode) 54 } 55 56 rootNode.nestedProperties = deterministNestedProperties(rootNode.nestedProperties) 57 58 return rootNode, nil 59 } 60 61 func (o openrpc) ASTForResult(methodName string) (astNode, error) { 62 m := o.method(methodName) 63 64 return o.parseSchema(m.Result.Schema) 65 } 66 67 func (o openrpc) method(methodName string) method { 68 for _, m := range o.Methods { 69 if m.Name == methodName { 70 return m 71 } 72 } 73 panic(fmt.Sprintf("No method %q in the openrpc.json file", methodName)) 74 } 75 76 func (o openrpc) component(ref string) schema { 77 componentName := strings.Split(ref, "/")[3] 78 componentSchema, ok := o.Components.Schemas[componentName] 79 if !ok { 80 panic(fmt.Sprintf("could not find the component %q", componentName)) 81 } 82 return componentSchema 83 } 84 85 func (o openrpc) parseParamSchema(p paramsDescriptor) (astNode, error) { 86 paramsNode, err := o.parseSchema(p.Schema) 87 if err != nil { 88 return astNode{}, fmt.Errorf("could not parse the params schema: %w", err) 89 } 90 paramsNode.name = p.Name 91 92 return paramsNode, nil 93 } 94 95 func (o openrpc) parseSchema(s schema) (astNode, error) { 96 if len(s.Ref) != 0 { 97 componentSchema := o.component(s.Ref) 98 return o.parseSchema(componentSchema) 99 } 100 101 if s.Type == "null" { 102 return astNode{}, nil 103 } 104 105 jsonType, err := openrpcTypeToJSONType(s.Type) 106 if err != nil { 107 return astNode{}, fmt.Errorf("could not figure out the JSON type from OpenRPC type %q: %w", s.Type, err) 108 } 109 110 if jsonType == nodeTypeObject { 111 return o.parseObjectSchema(s) 112 } else if jsonType == nodeTypeArray { 113 return o.parseArraySchema(s) 114 } 115 116 return astNode{ 117 nodeType: jsonType, 118 }, nil 119 } 120 121 func (o openrpc) parseArraySchema(s schema) (astNode, error) { 122 arrayNode := astNode{ 123 nodeType: nodeTypeArray, 124 } 125 126 if s.Items != nil { 127 itemProperty, err := o.parseSchema(*s.Items) 128 if err != nil { 129 return astNode{}, fmt.Errorf("could not parse the item schema: %w", err) 130 } 131 arrayNode.nestedProperties = []astNode{itemProperty} 132 } 133 return arrayNode, nil 134 } 135 136 func (o openrpc) parseObjectSchema(s schema) (astNode, error) { 137 if len(s.Properties) == 0 && len(s.PatternProperties) == 0 { 138 return astNode{nodeType: nodeTypeObject}, nil 139 } 140 141 if len(s.PatternProperties) != 0 { 142 // Very weird stuff to handle the weird definition of the map[string] in openRPC 143 // format... 144 for _, s := range s.PatternProperties { 145 patternNode, err := o.parseSchema(s.Schema) 146 if err != nil { 147 return astNode{}, fmt.Errorf("could not parse the schema for pattern properties: %w", err) 148 } 149 150 // Yes we return after the first element because we don't support 151 // multiple patterns. 152 return astNode{ 153 nodeType: nodeTypeObject, 154 nestedProperties: []astNode{patternNode}, 155 }, nil 156 } 157 } 158 159 nestedProperties := make([]astNode, 0, len(s.Properties)) 160 for propertyName, schema := range s.Properties { 161 propertyNode, err := o.parseSchema(schema) 162 if err != nil { 163 return astNode{}, fmt.Errorf("could not parse the schema for property %q: %w", propertyName, err) 164 } 165 propertyNode.name = propertyName 166 nestedProperties = append(nestedProperties, propertyNode) 167 } 168 169 return astNode{ 170 nodeType: nodeTypeObject, 171 nestedProperties: deterministNestedProperties(nestedProperties), 172 }, nil 173 } 174 175 type components struct { 176 Schemas map[string]schema `json:"schemas"` 177 } 178 179 type method struct { 180 Name string `json:"name"` 181 Params []paramsDescriptor `json:"params"` 182 Result resultDescriptor `json:"result"` 183 } 184 185 type paramsDescriptor struct { 186 Name string `json:"name"` 187 Schema schema `json:"schema"` 188 } 189 190 type resultDescriptor struct { 191 Schema schema `json:"schema"` 192 } 193 194 type schema struct { 195 Type string `json:"type,omitempty"` 196 Properties map[string]schema `json:"properties,omitempty"` 197 Ref string `json:"$ref,omitempty"` 198 Items *schema `json:"items,omitempty"` 199 PatternProperties map[string]patternProperty `json:"patternProperties"` 200 } 201 202 type patternProperty struct { 203 Schema schema `json:"schema"` 204 } 205 206 func parseASTFromDoc(t *testing.T, methodName string) (methodIODefinition, error) { 207 t.Helper() 208 209 rawDoc, err := vgfs.ReadFile("./openrpc.json") 210 if err != nil { 211 return methodIODefinition{}, fmt.Errorf("could not read the OpenRPC documentation file: %w", err) 212 } 213 214 doc := &openrpc{} 215 if err := json.Unmarshal(rawDoc, doc); err != nil { 216 return methodIODefinition{}, fmt.Errorf("could not parse the OpenRPC documentation file: %w", err) 217 } 218 219 paramsAST, err := doc.ASTForParams(methodName) 220 if err != nil { 221 return methodIODefinition{}, fmt.Errorf("could not build the params AST for method %q: %w", methodName, err) 222 } 223 224 resultAST, err := doc.ASTForResult(methodName) 225 if err != nil { 226 return methodIODefinition{}, fmt.Errorf("could not build the result AST for method %q: %w", methodName, err) 227 } 228 229 return methodIODefinition{ 230 Params: paramsAST, 231 Result: resultAST, 232 }, nil 233 } 234 235 func openrpcTypeToJSONType(openrpcType string) (nodeType, error) { 236 switch openrpcType { 237 case "string": 238 return nodeTypeString, nil 239 case "number": 240 return nodeTypeNumber, nil 241 case "boolean": 242 return nodeTypeBoolean, nil 243 case "object": 244 return nodeTypeObject, nil 245 case "array": 246 return nodeTypeArray, nil 247 default: 248 return nodeTypeUnknown, fmt.Errorf("openRPC type %q cannot be converted to node type", openrpcType) 249 } 250 }