Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions cmd/speccheck/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"regexp"
"strings"

openrpc "github.com/open-rpc/meta-schema"
"github.com/santhosh-tekuri/jsonschema/v5"
Expand All @@ -18,12 +17,54 @@ func checkSpec(methods map[string]*methodSchema, rts []*roundTrip, re *regexp.Re
if !ok {
return fmt.Errorf("undefined method: %s", rt.method)
}
// skip validator of test if name includes "invalid" as the schema
// doesn't yet support it.
// TODO(matt): create error schemas.
if strings.Contains(rt.name, "invalid") {

// TODO: pile up the errors instead of returning on the first one
if rt.response.Result == nil && rt.response.Error != nil {

errorResp := rt.response.Error
// TODO: remove this once the spec is updated, Geth return 3 for all VMErrors
if errorResp.Code == 3 {
continue
}

// Find matching error group
foundErrorCode := false
var foundErr *openrpc.ErrorObject

for _, errGroupRef := range method.errorGroups {
if errGroupRef.ErrorObjects != nil {
for _, errObjRef := range errGroupRef.ErrorObjects {
// Check if it's a valid error object and not a reference
if errObjRef.ErrorObject != nil && errObjRef.ErrorObject.Code != nil {
code := int(*errObjRef.ErrorObject.Code)
if errorResp.Code == code {
foundErrorCode = true
foundErr = errObjRef.ErrorObject

}
}
}
}
}

if !foundErrorCode {
// TODO: temporarily ignore this error but print until the spec is updated
fmt.Printf("[WARN]: ERROR CODE: %d not found for method %s in %s \n",
errorResp.Code, rt.method, rt.name)
continue
}

// Validate error message
if foundErr.Message != nil && string(*foundErr.Message) != errorResp.Message {
// TODO: temporarily ignore this error but print until the spec is updated (Discuss if validation is needed on this one)
fmt.Printf("[WARN]: ERROR MESSAGE: %q does not match expected: %q in %s \n",
errorResp.Message, string(*foundErr.Message), rt.name)
continue
}
// Skip result validation as this is an error response
continue
}

if len(method.params) < len(rt.params) {
return fmt.Errorf("%s: too many parameters", method.name)
}
Expand All @@ -40,10 +81,7 @@ func checkSpec(methods map[string]*methodSchema, rts []*roundTrip, re *regexp.Re
return fmt.Errorf("unable to validate parameter in %s: %s", rt.name, err)
}
}
if rt.response.Result == nil && rt.response.Error != nil {
// skip validation of errors, they haven't been standardized
continue
}

if err := validate(&method.result.schema, rt.response.Result, fmt.Sprintf("%s.result", rt.method)); err != nil {
// Print out the value and schema if there is an error to further debug.
buf, _ := json.Marshal(method.result.schema)
Expand Down
100 changes: 100 additions & 0 deletions cmd/speccheck/extended_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"encoding/json"
"errors"
"fmt"

openrpc "github.com/open-rpc/meta-schema"
)

type ErrorOrReference struct {
ErrorObject *openrpc.ErrorObject
ReferenceObject *openrpc.ReferenceObject
}

func (o *ErrorOrReference) UnmarshalJSON(bytes []byte) error {
var refObj openrpc.ReferenceObject
// If ErrorObj has a reference
if err := json.Unmarshal(bytes, &refObj); err == nil {
return fmt.Errorf("references not supported as error Objects: %v", *refObj.Ref)
}
var errObj openrpc.ErrorObject
if err := json.Unmarshal(bytes, &errObj); err == nil {
o.ErrorObject = &errObj
return nil
}
return errors.New("failed to unmarshal one of the object properties")
}

type ErrorGroupOrReference struct {
ErrorObjects []ErrorOrReference `json:"-"`
ReferenceObject *openrpc.ReferenceObject `json:"-"`
}

func (e *ErrorGroupOrReference) UnmarshalJSON(data []byte) error {
var refObj openrpc.ReferenceObject
// If ErrorGroup has a reference
if err := json.Unmarshal(data, &refObj); err == nil && refObj.Ref != nil {
return fmt.Errorf("references not supported in error groups: %v", *refObj.Ref)
}

var errors []ErrorOrReference
if err := json.Unmarshal(data, &errors); err != nil {
return err
}
e.ErrorObjects = errors

return nil
}

// ErrorGroups is an array of error groups or reference
type ErrorGroups []ErrorGroupOrReference

// Add support for error group extensions in method objects
type ExtendedMethodObject struct {
*openrpc.MethodObject
XErrorGroup ErrorGroups `json:"x-error-group,omitempty"`
}

// Wrap the standard MethodOrReference with extensions
type ExtendedMethodOrReference struct {
MethodObject *ExtendedMethodObject `json:"-"`
ReferenceObject *openrpc.ReferenceObject `json:"-"`
Raw map[string]interface{} `json:"-"`
}

// Wraps the standard OpenrpcDocument with methods that support extensions
type ExtendedOpenrpcDocument struct {
openrpc.OpenrpcDocument
Methods *[]ExtendedMethodOrReference `json:"methods"`
}

// UnmarshalJSON custom unmarshaller to capture both standard and extended fields
func (e *ExtendedMethodOrReference) UnmarshalJSON(data []byte) error {
var raw map[string]interface{}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}

e.Raw = raw

// Check if it's a $ref object, should never be true
if _, ok := raw["$ref"]; ok {
refObj := &openrpc.ReferenceObject{}
if err := json.Unmarshal(data, refObj); err != nil {
return err
}
e.ReferenceObject = refObj
return nil
}

methodObj := &ExtendedMethodObject{
MethodObject: &openrpc.MethodObject{},
}
if err := json.Unmarshal(data, methodObj); err != nil {
return err
}
e.MethodObject = methodObj
return nil
}
13 changes: 8 additions & 5 deletions cmd/speccheck/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ type ContentDescriptor struct {
// methodSchema stores all the schemas neccessary to validate a request or
// response corresponding to the method.
type methodSchema struct {
name string
params []*ContentDescriptor
result *ContentDescriptor
name string
params []*ContentDescriptor
result *ContentDescriptor
errorGroups ErrorGroups // Added error groups extension support
}

// parseSpec reads an OpenRPC specification and parses out each
Expand Down Expand Up @@ -79,6 +80,8 @@ func parseSpec(filename string) (map[string]*methodSchema, error) {
required: required,
schema: *obj.Schema.JSONSchemaObject,
}

ms.errorGroups = method.XErrorGroup
parsed[string(*method.Name)] = &ms
}

Expand Down Expand Up @@ -125,12 +128,12 @@ func checkCDOR(obj openrpc.ContentDescriptorOrReference) error {
return nil
}

func readSpec(path string) (*openrpc.OpenrpcDocument, error) {
func readSpec(path string) (*ExtendedOpenrpcDocument, error) {
spec, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var doc openrpc.OpenrpcDocument
var doc ExtendedOpenrpcDocument
if err := json.Unmarshal(spec, &doc); err != nil {
return nil, err
}
Expand Down