Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a custom error in golang for http responses

i wanted to create custom errors for my authentication service that i am currently working on. Since i have to create a error for every http responses, and i am fairly new to golang i am facing difficulties. The below code is the replica of the javascript code code that i wanted to implement here in go.

export abstract class CustomError extends Error {
abstract statusCode: number;

constructor(message: string) {
    super(message);

    Object.setPrototypeOf(this, CustomError.prototype);
}

abstract serializeErrors(): { message: string; field?: string }[];
}

To create extended classes based on the custom error like this

import { CustomError } from "./custom-error";

export class NotFoundError extends CustomError {
statusCode = 404;

constructor() {
    super("Route not found");

    Object.setPrototypeOf(this, NotFoundError.prototype);
}

serializeErrors() {
    return [{ message: "Not Found" }];
 }
}

so that i can be thrown or logged from the main file i.e. const existingUser = await User.findOne({ email });

    if (existingUser) {
        throw new BadRequestError("Email is already in use");
    }

so in simple language i wanted to create an object/model of CustomErrors that can help to create more diverse Erros like BadRequestError()

so, i need help regarding creating this one. And this is my first question here

like image 481
Aditya vatsa Avatar asked Oct 27 '25 12:10

Aditya vatsa


1 Answers

If you want http error responses in plain text, http.Error should be your choice. But if your authentication service needs error response in specific format (JSON/XML) then you need to create custom http errors which can be serialized and written into the response.

To create custom http error responses for JSON format (for XML format modify serialization), first you need to create some types -

type ErrFields map[string]string  // Error field-value pair type

type ResponseError struct {
    Msg    string `json:"message"` // Error message
    Status int    `json:"status"`  // Http status code
    Data   ErrFields               // For extra error fields e.g. reason, details, etc.
}

type ErrList []ResponseError       // Multiple http errors type

Methods for ResponseError type -

// AddErrField adds a new field to the response error with given key and value
func (err *ResponseError) AddErrField(key, value string) {
    if err.Data == nil {
        err.Data = make(ErrFields)
    }
    err.Data[key] = value
}

// RemoveErrField removes existing field matching given key from response error
func (err *ResponseError) RemoveErrField(key string) {
    delete(err.Data, key)
}

// MarshalJSON marshals the response error into json 
func (err *ResponseError) MarshalJSON() ([]byte, error) {
    // Determine json field name for error message
    errType := reflect.TypeOf(*err)
    msgField, ok := errType.FieldByName("Msg")
    msgJsonName := "message"
    if ok {
        msgJsonTag := msgField.Tag.Get("json")
        if msgJsonTag != "" {
            msgJsonName = msgJsonTag
        }
    }
    // Determine json field name for error status code
    statusField, ok := errType.FieldByName("Status")
    statusJsonName := "status"
    if ok {
        statusJsonTag := statusField.Tag.Get("json")
        if statusJsonTag != "" {
            statusJsonName = statusJsonTag
        }
    }
    fieldMap := make(map[string]string)
    fieldMap[msgJsonName] = err.Msg
    fieldMap[statusJsonName] = fmt.Sprintf("%d", err.Status)
    for key, value := range err.Data {
        fieldMap[key] = value
    }
    return json.Marshal(fieldMap)
}

// SerializeJSON converts response error into serialized json string
func (resErr *ResponseError) SerializeJSON() (string, error) {
    value, err := json.Marshal(resErr)
    if err != nil {
        return "", err
    }
    return string(value), nil
}

Methods for ErrList type -

// SerializeJSON converts error list into serialized json string
func (errList ErrList) SerializeJSON() (string, error) {
    value, err := json.Marshal(errList)
    if err != nil {
        return "", err
    }
    return string(value), nil
}

Now you can create custom http error responses by creating different values of ResponseError type -

// Error returns a general response error
func Error(msg string, status int) ResponseError {
    return ResponseError{msg, status, nil}
}

// Errors returns a error list containing given response errors
func Errors(errors ...ResponseError) ErrList {
    return errors
}

// Specific HTTP error responses

func ErrorNotFound() ResponseError {
    return Error("not found", http.StatusNotFound)
}

func ErrorBadRequest() ResponseError {
    return Error("bad request", http.StatusBadRequest)
}

func ErrorInternalServerError() ResponseError {
    return Error("internal server error", http.StatusInternalServerError)
}

func ErrorForbidden() ResponseError {
    return Error("forbidden", http.StatusForbidden)
}

You can add/remove custom fields to the ResponseError values -

notFoundErr := ErrorNotFound()
notFoundErr.AddErrField("reason", "given 'id' does not exist")
notFoundErr.RemoveErrField("reason")

Since in Go there is no concept of throw, you can only return response error from a function -

func Foo() (resErr ResponseError, ok bool) {
    ...
    if existingUser {
       resErr = ErrorBadRequest()
       resErr.AddErrField("reason", "Email is already in use")
       return resErr, true
    }
    ...
    return ResponseError{}, false
}

To serialize response error into JSON -

resErr, ok := Foo()
if !ok {
    json, err := resErr.SerializeJSON()
    if err != nil {
        // Handle serialization error
    }
}

See the Go playground example here.

like image 130
cod3rboy Avatar answered Oct 30 '25 05:10

cod3rboy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!