Skip to main content

Errors

Handling errors is always hard and you have to differentiate about some capabilites between server-to-server and server-to-frontend communications:

Server to Server

When using server to server communication you can use the regular go error interface. Since interfaces are not typed but still need to be able to checked on the client side, gotsrpc will wrap them during the encode phase to keep the information using mapstructure.

NOTE: Be aware though, that private attributes on structs will be lost during transportation

type CustomError struct {
	Message string
	data    string // will be lost
}

func (e *CustomError) Error() string {
	return e.Message
}

type Service interface {
	Do() (err error)
}

func main() {
	// ...
	err, clientErr := client.Do()
	if clientErr != nil {
		panic("client error")	
	}
	
	// you will be able to use errors.Is() or errors.As()
	var customErr *CustomError
	if errors.As(err, &customErr) {
		// ...	
	}
	if errors.Is(err, os.ErrExist) {
		// ...
	}
}

Server to Frontend

On the frontend side, everything needs to be strictly typed so sth like this, does not transport well, since an interface implementation can not be marshalled.

// from https://go.dev/blog/error-handling-and-go
type error interface {
    Error() string
}

String Error Types

Scalar string error types provide a nice way to combine Go constants, that translate to TypeScript enums as errors:

type ScalarError string

const (
    ErrorFoo ScalarError = "foo"
    ErrorBar ScalarError = "bar"
)

func (e *ScalarError) Error() string {
    return string(*e)
}

type Service interface {
    MightGoWrong() *ScalarError
}
export enum ScalarError {
	ErrorBar = "bar",
	ErrorFoo = "foo",
}

export interface ServiceClient {
    mightGoWrong(): Promise<ScalarError | null>;
}

Struct Error Types

If an enumeration is not enough and you want to add information to your errors a struct is a good choice (be careful not to expose secrets 😉) :

  • it can still implement the Error type
  • it is still typed in contrast to other alternatives like maps
type ErrorCode int

const (
    ErrorCodeFoo ErrorCode = 1
    ErrorCodeBar ErrorCode = 2
)

type StructError struct {
    Message string `json:"message,omitempty"`
    Code ErrorCode `json:"errorCode"`
}

func (e *StructError) Error() string {
    return e.Message
}

type Service struct {
    MightGoWrong() *StructError
}
export enum ErrorCode {
	Bar = 2,
	Foo = 1,
}

export interface StructError {
	message?:string;
	errorCode:ErrorCode;
}

export interface ServiceClient {
    mightGoWrong(): Promise<StructError | null>;
}