///|
/// Failures distinguished by transport, HTTP status, decoding, or configuration.
pub(all) suberror SdkError {
Transport(@http.HttpError)
Status(status~ : Int32, headers~ : @http.Headers, body~ : Bytes)
RateLimited(retry_after_ms~ : Int32?, headers~ : @http.Headers, body~ : Bytes)
Decode(message~ : String, body~ : Bytes)
Config(String)
} derive(Debug)
///|
/// Formats the classified failure for debugging.
pub extend SdkError with @debug.Debug::{to_repr}
///|
/// The most bytes of a response body an `SdkError` built here keeps: an
/// error body is for diagnosis, so a longer one is cut to its first 1 MiB
/// rather than held whole for as long as the error lives.
let max_error_body_bytes : Int32 = 1048576
///|
/// `body` itself when it fits in `max_error_body_bytes`, otherwise a copy of
/// its first `max_error_body_bytes` bytes.
fn error_body(body : Bytes) -> Bytes {
if body.length() <= max_error_body_bytes {
body
} else {
body[:max_error_body_bytes].to_owned()
}
}
///|
/// Returns successful responses unchanged and classifies all other statuses.
/// The error keeps at most the first 1 MiB of the body, as a streaming
/// error body is read; the status and headers are kept whole.
///
/// ```mbt check
/// test {
/// let response : @http.Response = {
/// status: 204,
/// headers: @http.Headers::new(),
/// body: b"",
/// }
/// assert_eq(@runtime.classify(response), response)
/// let error = @runtime.Transport(@http.Timeout(phase=Head, ms=500))
/// assert_eq(error.status(), None)
/// assert_true(error.is_retryable())
/// }
/// ```
pub fn classify(
response : @http.Response,
now_unix_ms? : Int64,
) -> @http.Response raise SdkError {
if response.status >= 200 && response.status <= 299 {
response
} else if response.status == 429 {
raise RateLimited(
retry_after_ms=retry_after_ms(response.headers, now_unix_ms?),
headers=response.headers,
body=error_body(response.body),
)
} else {
raise Status(
status=response.status,
headers=response.headers,
body=error_body(response.body),
)
}
}
///|
/// Returns an HTTP status only for failures with an HTTP status.
pub fn SdkError::status(self : SdkError) -> Int32? {
match self {
Status(status~, ..) => Some(status)
RateLimited(..) => Some(429)
_ => None
}
}
///|
/// Reports transient failures; request idempotency remains the caller's
/// concern. Every transport failure but `Protocol` is transient, and a
/// proxy's refusal to open a tunnel only when its status is 502, 503 or 504.
pub fn SdkError::is_retryable(self : SdkError) -> Bool {
match self {
Transport(Connect(_) | Timeout(..) | Network(..)) | RateLimited(..) => true
Transport(Proxy(status=502 | 503 | 504, ..)) => true
Status(status=408 | 409 | 500 | 502 | 503 | 504, ..) => true
_ => false
}
}