Skip to content

soyacen/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/soyacen/status/cmd/proto-gen-status@latest

定义错误

syntax = "proto3";
package leo.example.status.errors;
option go_package = "github.com/soyacen/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/soyacen/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

 
 
 

Contributors

Languages