Skip to content

Add query params, function docs, and configurable OpenAPI metadata - #9

Merged
andreashasse merged 3 commits into
mainfrom
query_params_function_docs_metadata
Mar 23, 2026
Merged

andreashasse merged 3 commits into
mainfrom
query_params_function_docs_metadata

Conversation

@andreashasse

Copy link
Copy Markdown
Owner

Summary

  • Query parameter support: Handlers are now 4-arity (PathArgs, QueryArgs, Headers, Body), matching the phoenix_spectral convention. Query params are extracted from the URL via elli_request:get_args/1, type-checked with spectra, and emitted as in: query parameters in the generated OpenAPI spec.
  • Function doc annotations: -spectra(#{summary => ..., description => ...}) placed before a -spec (before the first function definition) populates the OpenAPI operation's summary/description/deprecated via spectra_openapi:endpoint/3.
  • Configurable OpenAPI metadata: setup_routes/2 now accepts a full openapi_metadata() map (title, version, servers, contact, license, description). elli_openapi_handler also accepts {MetaData, Routes} as callback_args for backwards-compatible configuration.
  • Fix double-encoding bug: generate_openapi_spec now uses the pre_encoded option so spectra returns a raw Erlang map, avoiding double JSON encoding that was introduced in spectra 0.8.x.

Test plan

  • All 28 CT tests pass (make build-test)
  • eqwalizer reports NO ERRORS
  • list_users/4 demo handler with optional page/per_page query params works end-to-end
  • Missing required query params return 400
  • Invalid query param types (e.g. page=notanumber) return 400
  • Function -spectra() doc annotations appear as summary/description in generated spec
  • GET /api/users route with query params shows parameters with in: query in spec

🤖 Generated with Claude Code

- **4-arity handlers**: action signatures now take (PathArgs, QueryArgs,
  Headers, Body) matching the phoenix_spectral convention. Query params
  are decoded from the URL query string using elli_request:get_args/1,
  type-checked via spectra, and passed as the second argument.

- **OpenAPI query parameter generation**: `to_endpoint` emits `in: query`
  parameters for each field in the QueryArgs map type; required fields
  (`:=`) are marked required, optional (`=>`) are not.

- **Function doc support**: `-spectra(#{summary => ..., description => ...})`
  placed before a `-spec` populates the OpenAPI operation's summary and
  description fields via `spectra_openapi:endpoint/3`.

- **Configurable OpenAPI metadata**: `setup_routes/2` now accepts a full
  `openapi_metadata()` map (servers, contact, license, description, etc.).
  `elli_openapi_handler` accepts `{MetaData, Routes}` as callback_args
  for backward-compatible metadata configuration.

- **Fix double-encoding bug**: `generate_openapi_spec` now uses
  `pre_encoded` option so spectra returns a raw map, preventing
  double-encoding of the JSON output.

- Demo updated with `list_users/4` showing optional query params (page,
  per_page). All 28 tests pass, eqwalizer clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates elli_openapi to support typed query parameters, enrich generated OpenAPI operations with function doc metadata, and make OpenAPI metadata configurable while addressing a JSON double-encoding issue introduced by newer spectra versions.

Changes:

  • Extend handler signature to 4-arity (PathArgs, QueryArgs, Headers, Body) and add query param decoding + OpenAPI in: query generation.
  • Add support for -spectra(#{summary/description/deprecated => ...}) function doc annotations to populate OpenAPI operation metadata.
  • Allow passing full OpenAPI metadata via setup_routes/2 and {MetaData, Routes} callback args; update spectra dependency to 0.8.2 and use pre_encoded to avoid double JSON encoding.

Reviewed changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/elli_openapi_integration_SUITE.erl Updates routes to 4-arity, adds query param + function doc OpenAPI tests, and adds list-users integration tests.
src/elli_openapi_handler.erl Adds {MetaData, Routes} startup support and refactors module loading before route setup.
src/elli_openapi_demo.erl Updates demo handlers/specs to 4-arity, adds query param demo endpoint, and adds -spectra doc annotations.
src/elli_openapi.erl Implements query param decoding, emits query params into OpenAPI, adds configurable metadata, and fixes OpenAPI generation encoding via pre_encoded.
rebar.config Bumps spectra dependency constraint to ~> 0.8.2.
rebar.lock Locks spectra to 0.8.2 with updated hashes.
Comments suppressed due to low confidence (1)

test/elli_openapi_integration_SUITE.erl:287

  • get_user_not_found/1 no longer exercises the /api/users/{userId} handler; it requests /api/nonexistent, so the 404 is coming from the router rather than the get_user endpoint. Either rename this testcase to reflect what it validates (unknown route) or update it to hit a path that matches the get_user route and returns 404 from the handler.
get_user_not_found(Config) ->
    Url = url(Config, "/api/nonexistent"),

    ?assertMatch(
        {ok, {{_, 404, _}, _Headers, _ResponseBody}},
        http_get(Url, [{"authorization", "Bearer token123"}])
    ),

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/elli_openapi.erl
Comment thread src/elli_openapi.erl Outdated
Comment on lines +27 to +34
handle_event(elli_startup, [], {MetaData, Routes}) when is_map(MetaData) ->
ensure_modules_loaded(Routes),
elli_openapi:setup_routes(MetaData, Routes),
ok;
handle_event(elli_startup, [], Routes) ->
ensure_modules_loaded(Routes),
elli_openapi:setup_routes(Routes),
ok;

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New behavior in handle_event/3 supports callback_args as {MetaData, Routes}, but the integration suite still only covers the legacy Routes-only startup path. Consider adding a CT case that starts Elli with {MetaData, Routes} and asserts the generated spec reflects the provided metadata (e.g. title/version/servers) to prevent regressions.

Copilot uses AI. Check for mistakes.
andreashasse and others added 2 commits March 22, 2026 21:39
- Add search_users/4 with required query param `query := binary()`
- Add GET /api/search route to integration test suite
- Add search_users_missing_required_query_param test (verifies 400 when
  required query param is absent)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace nested case pyramid with a flat maybe block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@andreashasse
andreashasse merged commit 2f82ab9 into main Mar 23, 2026
2 checks passed
@andreashasse
andreashasse deleted the query_params_function_docs_metadata branch March 27, 2026 05:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants