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  }