Skip to content

Conversation

@gdeest
Copy link
Contributor

@gdeest gdeest commented Dec 12, 2025

NOTE: This PR is based on #1859 . You may skip the first two commits when reviewing (but the second one is necessary for the tests to pass).

The goal of this PR is to start exploring the standardization of the core of servant around One True Endpoint Combinator (hereinfafter referred to as OTEC) that supports all of our use cases.

Current situation is more or less the following:

  • Verb / NoContentVerb is the historical endpoint combinator in servant.

  • The Stream combinator is distinct from Verb and supports advanced streaming usages with custom framing and arbitrary item type.

  • UVerb improved upon Verb by allowing multiple instances, but leaks details of the HTTP protocol into handler types (namely HTTP status codes) and is about to be deprecated (see Deprecate UVerb #1820 ).

  • MultiVerb is the closest thing we have to OTEC but:

    1. Does not properly support streaming yet. RespondStreaming is limited to SourceIO ByteString and is not as expressive as the current Stream combinator.
    2. Exhibits some hopefully minor semantic differences w.r.t Verb that need to be clarified.

This PR tries to re-express the Verb semantics for HasServer and HasClient in terms of MultiVerb instances, in order to explore the feasibility of the OTEC vision, verify that MultiVerb provides everything we need, and work out some backward compatibility solution. It turns out to be easier than I feared, but does require some breaking changes. Notably:

  • Verb status codes must now have KnownStatus instances, not just KnownNat.
  • The Verb method parameter was (probably) needlessly polykinded ; it must now be restricted to StdMethod, as this is what MultiVerb does, in order to support delegation.
  • Some semantic changes around header handling in clients are. needed for both MultiVerb and Verb:
    a) MultiVerb incorrectly removed duplicate headers when parsing responses (see Fix handling of duplicate headers in MultiVerb client #1859 ), which made Verb test case failed when expressed in terms of MultiVerb. We fix this.
    b) Verb client implementation did not really check that headers were provided even when marked as mandatory. We could break user code that relies on some server not honoring the Servant API from which the client was derived.

Hopefully we can do something with the work on this branch.

Doing the same thing for Stream is essentially feasible but requires extending RespondStreaming to support the features required by Stream (or add a new response type to MultiVerb). I have some non-primetime-ready code around that I'll consolidate in a PR later.

The end goal would be:

  • To make MultiVerb the OTEC.
  • Ultimately, to rename MultiVerb as Verb, and possibly move current Verb to a dedicated module, or rename it to SimpleVerb.

Add test case that directly exercises MultiVerb's extractHeaders function
with duplicate header names (Set-Cookie headers). This validates the fix
in commit c2cc84f which changed extractHeaders to use findIndexL + deleteAt
instead of Seq.partition, allowing multiple headers with the same name to
be correctly parsed.

The test uses WithHeaders with two Set-Cookie headers and verifies both
cookies are properly extracted on the client side.
The previous implementation used Seq.partition to remove ALL
headers with the matching name at once.

This commit changes its behavior to accept duplicate header names
(particularly important for some special headers such as `Set-Cookie`).
@gdeest
Copy link
Contributor Author

gdeest commented Dec 12, 2025

cc @alpmestan (let's discuss #841 again ;-) )

@gdeest gdeest force-pushed the humble-unification branch from d09fce0 to 5d848b5 Compare December 12, 2025 14:18
This is the first step toward unifying endpoint types around MultiVerb.

Changes:
- Verb's HasServer instances now delegate to MultiVerb
- NoContentVerb delegates to MultiVerb with RespondAs '() 204
- Verb and NoContentVerb method parameter changed from polymorphic (k1)
  to strict (StdMethod) for consistency with MultiVerb.

  This is a breaking change, but arguably not a major one: I doubt that
  this extra polymorphism was ever used.

- Added KnownStatus constraint to Verb instances. We previously only required `KnownNat`,
  but we need `KnownStatus` to be able to express `Verb` in terms of `MultiVerb.

  This is another potential breaking change: users using non-standard, custom statuses will
  have to implement `KnownStatus` instances.

- Added ResponseRender instance for Respond with Headers to support
  the delegation

Removed dead code:
- methodRouter (was only used by Verb)
- noContentRouter (was only used by NoContentVerb)
- responseLBS import (no longer needed)

Test changes:
- Added KnownStatus instances for non-standard test status codes
  (210, 214, 280)
HasClient instances become thin wrappers around the MultiVerb one.

Introduced a NPToHList class as a compatiblity layer between Verb / MultiVerb
for header handling.

Breaking change: Response header handling is now stricter in clients, matching
MultiVerb behavior.

Before: Missing/malformed headers returned MissingHeader/UndecodableHeader
constructors - callers could inspect and handle gracefully.

After: Missing/malformed headers fail the request immediately with
"Failed to parse headers".

In a way, this enforces the API contract: if a header is declared in the type,
it must be present and valid. Use Optional headers if truly optional.
@gdeest gdeest force-pushed the humble-unification branch from 5d848b5 to 37b543a Compare December 12, 2025 15:18
Currently failing due to Verb now fixing method to `StdMethod`
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