Skip to content
/ status Public

Generate error status codes based on Protocol Buffer enums for use in both gRPC and HTTP services.

Notifications You must be signed in to change notification settings

go-leo/status

Repository files navigation

Status

status 是一个微服务常用的状态码管理工具,参考 google Status 规范设计。 可以在Http和GRPC服务中使用。

错误代码

下面是一个表格,其中包含google.rpc.Code中定义的所有gRPC错误代码及其原因的简短说明。

HTTPRPC描述
200OK没有错误
400INVALID_ARGUMENT客户端指定了无效的参数。 检查错误消息和错误详细信息以获取更多信息。
400FAILED_PRECONDITION请求不能在当前系统状态下执行,例如删除非空目录。
400OUT_OF_RANGE客户端指定了无效的范围。
401UNAUTHENTICATED由于遗失,无效或过期的OAuth令牌而导致请求未通过身份验证。
403PERMISSION_DENIED客户端没有足够的权限。这可能是因为OAuth令牌没有正确的范围,客户端没有权限,或者客户端项目尚未启用API。
404NOT_FOUND找不到指定的资源,或者该请求被未公开的原因(例如白名单)拒绝。
409ABORTED并发冲突,例如读-修改-写冲突。
409ALREADY_EXISTS客户端尝试创建的资源已存在。
429RESOURCE_EXHAUSTED资源配额达到速率限制。 客户端应该查找google.rpc.QuotaFailure错误详细信息以获取更多信息。
499CANCELLED客户端取消请求
500DATA_LOSS不可恢复的数据丢失或数据损坏。 客户端应该向用户报告错误。
500UNKNOWN未知的服务器错误。 通常是服务器错误。
500INTERNAL内部服务错误。 通常是服务器错误。
501NOT_IMPLEMENTED服务器未实现该API方法。
503UNAVAILABLE暂停服务。通常是服务器已经关闭。
504DEADLINE_EXCEEDED已超过请求期限。如果重复发生,请考虑降低请求的复杂性。

要处理错误,您可以检查返回状态码的描述,并相应地修改您的请求。

Install

go get github.com/go-leo/status/cmd/proto-gen-status@latest

定义错误

syntax = "proto3";
package leo.example.status.errors;
option go_package = "github.com/go-leo/status/example/api/status/v1;status";

import "leo/status/annotations.proto";

enum Errors {
  option (leo.status.default_rpc_status) = INTERNAL;
  option (leo.status.default_http_status) = 500;

  Default = 0;

  JustRpcStatus = 1 [ (leo.status.rpc_status) = INVALID_ARGUMENT ];

  JustHttpStatus = 2 [ (leo.status.http_status) = 400 ];

  JustMessage = 3 [ (leo.status.message) = "just message" ];

  AllHave = 4 [
    (leo.status.rpc_status) = INVALID_ARGUMENT,
    (leo.status.http_status) = 401,
    (leo.status.message) = "all have"
  ];
}

注意:

  • 枚举类型的default_rpc_statusdefault_http_status需要配置,否则代码生成器跳过此枚举类型
  • 如果枚举值指定了rpc_status(http_status), 则使用指定的 rpc_status(http_status),否则使用default_rpc_status(default_http_status)

代码生成命令

protoc \
--proto_path=. \
--proto_path=../proto/ \
--proto_path=../third_party \
--go_out=. \
--go_opt=paths=source_relative \
--status_out=. \
--status_opt=paths=source_relative \
*/*.proto

注意事项:

生成后的代码

// Code generated by protoc-gen-status. DO NOT EDIT.

package status

import (
	status "github.com/go-leo/status"
	codes "google.golang.org/grpc/codes"
)

var clean_ErrDefault = ErrDefault()

func ErrDefault(opts ...status.Option) status.Status {
	return status.New(codes.Internal, append([]status.Option{status.HttpStatus(500), status.Identifier("Errors_Default"), status.Message("")}, opts...)...)
}

func IsDefault(err error) (status.Status, bool) {
	st, ok := status.From(err)
	if !ok {
		return st, false
	}
	return st, clean_ErrDefault.Is(st)
}

var clean_ErrJustRpcStatus = ErrJustRpcStatus()

func ErrJustRpcStatus(opts ...status.Option) status.Status {
	return status.New(codes.InvalidArgument, append([]status.Option{status.HttpStatus(500), status.Identifier("Errors_JustRpcStatus"), status.Message("")}, opts...)...)
}

func IsJustRpcStatus(err error) (status.Status, bool) {
	st, ok := status.From(err)
	if !ok {
		return st, false
	}
	return st, clean_ErrJustRpcStatus.Is(st)
}

var clean_ErrJustHttpStatus = ErrJustHttpStatus()

func ErrJustHttpStatus(opts ...status.Option) status.Status {
	return status.New(codes.Internal, append([]status.Option{status.HttpStatus(400), status.Identifier("Errors_JustHttpStatus"), status.Message("")}, opts...)...)
}

func IsJustHttpStatus(err error) (status.Status, bool) {
	st, ok := status.From(err)
	if !ok {
		return st, false
	}
	return st, clean_ErrJustHttpStatus.Is(st)
}

var clean_ErrJustMessage = ErrJustMessage()

func ErrJustMessage(opts ...status.Option) status.Status {
	return status.New(codes.Internal, append([]status.Option{status.HttpStatus(500), status.Identifier("Errors_JustMessage"), status.Message("just message")}, opts...)...)
}

func IsJustMessage(err error) (status.Status, bool) {
	st, ok := status.From(err)
	if !ok {
		return st, false
	}
	return st, clean_ErrJustMessage.Is(st)
}

var clean_ErrAllHave = ErrAllHave()

func ErrAllHave(opts ...status.Option) status.Status {
	return status.New(codes.InvalidArgument, append([]status.Option{status.HttpStatus(401), status.Identifier("Errors_AllHave"), status.Message("all have")}, opts...)...)
}

func IsAllHave(err error) (status.Status, bool) {
	st, ok := status.From(err)
	if !ok {
		return st, false
	}
	return st, clean_ErrAllHave.Is(st)
}

注意事项:

  • ErrInvalidPassword会创建并返回一个status.Status
  • IsInvalidPassword会判断传入的error是否是ErrInvalidPassword错误,在没有修改Identifier情况下是相同的

gRPC中使用

Server

type server struct {
	helloworldpb.UnimplementedGreeterServer
}

func (s *server) SayHello(_ context.Context, in *helloworldpb.HelloRequest) (*helloworldpb.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	switch in.GetName() {
	case "Default":
		return nil, statuspb.ErrDefault(status.ErrorInfo("reason", "domain", map[string]string{"key": "value"}))
	case "JustRpcStatus":
		return nil, statuspb.ErrJustRpcStatus(status.RetryInfo(time.Second))
	case "JustHttpStatus":
		return nil, statuspb.ErrJustHttpStatus(status.DebugInfo([]string{"stack entry"}, "stack entry"))
	case "JustMessage":
		return nil, statuspb.ErrJustMessage(status.QuotaFailure([]*errdetails.QuotaFailure_Violation{{Subject: "subject", Description: "description"}}))
	case "AllHave":
		return nil, statuspb.ErrAllHave(status.PreconditionFailure([]*errdetails.PreconditionFailure_Violation{{Subject: "subject", Description: "description"}}))
	case "Custom":
		return nil, status.New(
			codes.Unknown,
			status.Message("custom message"),
			status.BadRequest([]*errdetails.BadRequest_FieldViolation{{Field: "field", Description: "description"}}),
			status.RequestInfo("request_id", "serving_data"),
			status.ResourceInfo("resource_type", "resource_name", "owner", "description"),
			status.Help([]*errdetails.Help_Link{{Url: "url", Description: "description"}}),
			status.LocalizedMessage("locale", "message"),
		)
	}
	return &helloworldpb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

运行Servergo run ./main.go

Client

	r, err := c.SayHello(ctx, &helloworldpb.HelloRequest{Name: *name})
	if err != nil {
		var st status.Status
		var ok bool
		if st, ok = statuspb.IsDefault(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("default error: %v, json: %s", st, jsonData)
		} else if st, ok = statuspb.IsJustRpcStatus(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("just rpc status error: %v, json: %s", st, jsonData)
		} else if st, ok = statuspb.IsJustHttpStatus(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("just http status error: %v, json: %s", st, jsonData)
		} else if st, ok = statuspb.IsJustMessage(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("just message error: %v, json: %s", st, jsonData)
		} else if st, ok = statuspb.IsAllHave(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("all have error: %v, json: %s", st, jsonData)
		} else {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("custom error: %v, json: %s", st, jsonData)
		}
	}
	log.Printf("Greeting: %s", r.GetMessage())

运行Client

go run ./main.go -name Default
go run ./main.go -name JustRpcStatus
go run ./main.go -name JustHttpStatus
go run ./main.go -name JustMessage
go run ./main.go -name AllHave
go run ./main.go -name Custom

完成gRPC例子见grpc

HTTP中使用

Server

	mux := http.NewServeMux()
	mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		content, _ := io.ReadAll(r.Body)
		name := string(content)
		log.Printf("Received: %v", name)
		var st status.Status
		switch name {
		case "Default":
			st = statuspb.ErrDefault(status.ErrorInfo("reason", "domain", map[string]string{"key": "value"}), status.Headers(http.Header{"key": []string{"value"}}))
		case "JustRpcStatus":
			st = statuspb.ErrJustRpcStatus(status.RetryInfo(time.Second), status.Headers(http.Header{"key": []string{"value"}}))
		case "JustHttpStatus":
			st = statuspb.ErrJustHttpStatus(status.DebugInfo([]string{"stack entry"}, "stack entry"), status.Headers(http.Header{"key": []string{"value"}}))
		case "JustMessage":
			st = statuspb.ErrJustMessage(status.QuotaFailure([]*errdetails.QuotaFailure_Violation{{Subject: "subject", Description: "description"}}), status.Headers(http.Header{"key": []string{"value"}}))
		case "AllHave":
			st = statuspb.ErrAllHave(status.PreconditionFailure([]*errdetails.PreconditionFailure_Violation{{Subject: "subject", Description: "description"}}), status.Headers(http.Header{"key": []string{"value"}}))
		case "Custom":
			st = status.New(
				codes.Unknown,
				status.Message("custom message"),
				status.BadRequest([]*errdetails.BadRequest_FieldViolation{{Field: "field", Description: "description"}}),
				status.RequestInfo("request_id", "serving_data"),
				status.ResourceInfo("resource_type", "resource_name", "owner", "description"),
				status.Help([]*errdetails.Help_Link{{Url: "url", Description: "description"}}),
				status.LocalizedMessage("locale", "message"),
				status.Headers(http.Header{"key": []string{"value"}}),
			)
		}
		if st == nil {
			_, _ = w.Write([]byte("Hello " + name))
			return
		}

		var contentType string
		var body []byte
		if jsonBody, marshalErr := st.MarshalJSON(); marshalErr == nil {
			contentType, body = "application/json; charset=utf-8", jsonBody
		}
		w.Header().Set("Content-Type", contentType)
		for k, values := range st.Headers() {
			for _, v := range values {
				w.Header().Add(k, v)
			}
		}
		w.WriteHeader(st.StatusCode())
		_, _ = w.Write(body)
	})

运行Servergo run ./main.go

Client

	resp, err := http.Post("http://"+*addr+"/hello", "text/plain", bytes.NewBuffer([]byte(*name)))
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	err, ok := status.From(resp)
	if ok {
		var st status.Status
		var ok bool
		if st, ok = statuspb.IsDefault(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("default error: %v, json: %s, header: %v", st, jsonData, st.Headers())
		} else if st, ok = statuspb.IsJustRpcStatus(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("just rpc status error: %v, json: %s, header: %v", st, jsonData, st.Headers())
		} else if st, ok = statuspb.IsJustHttpStatus(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("just http status error: %v, json: %s, header: %v", st, jsonData, st.Headers())
		} else if st, ok = statuspb.IsJustMessage(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("just message error: %v, json: %s, header: %v", st, jsonData, st.Headers())
		} else if st, ok = statuspb.IsAllHave(err); ok {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("all have error: %v, json: %s, header: %v", st, jsonData, st.Headers())
		} else {
			jsonData, _ := st.MarshalJSON()
			log.Fatalf("custom error: %v, json: %s, header: %v", st, jsonData, st.Headers())
		}
	}

	message, _ := io.ReadAll(resp.Body)
	log.Printf("Greeting: %s", message)
}

运行Client

go run ./main.go -name Default
go run ./main.go -name JustRpcStatus
go run ./main.go -name JustHttpStatus
go run ./main.go -name JustMessage
go run ./main.go -name AllHave
go run ./main.go -name Custom

完成gRPC例子见grpc

Reference

About

Generate error status codes based on Protocol Buffer enums for use in both gRPC and HTTP services.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages