Creating a custom error in golang for http responses

huangapple go评论73阅读模式
英文:

Creating a custom error in golang for http responses

问题

我想为我目前正在开发的身份验证服务创建自定义错误。由于我必须为每个HTTP响应创建一个错误,并且我对Go语言还比较陌生,所以我遇到了一些困难。下面的代码是我想在Go中实现的JavaScript代码的复制品。

type CustomError struct {
    message string
}

func (e *CustomError) Error() string {
    return e.message
}

func (e *CustomError) SerializeErrors() []map[string]string {
    return []map[string]string{{"message": e.message}}
}

type NotFoundError struct {
    CustomError
}

func NewNotFoundError() *NotFoundError {
    return &NotFoundError{CustomError{"Route not found"}}
}

func main() {
    existingUser := User.findOne(email)

    if existingUser != nil {
        panic(NewBadRequestError("Email is already in use"))
    }
}

简单来说,我想创建一个名为CustomError的对象/模型,它可以帮助创建更多不同的错误,比如BadRequestError。

这是我的第一个问题,我需要关于创建这个的帮助。

英文:

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

答案1

得分: 2

在Go语言中,你可以通过实现error接口来创建自定义错误类型。

error接口的定义如下:

type error interface {
    Error() string
}

如果你对Go语言完全不熟悉,我建议你从[Go之旅(https://tour.golang.org/methods/9)]开始学习接口的使用。

例如:

type SyntaxError struct {
    msg    string // 错误描述
    Offset int64  // 在读取Offset字节后发生错误
}

func (e *SyntaxError) Error() string { return e.msg }

更多详细信息,请参阅官方Go博客文章


如果你特别想了解HTTP错误,服务器端的错误可以通过向http.ResponseWriter写入错误状态以及所需的错误消息来完成,你可以使用http.Error辅助函数。例如:

func myHandler(w http.ResponseWriter, req *http.Request) {
    if (somethingIsWrong) {
        http.Error(w, "错误消息", http.StatusBadRequest)
    }
}

http.Error的最后一个参数是HTTP状态码,请查阅net/http标准库的文档以了解可用的选项。

现在,将这两者连接起来,http.Error通常会使用你自定义错误的Error()方法作为消息,而状态码则是应用程序特定的。

英文:

In Go you create a custom error type simply by implementing the error interface.

The error interface is:

type error interface {
    Error() string
}

[if you're completely new to Go, I suggest starting with the Tour of Go on interfaces]

For example:

type SyntaxError struct {
    msg    string // description of error
    Offset int64  // error occurred after reading Offset bytes
}

func (e *SyntaxError) Error() string { return e.msg }

See this official Go blog post for more details.


This is for error types; if you're specifically looking for HTTP errors, those in a server are done by writing an error status to a http.ResponseWriter along with the error message you need, and you can use the helper http.Error for this. Example:

func myHandler(w http.ResponseWriter, req *http.Request) {
  if (somethingIsWrong) {
    http.Error(w, "the error message", http.StatusBadRequest)
  }
}

The last param to http.Error is the HTTP status; check out the net/http stdlib package docs for the options you have there.

Now, to connect the two, http.Error would typically use the Error() method of your custom error as the message, and the status is really application specific.

答案2

得分: 2

如果您希望以纯文本形式获取HTTP错误响应,http.Error 应该是您的选择。但是,如果您的身份验证服务需要特定格式(JSON/XML)的错误响应,则需要创建可序列化并写入响应的自定义HTTP错误。

要为JSON格式创建自定义HTTP错误响应(对于XML格式,请修改序列化),首先需要创建一些类型:

type ErrFields map[string]string  // 错误字段-值对类型

type ResponseError struct {
	Msg    string `json:"message"` // 错误消息
	Status int    `json:"status"`  // HTTP状态码
	Data   ErrFields 			   // 用于额外的错误字段,例如原因、详细信息等
}

type ErrList []ResponseError       // 多个HTTP错误类型

ResponseError 类型的方法:

// AddErrField 向响应错误添加新字段,使用给定的键和值
func (err *ResponseError) AddErrField(key, value string) {
	if err.Data == nil {
		err.Data = make(ErrFields)
	}
	err.Data[key] = value
}

// RemoveErrField 从响应错误中删除与给定键匹配的现有字段
func (err *ResponseError) RemoveErrField(key string) {
	delete(err.Data, key)
}

// MarshalJSON 将响应错误序列化为JSON
func (err *ResponseError) MarshalJSON() ([]byte, error) {
	// 确定错误消息的JSON字段名称
	errType := reflect.TypeOf(*err)
	msgField, ok := errType.FieldByName("Msg")
	msgJsonName := "message"
	if ok {
		msgJsonTag := msgField.Tag.Get("json")
		if msgJsonTag != "" {
			msgJsonName = msgJsonTag
		}
	}
	// 确定错误状态码的JSON字段名称
	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 将响应错误转换为序列化的JSON字符串
func (resErr *ResponseError) SerializeJSON() (string, error) {
	value, err := json.Marshal(resErr)
	if err != nil {
		return "", err
	}
	return string(value), nil
}

ErrList 类型的方法:

// SerializeJSON 将错误列表转换为序列化的JSON字符串
func (errList ErrList) SerializeJSON() (string, error) {
	value, err := json.Marshal(errList)
	if err != nil {
		return "", err
	}
	return string(value), nil
}

现在,您可以通过创建不同的 ResponseError 类型的值来创建自定义的HTTP错误响应:

// Error 返回一个通用的响应错误
func Error(msg string, status int) ResponseError {
	return ResponseError{msg, status, nil}
}

// Errors 返回包含给定响应错误的错误列表
func Errors(errors ...ResponseError) ErrList {
	return errors
}

// 特定的HTTP错误响应

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)
}

您可以向 ResponseError 值添加/删除自定义字段:

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

由于Go语言中没有抛出异常的概念,您只能从函数中返回响应错误:

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

要将响应错误序列化为JSON:

resErr, ok := Foo()
if !ok {
    json, err := resErr.SerializeJSON()
    if err != nil {
        // 处理序列化错误
    }
}

在这里查看Go Playground示例:链接

英文:

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.

huangapple
  • 本文由 发表于 2021年7月10日 21:37:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/68328253.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定