fix(handlers): validate all modifier arguments - #321
Conversation
Modifier arguments were passed straight to sharp, which threw raw errors
with no status code and surfaced as a 500. Validate every argument up
front and return a 400 instead, via reusable `VNumber` / `VEnum` /
`VColor` / `VSize` / `VRequired` arg mappers whose bounds mirror the ones
sharp enforces. `applyHandler` also wraps `apply()` so sharp's own
validation (unknown colour names) surfaces as a 400 rather than a 500.
Fixes several pre-existing 500s found along the way:
- `/trim_100/`: sharp requires `trim({ threshold })`, a bare number throws
- `/sharpen/`: sharp rejects an options object without a `sigma`
- `/blur/`, `/gamma/`, `/threshold/`, ...: valueless modifiers passed `""`
straight through instead of falling back to the sharp defaults
- `/extract_0_0_10/`: sharp requires all four values
Two-word `position` values (`right top`) were unreachable since `_`
separates arguments, so `pos_right-top` is now accepted as well.
Also document the accepted values and ranges of every modifier.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughModifier handling now uses typed argument mappers with explicit validation, structured 400 errors, updated Sharp handler contracts, expanded tests, and more precise README documentation. ChangesModifier validation and handler contracts
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ModifierRequest
participant applyHandler
participant ArgMapper
participant SharpPipeline
ModifierRequest->>applyHandler: modifier arguments
applyHandler->>ArgMapper: parse and validate arguments
ArgMapper-->>applyHandler: typed values or HTTPError
applyHandler->>SharpPipeline: invoke handler with typed values
SharpPipeline-->>applyHandler: result or parsing error
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #321 +/- ##
==========================================
+ Coverage 80.08% 84.90% +4.82%
==========================================
Files 9 9
Lines 487 563 +76
Branches 132 159 +27
==========================================
+ Hits 390 478 +88
+ Misses 79 69 -10
+ Partials 18 16 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Accept the numeric gravity/strategy `position` constants sharp supports (and `HandlerContext.position` already typed), which the enum rejected. - Bound `rotate` to angles libvips can fit in a `gdouble`, so `/rotate_1e10/` is a 400 rather than a 500. - Use `HTTPError.isError()` instead of `instanceof`, which is unreliable across h3 copies and would rewrite a custom handler's status code. - Give the invalid-args test a full context: with an incomplete one, `apply` threw on its own, so the `resize` cases passed regardless of `VSize`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Ran an independent review pass over the diff (verified against sharp 0.35.3's source and by running the real pipeline, not just reading the code). Pushed 1b76f70 with the fixes; the rest is written up below for your call. Fixed
Not changed — flagging for your call
Correction to the commit messageIt claims two-word positions "were unreachable since Everything else in the description checked out: all four claimed 500-fixes reproduce as 500 on |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
test/index.test.ts (2)
106-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded source dimensions make this assertion fixture-brittle.
Deriving the base size from
bliss.jpgmeta (or from an unmodifiedprocess()result) keeps the test valid if the fixture is ever replaced.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/index.test.ts` around lines 106 - 113, Update the “extend resizes the canvas” test to derive the original width and height from bliss.jpg metadata or an unmodified process() result instead of hardcoding 3840 and 2160. Use those derived dimensions when asserting the extended width and height, while preserving the existing extension offsets.
55-55: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value
enlargecase upscales to 8000x8000 in CI.That's a ~64 MP output per run for a fixture that is 3840x2160; a smaller target (e.g.
5000x5000or a smaller source) keeps the assertion while cutting memory/CPU in the test suite.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/index.test.ts` at line 55, Reduce the target dimensions in the `enlarge` test case within the test fixture configuration from 8000x8000 to a smaller size such as 5000x5000, preserving the upscaling behavior and existing assertion while lowering CI memory and CPU usage.README.md (1)
233-233: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueColour docs omit the functional notations the validator accepts.
VColoralso acceptsrgb()/rgba()/hsl()/hsla()/hwb(); worth listing so users know they are supported (URL-encoding caveats aside).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` at line 233, Update the Colours documentation to list the supported functional colour notations rgb(), rgba(), hsl(), hsla(), and hwb() alongside the existing hex and CSS colour-name formats, while preserving the current URL-path caveat.src/handlers/handlers.ts (1)
245-256: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
sharpensilently dropsflat/jaggedwhensigmais omitted.
/sharpen__1_2/validates fine but the two provided values are discarded. Rejecting that combination (or documenting it) would be more predictable than ignoring user input.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/handlers/handlers.ts` around lines 245 - 256, The sharpen apply handler currently ignores provided flat and jagged values when sigma is omitted. Update the apply logic for sharpen so supplying flat or jagged without sigma is rejected or otherwise explicitly handled, rather than silently calling pipe.sharpen() and discarding those values; preserve the existing default sharpening behavior when all options are omitted.src/handlers/utils.ts (2)
237-252: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueBlanket 400 also hides genuine server-side faults.
Any throw from
handler.apply(including aTypeErrorfrom a handler bug) is reported as a client error, which will mask real 500s in metrics/logs. Consider logging the originalcauseat warn/error level, or restricting the 400 conversion to sharp validation errors.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/handlers/utils.ts` around lines 237 - 252, Restrict the error conversion in the handler.apply catch block to known sharp validation errors, allowing unexpected handler faults such as TypeError to propagate as server errors. Preserve existing HTTPError passthrough and the 400 IPX_INVALID_MODIFIER response for genuine modifier-validation failures; use the original error type or established sharp validation symbols to distinguish them.
56-61: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueWiden
isOmitted’s parameter type soundefinedis type-safe.
isOmittedis used with the raw argument fromparseArgs, where missing slots can beundefined, but it is currently typed asstring. TypeScript 6 withstrictdoes not error onstring === undefined; widening the declared type instead makes the omission contract explicit without changing behavior.♻️ Suggested typing fix
-function isOmitted(argument: string): boolean { +function isOmitted(argument: string | undefined): boolean {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/handlers/utils.ts` around lines 56 - 61, Update the isOmitted function parameter type to accept string or undefined, matching raw parseArgs values while preserving its existing omission checks and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/handlers/handlers.ts`:
- Around line 99-104: Update the kernel handler in src/handlers/handlers.ts at
lines 99-104 to set order: -1, matching the other context modifiers so it
executes before resize. In test/index.test.ts at line 56, add coverage with
resize before kernel; README.md at line 240 requires no direct change and should
remain once ordering is enforced.
---
Nitpick comments:
In `@README.md`:
- Line 233: Update the Colours documentation to list the supported functional
colour notations rgb(), rgba(), hsl(), hsla(), and hwb() alongside the existing
hex and CSS colour-name formats, while preserving the current URL-path caveat.
In `@src/handlers/handlers.ts`:
- Around line 245-256: The sharpen apply handler currently ignores provided flat
and jagged values when sigma is omitted. Update the apply logic for sharpen so
supplying flat or jagged without sigma is rejected or otherwise explicitly
handled, rather than silently calling pipe.sharpen() and discarding those
values; preserve the existing default sharpening behavior when all options are
omitted.
In `@src/handlers/utils.ts`:
- Around line 237-252: Restrict the error conversion in the handler.apply catch
block to known sharp validation errors, allowing unexpected handler faults such
as TypeError to propagate as server errors. Preserve existing HTTPError
passthrough and the 400 IPX_INVALID_MODIFIER response for genuine
modifier-validation failures; use the original error type or established sharp
validation symbols to distinguish them.
- Around line 56-61: Update the isOmitted function parameter type to accept
string or undefined, matching raw parseArgs values while preserving its existing
omission checks and behavior.
In `@test/index.test.ts`:
- Around line 106-113: Update the “extend resizes the canvas” test to derive the
original width and height from bliss.jpg metadata or an unmodified process()
result instead of hardcoding 3840 and 2160. Use those derived dimensions when
asserting the extended width and height, while preserving the existing extension
offsets.
- Line 55: Reduce the target dimensions in the `enlarge` test case within the
test fixture configuration from 8000x8000 to a smaller size such as 5000x5000,
preserving the upscaling behavior and existing assertion while lowering CI
memory and CPU usage.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: bce1fcba-2bc9-45ea-a7cf-c2d75b2281e3
📒 Files selected for processing (6)
README.mdsrc/handlers/handlers.tssrc/handlers/utils.tstest/handlers/handlers.test.tstest/handlers/utils.test.tstest/index.test.ts
`kernel` has no `order`, so it runs first only because handlers sort by
name ("kernel" < "resize" and < "s"). The existing case just asserted a
Buffer came back, which would still pass if the kernel were dropped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Modifier arguments were passed straight to sharp, which threw raw errors with no status code and surfaced as a 500. Every argument is now validated up front and returns a 400 instead.
Follows up on the
VArgTODO insrc/handlers/utils.ts.Arg mappers
Reusable mappers in
src/handlers/utils.ts, wired into every handler:VNumber(name, { min, max, integer })NaN/Infinity/true/nullVEnum(name, values)VColor(name)#optional and re-added), functional or namedVSize(name){width}x{height}or square{size}, positive integersVRequired(name, mapper)Bounds mirror the ones sharp enforces (read off
sharp/dist/{resize,operation,colour}.mjsrather than guessed).VArgis unchanged and still exported — it is the base coercion the mappers build on.applyHandleralso wrapshandler.apply()so sharp's own validation (unknown colour names, which sharp only parses when the colour is used) surfaces as a400 IPX_INVALID_MODIFIERrather than a 500.Errors are
400 IPX_INVALID_MODIFIER_ARG, or400 IPX_MISSING_MODIFIER_ARGfor a required argument:Bugs fixed along the way
Pre-existing 500s, not regressions from this change:
/trim_100/— sharp 0.35 requirestrim({ threshold }); a bare number throwsExpected object for trim. The documented README example was broken./sharpen/— passed{ sigma: undefined, … }, and sharp rejects an options object without asigma. Now falls back tosharpen()./blur/,/gamma/,/threshold/and other valueless forms — passed""straight through, which sharp rejects.""is now treated as omitted so sharp's defaults apply./extract_0_0_10/— sharp requires all four values; missing ones now give a clear 400.Positions
_separates modifier arguments, so sharp's two-word positions (right top) were effectively unreachable —pos_right_topsilently droppedtop.pos_right-topis now accepted, alongside the existing%20form.Notes
qualityis now1–100(README previously said0–100).0is only valid for PNG in sharp, so this takes the safe intersection. Happy to allow0instead if you prefer.sharp.toBuffer()are still 500s — e.g./extract_0_0_99999999_99999999/passesextract()'s validation and only fails later in libvips. Out of scope for the arg TODO, but it is the remaining path where bad input yields a 500.Docs
The modifiers table now documents the accepted values and ranges for every modifier, plus the shared colour syntax.
Tests
test/handlers/utils.test.ts— unit tests per mapper.test/handlers/handlers.test.ts— table-driven valid/invalid cases for every modifier, driven throughapplyHandler.test/index.test.ts— every modifier exercised against real sharp (mocking is what hid thetrim/sharpen/ valueless-arg bugs), plus 400 assertions for invalid input.🤖 Generated with Claude Code
Summary by CodeRabbit