node.jsgoerror-handlingbackendmux

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


Solution

  • 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.