From cc303bc56f3c30a0dea1035bcd1fc1aec6b740d2 Mon Sep 17 00:00:00 2001 From: simsonraj Date: Sun, 20 Apr 2025 21:06:12 +0530 Subject: [PATCH 1/4] support for the error groups in speccheck --- cmd/speccheck/extended_types.go | 68 +++++++++++++++++++++++++++++++++ cmd/speccheck/spec.go | 13 ++++--- 2 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 cmd/speccheck/extended_types.go diff --git a/cmd/speccheck/extended_types.go b/cmd/speccheck/extended_types.go new file mode 100644 index 0000000..65b8ee8 --- /dev/null +++ b/cmd/speccheck/extended_types.go @@ -0,0 +1,68 @@ +package main + +import ( + "encoding/json" + + openrpc "github.com/open-rpc/meta-schema" +) + +// ErrorObject represents a single error in an error group +type ErrorObject struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` +} + +// ErrorGroup represents a group of errors +type ErrorGroup []ErrorObject + +// ErrorGroups is an array of error groups +type ErrorGroups []ErrorGroup + +// 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 +} diff --git a/cmd/speccheck/spec.go b/cmd/speccheck/spec.go index 9413ef5..5188565 100644 --- a/cmd/speccheck/spec.go +++ b/cmd/speccheck/spec.go @@ -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 + errorGroup ErrorGroups // Added error groups extension support } // parseSpec reads an OpenRPC specification and parses out each @@ -79,6 +80,8 @@ func parseSpec(filename string) (map[string]*methodSchema, error) { required: required, schema: *obj.Schema.JSONSchemaObject, } + + ms.errorGroup = method.XErrorGroup parsed[string(*method.Name)] = &ms } @@ -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 } From a6d20e05c3379cd6913fe80c0ce5f298b884dedb Mon Sep 17 00:00:00 2001 From: simsonraj Date: Sun, 20 Apr 2025 22:12:03 +0530 Subject: [PATCH 2/4] errorgroup validation during speccheck run --- cmd/speccheck/check.go | 46 +++++++++++++++++++++++++++++++++--------- cmd/speccheck/spec.go | 10 ++++----- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/cmd/speccheck/check.go b/cmd/speccheck/check.go index 586442f..39f68af 100644 --- a/cmd/speccheck/check.go +++ b/cmd/speccheck/check.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "regexp" - "strings" openrpc "github.com/open-rpc/meta-schema" "github.com/santhosh-tekuri/jsonschema/v5" @@ -18,10 +17,42 @@ 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 foundErrGroup *ErrorObject + for _, errGroups := range method.errorGroups { + for _, errGrp := range errGroups { + if errorResp.Code == errGrp.Code { + foundErrorCode = true + foundErrGroup = &errGrp + } + } + } + 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 foundErrGroup.Message != "" && foundErrGroup.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, foundErrGroup.Message, rt.name) + continue + } + // Skip result validation as this is an error response continue } if len(method.params) < len(rt.params) { @@ -40,10 +71,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) diff --git a/cmd/speccheck/spec.go b/cmd/speccheck/spec.go index 5188565..036e73a 100644 --- a/cmd/speccheck/spec.go +++ b/cmd/speccheck/spec.go @@ -17,10 +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 - errorGroup ErrorGroups // Added error groups extension support + name string + params []*ContentDescriptor + result *ContentDescriptor + errorGroups ErrorGroups // Added error groups extension support } // parseSpec reads an OpenRPC specification and parses out each @@ -81,7 +81,7 @@ func parseSpec(filename string) (map[string]*methodSchema, error) { schema: *obj.Schema.JSONSchemaObject, } - ms.errorGroup = method.XErrorGroup + ms.errorGroups = method.XErrorGroup parsed[string(*method.Name)] = &ms } From 1859fe90a0979b7706d720d9f281f201143a30f4 Mon Sep 17 00:00:00 2001 From: simsonraj Date: Sun, 27 Apr 2025 23:26:18 +0530 Subject: [PATCH 3/4] added errorgroup or reference to invalidate the error refs during speccheck --- cmd/speccheck/check.go | 26 +++++++++++++++------- cmd/speccheck/extended_types.go | 38 +++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/cmd/speccheck/check.go b/cmd/speccheck/check.go index 39f68af..566b206 100644 --- a/cmd/speccheck/check.go +++ b/cmd/speccheck/check.go @@ -29,15 +29,24 @@ func checkSpec(methods map[string]*methodSchema, rts []*roundTrip, re *regexp.Re // Find matching error group foundErrorCode := false - var foundErrGroup *ErrorObject - for _, errGroups := range method.errorGroups { - for _, errGrp := range errGroups { - if errorResp.Code == errGrp.Code { - foundErrorCode = true - foundErrGroup = &errGrp + 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", @@ -46,15 +55,16 @@ func checkSpec(methods map[string]*methodSchema, rts []*roundTrip, re *regexp.Re } // Validate error message - if foundErrGroup.Message != "" && foundErrGroup.Message != errorResp.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, foundErrGroup.Message, rt.name) + 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) } diff --git a/cmd/speccheck/extended_types.go b/cmd/speccheck/extended_types.go index 65b8ee8..26f3c8b 100644 --- a/cmd/speccheck/extended_types.go +++ b/cmd/speccheck/extended_types.go @@ -2,22 +2,42 @@ package main import ( "encoding/json" + "fmt" openrpc "github.com/open-rpc/meta-schema" ) -// ErrorObject represents a single error in an error group -type ErrorObject struct { - Code int `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` +type ErrorGroupOrReference struct { + ErrorObjects []openrpc.ErrorOrReference `json:"-"` + ReferenceObject *openrpc.ReferenceObject `json:"-"` } -// ErrorGroup represents a group of errors -type ErrorGroup []ErrorObject +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 []openrpc.ErrorOrReference + if err := json.Unmarshal(data, &errors); err == nil { + // If the ErrorObject has a reference TODO: validate if this case is needed + for _, errObj := range errors { + if errObj.ReferenceObject != nil { + if err := json.Unmarshal(data, errObj.ErrorObject); err == nil { + return fmt.Errorf("references not supported in error Objects: %v", *refObj.Ref) + } + } + } + e.ErrorObjects = errors + return nil + } + + return fmt.Errorf("failed to unmarshal error group") +} -// ErrorGroups is an array of error groups -type ErrorGroups []ErrorGroup +// ErrorGroups is an array of error groups or reference +type ErrorGroups []ErrorGroupOrReference // Add support for error group extensions in method objects type ExtendedMethodObject struct { From ad3c399be43c36a0e20e8b86b492d1ca85ff17cb Mon Sep 17 00:00:00 2001 From: simsonraj Date: Mon, 28 Apr 2025 00:04:33 +0530 Subject: [PATCH 4/4] extended unmarshalling for ErrorObjects --- cmd/speccheck/extended_types.go | 42 +++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/cmd/speccheck/extended_types.go b/cmd/speccheck/extended_types.go index 26f3c8b..502d272 100644 --- a/cmd/speccheck/extended_types.go +++ b/cmd/speccheck/extended_types.go @@ -2,14 +2,34 @@ 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 []openrpc.ErrorOrReference `json:"-"` - ReferenceObject *openrpc.ReferenceObject `json:"-"` + ErrorObjects []ErrorOrReference `json:"-"` + ReferenceObject *openrpc.ReferenceObject `json:"-"` } func (e *ErrorGroupOrReference) UnmarshalJSON(data []byte) error { @@ -19,21 +39,13 @@ func (e *ErrorGroupOrReference) UnmarshalJSON(data []byte) error { return fmt.Errorf("references not supported in error groups: %v", *refObj.Ref) } - var errors []openrpc.ErrorOrReference - if err := json.Unmarshal(data, &errors); err == nil { - // If the ErrorObject has a reference TODO: validate if this case is needed - for _, errObj := range errors { - if errObj.ReferenceObject != nil { - if err := json.Unmarshal(data, errObj.ErrorObject); err == nil { - return fmt.Errorf("references not supported in error Objects: %v", *refObj.Ref) - } - } - } - e.ErrorObjects = errors - return nil + var errors []ErrorOrReference + if err := json.Unmarshal(data, &errors); err != nil { + return err } + e.ErrorObjects = errors - return fmt.Errorf("failed to unmarshal error group") + return nil } // ErrorGroups is an array of error groups or reference