api-surface #1

Merged
luna merged 51 commits from api-surface into mistress 2026-07-05 14:43:13 +00:00
2 changed files with 39 additions and 1 deletions
Showing only changes of commit bd911d6205 - Show all commits

fix(api/endpoint): stop silently discarding non-map form bodies

stringify_values/1 had a catch-all that coerced any non-map payload to
%{}, silently dropping all form fields on the files: :form path. Narrow
the catch-all to nil only (the legitimate payload || %{} case); any other
non-map now raises FunctionClauseError instead of vanishing on the wire.

Add prepare_body/3 form-field tests covering the map, nil, and
non-map-raises cases.
Luna 2026-07-05 06:56:57 -03:00

View file

@ -251,7 +251,9 @@ defmodule Dexcord.Api.Endpoint do
defp stringify_values(payload) when is_map(payload),
do: Map.new(payload, fn {k, v} -> {k, to_string(v)} end)
defp stringify_values(_payload), do: %{}
# A nil payload is legitimate (payload || %{} semantics); any other non-map
# is a caller error and must raise rather than silently drop the form fields.
defp stringify_values(nil), do: %{}
@doc false
def encode_body(nil, _spec), do: nil

View file

@ -136,6 +136,42 @@ defmodule Dexcord.Api.EndpointTest do
end
end
describe "prepare_body/3 with form-field files (sticker create)" do
defp form_spec do
%{body: true, files: :form, binary_wrap: nil}
end
test "stringifies every payload value onto bare form parts" do
file = %{filename: "s.png", data: <<1>>}
assert {:form_multipart, fields, out_file} =
Endpoint.prepare_body(form_spec(), [name: "sticker", tags: "cat"], files: [file])
assert fields == %{"name" => "sticker", "tags" => "cat"}
assert out_file.filename == "s.png"
refute Map.has_key?(out_file, :description)
end
test "a nil body is legitimate and yields empty form fields" do
file = %{filename: "s.png", data: <<1>>}
assert {:form_multipart, %{}, out_file} =
Endpoint.prepare_body(form_spec(), nil, files: [file])
assert out_file.filename == "s.png"
end
test "a non-map, non-nil payload raises rather than silently vanishing" do
file = %{filename: "s.png", data: <<1>>}
# A top-level list body encodes to a list, which must not be coerced to %{}.
assert_raise FunctionClauseError, fn ->
Endpoint.prepare_body(form_spec(), [%Dexcord.Test.Gadget{id: 1, name: "a"}],
files: [file]
)
end
end
end
describe "decode_response/2 (AC1.6 + passthrough)" do
test "decodes a single map to the returns struct" do
assert {:ok, %Dexcord.User{id: 1}} =