From 3bfe4571d397f251642aad380a250029af1c9e29 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Thu, 1 Oct 2026 23:24:46 -0400 Subject: [PATCH] Add status-codes constants Use the constants cli from componentized/constants to generate a component containing named constants for each HTTP status code. This also sets up support for adding any other additional constants components in the future. Additionally, wit/deps resolutions are now implicitly discovered and the relationships between wit directories is resolved correctly. Signed-off-by: Scott Andrews --- Makefile | 49 ++- README.md | 1 + components/status-codes/README.md | 3 + .../wit/status-codes.constants.wit | 5 + components/status-codes/wkg.lock | 4 + components/status-codes/wkg.toml | 2 + .../deps/wasi-logging-0.1.0-draft/package.wit | 36 -- tools/Cargo.toml | 1 + wit/{http.wit => client.wit} | 0 wit/status-codes.wit | 314 ++++++++++++++++++ 10 files changed, 366 insertions(+), 49 deletions(-) create mode 100644 components/status-codes/README.md create mode 100644 components/status-codes/wit/status-codes.constants.wit create mode 100644 components/status-codes/wkg.lock create mode 100644 components/status-codes/wkg.toml delete mode 100644 components/wit/deps/wasi-logging-0.1.0-draft/package.wit rename wit/{http.wit => client.wit} (100%) create mode 100644 wit/status-codes.wit diff --git a/Makefile b/Makefile index 2ce997e..e6c8d61 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,5 @@ SHELL := /bin/bash -# prefer paths are relative to the root of the repository, where make runs, to keep the logs readable - -WKG_CONFIG := .config/wasm-pkg/config.toml - export RUST_BACKTRACE ?= 1 export WASMTIME_BACKTRACE_DETAILS ?= 1 @@ -15,8 +11,14 @@ export PATH := $(abspath $(TOOLS_DIR))/bin:$(PATH) # cargo binstall downloads prebuilt binaries, without it the tools are built with cargo install CARGO_INSTALL := $(if $(shell command -v cargo-binstall 2> /dev/null),cargo binstall --no-confirm --disable-telemetry,cargo install) -COMPONENTS = $(sort $(notdir $(patsubst %/,%,$(dir $(wildcard $(addprefix components/*/,*.properties *.wac *.wkg Cargo.toml)))))) -TOOLS := static-config wac-cli wasm-tools wkg +COMPONENTS = $(sort $(foreach file,$(wildcard $(addprefix components/*/,wit/*.constants.wit *.properties *.wac *.wkg Cargo.toml)),$(word 2,$(subst /, ,$(file))))) +TOOLS := componentized-constants-cli static-config wac-cli wasm-tools wkg + +export WKG_CONFIG_FILE := $(abspath .config/wasm-pkg/config.toml) + +# a path relative to the root of the repository, e.g. `wit` for `components/../wit` +relpath = $(if $(filter $(CURDIR),$(abspath $(1))),.,$(patsubst $(CURDIR)/%,%,$(abspath $(1)))) + .PHONY: all all: components @@ -31,7 +33,7 @@ clean-components: clean-wit .PHONY: clean-wit ## Remove the fetched wit dependencies, fetched again by `make wit` clean-wit: - rm -rf wit/deps components/wit/deps components/*/wit/deps + rm -rf $(WIT_DEPS) .PHONY: test test: components @@ -68,7 +70,15 @@ define BUILD_COMPONENT .PHONY: components/$1 components/$1: ${COMPONENTS_DIR}/$1/$1.wasm ${COMPONENTS_DIR}/$1/$1.debug.wasm -ifneq ($(wildcard components/$1/$1.properties),) +ifneq ($(wildcard components/$1/wit/$1.constants.wit),) + +${COMPONENTS_DIR}/$1/$1.wasm: components/$1/wit/deps ${COMPONENTS_DIR}/$1/README.md | $(call tool,componentized-constants-cli) + constants --wit components/$1/wit -o ${COMPONENTS_DIR}/$1/$1.wasm + +${COMPONENTS_DIR}/$1/$1.debug.wasm: components/$1/wit/deps ${COMPONENTS_DIR}/$1/README.md | $(call tool,componentized-constants-cli) + constants --wit components/$1/wit -o ${COMPONENTS_DIR}/$1/$1.debug.wasm + +else ifneq ($(wildcard components/$1/$1.properties),) ${COMPONENTS_DIR}/$1/$1.wasm: components/$1/$1.properties ${COMPONENTS_DIR}/$1/README.md | $(call tool,static-config) static-config -f components/$1/$1.properties -o ${COMPONENTS_DIR}/$1/$1.wasm @@ -121,14 +131,27 @@ ${COMPONENTS_DIR}/interface.wasm: wit/deps README.md | $(call tool,wkg) wkg build -o ${COMPONENTS_DIR}/interface.wasm @cp README.md ${COMPONENTS_DIR}/README.md +# directories with a wkg.toml, each fetches the dependencies of its wit directory into wit/deps, +# e.g. `.` and `components` +WKG_DIRS := $(sort $(patsubst ./%,%,$(patsubst %/,%,$(dir $(shell find . -name wkg.toml -not -path './target/*' -not -path '*/deps/*'))))) + +# the wit/deps directory of a directory with a wkg.toml, e.g. `wit/deps` for `.` +wit_deps = $(patsubst ./%,%,$(1)/wit/deps) + +WIT_DEPS := $(foreach dir,$(WKG_DIRS),$(call wit_deps,$(dir))) + .PHONY: wit -wit: wit/deps components/wit/deps +wit: $(WIT_DEPS) -wit/deps: wkg.toml $(shell find wit -type f -name "*.wit" -not -path "deps") | $(call tool,wkg) - wkg fetch --config $(WKG_CONFIG) +define FETCH_WIT + +# a package overridden with a local path, e.g. `{ path = "../wit" }`, has its dependencies fetched first +$(call wit_deps,$1): $1/wkg.toml $(shell find $1/wit -type f -name "*.wit" -not -path "*/deps/*") $(foreach path,$(shell sed -n 's/.*path *= *"\(.*\)".*/\1/p' $1/wkg.toml),$(call relpath,$1/$(path))/deps) | $(call tool,wkg) + $(if $(filter .,$1),,cd $1 && )wkg fetch + +endef -components/wit/deps: wit/deps components/wkg.toml $(shell find components/wit -type f -name "*.wit" -not -path "deps") | $(call tool,wkg) - ( cd components && wkg fetch --config ../$(WKG_CONFIG) ) +$(foreach dir,$(WKG_DIRS),$(eval $(call FETCH_WIT,$(dir)))) # sign published components with cosign, `SIGN=false` to push without signing, e.g. to a local registry SIGN ?= true diff --git a/README.md b/README.md index 9abb05d..fed7629 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ A collection of utility components that remix wasi:http types and interfaces. ## Components - [`client`](./components/client/) +- [`status-codes`](./components/status-codes/) - [`trace`](./components/trace/) - [`trace-client`](./components/trace-client/) - [`trace-handler`](./components/trace-handler/) diff --git a/components/status-codes/README.md b/components/status-codes/README.md new file mode 100644 index 0000000..253e771 --- /dev/null +++ b/components/status-codes/README.md @@ -0,0 +1,3 @@ +# `status-codes` + +HTTP status code constants. diff --git a/components/status-codes/wit/status-codes.constants.wit b/components/status-codes/wit/status-codes.constants.wit new file mode 100644 index 0000000..79fa109 --- /dev/null +++ b/components/status-codes/wit/status-codes.constants.wit @@ -0,0 +1,5 @@ +package componentized:http-component; + +world status-codes { + export componentized:http/status-codes@0.1.0-dev; +} diff --git a/components/status-codes/wkg.lock b/components/status-codes/wkg.lock new file mode 100644 index 0000000..2ad4aa8 --- /dev/null +++ b/components/status-codes/wkg.lock @@ -0,0 +1,4 @@ +# This file is automatically generated. +# It is not intended for manual editing. +version = 1 +packages = [] diff --git a/components/status-codes/wkg.toml b/components/status-codes/wkg.toml new file mode 100644 index 0000000..e6b084a --- /dev/null +++ b/components/status-codes/wkg.toml @@ -0,0 +1,2 @@ +[overrides] +"componentized:http" = { path = "../../wit" } diff --git a/components/wit/deps/wasi-logging-0.1.0-draft/package.wit b/components/wit/deps/wasi-logging-0.1.0-draft/package.wit deleted file mode 100644 index 164cb5b..0000000 --- a/components/wit/deps/wasi-logging-0.1.0-draft/package.wit +++ /dev/null @@ -1,36 +0,0 @@ -package wasi:logging@0.1.0-draft; - -/// WASI Logging is a logging API intended to let users emit log messages with -/// simple priority levels and context values. -interface logging { - /// A log level, describing a kind of message. - enum level { - /// Describes messages about the values of variables and the flow of - /// control within a program. - trace, - /// Describes messages likely to be of interest to someone debugging a - /// program. - debug, - /// Describes messages likely to be of interest to someone monitoring a - /// program. - info, - /// Describes messages indicating hazardous situations. - warn, - /// Describes messages indicating serious errors. - error, - /// Describes messages indicating fatal errors. - critical, - } - - /// Emit a log message. - /// - /// A log message has a `level` describing what kind of message is being - /// sent, a context, which is an uninterpreted string meant to help - /// consumers group similar messages, and a string containing the message - /// text. - log: func(level: level, context: string, message: string); -} - -world imports { - import logging; -} diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 514cd37..6b91f90 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -13,6 +13,7 @@ publish = false path = "lib.rs" [dependencies] +componentized-constants-cli = "=0.1.0-dev" static-config = "=0.2.0" wac-cli = "=0.11.0" wasm-tools = "=1.259.0" diff --git a/wit/http.wit b/wit/client.wit similarity index 100% rename from wit/http.wit rename to wit/client.wit diff --git a/wit/status-codes.wit b/wit/status-codes.wit new file mode 100644 index 0000000..3b6f34f --- /dev/null +++ b/wit/status-codes.wit @@ -0,0 +1,314 @@ +/// HTTP status codes from the IANA HTTP Status Code Registry, +/// https://www.iana.org/assignments/http-status-codes/ +/// @since(version = 0.1.0-dev) +interface status-codes { + /// 100 Continue + /// [RFC9110, Section 15.2.1] + /// @value 100 + continue: func() -> u16; + + /// 101 Switching Protocols + /// [RFC9110, Section 15.2.2] + /// @value 101 + switching-protocols: func() -> u16; + + /// 102 Processing + /// [RFC2518] + /// @value 102 + processing: func() -> u16; + + /// 103 Early Hints + /// [RFC8297] + /// @value 103 + early-hints: func() -> u16; + + /// 200 OK + /// [RFC9110, Section 15.3.1] + /// @value 200 + ok: func() -> u16; + + /// 201 Created + /// [RFC9110, Section 15.3.2] + /// @value 201 + created: func() -> u16; + + /// 202 Accepted + /// [RFC9110, Section 15.3.3] + /// @value 202 + accepted: func() -> u16; + + /// 203 Non-Authoritative Information + /// [RFC9110, Section 15.3.4] + /// @value 203 + non-authoritative-information: func() -> u16; + + /// 204 No Content + /// [RFC9110, Section 15.3.5] + /// @value 204 + no-content: func() -> u16; + + /// 205 Reset Content + /// [RFC9110, Section 15.3.6] + /// @value 205 + reset-content: func() -> u16; + + /// 206 Partial Content + /// [RFC9110, Section 15.3.7] + /// @value 206 + partial-content: func() -> u16; + + /// 207 Multi-Status + /// [RFC4918] + /// @value 207 + multi-status: func() -> u16; + + /// 208 Already Reported + /// [RFC5842] + /// @value 208 + already-reported: func() -> u16; + + /// 226 IM Used + /// [RFC3229] + /// @value 226 + im-used: func() -> u16; + + /// 300 Multiple Choices + /// [RFC9110, Section 15.4.1] + /// @value 300 + multiple-choices: func() -> u16; + + /// 301 Moved Permanently + /// [RFC9110, Section 15.4.2] + /// @value 301 + moved-permanently: func() -> u16; + + /// 302 Found + /// [RFC9110, Section 15.4.3] + /// @value 302 + found: func() -> u16; + + /// 303 See Other + /// [RFC9110, Section 15.4.4] + /// @value 303 + see-other: func() -> u16; + + /// 304 Not Modified + /// [RFC9110, Section 15.4.5] + /// @value 304 + not-modified: func() -> u16; + + /// 305 Use Proxy + /// [RFC9110, Section 15.4.6] + /// @value 305 + use-proxy: func() -> u16; + + /// 307 Temporary Redirect + /// [RFC9110, Section 15.4.8] + /// @value 307 + temporary-redirect: func() -> u16; + + /// 308 Permanent Redirect + /// [RFC9110, Section 15.4.9] + /// @value 308 + permanent-redirect: func() -> u16; + + /// 400 Bad Request + /// [RFC9110, Section 15.5.1] + /// @value 400 + bad-request: func() -> u16; + + /// 401 Unauthorized + /// [RFC9110, Section 15.5.2] + /// @value 401 + unauthorized: func() -> u16; + + /// 402 Payment Required + /// [RFC9110, Section 15.5.3] + /// @value 402 + payment-required: func() -> u16; + + /// 403 Forbidden + /// [RFC9110, Section 15.5.4] + /// @value 403 + forbidden: func() -> u16; + + /// 404 Not Found + /// [RFC9110, Section 15.5.5] + /// @value 404 + not-found: func() -> u16; + + /// 405 Method Not Allowed + /// [RFC9110, Section 15.5.6] + /// @value 405 + method-not-allowed: func() -> u16; + + /// 406 Not Acceptable + /// [RFC9110, Section 15.5.7] + /// @value 406 + not-acceptable: func() -> u16; + + /// 407 Proxy Authentication Required + /// [RFC9110, Section 15.5.8] + /// @value 407 + proxy-authentication-required: func() -> u16; + + /// 408 Request Timeout + /// [RFC9110, Section 15.5.9] + /// @value 408 + request-timeout: func() -> u16; + + /// 409 Conflict + /// [RFC9110, Section 15.5.10] + /// @value 409 + conflict: func() -> u16; + + /// 410 Gone + /// [RFC9110, Section 15.5.11] + /// @value 410 + gone: func() -> u16; + + /// 411 Length Required + /// [RFC9110, Section 15.5.12] + /// @value 411 + length-required: func() -> u16; + + /// 412 Precondition Failed + /// [RFC9110, Section 15.5.13] + /// @value 412 + precondition-failed: func() -> u16; + + /// 413 Content Too Large + /// [RFC9110, Section 15.5.14] + /// @value 413 + content-too-large: func() -> u16; + + /// 414 URI Too Long + /// [RFC9110, Section 15.5.15] + /// @value 414 + uri-too-long: func() -> u16; + + /// 415 Unsupported Media Type + /// [RFC9110, Section 15.5.16] + /// @value 415 + unsupported-media-type: func() -> u16; + + /// 416 Range Not Satisfiable + /// [RFC9110, Section 15.5.17] + /// @value 416 + range-not-satisfiable: func() -> u16; + + /// 417 Expectation Failed + /// [RFC9110, Section 15.5.18] + /// @value 417 + expectation-failed: func() -> u16; + + /// 418 I'm a teapot, unused by HTTP and reserved so it isn't assigned + /// [RFC2324], [RFC9110, Section 15.5.19] + /// @value 418 + im-a-teapot: func() -> u16; + + /// 421 Misdirected Request + /// [RFC9110, Section 15.5.20] + /// @value 421 + misdirected-request: func() -> u16; + + /// 422 Unprocessable Content + /// [RFC9110, Section 15.5.21] + /// @value 422 + unprocessable-content: func() -> u16; + + /// 423 Locked + /// [RFC4918] + /// @value 423 + locked: func() -> u16; + + /// 424 Failed Dependency + /// [RFC4918] + /// @value 424 + failed-dependency: func() -> u16; + + /// 425 Too Early + /// [RFC8470] + /// @value 425 + too-early: func() -> u16; + + /// 426 Upgrade Required + /// [RFC9110, Section 15.5.22] + /// @value 426 + upgrade-required: func() -> u16; + + /// 428 Precondition Required + /// [RFC6585] + /// @value 428 + precondition-required: func() -> u16; + + /// 429 Too Many Requests + /// [RFC6585] + /// @value 429 + too-many-requests: func() -> u16; + + /// 431 Request Header Fields Too Large + /// [RFC6585] + /// @value 431 + request-header-fields-too-large: func() -> u16; + + /// 451 Unavailable For Legal Reasons + /// [RFC7725] + /// @value 451 + unavailable-for-legal-reasons: func() -> u16; + + /// 500 Internal Server Error + /// [RFC9110, Section 15.6.1] + /// @value 500 + internal-server-error: func() -> u16; + + /// 501 Not Implemented + /// [RFC9110, Section 15.6.2] + /// @value 501 + not-implemented: func() -> u16; + + /// 502 Bad Gateway + /// [RFC9110, Section 15.6.3] + /// @value 502 + bad-gateway: func() -> u16; + + /// 503 Service Unavailable + /// [RFC9110, Section 15.6.4] + /// @value 503 + service-unavailable: func() -> u16; + + /// 504 Gateway Timeout + /// [RFC9110, Section 15.6.5] + /// @value 504 + gateway-timeout: func() -> u16; + + /// 505 HTTP Version Not Supported + /// [RFC9110, Section 15.6.6] + /// @value 505 + http-version-not-supported: func() -> u16; + + /// 506 Variant Also Negotiates + /// [RFC2295] + /// @value 506 + variant-also-negotiates: func() -> u16; + + /// 507 Insufficient Storage + /// [RFC4918] + /// @value 507 + insufficient-storage: func() -> u16; + + /// 508 Loop Detected + /// [RFC5842] + /// @value 508 + loop-detected: func() -> u16; + + /// 510 Not Extended, obsoleted + /// [RFC2774], [Status change of HTTP experiments to Historic] + /// @value 510 + not-extended: func() -> u16; + + /// 511 Network Authentication Required + /// [RFC6585] + /// @value 511 + network-authentication-required: func() -> u16; +}