feat(0119): reject every invalid input with a 400 envelope - #217
Open
stkrolikiewicz wants to merge 5 commits into
Open
feat(0119): reject every invalid input with a 400 envelope#217stkrolikiewicz wants to merge 5 commits into
stkrolikiewicz wants to merge 5 commits into
Conversation
Axum's own Query/Json/Path rejections answered in text/plain with 415/422 for body failures, bypassing the ErrorEnvelope contract. New wrapper extractors (ValidatedQuery/ValidatedJson/ValidatedPath) map every rejection to 400 + envelope; new invalid_body code; bad_request now carries Cache-Control: no-store; /prices/batch gets an explicit 16 KB body limit so an oversized body is refused before parsing. CH-less negative tests prove each rejection fires before any ClickHouse call.
… fns The six param enums (SortCol, Order, TypeFilter, Timeframe, Granularity, BaseCurrency) now derive Deserialize + ToSchema with explicit per-variant renames, so params deserialize straight into typed forms: an unknown token fails serde with a message enumerating the valid values (400 envelope via ValidatedQuery), and the same enums publish into OpenAPI. Deletes the six stringly parse() fns. Case policy: exact documented tokens; base_currency keeps lowercase usd/xlm aliases (historically case-insensitive). New: search capped at 1-12 ASCII alphanumeric (SEP-11 alphanum12 prefix).
Cursor: deny_unknown_fields (foreign lookalikes 400), 256-char token cap, and valid_for(sort) — a numeric sort requires a finite-parseable v, which previously reached ClickHouse toFloat64() and threw a 500 from client input; code sorts require an asset-code shape. OHLCV: parse_time (chrono; already in the build graph transitively) replaces the shape-only valid_iso8601, rejecting impossible dates that CH would have interpreted freely; the validated epoch (not the raw string) is bound via toDateTime(?) so exactly one interpretation of the window exists. New window rule before any DB call: start < end, and ceil(span/granularity) <= OHLCV_MAX_POINTS with a message naming the count — replacing the silent newest-5000 truncation. The timeframe window anchors to end when only end is given (?end=..&timeframe=7d is the 7d window ending there); timeframe=all starts at Stellar genesis.
…s-api Registers the six query-param enums in ApiDoc components — utoipa-axum collects body schemas from routes but a params-tuple $ref is NOT auto-registered, so the served document carried dangling refs (caught by the new enum-publication contract test). BatchRequest.assets publishes minItems/maxItems mirroring MAX_BATCH; search publishes its 1-12 length bounds. Task file gains the AC 1 param -> rule -> error-code table and the recorded policies (unknown params ignored, exact-token case with the base_currency lowercase exception, cursor sort-binding limitation, CH-in-CI deliberately deferred to 0120/0122). CI clippy line now includes prices-api.
…eframe cliff Local-CH smoke caught the one positive-path change 0119 makes: timeframe=all&granularity=1h is now an up-front 400 (genesis to now is ~95k hourly buckets against the 5000 cap), where the merge test leaned on the old silent newest-5000 truncation. Narrowed with explicit start/end around the seeded candles — merge + backfill_note coverage unchanged. All 21 integration tests pass against the prod-pinned CH 26.3.10.60, confirming the toDateTime epoch binds. Task notes record the consumer-visible change and the ~2029-06 cliff where bare timeframe=all at 1d itself crosses 5000 buckets.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ValidatedQuery/ValidatedJson/ValidatedPathextractors route every axum rejection through the standardErrorEnvelope— the old text/plain 400/415/422 bodies are gone; 400s now carryCache-Control: no-store, andPOST /prices/batchgets an explicit 16 KB body limitparse_*fns deleted): unknown tokens 400 with the valid values enumerated,searchis capped at 1–12 ASCII alphanumerics, and the same enums publish into OpenAPI (contract-tested — the test caught dangling $refs utoipa-axum does not auto-register)deny_unknown_fields, 256-char token cap, and payload type-checked against the active sort — closes a client-reachable 500 (corruptvreaching ClickHousetoFloat64)toDateTime(?), plus an explicit window rule (start < end, ≤5000 points at the chosen granularity) replacing silent truncation; the timeframe window anchors toendwhen onlyendis givenAppState::without_ch()(panics on any CH access, proving validation precedes every query) and run in the existing CIcargo teststep;prices-apiadded to the clippy-D warningsgate; all 21 live-CH integration tests pass against the prod-pinned 26.3.10.60Task:
lore/1-tasks/active/0119_FEATURE_api-input-validation-hardening.md(param → rule → error-code table + recorded policies)