Skip to content

Add a Cache-Control directive parser#15

Merged
hellerve merged 1 commit into
masterfrom
claude/cache-control-parser
Jul 13, 2026
Merged

Add a Cache-Control directive parser#15
hellerve merged 1 commit into
masterfrom
claude/cache-control-parser

Conversation

@carpentry-agent

Copy link
Copy Markdown

What

Adds a CacheControl module that parses Cache-Control header values
(RFC 7234 §5.2) into a (Map String String) of lower-cased directive names to
values, plus convenience accessors:

  • CacheControl.parse s — splits on commas while respecting quoted-strings
    (so a quoted field-name list like no-cache="Set-Cookie, X" stays a single
    directive), lower-cases directive names, strips surrounding quotes from
    values, and maps valueless directives (e.g. no-cache) to "". An empty
    header yields an empty map.
  • has?, directive (→ Maybe String)
  • max-age, s-maxage (→ Maybe Int)
  • no-cache?, no-store?, must-revalidate?, public?, private?

Why

The library already parses Accept, MediaType, multipart/form-data, and
Set-Cookie, but had no way to read Cache-Control — a core header for HTTP
caching semantics — so callers had to hand-parse it. This is the natural
additive companion to the existing Accept/MediaType parsers, and reuses
their conventions (comma/;-splitting that honours quotes, ascii-to-lower
keys, quoted-value stripping).

Tests

11 new assertions: max-age/s-maxage as Int, valueless-directive
detection, case-insensitive directive names, max-age=0, max-age absent →
Nothing, unquoting, and the quoted-comma case (asserting it stays one
directive). The parser mirrors Accept.parse's empty-segment skipping, so it
is unaffected by the pre-existing Form.parse "" test failure. Suite: 131
passed / 1 failed
locally (the 1 failure is that unrelated pre-existing
form-data on empty body bug). carp-fmt --check and angler clean on
http.carp and test/http.carp.


Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.

Parse Cache-Control header values (RFC 7234 §5.2) into a map of
lower-cased directive names to values, with convenience accessors.

The library already parses Accept, MediaType, multipart and Set-Cookie
headers but could not read Cache-Control, so callers had to hand-parse
it. CacheControl.parse splits on commas while respecting quoted-strings
(so a quoted field-name list like no-cache="Set-Cookie, X" stays one
directive), lower-cases directive names, unquotes values, and maps
valueless directives to the empty string. Accessors: has?, directive,
max-age, s-maxage, no-cache?, no-store?, must-revalidate?, public?,
private?.

Adds 11 tests.

@carpentry-reviewer carpentry-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build & Tests

Checked out claude/cache-control-parser and ran carp -x test/http.carp: 131 passed / 1 failed locally. The single failure is the pre-existing form-data on empty body returns empty map bug in the Form module (untouched by this PR, masked in CI via continue-on-error), exactly as the PR states. All 11 new CacheControl tests pass. CI green on ubuntu + macos.

Findings

Minor (non-blocking): a valueless or empty numeric directive reports Just 0 instead of Nothing. In int-directive (http.carp:1023), a present-but-valueless directive maps to "", and Int.from-string "" returns Just 0 (its C strtol wrapper treats the empty string as a fully-consumed parse). So:

  • (CacheControl.max-age &(CacheControl.parse "max-age"))Just 0
  • (CacheControl.max-age &(CacheControl.parse "max-age="))Just 0

The docstring says "when present and numeric", so an empty value arguably should be Nothing. Impact is low — this only happens on malformed input (RFC 7234 requires max-age=<seconds>), and 0 reads as "must revalidate", a safe interpretation. If you want to tighten it, a one-liner in int-directive does it:

(Maybe.Just v) (if (String.empty? &v) (Maybe.Nothing) (Int.from-string &v))

Things I checked that are correct:

  • Non-numeric (max-age=abc) and trailing-junk (max-age=3600x) values correctly return Nothingstrtol's whole-string check catches these.
  • Quoted-comma handling works (no-cache="Set-Cookie, X" stays one directive), including with an escaped inner quote, and = inside a quoted value (foo="a=b"a=b).
  • Case-insensitivity, whitespace around =, outer whitespace, empty/all-comma headers, and duplicate directives (last wins) all behave sensibly.
  • No out-of-bounds risk: every String.char-at is under a < len guard and every byte-slice uses bounded indices — no segfault surface.

Optional polish: the README "## Types" table and usage sections aren't updated, but the immediately-preceding sibling parser (Accept, #13) also isn't in the README, so this is consistent with recent practice — noting only for completeness, not as a gap.

Verdict: merge

Core parsing of well-formed Cache-Control headers is correct and thoroughly tested, and it fits the existing header-parser conventions. The Just 0-on-empty edge is a minor, safe quirk on malformed input worth a one-line guard if you care, but it doesn't block.

@hellerve
hellerve marked this pull request as ready for review July 13, 2026 21:14
@hellerve
hellerve merged commit 10f3b54 into master Jul 13, 2026
2 checks passed
@hellerve
hellerve deleted the claude/cache-control-parser branch July 13, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant