// Package shared provides shared request parsing and error normalization used // by the user-service application and transport layers. package shared import ( "errors" "net/http" "strings" ) const ( // ErrorCodeInvalidRequest reports malformed or semantically invalid caller // input. ErrorCodeInvalidRequest = "invalid_request" // ErrorCodeConflict reports that the requested mutation conflicts with the // current source-of-truth state. ErrorCodeConflict = "conflict" // ErrorCodeSubjectNotFound reports that the requested user subject does not // exist. ErrorCodeSubjectNotFound = "subject_not_found" // ErrorCodeServiceUnavailable reports that a required dependency is // temporarily unavailable. ErrorCodeServiceUnavailable = "service_unavailable" // ErrorCodeInternalError reports that a local invariant failed unexpectedly. ErrorCodeInternalError = "internal_error" ) var internalErrorStatusCodes = map[string]int{ ErrorCodeInvalidRequest: http.StatusBadRequest, ErrorCodeConflict: http.StatusConflict, ErrorCodeSubjectNotFound: http.StatusNotFound, ErrorCodeServiceUnavailable: http.StatusServiceUnavailable, ErrorCodeInternalError: http.StatusInternalServerError, } var internalStableMessages = map[string]string{ ErrorCodeConflict: "request conflicts with current state", ErrorCodeSubjectNotFound: "subject not found", ErrorCodeServiceUnavailable: "service is unavailable", ErrorCodeInternalError: "internal server error", } // InternalErrorProjection stores the transport-ready representation of one // normalized trusted-internal error. type InternalErrorProjection struct { // StatusCode stores the HTTP status returned to the trusted caller. StatusCode int // Code stores the stable machine-readable error code written into the JSON // envelope. Code string // Message stores the stable or caller-safe message written into the JSON // envelope. Message string } // ServiceError stores one normalized application-layer failure. type ServiceError struct { // Code stores the stable machine-readable error code. Code string // Message stores the caller-safe error message. Message string // Err stores the wrapped underlying cause when one exists. Err error } // Error returns the caller-safe message of ServiceError. func (err *ServiceError) Error() string { if err == nil { return "" } if strings.TrimSpace(err.Message) != "" { return err.Message } if strings.TrimSpace(err.Code) != "" { return err.Code } if err.Err != nil { return err.Err.Error() } return ErrorCodeInternalError } // Unwrap returns the wrapped underlying cause. func (err *ServiceError) Unwrap() error { if err == nil { return nil } return err.Err } // NewServiceError returns one new normalized application-layer error. func NewServiceError(code string, message string, err error) *ServiceError { return &ServiceError{ Code: strings.TrimSpace(code), Message: strings.TrimSpace(message), Err: err, } } // InvalidRequest returns one normalized invalid-request error. func InvalidRequest(message string) *ServiceError { return NewServiceError(ErrorCodeInvalidRequest, strings.TrimSpace(message), nil) } // Conflict returns one normalized conflict error. func Conflict() *ServiceError { return NewServiceError(ErrorCodeConflict, "", nil) } // SubjectNotFound returns one normalized subject-not-found error. func SubjectNotFound() *ServiceError { return NewServiceError(ErrorCodeSubjectNotFound, "", nil) } // ServiceUnavailable returns one normalized dependency-unavailable error. func ServiceUnavailable(err error) *ServiceError { return NewServiceError(ErrorCodeServiceUnavailable, "", err) } // InternalError returns one normalized invariant-failure error. func InternalError(err error) *ServiceError { return NewServiceError(ErrorCodeInternalError, "", err) } // CodeOf returns the normalized service error code carried by err when one is // available. func CodeOf(err error) string { serviceErr, ok := errors.AsType[*ServiceError](err) if !ok || serviceErr == nil { return "" } return serviceErr.Code } // ProjectInternalError normalizes err to the frozen trusted-internal HTTP // error surface. func ProjectInternalError(err error) InternalErrorProjection { serviceErr, ok := errors.AsType[*ServiceError](err) code := CodeOf(err) if _, exists := internalErrorStatusCodes[code]; !exists { return InternalErrorProjection{ StatusCode: http.StatusInternalServerError, Code: ErrorCodeInternalError, Message: internalStableMessages[ErrorCodeInternalError], } } message := "" if ok && serviceErr != nil { message = serviceErr.Message } if stable, exists := internalStableMessages[code]; exists { message = stable } if strings.TrimSpace(message) == "" { message = internalStableMessages[ErrorCodeInternalError] } return InternalErrorProjection{ StatusCode: internalErrorStatusCodes[code], Code: code, Message: message, } }