Commit 53c687d
authored
feat(sqs): Phase 3.B — XML query-protocol support (3-verb proof) (#662)
## Summary
Phase **3.B** of
[`docs/design/2026_04_24_partial_sqs_compatible_adapter.md`](https://github.com/bootjp/elastickv/blob/main/docs/design/2026_04_24_partial_sqs_compatible_adapter.md)
§16.4. Adds the AWS SQS **query protocol** (form-encoded request, XML
response) alongside the existing JSON protocol on the same listener —
older `aws-sdk-java` v1, `boto < 1.34`, and AWS CLI clients can now talk
to elastickv without modification. Detection happens per-request via
`Content-Type` / `X-Amz-Target` / `Action` presence; no flag, no
separate port. See the new proposal doc committed alongside.
This is an **architectural proof PR**: dispatch + decoding + encoding +
error envelope + the `*Core` refactor pattern are all in place, with
**three verbs** wired end-to-end as concrete proof. Each follow-up verb
is a parser + response struct + one switch arm — no further design work
needed.
### Verbs in this PR
| Verb | Why it's in the proof set |
|---|---|
| `CreateQueue` | Exercises the `Attribute.N.{Name,Value}`
indexed-collection parser. |
| `ListQueues` | Exercises the repeated-element XML response shape. |
| `GetQueueUrl` | Exercises the `<ErrorResponse>` envelope path via
`QueueDoesNotExist`. |
Every other verb returns a structured **501 `NotImplementedYet`** XML
envelope so operators see the gap explicitly. `SendMessage` /
`ReceiveMessage` / `DeleteMessage` are the highest-priority follow-ups
(they need the same `*Core` refactor on the FIFO send loop).
### Key design points
- **No new listener / no flag.** `pickSqsProtocol(*http.Request)`
decides per request. JSON and Query share the SQS port and the SigV4
path.
- **Wire-format-free cores.** `createQueue` / `listQueues` /
`getQueueUrl` are now `decode → core → encode` with `core(ctx, in) (out,
error)`. The JSON wrappers are unchanged in behavior; existing JSON
tests pass without modification.
- **DoS protection inherited.** Body read is bounded by the same
`sqsMaxRequestBodyBytes` the JSON path uses.
- **SigV4 unchanged.** The signed canonical request includes the
form-encoded body, so the existing SigV4 middleware verifies query
requests without code changes.
- **Error parity.** `<Code>` reuses the existing `sqsErr*` constants.
HTTP status mirrors what the JSON path returns, so SDK retry classifiers
work across protocols.
- **Cyclomatic budget honoured.** `handle()` was refactored to extract
`handleQueryProtocol` — `cyclop ≤ 10` per project rules, no `//nolint`.
### Known limitation (design §11.4)
`proxyToLeader`'s error writer always emits the JSON envelope, so a
query-protocol client hitting a follower during a leader flip sees one
JSON error before retry lands on the new leader. Follow-up PR threads
the detected protocol onto the request context so the proxy emits
matching XML.
## Test plan
- [x] `go build ./...` — clean
- [x] `go test -count=1 -race -run
"TestSQS|QueryProtocol|TestPickSqs|TestCollectIndexedKV|TestWriteSQSQueryError"
./adapter/` — passes
- [x] `golangci-lint run ./adapter/...` — `0 issues.`
- [x] `pickSqsProtocol` table tests cover documented edge cases (header
precedence, charset suffix, GET with Action, missing Action, nil
request).
- [x] `collectIndexedKVPairs` tests cover happy path, orphan Name, empty
input, unrelated prefix.
- [x] End-to-end via the in-process listener: CreateQueue / ListQueues /
GetQueueUrl round-trip on the query side.
- [x] **Cross-protocol parity**: a queue created via Query is visible
via JSON `GetQueueUrl` with the same URL.
- [x] Error envelope: 4xx maps to `<Type>Sender</Type>`, 5xx to
`<Type>Receiver</Type>`, namespace pinned, `x-amzn-ErrorType` header
set.
- [x] Unknown verb returns 501 with the `NotImplementedYet` XML
envelope.
- [x] Missing `Action` parameter returns 400 (per design §3).
## Self-review (5 lenses)
1. **Data loss** — Wire-format change only. Cores are byte-for-byte
identical to the previous handler bodies; no Raft / OCC / MVCC code is
touched.
2. **Concurrency** — No new shared state. Detection is request-local.
Body parsing is bounded.
3. **Performance** — One additional `Content-Type` string compare per
request on the dispatch hot path. Negligible.
4. **Data consistency** — `*Core` returns the same business-logic
outputs as before; the JSON tests are the regression net for parity.
Cross-protocol parity test pins behaviour.
5. **Test coverage** — 10 new test cases cover detection, parsing,
envelope shape, and three end-to-end verbs. Existing `TestSQS*` race
suite passes on the refactor.
## Stacking
This PR is **independent** — branched from current `main` (which has
#638 + #649 merged). It does not depend on PR #650 / PR #659. Merge
whenever ready.5 files changed
Lines changed: 1288 additions & 29 deletions
File tree
- adapter
- docs/design
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
157 | 171 | | |
158 | 172 | | |
159 | 173 | | |
| |||
174 | 188 | | |
175 | 189 | | |
176 | 190 | | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
177 | 212 | | |
178 | 213 | | |
179 | 214 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
508 | 508 | | |
509 | 509 | | |
510 | 510 | | |
511 | | - | |
| 511 | + | |
| 512 | + | |
512 | 513 | | |
513 | 514 | | |
514 | 515 | | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
515 | 530 | | |
516 | 531 | | |
517 | | - | |
518 | | - | |
| 532 | + | |
519 | 533 | | |
520 | 534 | | |
521 | 535 | | |
522 | 536 | | |
523 | 537 | | |
524 | 538 | | |
525 | | - | |
526 | | - | |
| 539 | + | |
527 | 540 | | |
528 | 541 | | |
529 | | - | |
530 | | - | |
531 | | - | |
532 | | - | |
| 542 | + | |
| 543 | + | |
533 | 544 | | |
534 | | - | |
| 545 | + | |
535 | 546 | | |
536 | 547 | | |
537 | 548 | | |
| |||
684 | 695 | | |
685 | 696 | | |
686 | 697 | | |
687 | | - | |
688 | | - | |
689 | | - | |
| 698 | + | |
690 | 699 | | |
691 | 700 | | |
692 | 701 | | |
693 | 702 | | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
694 | 724 | | |
695 | 725 | | |
696 | 726 | | |
697 | | - | |
698 | 727 | | |
699 | 728 | | |
700 | 729 | | |
701 | 730 | | |
702 | 731 | | |
703 | 732 | | |
704 | | - | |
705 | | - | |
706 | | - | |
707 | | - | |
708 | | - | |
709 | | - | |
| 733 | + | |
710 | 734 | | |
711 | | - | |
| 735 | + | |
712 | 736 | | |
713 | | - | |
| 737 | + | |
714 | 738 | | |
715 | 739 | | |
716 | 740 | | |
| |||
796 | 820 | | |
797 | 821 | | |
798 | 822 | | |
799 | | - | |
| 823 | + | |
| 824 | + | |
800 | 825 | | |
801 | 826 | | |
802 | 827 | | |
803 | | - | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
804 | 839 | | |
805 | | - | |
806 | | - | |
| 840 | + | |
807 | 841 | | |
808 | 842 | | |
809 | | - | |
810 | | - | |
| 843 | + | |
811 | 844 | | |
812 | | - | |
| 845 | + | |
813 | 846 | | |
814 | 847 | | |
815 | 848 | | |
| |||
0 commit comments