// What a typed call can fail with.
//
// Two cases, and they are not the same kind of thing. `Slack` is every failure
// @client already had -- a refusal, a 429, a dropped socket -- passed through
// untouched. `ResponseShapeError` is this layer's own: Slack answered `ok:
// true` and the payload was not the shape @model claims.
//
// A separate type rather than a seventh `@api.SlackError` variant on purpose.
// That taxonomy's `code()` strings are node-slack-sdk's `ErrorCode` values
// verbatim and its messages are node's message text, so that a team migrating
// off the Node SDK keeps its log greps and its alert rules. There is no node
// counterpart to borrow for this, and inventing one would make @api's claim to
// be a faithful port false for the sake of a case that should never happen.

///|
/// A typed call's failure.
pub(all) suberror TypedError {
  /// Everything the low-level client could already fail with, unchanged.
  Slack(@api.SlackError)
  /// `ok: true`, and then a payload this version cannot read: the key absent,
  /// or holding something the entity refused.
  ///
  /// It should never happen against Slack -- removing `user` from
  /// `users.info` would break every SDK at once. It happens against a
  /// proxy that rewrites responses, a stub someone wrote by hand, or a
  /// `@model` that has fallen behind. Naming the method and the key is the
  /// whole of what makes that diagnosable.
  ResponseShapeError(api_method~ : String, key~ : String)
} derive(Debug)

///|
pub impl Show for TypedError with fn output(self, logger) {
  logger.write_string(self.describe_error())
}

///|
/// node-slack-sdk's `ErrorCode` for the wrapped case, and this package's own
/// for the case node has no name for.
pub fn TypedError::code(self : Self) -> String {
  match self {
    Slack(e) => e.code()
    ResponseShapeError(..) => "slack_response_shape_error"
  }
}

///|
pub fn TypedError::describe_error(self : Self) -> String {
  match self {
    Slack(e) => e.describe_error()
    ResponseShapeError(api_method~, key~) =>
      "\{api_method} answered ok, but '\{key}' was missing or not the shape this version models"
  }
}

///|
/// The wrapped `SlackError`, for the cases that actually happen.
///
/// `ResponseShapeError` is the rare one and the wrapping puts it in the way of
/// the common ones, so this is the shortcut: `if e.slack() is Some(err)` gets
/// back to a rate limit or a `missing_scope` in one step.
pub fn TypedError::slack(self : Self) -> @api.SlackError? {
  match self {
    Slack(e) => Some(e)
    ResponseShapeError(..) => None
  }
}

///|
/// Narrow an arbitrary `Error` to a `TypedError`.
///
/// Variant patterns are the only way to match an error value and they are only
/// in scope in the package that declares them, so a caller outside this one
/// cannot take a `TypedError` apart from an `Error` without this. The same
/// shape as `@api.to_slack_error`, which it delegates to.
pub fn to_typed_error(e : Error) -> TypedError {
  match e {
    Slack(_) as t => t
    ResponseShapeError(..) as t => t
    other => Slack(@api.to_slack_error(other))
  }
}