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