block-form catch, a jwt claims builder, and route groups - #633
Merged
Conversation
recovers with a value, but the recovery a server handler wants is usually control flow: answer a 401, skip this item, stop the loop. that shape had no direct spelling, so every handler grew a helper function whose only job was to route the error somewhere returnable. catch: now opens an indented block that runs on the error path. the block produces no value, so the checker requires it to leave — return, fail, continue or break (E253) — rather than fall through to a binding nothing wrote. the colon must open a block, so the inline form is untouched anywhere it appears. the lowering reuses the inline catch's arms: the ok path extracts the payload with the same borrowed-vs-fresh ownership rules, and the error path releases the failed result before the user's block runs. the regression case covers heap payloads, a borrowed operand, continue and break inside loops, and statement position; it is valgrind-clean with the struct freelist off.
building a claims object meant writing json by hand inside a pith string — escaped quotes, doubled braces, interpolation and epoch arithmetic in one literal. jwt.claims() replaces that: chainable setters for the registered claims, custom string and int claims, and expires_in that stamps iat and exp at signing time, so callers never touch epoch math. a custom claim spelling a registered name is refused rather than silently overriding the builder. reading came down to three nested json calls per field; Verified now answers subject(), claim(name) and claim_int(name) directly. the builder's escape test caught a real std.json bug: parse never decoded string escapes, so any value with a quote, backslash or \u in it survived encode and came back wrong — len 7 for a 5-byte string. both parse paths now decode the rfc 8259 escapes, including surrogate pairs, and reject malformed ones; the fast no-escape path is unchanged. smaller pieces in the same spirit: time.unix() (seconds, the unit protocols mean — the jwt clock bug was exactly a /1000 nobody wrote), json.of and json.of_map for small object bodies, req.bearer_token() to mirror the client's .bearer(), and body_json_or on responses to mirror requests.
…itten use_mw wraps every route, so a guard that belongs to some routes and not others meant an allowlist of public paths inside the middleware. group() binds a middleware to a path prefix instead: the public/protected split is spelled once, at the site that declares the routes, and the group guard runs inside the app-wide middleware. with_value and value give middleware a way to hand what they learned — who the caller is — to handlers on the request itself, rather than through module state. the auth example is the point of all of it. rewritten on the claims builder, catch blocks, bearer_token, group and request values, it has no escaped-json literal, no threadlocal, no path allowlist and no helper functions that existed only to route errors — and its output is unchanged, byte for byte. docs updated to the same shapes.
inserting the Scoped struct landed between App and its doc comment, and the claims() constructor shipped without one.
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
the auth example worked but read badly, and each rough patch traced to a missing piece one level down. this adds the pieces at their right layers and rewrites the example on top of them — its output is unchanged, byte for byte.
the language:
catchtakes a block.expr catch fallbackrecovers with a value, but a server handler's recovery is control flow — answer a 401, skip the row, stop the loop — and that shape had no spelling, so handlers grew helper functions whose only job was to route an error somewhere returnable.expr catch:opens an indented block that runs on the error path; the checker requires it to leave withreturn,fail,continueorbreak(E253), since the block produces no value for the binding. the lowering reuses the inline catch's arms and ownership rules, and the regression case — heap payloads, a borrowed operand, loops — is valgrind-clean.the library:
jwt.claims(). building a claims object meant escaped quotes, doubled braces, interpolation and epoch arithmetic in one string literal. the builder replaces the literal: chainable setters, custom claims,expires_instampingiat/expat signing time, and a signing method per algorithm. reading back issession.subject()/.claim(name)instead of three nested json calls. around it:time.unix(),json.of/of_map,req.bearer_token()and responsebody_json_or.the framework:
group()and request values.use_mwwraps every route, so a guard meant an allowlist of public paths inside the middleware.group(prefix, mw)binds the guard to a prefix — the public/protected split is spelled once, where the routes are declared — andwith_value/valuehand what the guard learned to handlers on the request itself, replacing a threadlocal.and one real bug the builder's tests caught: std.json's parse never decoded string escapes. any value with a quote, backslash or \u in it survived encode and came back wrong — a five-byte string returned as seven. both parse paths now decode the rfc 8259 escapes, surrogate pairs included, and reject malformed ones; the no-escape fast path is untouched. every http server reading a json body was exposed to this.
what was tested
make test,make bootstrap-verify(seed regenerated — parser, checker and emitter changed),make run-examples,make fuzz-checkandmake leak-check, all clean. new cases: the catch-block corpus case (valgrind-clean with the struct freelist off, E253 and E224 rejections verified), builder round trip through a real verifier, escape round trips including surrogate pairs, group scoping and request values through the served example.notes
the block form does not bind the error value;
catch err:is a natural follow-up once wanted. the rewrittenexamples/web_auth.pithis the before/after argument for the whole change — same behavior, no escaped-json literal, no threadlocal, no allowlist, no error-routing helpers.