DEV Community

Clavin June
Clavin June

Posted on • Originally published at clavinjune.dev on

1 1

My Custom Http Error In Golang

This is how I handle error with golang on http server

package e

import (
    "errors"
    "fmt"
    "net/http"
    "runtime"
)

// Error implements built in error with status code, caller, and message attribute
type Error struct {
    Err error
    statusCode int
    message string
    caller string
}

// New wraps err with defined statusCode and message
func New(err error, statusCode int, message string) error {
    return &Error{
        Err: err,
        statusCode: statusCode,
        message: message,
        caller: getCaller(),
    }
}

// Wrap wraps err with custom message
// Wrap's result inherit statusCode from err if err equals *Error
func Wrap(err error, msg string) error {
    var e *Error
    statusCode := http.StatusInternalServerError

    if errors.As(err, &e) {
        statusCode = e.statusCode
    }

    return &Error{
        Err: err,
        statusCode: statusCode,
        message: msg,
        caller: getCaller(),
    }
}

// From creates new error from defined statusCode
// if statusCode doesn't have any status text
// statusCode changed to http.StatusInternalServerError
func From(statusCode int) error {
    text := http.StatusText(statusCode)

    if text == "" {
        text = http.StatusText(http.StatusInternalServerError)
        statusCode = http.StatusInternalServerError
    }

    return &Error{
        Err: errors.New(text),
        statusCode: statusCode,
        message: "",
        caller: getCaller(),
    }
}

// Error returns error message with caller
func (e Error) Error() string {
    if e.message == "" {
        return e.Err.Error()
    }

    return fmt.Sprintf("%v\n[%v] > %v", e.Err, e.caller, e.message)
}

// Unwrap enables errors.As and errors.Is
func (e Error) Unwrap() error {
    return e.Err
}

// StatusCode returns e.statusCode
func (e Error) StatusCode() int {
    return e.statusCode
}

// getCaller uses log.Lshortfile to format the caller
func getCaller() string {
    _, file, line, ok := runtime.Caller(2)
    if !ok {
        file = "???"
        line = 0
    }

    short := file
    for i := len(file) - 1; i > 0; i-- {
        if file[i] == '/' {
            short = file[i+1:]
            break
        }
    }
    file = short

    return fmt.Sprintf("%s:%d", file, line)
}
Enter fullscreen mode Exit fullscreen mode

Usage:

package main

import (
    "database/sql"
    "errors"
    "fmt"

    "e"
)

func main() {
    base := e.New(sql.ErrNoRows, 404, "user not found")
    base2 := e.Wrap(base, "bleh")
    concrete := e.Wrap(base2, "there's no user with such credentials")

    fmt.Println(concrete)
    fmt.Println(errors.Is(concrete, base))

    var ee *Error
    if errors.As(concrete, &ee) {
        fmt.Println(ee.StatusCode())
    }
}
Enter fullscreen mode Exit fullscreen mode

Result:

sql: no rows in result set
[main.go:11] > user not found
[main.go:12] > bleh
[main.go:13] > there's no user with such credentials
true
404
Enter fullscreen mode Exit fullscreen mode

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay