api-surface #1

Merged
luna merged 51 commits from api-surface into mistress 2026-07-05 14:43:13 +00:00
Owner
No description provided.
luna added 51 commits 2026-07-05 14:40:58 +00:00
Completed brainstorming session. Design includes:
- Three compile-time DSLs (Struct/Enum/Flags) generating decode/encode/merge from single declarations, string-key matching with zero wire atomization
- Integer Snowflake rework, distinct channel structs + factory, first-class partials with Cache.fill hydration
- Endpoint DSL covering ~180 v10 REST endpoints + mix dexcord.coverage spec-diff gate
- 7 implementation phases
- Formatting: add field/hydrate/discord_struct to locals_without_parens
  in .formatter.exs so the no-paren data-DSL style is the formatted form;
  mix format then wrapped the long codec encode_value/2 clause. Both
  previously-unformatted files now satisfy mix format --check-formatted.
- Empty-struct guard: __before_compile__ now raises ArgumentError when a
  module does 'use Dexcord.Struct' with no field/hydrate declarations,
  instead of silently generating a bare defstruct []. Added a compile-time
  test mirroring the existing Code.compile_string validation tests.
Redeclare the full existing endpoint surface with the Dexcord.Api.Endpoint
DSL across six group modules (Channels, Messages, Users, Interactions,
Commands, Misc) and delete the old named endpoint functions plus their
private helpers (message_query/maybe_param/maybe_put) from Dexcord.Api,
keeping request/4 and the rest of the request pipeline untouched (AC1.7).

Dexcord.Api re-exports every group endpoint head as a generated defdelegate
driven by module_info(:exports), so Dexcord.Api.<endpoint> keeps working for
all 9 internal call sites and external callers. create_dm is registered by
hand (spec + snowflake sugar) because the generated create_dm(body, opts \\ [])
head would collide with create_dm/1.

Endpoints are now typed: get_current_user -> Dexcord.User, create/edit_message
-> Dexcord.Message, get_channel/start_thread -> Dexcord.Channel,
get_current_application -> Dexcord.ApplicationInfo, bulk_overwrite_* ->
[Dexcord.ApplicationCommand], etc. get_gateway_bot and
create_interaction_response stay :raw. Patch registrar to match
%Dexcord.ApplicationInfo{} and widen Config.put_application_id/1 to accept
integer ids.

Behavior changes (documented in tests):
- get_channel_messages/2 no longer collapses multiple anchors with a
  before>after>around precedence; it forwards every declared query key, so
  callers pass exactly one anchor (the /3 locator arity still enforces this).
- start_thread_with_message extra fields now travel in the body argument
  rather than a trailing opts list.
- *_id path params are validated via Snowflake.cast/1; integration-test
  fixtures updated from non-numeric placeholders to numeric snowflake ids.

Add test/dexcord/api_facade_test.exs asserting the facade cannot silently drop
any group endpoint export.

Claude-Session: https://claude.ai/code/session_01T4vgXtQLs2wfNwn6B1m4hm
- [Critical] Facade delegate set is now derived from static, compile-time-stable
  data (each group's __endpoints__/0 route table) instead of the group's
  module_info(:exports). module_info/1 is an auto-generated function that the
  Elixir compiler tracer does not record as a compile-time dependency, so no
  compile-order edge was established between Dexcord.Api and the group modules;
  under parallel/incremental compilation the export table was read incompletely,
  silently dropping delegates (e.g. Dexcord.Api.get_gateway_bot/0 undefined after
  a routine recompile). Arities are derived from segment param count + body flag
  (each head's opts \\ [] yields base and base+1), with get_channel_messages/3
  named explicitly as sugar. Verified: delegate always present in the compiled
  beam; 8x incremental-recompile loop of facade + gateway_integration all green.

- [Important] api_facade_test.exs is now deterministic: it asserts the facade's
  exported set equals the same statically-derived expected set the generator
  uses (no module_info), and ensures Dexcord.Api + groups are loaded before
  function_exported? probes (which do not auto-load), removing a false-negative
  async load race. Historically-load-bearing arity assertions kept.

- [Minor] decode_response/2 now normalizes returns:nil endpoints to {:ok, nil}
  even when the server sends a non-204 JSON body, instead of leaking the raw
  decoded map through the passthrough clause. Added a unit test.
Adds `Dexcord.Api.Coverage`, a testable core that diffs the compiled
endpoint route table against a pinned snapshot of Discord's official
OpenAPI spec (`priv/discord_openapi.json`). The spec is a coverage
CHECKLIST, not codegen input: it is refreshed only by the deliberate
`mix dexcord.coverage.refresh`, so upstream churn never breaks CI
spontaneously.

- `mix dexcord.coverage` compiles then exits non-zero on any in-scope
  endpoint missing from the declared surface (AC1.8/AC1.9).
- `mix dexcord.coverage.refresh` pins the spec via :httpc + castore,
  adding inets/ssl to the code path since Mix prunes them.
- `priv/coverage_allowlist.exs` records out-of-scope spec routes with a
  comment per family; reconciled against the real pin (Social SDK
  lobbies/partner-sdk, OIDC, application-by-id, scheduled-event
  exceptions, join requests, thread search, monetization).
- `.gitea/workflows/ci.yml` runs the suite + gate (Actions must be
  enabled on the Gitea remote).
- ratelimit_test.exs gains AC1.10 spot-checks proving the new Phase 5
  route shapes collapse to bounded, id-collapsed keys.
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.
Claude-Session: https://claude.ai/code/session_01T4vgXtQLs2wfNwn6B1m4hm
docs: add test plan for full API surface
Some checks failed
ci / test (push) Has been cancelled
ci / test (pull_request) Has been cancelled
ca5b3cf599
Claude-Session: https://claude.ai/code/session_01T4vgXtQLs2wfNwn6B1m4hm
luna merged commit 63392e7d58 into mistress 2026-07-05 14:43:13 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
luna/dexcord!1
No description provided.