api-surface #1
2 changed files with 35 additions and 0 deletions
feat(ratelimit): collapse invite codes in route keys; cover new route shapes
commit
09c9f2b65a
|
|
@ -108,6 +108,9 @@ defmodule Dexcord.Api.Ratelimit do
|
|||
template = "/" <> Enum.join(templatize(segments), "/")
|
||||
base = verb <> " " <> template
|
||||
|
||||
# As of the 2026-07 docs the separate message-delete bucket is no longer
|
||||
# documented, but this suffix only splits buckets more finely (safe) - kept
|
||||
# to avoid 429s should the server-side behavior still exist.
|
||||
if method == :delete and String.ends_with?(template, "/messages/:id") do
|
||||
base <> " [message-delete]"
|
||||
else
|
||||
|
|
@ -138,6 +141,11 @@ defmodule Dexcord.Api.Ratelimit do
|
|||
defp templatize(["reactions", _emoji | rest]),
|
||||
do: ["reactions", ":id" | templatize(rest)]
|
||||
|
||||
# invite codes are non-numeric, so they'd otherwise stay literal - collapse
|
||||
# them to keep bucket keys bounded and keep invite codes out of ETS.
|
||||
defp templatize(["invites", _code | rest]),
|
||||
do: ["invites", ":code" | templatize(rest)]
|
||||
|
||||
defp templatize([seg | rest]), do: [templatize_one(seg) | templatize(rest)]
|
||||
defp templatize([]), do: []
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,33 @@ defmodule Dexcord.Api.RatelimitTest do
|
|||
assert Ratelimit.route_key(:get, "/channels/1/messages?limit=50") ==
|
||||
"GET /channels/1/messages"
|
||||
end
|
||||
|
||||
test "invite codes collapse to :code and never leak literally" do
|
||||
key = Ratelimit.route_key(:delete, "/invites/aBcD3fG")
|
||||
assert key == "DELETE /invites/:code"
|
||||
refute key =~ "aBcD3fG"
|
||||
|
||||
# query strings are stripped before the code collapses.
|
||||
assert Ratelimit.route_key(:get, "/invites/xyz?with_counts=true") ==
|
||||
"GET /invites/:code"
|
||||
end
|
||||
|
||||
test "new route shapes collapse via the digits fallback and keep majors" do
|
||||
# sticker-pack id is a minor param.
|
||||
assert Ratelimit.route_key(:get, "/sticker-packs/123456789") ==
|
||||
"GET /sticker-packs/:id"
|
||||
|
||||
# application emojis: application id and emoji id are both minor.
|
||||
assert Ratelimit.route_key(:post, "/applications/123/emojis") ==
|
||||
"POST /applications/:id/emojis"
|
||||
|
||||
assert Ratelimit.route_key(:delete, "/applications/123/emojis/456") ==
|
||||
"DELETE /applications/:id/emojis/:id"
|
||||
|
||||
# guild id is a major param and stays literal even on emoji routes.
|
||||
assert Ratelimit.route_key(:get, "/guilds/1/emojis/2") ==
|
||||
"GET /guilds/1/emojis/:id"
|
||||
end
|
||||
end
|
||||
|
||||
describe "bucket + global math (injected clock)" do
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue