doge
запушил(а) strict-validation в Troglodyne/tPSGI
5eb7d55926 Give a callback the input of a request apart from the rest of its query
extract_query() merged the query string, the body, the path and
x-tpsgi-data into the same hash as everything tPSGI knows of the request:
the route, the method, the domain, the dispatcher. Validation now refuses
an undeclared name, but a declared one could still stand in for a key that
tPSGI or the application set, and an application that hands the query to a
template hands the request every variable of the template. @reserved
papered over part of it, by letting the query string carry the keys that
route() then overwrote.
Now extract_input() builds the input alone, and route() puts it in
$query->{input}. The rest of the query is what tPSGI sets, and no request
can reach it, so @reserved goes: a key such as route is a 400 like any
other undeclared key.
The input is flat, so a name can mean one thing only. new() dies on an
operation that gives one name to two of the path, the query and the body,
and on x-tpsgi-data that sets a name the request can give. A body schema
that says additionalProperties can still take any name, so validate()
refuses a field or file of such a body whose name is a path variable or a
query parameter. In CGI mode the input is empty, as the query was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9a36d98ac0 Refuse every name in a request that its operation does not declare
The router refused an undeclared query key only for an operation that
declared a query parameter or a request body. An operation that declared
neither took any key, and a body sent to an operation with no requestBody
was never checked at all. extract_query() merges the query, the fields of
the body and its files into the one hash that the callback gets. So any
name in a request could stand in for a key that the callback meant to set
itself, and the callback cannot tell the two apart.
Now each operation is strict. A query key must be a query parameter, or a
key that tPSGI sets itself. A body to an operation with no requestBody is
a 400. A field or a file must be a property of the schema of the media type
that was sent, unless that schema says additionalProperties, in which case
JSON::Validator decides as before. The files reach validate() by name,
because extract_query() merged them into the query unchecked.
CGI mode stays as it was. route() builds no query for an operation with
x-tpsgi-env, so nothing is validated, and the CGI reads the request from
%ENV. It is how a CGI that reads whatever it is sent can move to tPSGI one
route at a time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16f921ec94 Merge pull request #22 from Troglodyne-Internet-Widgets/openapi-routing
Route by an OpenAPI 3 document, rather than a list of regexes
be155af285 Take perl's own booleans in a router's document
A router had to write Mojo::JSON::true, by its full name, because the
true that use 5.040 imports failed the check against the OpenAPI schema.
JSON::Validator 5.19 compares enum values by a checksum, and a native
true does not hash as the JSON true of required: true.
The router now checks a copy of each document in which perl's booleans
are Cpanel::JSON::XS booleans. The callbacks in the copy are the same
subs, and the router's own document is left as it was. The documents in
the POD and the tests say plain true. Without the copy, t/TPSGI-Router.t
dies as it builds its router.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
266eafe760 Route by an OpenAPI 3 document, rather than a list of regexes
A router exported @routes: regex keys, tried with m/^$key$/ in array order
after an exact pass. The one real router builds that array from a hash, so
its regex routes were tried in an order that changed from process to
process, and the "longest first" that respond() promised was never done.
Every literal key was also tried as a regex, so the dot of /humans.txt
matched any character. Each parameter was checked by a coderef of its own,
and the catalog of routes could not be encoded, because of those coderefs.
A router now exports %openapi, an OpenAPI 3 document as Perl data, and the
new TPSGI::Router compiles it. The callback is the operation's
x-tpsgi-callback, x-tpsgi-data and x-tpsgi-env replace data and env, and the
callback finds the whole operation in $query->{dispatcher}. A template
takes {name}, several to a segment, and a {name+} that takes the rest of the
path. Its literal text is quotemeta'd, and no variable takes a newline,
which keeps what the old test of extract_query asked of a dot. The most
concrete template wins, by a total order of segment kind, then literal
length, then the string. So the order never comes from a hash.
JSON::Validator checks each request against the operation's parameters and
requestBody, and coerces the values. validate_request() coerces its own
copy of each value, so the router hands it getters and writes the coerced
values back. The old strict rule stays: an operation that declares a query
parameter or a body takes no undeclared query key. A body of a media type
that the operation does not take is a 415, and a path with a method that it
has no route for is a 405 with Allow, where it was a 400.
A router that still has @routes dies as it loads, through cannot_start(),
and says where to read. Compiling a document takes about 50 ms, against
about 27 us to validate a request, and new() runs per request. So the
router is built once per worker, for each list of router files.
A request that failed validation got badrequest() with the query, which has
no method and no path, so the log line warned and the warning was fatal. It
now gets what respond() knows of the request.
Each new rule was broken once on purpose, and the test of it failed: the
exact pass, quotemeta, the newline rule, the segment rank, the literal
length, the twin and duplicate templates, a {name+} that is not last, HEAD,
405 and Allow, 415, the undeclared key, the write-back of coerced values, a
callback by name and a missing one, an invalid document, a stale router,
the cache and x-tpsgi-data. Content type negotiation is JSON::Validator's
own negotiate_content_type(), not a second copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 дней назад