Skip to content

Do not attach a body to bodyless requests (fixes 404s under Req >= 0.7) - #64

Open
andrejj wants to merge 1 commit into
jaeyson:mainfrom
andrejj:fix-null-body-on-bodyless-get
Open

Do not attach a body to bodyless requests (fixes 404s under Req >= 0.7)#64
andrejj wants to merge 1 commit into
jaeyson:mainfrom
andrejj:fix-null-body-on-bodyless-get

Conversation

@andrejj

@andrejj andrejj commented Aug 13, 2026

Copy link
Copy Markdown

Problem

encode_body/1 serializes a nil body to the literal string "null", so every request without a body (GET/DELETE) carries a JSON body. Older Req versions silently dropped that body on bodyless methods, so this was masked. Req >= 0.7 transmits it, and Typesense then returns 404 Not Found for a GET-with-body on routes like /collections/:name.

Concretely, with Req 0.7:

# collection "foo" exists
OpenApiTypesense.Collections.get_collection("foo")
#=> {:error, %OpenApiTypesense.ApiResponse{message: "Not Found"}}

Anything built on get_collection/2 is affected — e.g. ex_typesense's collection existence checks and reindex flow, where collection_exists? starts returning false for collections that exist, so reindexing tries to recreate them and fails with "a collection with name ... already exists". list_collections fails too, but as a MatchError in parse_resp/2 (its operation spec has no 404 entry, so Enum.find/2 returns nil).

Root cause is not Req

The request was semantically wrong all along — a GET/DELETE with no body should not send a body. Older Req just tolerated it; Req 0.7 faithfully sends what the library asked for. So this is a library fix, not a Req-compat shim, and it needs no version bound on req — the output is now correct for any HTTP client.

Fix

Return nil from encode_body/1 when there is no request-body spec and no body, so Req omits the body. Requests that carry a body are unaffected — they never reach the new clause (they either have an opts[:request] content-type spec or a binary body).

Bisection that isolates it (identical GET, only the body differs):

request status
GET, no body 200
GET, body: "null" 404

Verification

  • Added a build_req_client/2 regression test asserting a bodyless GET yields req.body == nil.
  • mix test test/default_client_test.exs → green (resolves Req 0.7.x, i.e. tested against the version that exposed the bug).
  • Downstream: with this change, get_collection/list_collections/reindex work again under Req 0.7.

Notes

Alternative considered: omit the :body key in build_req_client/2 instead of passing body: nil. Functionally identical (Req omits a nil body); kept the change localized to encode_body/1.

Summary by Sourcery

Prevent JSON bodies from being attached to bodyless HTTP requests to avoid 404 responses from Typesense under newer Req versions.

Bug Fixes:

  • Return nil from request body encoding when both the body and request-body spec are absent so GET/DELETE requests are sent without a body, restoring correct behavior with Typesense and Req >= 0.7.

Documentation:

  • Document the fix in the changelog, including the previous behavior and its impact on Typesense collection operations.

Tests:

  • Add a regression test ensuring that building a bodyless GET request yields a Req client with a nil body.

`encode_body/1` serialized a nil body to the literal "null", so every request
without a body (GET/DELETE) carried a JSON body. Older HTTP clients silently
dropped that body, but Req >= 0.7 transmits it — and Typesense returns 404 for
a GET-with-body on routes like `/collections/:name`.

The practical effect with Req 0.7: `OpenApiTypesense.Collections.get_collection/2`
(and anything built on it, e.g. ex_typesense's collection existence checks and
reindexing) receives a spurious 404 for a collection that exists.

Fix: return nil from `encode_body/1` when there is no request-body spec and no
body, so Req omits the body entirely. Requests that do carry a body are
unaffected.
@sourcery-ai

sourcery-ai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR fixes incorrect handling of bodyless HTTP requests by ensuring that GET/DELETE requests without a body do not send the literal "null" as a JSON payload, adds a regression test to lock in the new behavior, and documents the fix in the changelog.

Sequence diagram for bodyless GET requests with updated encode_body behavior

sequenceDiagram
  actor User
  participant OpenApiTypesense_Client as OpenApiTypesense.Client
  participant Req
  participant Typesense

  User->>OpenApiTypesense_Client: get_collection(name)
  OpenApiTypesense_Client->>OpenApiTypesense_Client: encode_body(opts_request, body)
  alt before_fix
    Note over OpenApiTypesense_Client: {nil, nil} -> Jason.encode_to_iodata!(nil) == "null"
    OpenApiTypesense_Client->>Req: request(method: GET, url, body: "null")
    Req->>Typesense: GET /collections/:name with JSON body "null"
    Typesense-->>Req: 404 Not Found
    Req-->>OpenApiTypesense_Client: {:error, ApiResponse(message: Not Found)}
  else after_fix
    Note over OpenApiTypesense_Client: {nil, nil} -> nil (no body)
    OpenApiTypesense_Client->>Req: request(method: GET, url, body: nil)
    Req->>Typesense: GET /collections/:name without body
    Typesense-->>Req: 200 OK
    Req-->>OpenApiTypesense_Client: {:ok, ApiResponse(collection)}
  end
Loading

File-Level Changes

Change Details Files
Adjust request body encoding so truly bodyless requests send no body at all, avoiding unintended JSON payloads like "null".
  • Introduce a special-case encode_body/1 clause that returns nil when both the request-body spec and body are nil.
  • Preserve existing behavior for requests that either have a request-body spec or a non-nil body, still encoding them via Jason.
lib/open_api_typesense/client.ex
Add a regression test to assert that GET requests without a body result in a Req client with req.body == nil.
  • Create a tagged test "does not attach a body to bodyless requests" in DefaultClientTest.
  • Build a GET request to a collections route via Client.build_req_client/2 and assert the generated Req request has a nil body.
test/default_client_test.exs
Document the bug and its fix in the changelog for visibility and release notes.
  • Add a Fixed entry describing the previous behavior of encoding nil bodies as "null" and how it broke GET/DELETE semantics under Req >= 0.7.
  • Explain the impact on collection-related operations (e.g., get_collection and reindex) and that the fix restores correct behavior.
CHANGELOG.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gates Passed
3 Quality Gates Passed

See analysis details in CodeScene

Quality Gate Profile: The Bare Minimum
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 duplication

Metric Results
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

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.

1 participant