algorand / go-algorand-sdk

Algorand Golang SDK
https://pkg.go.dev/github.com/algorand/go-algorand-sdk/v2
MIT License
185 stars 95 forks source link

Fix API Error Wrappers #501

Open winder opened 1 year ago

winder commented 1 year ago

Subject of the issue

The way API errors are wrapped does not work. Having type aliases to an interface means that at runtime all of the errors are indistinguishable from one another.

Here is the current code:

type BadRequest error
type InvalidToken error
type NotFound error
type InternalError error

// extractError checks if the response signifies an error.
// If so, it returns the error.
// Otherwise, it returns nil.
func extractError(code int, errorBuf []byte) error {
    if code >= 200 && code < 300 {
        return nil
    }

    wrappedError := fmt.Errorf("HTTP %v: %s", code, errorBuf)
    switch code {
    case 400:
        return BadRequest(wrappedError)
    case 401:
        return InvalidToken(wrappedError)
    case 404:
        return NotFound(wrappedError)
    case 500:
        return InternalError(wrappedError)
    default:
        return wrappedError
    }
}

Solution

Create a real error and use that for the specific types. They should also include the error code, for example:

// You can edit this code!
// Click here and start typing.
package main

import "fmt"

type HTTPError struct {
    Msg  string
    Code uint
}

func (e HTTPError) Error() string {
    return fmt.Sprintf("Client error (%d): %s", e.Code, e.Msg)
}

type NotFound HTTPError

func (e NotFound) Error() string {
    return fmt.Sprintf("NotFound error (%d): %s", e.Code, e.Msg)
}

func main() {
    genericError := HTTPError{
        Msg:  "Method Not Found",
        Code: 404,
    }

    fmt.Printf("Error type: %T, Error message: %v\n", genericError, genericError)

    var notFound NotFound = NotFound{
        Msg:  "Method Not Found",
        Code: 404,
    }
    fmt.Printf("Error type: %T, Error message: %v\n", notFound, notFound)
}

Create types so that the extractError can be updated as follows:

// extractError checks if the response signifies an error.
// If so, it returns the error.
// Otherwise, it returns nil.
func extractError(code int, errorBuf []byte) error {
    if code >= 200 && code < 300 {
        return nil
    }

    switch code {
    case 401:
        return InvalidToken{Msg: errorBuf}
    case 404:
        return NotFound{Msg: errorBuf}
    case 500:
        return InternalError(wrappedError)
    default:
        if code < 400 {
            return ClientError{Code: code, Msg: errorBuf}
        } else if code < 500 {
            return ServerError{Code: code, Msg: errorBuf}
        }
        return UnknownError{Code: code, Msg: errorBuf}
    }
}
bbroder-algo commented 1 year ago

nice catch!