// Every failure this library produces.
//
// The `code()` strings are node-slack-sdk's `ErrorCode` values verbatim and
// `describe_error()` reproduces its message text, so a team migrating off the
// Node SDK keeps its log greps, its alert rules and its runbooks. The
// java-slack-sdk phrasing is available too, because it is the only one that
// surfaces `needed`/`provided` -- see `describe_error_java`.
///|
pub(all) suberror SlackError {
/// The transport could not complete the exchange at all: DNS, TLS, a dropped
/// connection. Nothing was learned about the request's validity.
RequestError(String)
/// A status Slack only uses for infrastructure failures.
///
/// Slack's own refusals arrive as HTTP 200 with `{"ok": false}` and surface
/// as `PlatformError`. A 5xx here means the request never reached the API.
HttpError(status~ : Int, headers~ : Map[String, String], body~ : String)
/// `{"ok": false, "error": "..."}`. A completed exchange carrying a refusal,
/// which is a different thing from a failed exchange -- and the reason this
/// carries the whole `ApiResponse`: `needed`, `provided` and
/// `response_metadata.messages` are usually where the actual answer is.
PlatformError(result~ : ApiResponse)
/// HTTP 429 whose `Retry-After` parsed.
RateLimitedError(retry_after~ : Int)
FileUploadInvalidArgumentsError(String)
FileUploadReadFileDataError(String)
} derive(Debug)
///|
/// node-slack-sdk's `ErrorCode`, verbatim.
pub fn SlackError::code(self : Self) -> String {
match self {
RequestError(_) => "slack_webapi_request_error"
HttpError(..) => "slack_webapi_http_error"
PlatformError(..) => "slack_webapi_platform_error"
RateLimitedError(..) => "slack_webapi_rate_limited_error"
FileUploadInvalidArgumentsError(_) =>
"slack_webapi_file_upload_invalid_args_error"
FileUploadReadFileDataError(_) =>
"slack_webapi_file_upload_read_file_data_error"
}
}
///|
pub impl Show for SlackError with fn output(self, logger) {
logger.write_string(self.describe_error())
}
///|
/// node-slack-sdk's message text, verbatim.
pub fn SlackError::describe_error(self : Self) -> String {
match self {
RequestError(m) => "A request error occurred: \{m}"
HttpError(status~, ..) =>
"An HTTP protocol error occurred: statusCode = \{status}"
PlatformError(result~) =>
"An API error occurred: \{result.error.unwrap_or("")}"
RateLimitedError(retry_after~) =>
"A rate-limit has been reached, you may retry this request in \{retry_after} seconds"
FileUploadInvalidArgumentsError(m) => m
FileUploadReadFileDataError(m) => m
}
}
///|
/// java-slack-sdk's `SlackApiException` phrasing.
///
/// Offered alongside the node one because the two ecosystems' operators
/// recognise different shapes, and because this is the only form that shows
/// `needed` and `provided` -- which, on a `missing_scope`, is the entire
/// content of the error.
pub fn SlackError::describe_error_java(self : Self) -> String {
match self {
PlatformError(result~) => {
let error = result.error.unwrap_or("")
let needed = result.needed.unwrap_or("")
let provided = result.provided.unwrap_or("")
let warning = result.warning.unwrap_or("")
"status: 200, error: \{error}, needed: \{needed}, provided: \{provided}, warning: \{warning}"
}
HttpError(status~, body~, ..) =>
if body.is_empty() {
"status: \{status}, no response body"
} else {
"status: \{status}, body: \{body}"
}
_ => self.describe_error()
}
}
///|
/// Narrow an arbitrary `Error` to a `SlackError`.
///
/// Variant patterns are the only way to match an error value -- the suberror
/// *type* name is not a pattern -- and they are only in scope inside this
/// package. Hence the helper, rather than every caller re-listing six variants.
/// The same shape as marianoguerra/mcp's `to_transport_error`.
pub fn to_slack_error(e : Error) -> SlackError {
match e {
RequestError(_) as t => t
HttpError(..) as t => t
PlatformError(..) as t => t
RateLimitedError(..) as t => t
FileUploadInvalidArgumentsError(_) as t => t
FileUploadReadFileDataError(_) as t => t
_ => RequestError(e.to_string())
}
}