Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constructing an error #55

Closed
Codebreaker101 opened this issue May 26, 2020 · 6 comments
Closed

Constructing an error #55

Codebreaker101 opened this issue May 26, 2020 · 6 comments

Comments

@Codebreaker101
Copy link

First of all, thank you for implementing the ability to deconstruct the error.

Now, I wish to add custom error codes into a service. For example: request validation errors, invalid authentication token error, missing tracing ID error.

The rsocket protocol states the following: [Error Code] Values in the range of 0x00301 to 0xFFFFFFFE are reserved for application layer errors.

With the current version (v0.5.10), the only way to pass custom error codes is with a custom error object serialized into an error string passed to a mono, flux and sink Error(err error) function or returned from the Acceptor function. This to me seems like a bad way of doing it, and I was wondering is there a possibility of implementing a way of specifying error code alongside the message?

From what I can see, with writeError function almost everything is in place for this to be supported and all we need is an interface to construct the new frame error. Something along the lines of:

struct ErrorStruct struct {
    code uint32
    message string
}

func NewErrorStruct(code uint32, message string) *ErrorStruct { ... }

// duplex.go
...
func (p *DuplexRSocket) writeError(sid uint32, e error) {
	// ignore sening error because current socket has been closed.
	if e == errSocketClosed {
		return
	}
	if v, ok := e.(*framing.FrameError); ok {
		p.sendFrame(v)
	} else if v, ok := e.(*ErrorStruct); ok {
                // perhaps some validation in a case the code in out of acceptable range
		p.sendFrame(framing.NewFrameError(sid, v.code, []byte(v.message)))
	} else {
		p.sendFrame(framing.NewFrameError(sid, common.ErrorCodeApplicationError, []byte(e.Error())))
	}
}
...
@jjeffcaii
Copy link
Member

Some Error Codes in RSocket protocol are meaningful. We usually use APPLICATION_ERROR for most of cases.
But I think that we can support those Error structs which implement rsocket.Error.

jjeffcaii added a commit that referenced this issue May 26, 2020
@jjeffcaii
Copy link
Member

@Codebreaker101 I've change some codes to support custom error. Now you just need implement rsocket.Error to create your own error struct.

If you have any problems, feel free to ask me. 😃

@Codebreaker101
Copy link
Author

Excellent work!

If you accept donations I would be happy to contribute.

@Codebreaker101
Copy link
Author

Codebreaker101 commented May 26, 2020

Now that we have custom error codes, I want to pretty print the error code, just like it is done in the
internal/common package as well as compare error codes. Since the interface demands the use of rsocket.ErrorCode as a return type of ErrorCode, I am forced to use internal ErrorCode implementation.

Do you perhaps have any input on how to improve the struggles of the example code below (check comments)?

package errors_test

import (
	"fmt"
	"log"
	"testing"

	"github.com/rsocket/rsocket-go"
)

type (
	ErrorCode uint32

	Error interface {
		rsocket.Error
	}
)

func (c ErrorCode) String() string {
	switch c {
	case ErrCodeMissingMetadata:
		return "MISSING_METADATA"
	default:
		return rsocket.ErrorCode(c).String()
	}
}
const (
	ErrCodeMissingMetadata ErrorCode = 0xA0000001
)

func New(code ErrorCode, data []byte) Error {
	return &customError{
		code: code,
		data: data,
	}
}

type customError struct {
	code ErrorCode
	data []byte
}

func (err *customError) Error() string {
	return fmt.Sprintf("%#x: %s", uint32(err.code), err.code)
}

func (err *customError) ErrorCode() rsocket.ErrorCode {
        // need to convert type, therefore losing `String()` implementation
	return rsocket.ErrorCode(err.code)
}

func (err *customError) ErrorData() []byte {
	return err.data
}

func TestCustomError(t *testing.T) {
	err := New(ErrCodeMissingMetadata, []byte("MyErrorData"))
	log.Println(err.Error())
	// logs "UNKNOWN"/ expected "MISSING_METADATA"
	log.Println(err.ErrorCode())
	log.Println(string(err.ErrorData()))
	// comparison is harder
	log.Println(ErrorCode(err.ErrorCode()) == ErrCodeMissingMetadata)
}

@jjeffcaii
Copy link
Member

I think that because the function ErrorCode returns the rsocket.ErrorCode instead of your custom ErrorCode and it call method of rsocket.ErrorCode#String.
Maybe you can and a function like this:

type customError struct {
	code ErrorCode
	data []byte
}

func (err customErr) Is(code ErrorCode) bool {
	return err.code == code
}

@Codebreaker101
Copy link
Author

That could work. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants