// Package errors provides a way to return detailed information // for an RPC request error. The error is normally JSON encoded. package errors // import "go.unistack.org/micro/v3/errors" import ( "bytes" "encoding/json" "fmt" "net/http" "strconv" "strings" ) var ( // ErrBadRequest returns then requests contains invalid data ErrBadRequest = &Error{Code: 400} // ErrUnauthorized returns then user have unauthorized call ErrUnauthorized = &Error{Code: 401} // ErrForbidden returns then user have not access the resource ErrForbidden = &Error{Code: 403} // ErrNotFound returns then user specify invalid endpoint ErrNotFound = &Error{Code: 404} // ErrMethodNotAllowed returns then user try to get invalid method ErrMethodNotAllowed = &Error{Code: 405} // ErrTimeout returns then timeout exceeded ErrTimeout = &Error{Code: 408} // ErrConflict returns then request create duplicate resource ErrConflict = &Error{Code: 409} // ErrInternalServerError returns then server cant process request because of internal error ErrInternalServerError = &Error{Code: 500} // ErNotImplemented returns then server does not have desired endpoint method ErNotImplemented = &Error{Code: 501} // ErrBadGateway returns then server cant process request ErrBadGateway = &Error{Code: 502} // ErrServiceUnavailable returns then service unavailable ErrServiceUnavailable = &Error{Code: 503} // ErrGatewayTimeout returns then server have long time to process request ErrGatewayTimeout = &Error{Code: 504} ) // Error type type Error struct { // ID holds error id or service, usually someting like my_service or id ID string // Detail holds some useful details about error Detail string // Status usually holds text of http status Status string // Code holds error code Code int32 } // Error satisfies error interface func (e *Error) Error() string { b, _ := json.Marshal(e) return string(b) } /* // Generator struct holds id of error type Generator struct { id string } // Generator can emit new error with static id func NewGenerator(id string) *Generator { return &Generator{id: id} } func (g *Generator) BadRequest(format string, args ...interface{}) error { return BadRequest(g.id, format, args...) } */ // New generates a custom error func New(id, detail string, code int32) error { return &Error{ ID: id, Code: code, Detail: detail, Status: http.StatusText(int(code)), } } // Parse tries to parse a JSON string into an error. If that // fails, it will set the given string as the error detail. func Parse(err string) *Error { e := &Error{} nerr := json.Unmarshal([]byte(err), e) if nerr != nil { e.Detail = err } return e } // BadRequest generates a 400 error. func BadRequest(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 400, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(400), } } // Unauthorized generates a 401 error. func Unauthorized(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 401, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(401), } } // Forbidden generates a 403 error. func Forbidden(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 403, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(403), } } // NotFound generates a 404 error. func NotFound(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 404, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(404), } } // MethodNotAllowed generates a 405 error. func MethodNotAllowed(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 405, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(405), } } // Timeout generates a 408 error. func Timeout(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 408, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(408), } } // Conflict generates a 409 error. func Conflict(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 409, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(409), } } // InternalServerError generates a 500 error. func InternalServerError(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 500, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(500), } } // NotImplemented generates a 501 error func NotImplemented(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 501, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(501), } } // BadGateway generates a 502 error func BadGateway(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 502, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(502), } } // ServiceUnavailable generates a 503 error func ServiceUnavailable(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 503, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(503), } } // GatewayTimeout generates a 504 error func GatewayTimeout(id, format string, args ...interface{}) error { return &Error{ ID: id, Code: 504, Detail: fmt.Sprintf(format, args...), Status: http.StatusText(504), } } // Equal tries to compare errors func Equal(err1 error, err2 error) bool { verr1, ok1 := err1.(*Error) verr2, ok2 := err2.(*Error) if ok1 != ok2 { return false } if !ok1 { return err1 == err2 } if verr1.Code != verr2.Code { return false } return true } // CodeIn return true if err has specified code func CodeIn(err interface{}, codes ...int32) bool { var code int32 switch verr := err.(type) { case *Error: code = verr.Code case int32: code = verr default: return false } for _, check := range codes { if code == check { return true } } return false } // FromError try to convert go error to *Error func FromError(err error) *Error { if verr, ok := err.(*Error); ok && verr != nil { return verr } return Parse(err.Error()) } // MarshalJSON returns error data func (e *Error) MarshalJSON() ([]byte, error) { return e.Marshal() } // UnmarshalJSON set error data func (e *Error) UnmarshalJSON(data []byte) error { return e.Unmarshal(data) } // ProtoMessage noop func func (e *Error) ProtoMessage() {} // Reset resets error func (e *Error) Reset() { *e = Error{} } // String returns error as string func (e *Error) String() string { return fmt.Sprintf(`{"id":"%s","detail":"%s","status":"%s","code":%d}`, addslashes(e.ID), addslashes(e.Detail), addslashes(e.Status), e.Code) } // Marshal returns error data func (e *Error) Marshal() ([]byte, error) { return []byte(e.String()), nil } // Unmarshal set error data func (e *Error) Unmarshal(data []byte) error { str := string(data) if len(data) < 41 { return fmt.Errorf("invalid data") } parts := strings.FieldsFunc(str[1:len(str)-1], func(r rune) bool { return r == ',' }) for _, part := range parts { nparts := strings.FieldsFunc(part, func(r rune) bool { return r == ':' }) for idx := 0; idx < len(nparts)/2; idx += 2 { val := strings.Trim(nparts[idx+1], `"`) if len(val) == 0 { continue } switch { case nparts[idx] == `"id"`: e.ID = val case nparts[idx] == `"detail"`: e.Detail = val case nparts[idx] == `"status"`: e.Status = val case nparts[idx] == `"code"`: c, err := strconv.ParseInt(val, 10, 32) if err != nil { return err } e.Code = int32(c) } idx++ } } return nil } func addslashes(str string) string { var buf bytes.Buffer for _, char := range str { switch char { case '\'', '"', '\\': buf.WriteRune('\\') } buf.WriteRune(char) } return buf.String() }