package bsl import ( "encoding/json" "errors" "fmt" "regexp" ) const ( // constraintViolationErr error type returned by BSL when the request body is not in the acceptable format. constraintViolationErr = "com.ncr.nep.common.exception.PayloadConstraintViolationException" // tenantAccessDeniedErr error type returned by BSL when the user does not have access to the requested organization (or sub organization). tenantAccessDeniedErr = "com.ncr.nep.common.exception.TenantAccessDeniedException" // accessDeniedErr error type returned by BSL when the user is not authorized to access that resource. accessDeniedErr = "com.ncr.nep.common.exception.AccessDeniedException" // invalidCredentialsErr error type returned by BSL when the user credentials supplied is invalid. invalidCredentialsErr = "com.ncr.nep.common.exception.InvalidCredentialsException" //nolint:gosec // resourceNotExistsErr error type returned when the requested resource does not exists or has been deleted. resourceNotExistsErr = "com.ncr.nep.common.exception.ResourceDoesNotExistException" // resourceAlreadyExistsErr error type returned when the requested already exists. resourceAlreadyExistsErr = "com.ncr.nep.common.exception.ResourceAlreadyExistsException" // inactiveAccountErr error type returned when the user account has been deactivated or status changed to inactive. inactiveAccountErr = "com.ncr.nep.security.authn.AccountInactiveException" // lockedAccountErr error type returned when the user account has been locked from more than 5 invalid password tries. lockedAccountErr = "com.ncr.nep.security.authn.AccountLockedException" // businessConstraintViolationErr error type returned similar to the constraintViolationErr. businessConstraintViolationErr = "com.ncr.nep.common.exception.BusinessConstraintViolationException" // genericErr error type returned when the client request is invalid or incomplete. genericErr = "javax.ws.rs.ClientErrorException" ) var ( // wellKnownErrorTypes maps the BSL error types to Edge Client readable messages. wellKnownErrorTypes = map[string]string{ tenantAccessDeniedErr: "Access Denied, Tenant cannot access resource", accessDeniedErr: "Access Denied, Authorization Required", invalidCredentialsErr: "Incorrect username, password, or organization.", resourceNotExistsErr: "Resource does not exist", resourceAlreadyExistsErr: "the resources already exists", constraintViolationErr: "Constraint violation", //can be returned when the resource already exists, check for conflict 409 statusCode too inactiveAccountErr: "Your account is inactive. Contact your organization admin to activate your account.", lockedAccountErr: "Your account has been locked. Contact your organization admin to unlock your account.", businessConstraintViolationErr: "Invalid Value supplied", genericErr: "Client Error Exception", } ) // Error represents the BSL error. type Error struct { Err error Message string StatusCode int Verbose string ErrorType string URL string Path string Method string } // ErrorResponse represents an error body response from BSL. type ErrorResponse struct { StatusCode int `json:"statusCode"` Message string `json:"message"` ErrorType string `json:"errorType"` Details []string `json:"details"` ConstraintViolations []ConstraintViolation `json:"constraintViolations"` } // ConstraintViolation is returned when the BSL error type is com.ncr.nep.common.exception.PayloadConstraintViolationException. type ConstraintViolation struct { InvalidValue string `json:"invalidValue"` Message string `json:"message"` PropertyPath string `json:"propertyPath"` } // New returns a new BSL Error. func New(msg string) *Error { return Wrap(errors.New(msg)) } // Wrap returns a new BSL Error from a stdlib error. func Wrap(err error) *Error { if err == nil { return &Error{} } msg := err.Error() if msg == "" { msg = "Internal Server Error" } return &Error{ Err: err, Message: msg, } } // SetMessage sets the message field of the BSL error. func (b *Error) SetMessage(msg string) *Error { b.Message = msg return b } // SetStatusCode sets the status code of the BSL error. func (b *Error) SetStatusCode(code int) *Error { b.StatusCode = code return b } // SetURL sets the url of the BSL error. func (b *Error) SetURL(url string) *Error { b.URL = url return b } // SetPath sets the path field of the BSL error. func (b *Error) SetPath(path string) *Error { b.Path = path return b } // SetMethod sets the method field of the BSL error. func (b *Error) SetMethod(method string) *Error { b.Method = method return b } // SetErrorType sets the error type of the BSL error. func (b *Error) SetErrorType(errorType string) *Error { b.ErrorType = errorType return b } // SetVerbose sets the verbose field with the entire error response returned by BSL. func (b *Error) SetVerbose(verbose string) *Error { b.Verbose = verbose return b } // Error implements the error interface and returns the error message of the BSL error. func (b *Error) Error() string { return b.Message } // Unwrap converts a BSL error to an stdlib error. func (b *Error) Unwrap() error { if b != nil { return b.Err } return nil } // UnmarshalErrorResponse unmarshals the error response body from BSL and sets the appropriate error message, // verbose, status code and error type. func (b *Error) UnmarshalErrorResponse(body []byte) *Error { if len(body) > 0 { bslErrResponse := &ErrorResponse{} err := json.Unmarshal(body, &bslErrResponse) if err != nil { b.Message = fmt.Sprintf("an error occurred unmarshaling json response. %s", err.Error()) return b } if val, exists := wellKnownErrorTypes[bslErrResponse.ErrorType]; exists { switch val { case wellKnownErrorTypes[constraintViolationErr], wellKnownErrorTypes[businessConstraintViolationErr], wellKnownErrorTypes[resourceNotExistsErr]: msg := stripSymbols(bslErrResponse.Message) if msg == "" { b.Message = val break } b.Message = msg default: b.Message = val } } return b.SetStatusCode(bslErrResponse.StatusCode).SetErrorType(bslErrResponse.ErrorType).SetVerbose(string(body)) } return b } // Extensions builds the error extensions that are displayed in the additional field in the graphql error response. func (b *Error) Extensions() map[string]interface{} { return map[string]interface{}{ "statusCode": b.StatusCode, //"verbose": b.Verbose, don't want expose implementation details "method": b.Method, "path": b.Path, "url": b.URL, "errorType": b.ErrorType, } } // Is checks if a BSL error and an stdlib error are equal. func (b *Error) Is(target error) bool { if target == nil { return false } return b.Message == target.Error() } // IsError checks if two BSL errors are equal. func (b *Error) IsError(target *Error) bool { return b.Message == target.Message && b.Method == target.Method && b.ErrorType == target.ErrorType && b.StatusCode == target.StatusCode && b.URL == target.URL && b.Path == target.Path } // stripSymbols strips the [ or ] symbols that BSL adds to error messages sometimes :( func stripSymbols(message string) string { reg, err := regexp.Compile(`[\[/]|[\]/]`) if err != nil { return "Internal Server Error" } return string(reg.ReplaceAll([]byte(message), []byte(""))) }