api/errors: refactor to grpc statas (#880)

* refactor to grpc status
pull/883/head
Tony Chen 4 years ago committed by GitHub
parent 8b875e43a5
commit 7c6f53132f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 73
      errors/errors.go
  2. 6
      errors/errors_test.go
  3. 80
      errors/http.go
  4. 93
      errors/types.go
  5. 2
      examples/blog/internal/server/grpc.go
  6. 2
      examples/blog/internal/server/http.go
  7. 3
      examples/helloworld/client/main.go
  8. 3
      examples/helloworld/server/main.go
  9. 9
      middleware/logging/logging.go
  10. 8
      middleware/metrics/metrics.go
  11. 105
      middleware/status/status.go
  12. 17
      middleware/status/status_test.go
  13. 9
      transport/http/client.go

@ -4,6 +4,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
) )
const ( const (
@ -11,42 +15,66 @@ const (
SupportPackageIsVersion1 = true SupportPackageIsVersion1 = true
) )
// Error contains an error response from the server. // Error is describes the cause of the error with structured details.
// For more details see https://github.com/go-kratos/kratos/issues/858. // For more details see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto.
type Error struct { type Error struct {
Code int `json:"code"` s *status.Status
Message string `json:"message"`
Domain string `json:"domain"`
Reason string `json:"reason"`
Metadata map[string]string `json:"metadata"`
} }
func (e *Error) Error() string { func (e *Error) Error() string {
return fmt.Sprintf("error: code = %d message = %s", e.Code, e.Message) return fmt.Sprintf("error: domain = %s reason = %s", e.Domain, e.Reason)
}
// GRPCStatus returns the Status represented by se.
func (e *Error) GRPCStatus() *status.Status {
s, err := e.s.WithDetails(&errdetails.ErrorInfo{
Domain: e.Domain,
Reason: e.Reason,
Metadata: e.Metadata,
})
if err != nil {
return e.s
}
return s
} }
// Is matches each error in the chain with the target value. // Is matches each error in the chain with the target value.
func (e *Error) Is(err error) bool { func (e *Error) Is(err error) bool {
if target := new(Error); errors.As(err, &target) { if target := new(Error); errors.As(err, &target) {
return target.Code == e.Code return target.Domain == e.Domain && target.Reason == e.Reason
} }
return false return false
} }
// WithMetadata with an MD formed by the mapping of key, value.
func (e *Error) WithMetadata(md map[string]string) *Error {
err := *e
err.Metadata = md
return &err
}
// New returns an error object for the code, message. // New returns an error object for the code, message.
func New(code int, message string) *Error { func New(code codes.Code, domain, reason, message string) *Error {
return &Error{ return &Error{
Code: code, s: status.New(codes.Code(code), message),
Message: message, Domain: domain,
Reason: reason,
} }
} }
// Newf New(code fmt.Sprintf(format, a...)) // Newf New(code fmt.Sprintf(format, a...))
func Newf(code int, format string, a ...interface{}) *Error { func Newf(code codes.Code, domain, reason, format string, a ...interface{}) *Error {
return New(code, fmt.Sprintf(format, a...)) return New(code, domain, reason, fmt.Sprintf(format, a...))
} }
// Errorf returns an error object for the code, message and error info. // Errorf returns an error object for the code, message and error info.
func Errorf(code int, domain, reason, format string, a ...interface{}) *ErrorInfo { func Errorf(code codes.Code, domain, reason, format string, a ...interface{}) error {
return &ErrorInfo{ return &Error{
err: Newf(code, format, a...), s: status.New(codes.Code(code), fmt.Sprintf(format, a...)),
Domain: domain, Domain: domain,
Reason: reason, Reason: reason,
} }
@ -54,12 +82,12 @@ func Errorf(code int, domain, reason, format string, a ...interface{}) *ErrorInf
// Code returns the code for a particular error. // Code returns the code for a particular error.
// It supports wrapped errors. // It supports wrapped errors.
func Code(err error) int { func Code(err error) codes.Code {
if err == nil { if err == nil {
return http.StatusOK return http.StatusOK
} }
if target := new(Error); errors.As(err, &target) { if target := new(Error); errors.As(err, &target) {
return target.Code return target.s.Code()
} }
return http.StatusInternalServerError return http.StatusInternalServerError
} }
@ -67,7 +95,7 @@ func Code(err error) int {
// Domain returns the domain for a particular error. // Domain returns the domain for a particular error.
// It supports wrapped errors. // It supports wrapped errors.
func Domain(err error) string { func Domain(err error) string {
if target := new(ErrorInfo); errors.As(err, &target) { if target := new(Error); errors.As(err, &target) {
return target.Domain return target.Domain
} }
return "" return ""
@ -76,17 +104,8 @@ func Domain(err error) string {
// Reason returns the reason for a particular error. // Reason returns the reason for a particular error.
// It supports wrapped errors. // It supports wrapped errors.
func Reason(err error) string { func Reason(err error) string {
if target := new(ErrorInfo); errors.As(err, &target) { if target := new(Error); errors.As(err, &target) {
return target.Reason return target.Reason
} }
return "" return ""
} }
// FromError try to convert an error to *Error.
// It supports wrapped errors.
func FromError(err error) *Error {
if target := new(Error); errors.As(err, &target) {
return target
}
return New(http.StatusInternalServerError, err.Error())
}

@ -4,14 +4,16 @@ import (
"errors" "errors"
"fmt" "fmt"
"testing" "testing"
"google.golang.org/grpc/codes"
) )
func TestError(t *testing.T) { func TestError(t *testing.T) {
var ( var (
base *Error base *Error
) )
err := Errorf(400, "domain", "reason", "message") err := Newf(codes.InvalidArgument, "domain", "reason", "message")
err2 := Errorf(400, "domain", "reason", "message") err2 := Newf(codes.InvalidArgument, "domain", "reason", "message")
err3 := err.WithMetadata(map[string]string{ err3 := err.WithMetadata(map[string]string{
"foo": "bar", "foo": "bar",
}) })

@ -1,80 +0,0 @@
package errors
import "net/http"
// BadRequest new BadRequest error that is mapped to a 400 response.
func BadRequest(domain, reason, message string) *ErrorInfo {
return Errorf(http.StatusBadRequest, domain, reason, message)
}
// IsBadRequest determines if err is an error which indicates a BadRequest error.
// It supports wrapped errors.
func IsBadRequest(err error) bool {
return Code(err) == http.StatusBadRequest
}
// Unauthorized new Unauthorized error that is mapped to a 401 response.
func Unauthorized(domain, reason, message string) *ErrorInfo {
return Errorf(http.StatusUnauthorized, domain, reason, message)
}
// IsUnauthorized determines if err is an error which indicates a Unauthorized error.
// It supports wrapped errors.
func IsUnauthorized(err error) bool {
return Code(err) == http.StatusUnauthorized
}
// Forbidden new Forbidden error that is mapped to a 403 response.
func Forbidden(domain, reason, message string) *ErrorInfo {
return Errorf(http.StatusForbidden, domain, reason, message)
}
// IsForbidden determines if err is an error which indicates a Forbidden error.
// It supports wrapped errors.
func IsForbidden(err error) bool {
return Code(err) == http.StatusForbidden
}
// NotFound new NotFound error that is mapped to a 404 response.
func NotFound(domain, reason, message string) *ErrorInfo {
return Errorf(http.StatusNotFound, domain, reason, message)
}
// IsNotFound determines if err is an error which indicates an NotFound error.
// It supports wrapped errors.
func IsNotFound(err error) bool {
return Code(err) == http.StatusNotFound
}
// Conflict new Conflict error that is mapped to a 409 response.
func Conflict(domain, reason, message string) *ErrorInfo {
return Errorf(http.StatusConflict, domain, reason, message)
}
// IsConflict determines if err is an error which indicates a Conflict error.
// It supports wrapped errors.
func IsConflict(err error) bool {
return Code(err) == http.StatusConflict
}
// InternalServer new InternalServer error that is mapped to a 500 response.
func InternalServer(domain, reason, message string) *ErrorInfo {
return Errorf(http.StatusInternalServerError, domain, reason, message)
}
// IsInternalServer determines if err is an error which indicates an InternalServer error.
// It supports wrapped errors.
func IsInternalServer(err error) bool {
return Code(err) == http.StatusInternalServerError
}
// ServiceUnavailable new ServiceUnavailable error that is mapped to a HTTP 503 response.
func ServiceUnavailable(domain, reason, message string) *ErrorInfo {
return Errorf(http.StatusServiceUnavailable, domain, reason, message)
}
// IsServiceUnavailable determines if err is an error which indicates a ServiceUnavailable error.
// It supports wrapped errors.
func IsServiceUnavailable(err error) bool {
return Code(err) == http.StatusServiceUnavailable
}

@ -1,37 +1,82 @@
package errors package errors
import ( import (
"errors" "google.golang.org/grpc/codes"
"fmt"
) )
// ErrorInfo is describes the cause of the error with structured details. // BadRequest new BadRequest error that is mapped to a 400 response.
// For more details see https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto. func BadRequest(domain, reason, message string) *Error {
type ErrorInfo struct { return Newf(codes.InvalidArgument, domain, reason, message)
err *Error
Domain string `json:"domain"`
Reason string `json:"reason"`
Metadata map[string]string `json:"metadata"`
} }
func (e *ErrorInfo) Error() string { // IsBadRequest determines if err is an error which indicates a BadRequest error.
return fmt.Sprintf("error: domain = %s reason = %s", e.Domain, e.Reason) // It supports wrapped errors.
func IsBadRequest(err error) bool {
return Code(err) == codes.InvalidArgument
} }
func (e *ErrorInfo) Unwrap() error {
return e.err // Unauthorized new Unauthorized error that is mapped to a 401 response.
func Unauthorized(domain, reason, message string) *Error {
return Newf(codes.Unauthenticated, domain, reason, message)
}
// IsUnauthorized determines if err is an error which indicates a Unauthorized error.
// It supports wrapped errors.
func IsUnauthorized(err error) bool {
return Code(err) == codes.Unauthenticated
}
// Forbidden new Forbidden error that is mapped to a 403 response.
func Forbidden(domain, reason, message string) *Error {
return Newf(codes.PermissionDenied, domain, reason, message)
}
// IsForbidden determines if err is an error which indicates a Forbidden error.
// It supports wrapped errors.
func IsForbidden(err error) bool {
return Code(err) == codes.PermissionDenied
}
// NotFound new NotFound error that is mapped to a 404 response.
func NotFound(domain, reason, message string) *Error {
return Newf(codes.NotFound, domain, reason, message)
}
// IsNotFound determines if err is an error which indicates an NotFound error.
// It supports wrapped errors.
func IsNotFound(err error) bool {
return Code(err) == codes.NotFound
}
// Conflict new Conflict error that is mapped to a 409 response.
func Conflict(domain, reason, message string) *Error {
return Newf(codes.Aborted, domain, reason, message)
}
// IsConflict determines if err is an error which indicates a Conflict error.
// It supports wrapped errors.
func IsConflict(err error) bool {
return Code(err) == codes.Aborted
}
// InternalServer new InternalServer error that is mapped to a 500 response.
func InternalServer(domain, reason, message string) *Error {
return Newf(codes.Internal, domain, reason, message)
}
// IsInternalServer determines if err is an error which indicates an Internal error.
// It supports wrapped errors.
func IsInternalServer(err error) bool {
return Code(err) == codes.Internal
} }
// Is matches each error in the chain with the target value. // ServiceUnavailable new ServiceUnavailable error that is mapped to a HTTP 503 response.
func (e *ErrorInfo) Is(err error) bool { func ServiceUnavailable(domain, reason, message string) *Error {
if target := new(ErrorInfo); errors.As(err, &target) { return Newf(codes.Unavailable, domain, reason, message)
return target.Domain == e.Domain && target.Reason == e.Reason
}
return false
} }
// WithMetadata with an MD formed by the mapping of key, value. // IsServiceUnavailable determines if err is an error which indicates a Unavailable error.
func (e *ErrorInfo) WithMetadata(md map[string]string) *ErrorInfo { // It supports wrapped errors.
err := *e func IsServiceUnavailable(err error) bool {
err.Metadata = md return Code(err) == codes.Unavailable
return &err
} }

@ -8,7 +8,6 @@ import (
"github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/middleware"
"github.com/go-kratos/kratos/v2/middleware/logging" "github.com/go-kratos/kratos/v2/middleware/logging"
"github.com/go-kratos/kratos/v2/middleware/recovery" "github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/middleware/status"
"github.com/go-kratos/kratos/v2/middleware/tracing" "github.com/go-kratos/kratos/v2/middleware/tracing"
"github.com/go-kratos/kratos/v2/transport/grpc" "github.com/go-kratos/kratos/v2/transport/grpc"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@ -19,7 +18,6 @@ func NewGRPCServer(c *conf.Server, tracer trace.TracerProvider, blog *service.Bl
var opts = []grpc.ServerOption{ var opts = []grpc.ServerOption{
grpc.Middleware( grpc.Middleware(
middleware.Chain( middleware.Chain(
status.Server(),
tracing.Server(tracing.WithTracerProvider(tracer)), tracing.Server(tracing.WithTracerProvider(tracer)),
logging.Server(log.DefaultLogger), logging.Server(log.DefaultLogger),
recovery.Recovery(), recovery.Recovery(),

@ -8,7 +8,6 @@ import (
"github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/middleware"
"github.com/go-kratos/kratos/v2/middleware/logging" "github.com/go-kratos/kratos/v2/middleware/logging"
"github.com/go-kratos/kratos/v2/middleware/recovery" "github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/middleware/status"
"github.com/go-kratos/kratos/v2/middleware/tracing" "github.com/go-kratos/kratos/v2/middleware/tracing"
"github.com/go-kratos/kratos/v2/transport/http" "github.com/go-kratos/kratos/v2/transport/http"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@ -28,7 +27,6 @@ func NewHTTPServer(c *conf.Server, tracer trace.TracerProvider, blog *service.Bl
} }
m := http.Middleware( m := http.Middleware(
middleware.Chain( middleware.Chain(
status.Server(),
tracing.Server(tracing.WithTracerProvider(tracer)), tracing.Server(tracing.WithTracerProvider(tracer)),
logging.Server(log.DefaultLogger), logging.Server(log.DefaultLogger),
recovery.Recovery(), recovery.Recovery(),

@ -9,7 +9,6 @@ import (
"github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/errors"
"github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/middleware"
"github.com/go-kratos/kratos/v2/middleware/recovery" "github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/middleware/status"
transgrpc "github.com/go-kratos/kratos/v2/transport/grpc" transgrpc "github.com/go-kratos/kratos/v2/transport/grpc"
transhttp "github.com/go-kratos/kratos/v2/transport/http" transhttp "github.com/go-kratos/kratos/v2/transport/http"
) )
@ -25,7 +24,6 @@ func callHTTP() {
transhttp.WithMiddleware( transhttp.WithMiddleware(
middleware.Chain( middleware.Chain(
recovery.Recovery(), recovery.Recovery(),
status.Client(),
), ),
), ),
) )
@ -59,7 +57,6 @@ func callGRPC() {
transgrpc.WithEndpoint("127.0.0.1:9000"), transgrpc.WithEndpoint("127.0.0.1:9000"),
transgrpc.WithMiddleware( transgrpc.WithMiddleware(
middleware.Chain( middleware.Chain(
status.Client(),
recovery.Recovery(), recovery.Recovery(),
), ),
), ),

@ -12,7 +12,6 @@ import (
"github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/middleware"
"github.com/go-kratos/kratos/v2/middleware/logging" "github.com/go-kratos/kratos/v2/middleware/logging"
"github.com/go-kratos/kratos/v2/middleware/recovery" "github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/middleware/status"
"github.com/go-kratos/kratos/v2/transport/grpc" "github.com/go-kratos/kratos/v2/transport/grpc"
"github.com/go-kratos/kratos/v2/transport/http" "github.com/go-kratos/kratos/v2/transport/http"
) )
@ -50,7 +49,6 @@ func main() {
grpc.Address(":9000"), grpc.Address(":9000"),
grpc.Middleware( grpc.Middleware(
middleware.Chain( middleware.Chain(
status.Server(),
logging.Server(logger), logging.Server(logger),
recovery.Recovery(), recovery.Recovery(),
), ),
@ -63,7 +61,6 @@ func main() {
httpSrv.HandlePrefix("/", pb.NewGreeterHandler(s, httpSrv.HandlePrefix("/", pb.NewGreeterHandler(s,
http.Middleware( http.Middleware(
middleware.Chain( middleware.Chain(
status.Server(),
logging.Server(logger), logging.Server(logger),
recovery.Recovery(), recovery.Recovery(),
), ),

@ -3,6 +3,7 @@ package logging
import ( import (
"context" "context"
"fmt" "fmt"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/errors"
@ -49,7 +50,7 @@ func Server(l log.Logger) middleware.Middleware {
"method", method, "method", method,
"args", args, "args", args,
"query", query, "query", query,
"code", errors.Code(err), "code", uint32(errors.Code(err)),
"error", err.Error(), "error", err.Error(),
) )
return nil, err return nil, err
@ -73,7 +74,7 @@ func Server(l log.Logger) middleware.Middleware {
"path", path, "path", path,
"method", method, "method", method,
"args", args, "args", args,
"code", errors.Code(err), "code", uint32(errors.Code(err)),
"error", err.Error(), "error", err.Error(),
) )
return nil, err return nil, err
@ -130,7 +131,7 @@ func Client(l log.Logger) middleware.Middleware {
"method", method, "method", method,
"args", args, "args", args,
"query", query, "query", query,
"code", errors.Code(err), "code", uint32(errors.Code(err)),
"error", err.Error(), "error", err.Error(),
) )
return nil, err return nil, err
@ -154,7 +155,7 @@ func Client(l log.Logger) middleware.Middleware {
"path", path, "path", path,
"method", method, "method", method,
"args", args, "args", args,
"code", errors.Code(err), "code", uint32(errors.Code(err)),
"error", err.Error(), "error", err.Error(),
) )
return nil, err return nil, err

@ -48,7 +48,7 @@ func Server(opts ...Option) middleware.Middleware {
var ( var (
method string method string
path string path string
code int code uint32
) )
if info, ok := grpc.FromServerContext(ctx); ok { if info, ok := grpc.FromServerContext(ctx); ok {
method = "POST" method = "POST"
@ -65,7 +65,7 @@ func Server(opts ...Option) middleware.Middleware {
startTime := time.Now() startTime := time.Now()
reply, err := handler(ctx, req) reply, err := handler(ctx, req)
if err != nil { if err != nil {
code = errors.Code(err) code = uint32(errors.Code(err))
} }
if options.requests != nil { if options.requests != nil {
options.requests.With(method, path, strconv.Itoa(int(code))).Inc() options.requests.With(method, path, strconv.Itoa(int(code))).Inc()
@ -90,7 +90,7 @@ func Client(opts ...Option) middleware.Middleware {
var ( var (
method string method string
path string path string
code int code uint32
) )
if info, ok := grpc.FromClientContext(ctx); ok { if info, ok := grpc.FromClientContext(ctx); ok {
method = "POST" method = "POST"
@ -102,7 +102,7 @@ func Client(opts ...Option) middleware.Middleware {
startTime := time.Now() startTime := time.Now()
reply, err := handler(ctx, req) reply, err := handler(ctx, req)
if err != nil { if err != nil {
code = errors.Code(err) code = uint32(errors.Code(err))
} }
if options.requests != nil { if options.requests != nil {
options.requests.With(method, path, strconv.Itoa(int(code))).Inc() options.requests.With(method, path, strconv.Itoa(int(code))).Inc()

@ -1,105 +0,0 @@
package status
import (
"context"
"github.com/go-kratos/kratos/v2/errors"
"github.com/go-kratos/kratos/v2/internal/http"
"github.com/go-kratos/kratos/v2/middleware"
//lint:ignore SA1019 grpc
"github.com/golang/protobuf/proto"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/status"
)
// HandlerFunc is middleware error handler.
type HandlerFunc func(context.Context, error) error
// Option is recovery option.
type Option func(*options)
type options struct {
handler HandlerFunc
}
// WithHandler with status handler.
func WithHandler(h HandlerFunc) Option {
return func(o *options) {
o.handler = h
}
}
// Server is an error middleware.
func Server(opts ...Option) middleware.Middleware {
options := options{
handler: encodeError,
}
for _, o := range opts {
o(&options)
}
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (interface{}, error) {
reply, err := handler(ctx, req)
if err != nil {
return nil, options.handler(ctx, err)
}
return reply, nil
}
}
}
// Client is an error middleware.
func Client(opts ...Option) middleware.Middleware {
options := options{
handler: decodeError,
}
for _, o := range opts {
o(&options)
}
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (interface{}, error) {
reply, err := handler(ctx, req)
if err != nil {
return nil, options.handler(ctx, err)
}
return reply, nil
}
}
}
func encodeError(ctx context.Context, err error) error {
var details []proto.Message
if target := new(errors.ErrorInfo); errors.As(err, &target) {
details = append(details, &errdetails.ErrorInfo{
Domain: target.Domain,
Reason: target.Reason,
Metadata: target.Metadata,
})
}
es := errors.FromError(err)
gs := status.New(http.GRPCCodeFromStatus(es.Code), es.Message)
gs, err = gs.WithDetails(details...)
if err != nil {
return err
}
return gs.Err()
}
func decodeError(ctx context.Context, err error) error {
gs := status.Convert(err)
code := http.StatusFromGRPCCode(gs.Code())
message := gs.Message()
for _, detail := range gs.Details() {
switch d := detail.(type) {
case *errdetails.ErrorInfo:
return errors.Errorf(
code,
d.Domain,
d.Reason,
message,
).WithMetadata(d.Metadata)
}
}
return errors.New(code, message)
}

@ -1,17 +0,0 @@
package status
import (
"context"
"testing"
"github.com/go-kratos/kratos/v2/errors"
)
func TestErrEncoder(t *testing.T) {
err := errors.BadRequest("test", "invalid_argument", "format")
en := encodeError(context.Background(), err)
de := decodeError(context.Background(), en)
if !errors.IsBadRequest(de) {
t.Errorf("expected %v got %v", err, de)
}
}

@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/encoding"
"github.com/go-kratos/kratos/v2/errors"
xhttp "github.com/go-kratos/kratos/v2/internal/http" xhttp "github.com/go-kratos/kratos/v2/internal/http"
"github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/middleware"
"github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport"
@ -75,7 +74,7 @@ func NewTransport(ctx context.Context, opts ...ClientOption) (http.RoundTripper,
ctx: ctx, ctx: ctx,
timeout: 500 * time.Millisecond, timeout: 500 * time.Millisecond,
transport: http.DefaultTransport, transport: http.DefaultTransport,
errorDecoder: checkResponse, errorDecoder: CheckResponse,
} }
for _, o := range opts { for _, o := range opts {
o(options) o(options)
@ -145,9 +144,9 @@ func Do(client *http.Client, req *http.Request, target interface{}) error {
return codec.Unmarshal(data, target) return codec.Unmarshal(data, target)
} }
// checkResponse returns an error (of type *Error) if the response // CheckResponse returns an error (of type *Error) if the response
// status code is not 2xx. // status code is not 2xx.
func checkResponse(ctx context.Context, res *http.Response) error { func CheckResponse(ctx context.Context, res *http.Response) error {
if res.StatusCode >= 200 && res.StatusCode <= 299 { if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil return nil
} }
@ -158,5 +157,5 @@ func checkResponse(ctx context.Context, res *http.Response) error {
return status.ErrorProto(st) return status.ErrorProto(st)
} }
} }
return errors.New(res.StatusCode, "") return status.Error(xhttp.GRPCCodeFromStatus(res.StatusCode), res.Status)
} }

Loading…
Cancel
Save