Add configurable CORS middleware with defserver integration#30
Add configurable CORS middleware with defserver integration#30carpentry-agent[bot] wants to merge 1 commit into
Conversation
CORS.setup configures origin/methods/headers/max-age in one call. CORS.set-credentials! and CORS.set-expose-headers! control optional headers. The after-hook adds Vary: Origin for non-wildcard origins and conditionally adds credentials and expose-headers. A new (cors ...) form in defserver registers both hooks automatically, replacing the manual (before CORS.before-hook) / (after CORS.after-hook) pattern. CORS.configure is preserved for backward compatibility.
There was a problem hiding this comment.
Build & Tests
CI passes on macos-latest. Cannot build locally (no Carp compiler on this machine), relying on CI results. 18 new tests in test/cors_test.carp all pass.
Findings
-
credentials: truewithorigin: "*"is not validated. The CORS spec (§3.2.5) saysAccess-Control-Allow-Originmust not be*when credentials are enabled — browsers will silently reject such responses. Theset-credentials!doc mentions this, but the code doesn't warn at configuration time. Consider printing a warning inset-credentials!orsetupwhen both conditions hold. Not blocking, but users hitting this would get confusing browser errors. -
Varyheader accumulation (minor).after-hookaddsVary: OriginviaResponse.with-header, which appends to the header's value array. If the application already setsVary(e.g.,Vary: Accept-Encoding), this creates a second array entry (separate header line) rather than comma-joining. Per HTTP spec this is valid, but less clean. Minor, not blocking. -
(cors ...)form arity validation (minor). Thedefservermacro handles arity 2 ((cors @"*")) and arity 5 ((cors @"origin" @"methods" @"headers" @"max-age")), but other arities (3, 4, 6+) would crash at macro expansion with unhelpfulcar/cdrerrors. A compile-time error, not runtime, so not critical — but a guard with a clear error message would improve the experience. -
Test workaround is documented. The
add-cflag "-Wno-incompatible-pointer-types-discards-qualifiers"in the test file works around a pre-existing datetime dependency issue and is noted in the PR description. Doesn't affect the library itself. -
Hook ordering is correct. CORS before-hooks are prepended (preflight short-circuits before user hooks), CORS after-hooks are appended (CORS headers added after user after-hooks).
-
API design is consistent. The global-state approach (module-level
defvariables) matches the existing CORS module and how the web framework operates. Thedefserver(cors ...)integration is clean and well-documented. -
CHANGELOG is updated. Good.
-
Test coverage is solid. Tests cover: before-hook pass-through, preflight 204, configurable methods/headers/max-age, credentials on/off for both preflight and normal responses, expose-headers on/off, Vary header presence/absence,
web-build-responsepipeline integration, and backward compatibility ofCORS.configure.
Verdict: merge
Good feature addition with thorough test coverage. The credentials+wildcard validation gap (finding #1) is the most substantive concern — worth addressing in a follow-up — but it's a documentation-vs-enforcement issue common across CORS libraries, and the docs do warn about it.
Summary
The existing CORS module only allowed configuring the origin via
CORS.configure. This PR makes the CORS middleware fully configurable and adds a convenient(cors ...)form fordefserver.New API
CORS.setup— configure origin, methods, headers, and max-age in one call:CORS.set-credentials!— enableAccess-Control-Allow-Credentials:CORS.set-expose-headers!— setAccess-Control-Expose-Headers:(cors ...)indefserver— registers both CORS hooks automatically:Or just
(cors @"*")for defaults.Behaviour changes
after-hooknow addsVary: Originwhen the configured origin is not*(correct cache behaviour per the CORS spec)after-hookconditionally addsAccess-Control-Allow-Credentials: trueandAccess-Control-Expose-Headersbefore-hookincludesAccess-Control-Allow-Credentialson preflight responses when credentials are enabledCORS.configureis preserved for backward compatibilityTests
18 new tests in
test/cors_test.carpcovering:web-build-responsepipelineCORS.configureNote: The existing
test/web.carphas a pre-existing C compilation error from the datetime dependency (tm_zoneconst qualifier mismatch) that is unrelated to this PR. The CORS tests useadd-cflagto work around it.Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.