Ship the methods a request carries, as booleans a consumer can read
Tests / vet + fmt + tests (pull_request) Successful in 1m24s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped

This commit is contained in:
2026-09-20 00:32:42 +02:00
parent e7a906debf
commit caec22001d
5 changed files with 51 additions and 56 deletions
+5 -4
View File
@@ -44,10 +44,11 @@ replacement, and each removed path, column or flag.
`device` and `os` columns. `misc.car` keeps its makes and models in a `make` and a `device` and `os` columns. `misc.car` keeps its makes and models in a `make` and a
`model` column. `model` column.
- `misc.httpmethod`, `misc.protocol` and `misc.port` are IANA's registries as tables. - `misc.httpmethod`, `misc.protocol` and `misc.port` are IANA's registries as tables.
`misc.httpmethod` carries `safe` and `idempotent` beside the method; `misc.protocol` `misc.httpmethod` is the nine methods the HTTP core specification defines, carrying
the keyword, its `number` and its `name`, selectable by either; `misc.port` renders a `safe` and `idempotent` as `true` or `false`; `misc.protocol` the keyword, its
TCP port number, with the IANA `service` name beside it, keeping the first service the `number` and its `name`, selectable by either; `misc.port` renders a TCP port number,
registry describes for a port so a number selects one row. with the IANA `service` name beside it, keeping the first service the registry
describes for a port so a number selects one row.
- `geo.SE` and `geo.US`: five linked tables per country, `region`, `municipality`, - `geo.SE` and `geo.US`: five linked tables per country, `region`, `municipality`,
`locality`, `postal-code` and `street`, weighted by population and address counts `locality`, `postal-code` and `street`, weighted by population and address counts
and built from SCB, GeoNames, Trafikverket NVDB and the US Census Bureau, and an and built from SCB, GeoNames, Trafikverket NVDB and the US Census Bureau, and an
+16 -4
View File
@@ -170,10 +170,18 @@ which `--list` shows. `car`, `currency`, `httpmethod`, `httpstatus`, `language`,
`mimetype`, `port`, `protocol`, `territory`, `timezone` and `useragent` are `mimetype`, `port`, `protocol`, `territory`, `timezone` and `useragent` are
[tables](#table), so `misc.territory[SE].capital` and `misc.currency[Euro].symbol` [tables](#table), so `misc.territory[SE].capital` and `misc.currency[Euro].symbol`
select a row; `car` and `useragent` carry no key or name, so they are drawn from rather select a row; `car` and `useragent` carry no key or name, so they are drawn from rather
than selected in. `misc.httpmethod`, `misc.protocol` and `misc.port` are IANA's than selected in.
registries: a method carries the register's `yes` or `no` for `safe` and `idempotent`,
a protocol its `number` and its spelled-out `name`, and `misc.port` renders the number `misc.httpmethod`, `misc.protocol` and `misc.port` are IANA's registries.
a port field holds, with the IANA `service` name beside it. `misc.httpmethod` is the nine methods the HTTP core specification defines, not the
WebDAV extensions the register also holds, and its `safe` and `idempotent` are `true`
or `false`, so a Go `bool` reads them. `misc.protocol` carries a `number` and a `name`,
the keyword itself where the register spells none out, and selects by either spelling.
`misc.port` is the TCP assignments, rendering the number a port field holds with the
IANA `service` beside it, and selecting by number alone. A draw spans the whole
register, so pin `misc.port[443]` where a fixture needs a port a reader recognises;
it stops at 49150, IANA assigning nothing above, so an ephemeral source port is
`{int(49152,65535)}`.
[`DATA-LICENSES.md`](DATA-LICENSES.md) names each table's source and licence. [`DATA-LICENSES.md`](DATA-LICENSES.md) names each table's source and licence.
`misc.timezone` is every zone tzdb gives a shipped territory, from one apiece for most `misc.timezone` is every zone tzdb gives a shipped territory, from one apiece for most
@@ -1188,6 +1196,10 @@ the Development section below, and who ships a register the four above then draw
worth more than the last rows of a table. Where no such link can hold the fact worth more than the last rows of a table. Where no such link can hold the fact
stays a column. Layer your own `misc.territory` over the shipped one and you stays a column. Layer your own `misc.territory` over the shipped one and you
must layer `misc.timezone` too, or the link fails at load naming the row. must layer `misc.timezone` too, or the link fails at load naming the row.
- **`misc.port` selects by number, and carries no `name`.** 29 of its services sit on
more than one port, `http-alt` on three, so `misc.port[http-alt]` could name no one
row. The number is what a port field holds anyway, and `misc.port[443].service`
reads the other way.
- **`misc.car` is one flat table, not a make linked to its models.** A row is a make - **`misc.car` is one flat table, not a make linked to its models.** A row is a make
and a model drawn together, so no render pairs a Volvo with a RAV4. Two linked and a model drawn together, so no render pairs a Volvo with a RAV4. Two linked
tables would reach the same pairs and add a selector nothing asks for; a make alone tables would reach the same pairs and add a selector nothing asks for; a make alone
+16 -8
View File
@@ -3,8 +3,10 @@
data-import/httpmethod.py [--source URL_OR_FILE] [--cache DIR] [--out FILE] data-import/httpmethod.py [--source URL_OR_FILE] [--cache DIR] [--out FILE]
A row needs a name of letters and hyphens, which drops the registry's `*`, and both of A method ships when the register cites the HTTP core specification for it, RFC 9110
the answers the registry gives for it. section 9.3 or RFC 5789, which is the nine a request carries; the WebDAV and DeltaV
extensions the register also holds do not. The register's `yes` and `no` ship as `true`
and `false`, which a consumer's boolean reads.
""" """
import argparse import argparse
import csv import csv
@@ -20,19 +22,23 @@ SOURCE = "https://www.iana.org/assignments/http-methods/methods.csv"
OUT = Path(__file__).resolve().parent.parent / "data" / "misc" / "httpmethod.tsv" OUT = Path(__file__).resolve().parent.parent / "data" / "misc" / "httpmethod.tsv"
CACHE = Path(__file__).resolve().parent / "cache" CACHE = Path(__file__).resolve().parent / "cache"
COLUMNS = ["idempotent", "method", "safe"] COLUMNS = ["idempotent", "method", "safe"]
ANSWERS = ("yes", "no") CORE = re.compile(r"\[RFC9110, Section 9\.3\.|\[RFC5789, Section 2\]")
BOOLEAN = {"yes": "true", "no": "false"}
EXPECTED = 9
def rows(text): def rows(text):
for r in csv.DictReader(io.StringIO(text)): for r in csv.DictReader(io.StringIO(text)):
method = (r["Method Name"] or "").strip() method = (r["Method Name"] or "").strip()
safe, idempotent = (r["Safe"] or "").strip(), (r["Idempotent"] or "").strip() if not CORE.search(r["Reference"] or ""):
if not re.fullmatch(r"[A-Za-z][A-Za-z-]*", method) or not safe or not idempotent:
continue continue
for column, answer in (("Safe", safe), ("Idempotent", idempotent)): answers = {}
if answer not in ANSWERS: for column in ("Safe", "Idempotent"):
answer = (r[column] or "").strip()
if answer not in BOOLEAN:
sys.exit(f"{method}: {column} {answer!r} is neither yes nor no") sys.exit(f"{method}: {column} {answer!r} is neither yes nor no")
yield {"idempotent": idempotent, "method": method, "safe": safe} answers[column] = BOOLEAN[answer]
yield {"idempotent": answers["Idempotent"], "method": method, "safe": answers["Safe"]}
def main(): def main():
@@ -42,6 +48,8 @@ def main():
p.add_argument("--out", default=str(OUT)) p.add_argument("--out", default=str(OUT))
a = p.parse_args() a = p.parse_args()
table = sorted(rows(source.fetch(a.source, a.cache, "http-methods.csv").decode("utf-8")), key=lambda r: r["method"]) table = sorted(rows(source.fetch(a.source, a.cache, "http-methods.csv").decode("utf-8")), key=lambda r: r["method"])
if len(table) != EXPECTED:
sys.exit(f"{len(table)} methods cite the core specification, not {EXPECTED}; the register's Reference column has moved")
tsv.write(a.out, COLUMNS, table) tsv.write(a.out, COLUMNS, table)
+9 -40
View File
@@ -1,41 +1,10 @@
idempotent method safe idempotent method safe
yes ACL no false CONNECT false
yes BASELINE-CONTROL no true DELETE false
yes BIND no true GET true
yes CHECKIN no true HEAD true
yes CHECKOUT no true OPTIONS true
no CONNECT no false PATCH false
yes COPY no false POST false
yes DELETE no true PUT false
yes GET yes true TRACE true
yes HEAD yes
yes LABEL no
yes LINK no
no LOCK no
yes MERGE no
yes MKACTIVITY no
yes MKCALENDAR no
yes MKCOL no
yes MKREDIRECTREF no
yes MKWORKSPACE no
yes MOVE no
yes OPTIONS yes
yes ORDERPATCH no
no PATCH no
no POST no
yes PRI yes
yes PROPFIND yes
yes PROPPATCH no
yes PUT no
yes QUERY yes
yes REBIND no
yes REPORT yes
yes SEARCH yes
yes TRACE yes
yes UNBIND no
yes UNCHECKOUT no
yes UNLINK no
yes UNLOCK no
yes UPDATE no
yes UPDATEREDIRECTREF no
yes VERSION-CONTROL no
1 idempotent method safe
2 yes false ACL CONNECT no false
3 yes true BASELINE-CONTROL DELETE no false
4 yes true BIND GET no true
5 yes true CHECKIN HEAD no true
6 yes true CHECKOUT OPTIONS no true
7 no false CONNECT PATCH no false
8 yes false COPY POST no false
9 yes true DELETE PUT no false
10 yes true GET TRACE yes true
yes HEAD yes
yes LABEL no
yes LINK no
no LOCK no
yes MERGE no
yes MKACTIVITY no
yes MKCALENDAR no
yes MKCOL no
yes MKREDIRECTREF no
yes MKWORKSPACE no
yes MOVE no
yes OPTIONS yes
yes ORDERPATCH no
no PATCH no
no POST no
yes PRI yes
yes PROPFIND yes
yes PROPPATCH no
yes PUT no
yes QUERY yes
yes REBIND no
yes REPORT yes
yes SEARCH yes
yes TRACE yes
yes UNBIND no
yes UNCHECKOUT no
yes UNLINK no
yes UNLOCK no
yes UPDATE no
yes UPDATEREDIRECTREF no
yes VERSION-CONTROL no
+5
View File
@@ -103,6 +103,11 @@ by a `data-import/` script, as README goal 10 asks.
- Add `{base64url(n)}` for JWT shapes. - Add `{base64url(n)}` for JWT shapes.
- Add a `unix` layout to `date()` once something needs it. - Add a `unix` layout to `date()` once something needs it.
- Name a spelling that works when a row selector misses: `misc.protocol[tcp]`,
`misc.httpmethod[get]` and `misc.territory[se]` all answer "no row … has key or name"
and stop there, where a case-insensitive match could name the row that exists, and a
table with no `name` column could say it selects by key alone.
### Open questions to settle ### Open questions to settle
- Decide whether `misc.browser` becomes a parent of `misc.useragent`, so - Decide whether `misc.browser` becomes a parent of `misc.useragent`, so