glyde/rest/error
What Discord says when it says no. Split by body shape, not status code: the five bodies behind a non-2xx share almost no fields.
Only what one HTTP response carries. The caller does the IO, so a dead socket is theirs to describe.
Types
pub type ApiError {
Discord(
status: Int,
code: ErrorCode,
message: String,
fields: List(FieldError),
raw: String,
)
RateLimited(status: Int, limit: RateLimit)
OAuth(
status: Int,
error: OAuthError,
description: option.Option(String),
)
Opaque(
status: Int,
content_type: option.Option(String),
body: String,
)
NotText(
status: Int,
content_type: option.Option(String),
bytes: Int,
)
Malformed(DecodeFailure)
}
Constructors
-
Discord( status: Int, code: ErrorCode, message: String, fields: List(FieldError), raw: String, )A 4xx or 5xx carrying a Discord error object.
fieldsis empty unless Discord sent a nestederrorsobject, which in practice means 50035. -
RateLimited(status: Int, limit: RateLimit)A 429 from Discord itself. Its body has no
codeand noerrors. -
OAuth( status: Int, error: OAuthError, description: option.Option(String), )The OAuth2 token endpoints answer in RFC 6749’s shape, which shares not one field with Discord’s own.
-
Opaque( status: Int, content_type: option.Option(String), body: String, )A non-2xx whose body was none of the above: a Cloudflare ban page, an HTML 502, an empty body. A 429 lands here when its body is not Discord’s rate-limit shape, because that is a ban and retrying extends it.
-
NotText( status: Int, content_type: option.Option(String), bytes: Int, )The bytes were not UTF-8, so there is no text to read or parse at all. Discord answers in UTF-8 everywhere, so this came from something in between.
bytesis the length, because the body cannot be shown. -
Malformed(DecodeFailure)A 2xx whose payload did not match the decoder.
What a code says about the request that produced it. is_transient and
is_token_fatal are questions asked of this, so a code cannot be filed
one way and answered another.
pub type CodeClass {
TokenFatal
PathTokenDead
Forbidden
Gone
ResourceRateLimited
Transient
BadRequest
Moderation
UnknownClass
}
Constructors
-
TokenFatalThe client’s own token is finished: stop, do not retry.
-
PathTokenDeadThe webhook or interaction token in the URL is gone. Says nothing about the client’s token, which is the whole point of keeping it apart.
-
ForbiddenPermanently wrong for this caller or this input.
-
GoneThe thing addressed is not there any more. Drop it from any cache.
-
ResourceRateLimitedRate-limit shaped, but on a resource rather than a route bucket. Do not feed these to the limiter.
-
TransientDiscord’s own wording tells you to try again.
-
BadRequestThe request has to change before it can work.
-
ModerationThe content has to change, not the call.
-
UnknownClassA code this build does not name, and
general_error, which names no class either. Discord adds codes continuously.
A decode failure that kept the raw text, which the stdlib error drops. Without it, a shape change and a wrong decoder look the same afterwards.
pub type DecodeFailure {
DecodeFailure(error: json.DecodeError, raw: String)
}
Constructors
-
DecodeFailure(error: json.DecodeError, raw: String)
Discord’s code field. Open, not an enum: Discord calls its own table
neither exhaustive nor stable, so a closed set would reject new codes.
pub opaque type ErrorCode
One leaf of the recursive errors object. code is an open set of strings
Discord has never documented, empty when the leaf was a bare string.
pub type FieldError {
FieldError(
path: List(PathSegment),
code: String,
message: String,
)
}
Constructors
-
FieldError( path: List(PathSegment), code: String, message: String, )
The error an OAuth2 token endpoint answers with, from RFC 6749 section
5.2. Open at the tail, like every other enum we read off the wire.
pub type OAuthError {
InvalidRequest
InvalidClient
InvalidGrant
UnauthorizedClient
UnsupportedGrantType
InvalidScope
UnknownOAuthError(String)
}
Constructors
-
InvalidRequestThe request is missing a parameter, or repeats one.
-
InvalidClientThe client id or secret is wrong. Re-authenticating with them will not work either.
-
InvalidGrantThe code, refresh token or grant is expired, revoked, or was issued to another client.
-
UnauthorizedClientThis client is not allowed this grant type.
-
UnsupportedGrantTypeThe grant type is one the endpoint does not support.
-
InvalidScopeA scope was unknown, malformed, or wider than the one already granted.
-
UnknownOAuthError(String)A name RFC 6749 does not define, kept as sent.
One step of the path into the request body Discord rejected. Index stays
apart from Key so the array versus object distinction survives.
pub type PathSegment {
Key(String)
Index(Int)
}
Constructors
-
Key(String) -
Index(Int)
pub type RateLimit {
RateLimit(
retry_after: Float,
scope: limiter.Scope,
code: option.Option(ErrorCode),
bucket: option.Option(String),
)
}
Constructors
-
RateLimit( retry_after: Float, scope: limiter.Scope, code: option.Option(ErrorCode), bucket: option.Option(String), )Arguments
- retry_after
-
Seconds, from the body. The
Retry-Afterheader is the same number rounded up to a whole second. - scope
-
The answer
headers.resolve_scopegives, from the scope header, the body’sglobalflag and the shape of the response.is_globalasks the only question most callers have. - code
-
Present when the limit is on a resource, slowmode say, not a route.
- bucket
-
Absent on a global 429, which carries no bucket headers at all.
Values
pub const announcement_rate_limit: ErrorCode
pub const api_resource_overloaded: ErrorCode
pub const automod_blocked: ErrorCode
pub const automod_title_blocked: ErrorCode
pub const cannot_act_on_dm: ErrorCode
pub const cannot_message_user: ErrorCode
pub const cannot_send_sticker: ErrorCode
pub const channel_write_rate_limit: ErrorCode
pub fn classify(code: ErrorCode) -> CodeClass
Every code this build names, and what it means for the caller. The numbers are the constants above, spelled out because Gleam has no constants in patterns and this is asked several times per failed request.
pub const cloudflare_blocked: ErrorCode
A well formed Discord error, unlike the Cloudflare ban page, which has no
JSON body at all and lands in Opaque.
pub fn code_from_int(value: Int) -> ErrorCode
pub fn code_to_int(code: ErrorCode) -> Int
pub fn counts_as_invalid_request(error: ApiError) -> Bool
Whether this counts against Discord’s budget of 10,000 invalid requests per 10 minutes, which ends in a Cloudflare ban of the whole IP.
pub fn describe(error: ApiError) -> String
One line per failure, for a host that just wants to print it. A caller
acting on a 429 reads retry_advice off the typed value instead.
pub const empty_message: ErrorCode
pub const entity_too_large: ErrorCode
pub const explicit_content_blocked: ErrorCode
pub const feature_temporarily_disabled: ErrorCode
pub const file_too_large: ErrorCode
pub fn from_response(
status status: Int,
headers sent: List(#(String, String)),
body body: String,
) -> ApiError
Classify a non-2xx by the shape of its body.
pub const general_error: ErrorCode
Discord’s catch-all: it names no class, which is why classify answers
UnknownClass for it. On a 401 that means this call was rejected, where
any other code on a 401 means the token is finished. See is_token_fatal.
pub const guild_write_rate_limit: ErrorCode
pub const harmful_link_blocked: ErrorCode
pub const index_not_available: ErrorCode
pub const interaction_already_acknowledged: ErrorCode
You already responded. Send a follow-up instead.
pub const interaction_send_failed: ErrorCode
pub const invalid_api_version: ErrorCode
glyde built a URL for an API version Discord no longer serves. Our bug.
pub const invalid_authentication_token: ErrorCode
pub const invalid_client_secret: ErrorCode
pub const invalid_file: ErrorCode
pub const invalid_form_body: ErrorCode
The one that carries a nested errors object.
pub const invalid_json: ErrorCode
glyde sent JSON Discord could not parse. Our bug.
pub const invalid_oauth2_token: ErrorCode
pub const invalid_webhook_token: ErrorCode
pub fn is_global(limit: RateLimit) -> Bool
Whether the whole token is limited rather than one route.
pub fn is_path_token_dead(error: ApiError) -> Bool
The webhook or interaction token in the URL is gone: deleted, rotated, or spent. Fix it by getting a new one, not by re-authenticating.
pub fn is_token_fatal(error: ApiError) -> Bool
The client’s own token is finished and it must stop, not retry. A 401 carrying any code other than 0 is Discord rejecting the token, not the call, so an unnamed code on a 401 still counts.
pub const max_old_message_edits: ErrorCode
pub const messages_temporarily_disabled: ErrorCode
pub const missing_access: ErrorCode
pub const missing_oauth2_scope: ErrorCode
pub const missing_permissions: ErrorCode
pub const no_mutual_guilds: ErrorCode
pub const not_authorized_for_application: ErrorCode
pub fn not_text(
status status: Int,
headers sent: List(#(String, String)),
bytes bytes: Int,
) -> ApiError
For a response whose body would not become a String. Separate from
from_response, which classifies by what the text says.
pub fn oauth_error_to_string(error: OAuthError) -> String
The name as the endpoint spells it, which is what a log should show.
pub const owner_only: ErrorCode
pub fn path_to_string(path: List(PathSegment)) -> String
Render a path as embed.fields[0].value. The empty path is the request as
a whole, which Discord reports by putting _errors at the root.
pub const reaction_blocked: ErrorCode
pub const resource_rate_limited: ErrorCode
pub const slowmode_rate_limit: ErrorCode
pub const too_few_or_many_to_delete: ErrorCode
pub const too_many_followups: ErrorCode
pub const too_old_to_bulk_delete: ErrorCode
pub const two_factor_required: ErrorCode
pub const unauthorized: ErrorCode
pub const unique_username_failed: ErrorCode
pub const unknown_ban: ErrorCode
pub const unknown_channel: ErrorCode
pub const unknown_emoji: ErrorCode
pub const unknown_guild: ErrorCode
pub const unknown_interaction: ErrorCode
The three second deadline for the first response passed, or the token was already spent. Retrying always fails.
pub const unknown_member: ErrorCode
pub const unknown_message: ErrorCode
pub const unknown_role: ErrorCode
pub const unknown_token: ErrorCode
pub const unknown_user: ErrorCode
pub const unknown_webhook: ErrorCode
Also what a follow-up gets once the 15 minute interaction token expires.
pub const verification_required: ErrorCode