77801d13

Add autodocodec-http-api-data for form-urlencoded data

Derives `Web.FormUrlEncoded.ToForm` and `FromForm` from a `HasObjectCodec`
instance, so a type with a codec can be posted as
`application/x-www-form-urlencoded` without a second, hand-written description
of its fields.

## Why

The motivating case is an API endpoint that a shell should be able to hit with
curl alone:

```
curl --netrc --silent --show-error --fail-with-body \
  --data-urlencode "clone-url=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" \
  --data-urlencode "message=$COMMIT_MESSAGE" \
  https://api.nix-ci.com/suite
```

The point of the form is that `--data-urlencode` escapes each value, so a commit
message containing a quote or a newline needs no JSON encoder in the shell.

The type already had a `HasObjectCodec` instance naming all its fields, but with
no autodocodec support the `FromForm` instance had to be written by hand, so the
field names were spelled twice and a test had to assert the two lists agreed.
That test is exactly the check this package makes unnecessary.

`http-api-data`'s own generic derivation is not a substitute: it derives names
from Haskell record fields via a label modifier, so it would be a third
independent spelling of the same names rather than a shared one.

## What

`autodocodec-servant-multipart`'s interpreter retargeted at
`Web.FormUrlEncoded.Form`, which is likewise a flat map from text keys to text
values. A `Form`'s `HashMap Text [Text]` models repeated keys natively, so
`ArrayOfCodec` fits it better than it fits multipart's flat `[Input]`.

Three places where copying multipart verbatim would have been wrong:

- **`unionForm`, not `<>`.** `Form` newtype-derives `Semigroup` from `HashMap`,
  whose union is left-biased, so `<>` would silently drop the second form's
  values under a shared key — where multipart's `mappendMultipartData`
  concatenates.
- **Booleans encode lower case.** Multipart's encoder emits Haskell spelling;
  `toUrlPiece @Bool` is `T.toLower . show`, so `"True"` would round-trip through
  our own decoder but read as unconventional to every other form consumer. The
  decoder still accepts either spelling.
- **A field is absent exactly when its key is absent**, which is lossless and
  matches `http-api-data`'s own `lookupMaybe`. `dropEmptyFormValues :: Form ->
  Form` is exported separately for callers who need a shell's unset variable
  (`--data-urlencode "x=$UNSET"` sends `x=`) to read as absence, at the cost of
  making a genuinely empty value inexpressible. That is a caller's choice rather
  than a policy threaded through the interpreter.

Nested values keep multipart's behaviour — JSON-encoded into the text slot —
documented in the module header as something no other form parser will
understand.

## Tests

`autodocodec-api-usage/test/Autodocodec/FormUrlEncodedSpec.hs` mirrors
`MultipartSpec`: "matches the encoding", "matches the decoding" and a
round trip, over `Example`, `Via`, `LegacyValue`, `LegacyObject`, `These`,
`Expression`, `ListsExample` and `Overlap`. Hand-written `ToForm`/`FromForm
Example` in `Usage.hs` give the first two something independent to compare
against, as the multipart pair do.

Two additions beyond that mirror:

- `dropEmptyFormValues` asserted on both sides of the policy: an empty value
  stays a value without it, and reads as an absent field with it.
- A round trip through `urlEncodeFormStable`/`urlDecodeForm`, so the actual
  percent-escaped wire bytes are covered. That escaping is the reason the
  feature exists.

**No `xdescribe` was needed.** `MultipartSpec` skips `Example` with "does not
hold."; here the whole corpus round-trips, `Example` included. I ran 480,000
examples per property rather than trusting the default 100 before concluding
that. Grouping repeated keys is what makes the difference.

## Checks

- `nix flake check` passes. The new package builds under `-Werror` against
  nixpkgs 26.05, 25.11 and 25.05 plus horizon-advance, so against four
  `http-api-data` versions.
- Full `autodocodec-api-usage` suite: 77,482 examples, 0 failures.
- `pre-commit run -a` clean.

`stack build --pedantic` cannot pass in this tree, and not because of this
change: `autodocodec-swagger2` has an unused `aeson` dependency that
`-Wunused-packages` rejects under `-Werror`. I confirmed that on a clean tree
before working around it. It does not affect `nix flake check`, whose override
does not enable that warning, but it does mean the cheapest feedback loop is
unusable until a separate one-line fix to `autodocodec-swagger2/package.yaml`.

## Not in this PR

`cabal.project` needs no change (it globs `*/*.cabal`); `stack.yaml` does, since
it lists packages explicitly. Replacing the first hand-written caller lives in
the nix-ci tree, on branch `openapi-spec` (NorfairKing/nix-ci#463).

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 6m35s 1s 1s 6m36s 6m35s
Eval 9m23s 58s 12m01s 21m25s 2m46s
Build 10m21s 0s 0s 10m21s 0s
Suite 6m35s 1m00s 14m50s 21m25s 20m25s