Skip to content

feat(fetch): add result-based SafeFetch wrapper [Codex] - #367

Merged
jderochervlk merged 8 commits into
mainfrom
feat/safe-fetch
Nov 5, 2026
Merged

jderochervlk merged 8 commits into
mainfrom
feat/safe-fetch

Conversation

@jderochervlk

@jderochervlk jderochervlk commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #363

Summary

Add an opt-in WebAPI.SafeFetch module that converts expected Fetch, HTTP-status, and response-body failures into composable result values. Raw WebAPI.Fetch and WebAPI.Response bindings remain unchanged.

  • Select the separate WebAPI.SafeFetch feature; it enables WebAPI.Fetch and the shared raw types transitively.
  • Use tagged polymorphic errors consistently: #FetchRejected(cause), #ResponseNotOk({response}), and #BodyReadRejected({response, cause}).
  • Public signatures expose extensible error rows, so let? can compose fetch, body reading, and application decoding without mapError adapters.
  • Keep the original response and rejection cause. Successful body reads retain {response, body}; the body has been consumed.
  • Cover all six body readers. json returns JSON.t; application-specific validation stays with the caller.

This intentionally changes the experimental API: consumers that previously selected only WebAPI.Fetch must explicitly select WebAPI.SafeFetch to use the wrapper.

Usage

The locked ReScript 13 alpha compiler requires LetUnwrap for let?; ordinary switch usage does not require that setting.

{
  "experimental-features": {"LetUnwrap": true},
  "dependencies": [
    {"name": "@rescript/webapi", "features": ["WebAPI.SafeFetch"]}
  ]
}
type user = {name: string}

let decodeUser = json =>
  switch json {
  | JSON.Object(dict{"name": JSON.String(name)}) => Ok({name: name})
  | _ => Error(#InvalidUser)
  }

let loadUser = async url => {
  let? Ok(response) = await WebAPI.SafeFetch.fetch(url)
  let? Ok({body}) = await response->WebAPI.SafeFetch.json
  let? Ok(user) = body->decodeUser
  Ok(user)
}

The caller handles four distinct error tags in one switch: request rejection, non-OK HTTP status, body-read rejection, and application-owned #InvalidUser. Malformed JSON is a body-read error; valid JSON with the wrong shape is an application-decoding error. A non-OK response remains available for reading error details.

Review follow-up

  • Converted all error stages to polymorphic variants, including checkOk and body readers.
  • Removed redundant implementation annotations; retained the checkOk response annotation required for record-field inference.
  • Moved the wrapper to src/safe-fetch with its own feature and added isolated public-consumer checks.
  • Added documented and compiled let? examples plus runtime propagation/short-circuit tests.

Both fetch entry points still construct their final result directly and call native fetch. The late public alias prevents native-fetch shadowing, optional initializers are forwarded or omitted, and the only generated runtime import is exception normalization. exn preserves arbitrary JavaScript abort reasons via JsExn.fromException.

Validation

Passed locally with Node 26.3.0 and locked ReScript 13.0.0-alpha.5:

  • npm ci
  • npm run format:check
  • npm run build
  • npm run check:features — four checker unit tests, all 28 clean public feature builds, a SafeFetch-only consumer with composed application errors, a raw Fetch-only consumer, and a negative check proving SafeFetch is unavailable with only Fetch enabled
  • npm test — all seven runtime suites, including native default/custom abort reasons, both fetch entry points, optional initialization, synchronous/asynchronous rejection, body readers, and composed stage-specific failures/short-circuiting
  • npm run build:docs — 305 pages, including the generated SafeFetch API page

@jderochervlk

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-05T15:13:55.736188Z cf80f72 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread src/fetch/Fetch.res Resolved
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 34977ccebd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@jderochervlk
jderochervlk marked this pull request as ready for review October 13, 2026 13:27
Comment thread src/fetch/SafeFetch.res Resolved
@fhammerschmidt

Copy link
Copy Markdown
Member

Also maybe add some examples that use let?

@tsnobip tsnobip left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

there are a few open questions we should likely settle here before we generalize this kind of helper modules. Please chime in @fhammerschmidt @jderochervlk and anybody who'd like to! :)

Comment thread src/fetch/SafeFetch.res Resolved
Comment thread src/fetch/SafeFetch.res Resolved
Comment thread rescript.json Resolved
@jderochervlk

Copy link
Copy Markdown
Collaborator Author

The requested let? examples are now included in cf80f72: fetch → JSON reading, application-owned decoding errors, and exhaustive handling of all four failure stages. The matching external-consumer fixture compiles against the public .resi with only WebAPI.SafeFetch enabled, and the runtime suite verifies propagation and short-circuiting. The docs also include the LetUnwrap experimental setting required by the locked ReScript 13 alpha compiler; ordinary switch use does not require it. CI passed for cf80f72.

@jderochervlk
jderochervlk requested review from tsnobip and zth November 5, 2026 15:27
@jderochervlk
jderochervlk merged commit c005a91 into main Nov 5, 2026
3 checks passed
@jderochervlk
jderochervlk deleted the feat/safe-fetch branch November 5, 2026 15:32
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.

Add an opt-in SafeFetch wrapper for result-based Fetch workflows

4 participants