Skip to content

refactor!: Simplify response and module APIs and fix server behaviour - #7

Merged
Fuwn merged 40 commits into
mainfrom
refactor/api-cleanup
Sep 12, 2026
Merged

Fuwn merged 40 commits into
mainfrom
refactor/api-cleanup

Conversation

@Fuwn

@Fuwn Fuwn commented Sep 12, 2026

Copy link
Copy Markdown
Member

Windmark's router could lose registered partials when starting a server, block the runtime during asynchronous module attachment, and alter captured parameters during case-insensitive lookup. Response fields also allowed conflicting text and binary payloads, and the library handled application logging.

This change fixes those behaviours, gives responses a single explicit payload, moves module state synchronisation into module implementations, and lets applications initialise their own logger. It prepares Windmark 0.8.0 and Rossweisse 0.0.4.

Changes

  • Preserve partial registrations across cloned routers and server restarts. Bound servers retain their routes, callbacks, partials, TLS configuration, and connection limits; module registrations and scheduling remain shared.
  • Add Router::bind, Server::local_addr, controlled shutdown, and optional connection caps and stage deadlines. The existing Router::run remains available.
  • Decode complete request frames before parsing, validate request URIs and response metadata, and load complete TLS certificate chains.
  • Preserve parameter names and captured values during case-insensitive ASCII routing. Reject conflicting registrations when that mode is enabled.
  • Replace public response macros with closures and constructors, remove the response-macros and logger features, and eliminate the paste dependency.
  • Preserve Rossweisse declarations, visibility, attributes, generics, and index handler names. Report macro errors at the offending syntax.
  • Simplify response assembly and examples, move tests to top-level test modules, and check formatting, consumer compatibility, and both runtimes in CI.

Consumer Migration

Responses

Replace response macros with closures. Asynchronous handlers can return an async block. Handlers that move captured state into an asynchronous block may need to clone it for each request.

-router.mount("/", windmark::success!("Hello"));
+router.mount("/", |_| Response::success("Hello"));

Response fields are private. Use accessors to read or edit content and metadata; replace the response to change its status or switch between text and binary payloads.

-let status = response.status;
-response.mime = None;
+let status = response.status();
+*response.mime_mut() = None;
-response.status = 51;
-response.content = "Missing".into();
+response = Response::not_found("Missing");

Numeric statuses, binary constructors, and optional MIME inference remain available. Internal binary statuses 21 and 22 still map to status 20 on the wire.

Modules

Await asynchronous attachment:

-router.attach_async(module);
+router.attach_async(module).await;

Request hooks now receive &self, while attachment hooks retain &mut self. Synchronous modules require Send + Sync. Mutable state requires appropriate synchronisation, such as an atomic counter:

-requests: usize,
+requests: AtomicUsize,
-fn on_pre_route(&mut self, _: &HookContext) {
-    self.requests += 1;
+fn on_pre_route(&self, _: &HookContext) {
+    self.requests.fetch_add(1, Ordering::Relaxed);
 }

The same receiver change applies to asynchronous request hooks. Module hook phases remain exclusive across requests by default. AllowConcurrentModules permits overlapping invocations of the same module; exhaustive matches on RouterOption must handle this new variant. A poisoned synchronous module no longer disables unrelated modules.

Logging

Remove the logger feature and configure a backend for the application. For example, add pretty_env_logger as an application dependency:

-router.enable_default_logger(true);
-router.set_log_level(log::LevelFilter::Info);
+pretty_env_logger::formatted_builder()
+    .parse_filters("windmark=info")
+    .try_init()?;

Rossweisse

Call an index handler by its declared name rather than the generated __router_index:

-Capsule::__router_index(context);
+Capsule::index(context);

An ordinary route named __router_index now mounts at /__router_index. Duplicate or unknown field initialisers, reserved names, and unsupported route arguments generate diagnostics. Previously discarded attributes now take effect.

Behavioural Compatibility

  • Malformed request URIs are rejected before normalisation. Invalid response statuses or metadata result in a temporary failure. Multiple language tags are quoted, and empty error headers omit the space.
  • Text responses use LF line endings, and non-empty footers end with a newline. Binary payloads are unchanged.
  • File-based credential setters replace earlier inline credentials. An explicitly supplied TLS acceptor retains precedence.
  • Stopping a server drains connections for the configured period, which defaults to zero, and cancels unfinished tasks. Dropping the running server future also cancels its connection tasks. Connection caps and stage deadlines remain opt-in.

Validation

The test and Clippy matrix passed on macOS with Rust 1.95.0, using both the existing lockfiles and fresh dependency resolution:

  • Default configuration: 90 tests passed.
  • Tokio with MIME inference: 91 tests passed.
  • Async-std with MIME inference: 91 tests passed.
  • Strict Clippy checks passed for all targets across all three configurations.

Workspace and consumer formatting and just checkfc passed using nightly. Documentation built with warnings denied using Rust 1.95.0.

After the version updates, all 90 default-configuration tests passed again, and Cargo packaged and verified both release versions. Linux CI also passed formatting and the full test and Clippy matrix for both the push and pull request runs. Neither crate has been published.

Fuwn added 30 commits June 13, 2026 14:13
Avoid entering and blocking a runtime from synchronous router configuration.
BREAKING CHANGE: Conflicting routes now cause a panic when mounting routes
or enabling case-insensitive matching.
Snapshot shared partial handlers instead of draining their registrations,
so starting one router does not remove partials from its clones or later runs.
BREAKING CHANGE: Different modules may execute concurrently across requests.
A poisoned synchronous module no longer disables other modules.
BREAKING CHANGE: RouterOption gains AllowConcurrentModules, so exhaustive
matches must handle the new variant.
BREAKING CHANGE: Callers of the generated __router_index method must use
the declared handler name. An ordinary route explicitly named
__router_index now mounts at /__router_index rather than /.
BREAKING CHANGE: Invalid or duplicate field initialisers and reserved
names now produce errors.
BREAKING CHANGE: File credential setters replace earlier inline values.
Explicit custom TLS acceptors retain precedence over credentials.
BREAKING CHANGE: Invalid response statuses or metadata produce a temporary
failure. Multiple language tags are quoted, and empty errors omit the space.
BREAKING CHANGE: Stopping or dropping the server cancels unfinished
connection tasks after the configured drain period, which defaults to
zero. Admission limits and stage deadlines remain opt-in.
@Fuwn
Fuwn merged commit bce4467 into main Sep 12, 2026
12 checks passed
@Fuwn
Fuwn deleted the refactor/api-cleanup branch September 12, 2026 12:18
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