Skip to content

chore: clean code - #90

Merged
j03-dev merged 6 commits into
mainfrom
chore/clean_dode
Aug 3, 2026
Merged

chore: clean code#90
j03-dev merged 6 commits into
mainfrom
chore/clean_dode

Conversation

@j03-dev

@j03-dev j03-dev commented Aug 3, 2026

Copy link
Copy Markdown
Owner
  • chore: first try
  • chore: pass middleware instead of router
  • chore: make test sync
  • chore: slice middlewares
  • chore: remove transmute in find method
  • chore: improve code as possible

Summary by CodeRabbit

  • Bug Fixes

    • Improved request processing and middleware execution.
    • Ensured handler errors are returned as responses.
    • Improved fallback behavior by returning an internal server error when request delivery fails.
  • Refactor

    • Simplified routing, request context handling, and middleware management.
    • Removed unnecessary asynchronous setup from the sample application.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR refactors request processing around shared Context state, route-owned middleware, and response channels. It updates MiddlewareChain, router middleware storage, route dispatch, error responses, and the test application startup mode.

Changes

Request dispatch refactor

Layer / File(s) Summary
Middleware storage and execution contracts
src/routing.rs, src/middleware.rs
Router.middlewares now uses optional shared immutable storage. MiddlewareChain accepts middleware slices through static execution methods. Router::find no longer uses transmute.
Shared server context and request construction
src/lib.rs, src/request.rs
RequestContext is renamed to Context. run_server creates listener, shutdown, request-channel, and context state. Request construction uses the chained builder flow.
Route dispatch and response delivery
src/lib.rs, src/request.rs, tests/app.py
Matched routes pass middleware into ProcessRequest. Handler errors become responses before wrapper application and sending. Dispatch or response-channel failures return 500 Internal Server Error. The test application starts synchronously.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConnectionHandler
  participant process_requests
  participant MiddlewareChain
  participant PythonHandler
  participant ResponseSender
  ConnectionHandler->>process_requests: submit ProcessRequest
  process_requests->>MiddlewareChain: execute route middleware
  MiddlewareChain->>PythonHandler: invoke wrapped route handler
  PythonHandler-->>process_requests: return handler result or error
  process_requests->>ResponseSender: send converted response
Loading

Possibly related PRs

  • j03-dev/oxapy#84: Refactors the same request-processing and middleware dispatch paths.
  • j03-dev/oxapy#85: Overlaps in ProcessRequest, RequestContext, and request-dispatch changes.
  • j03-dev/oxapy#87: Continues the middleware, routing, and MiddlewareChain refactor.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title indicates code cleanup but does not identify the main changes, such as middleware handling, synchronous tests, or removal of transmute. Use a specific title that identifies the primary cleanup, such as middleware refactoring and removal of transmute from route lookup.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/clean_dode

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
tests/app.py (1)

9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a return type hint to main.

-def main():
+def main() -> None:

As per path instructions: "Use type hints for function signatures in Python tests and handlers".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/app.py` at line 9, Add the appropriate return type hint to the `main`
function signature in `tests/app.py`, following the project’s existing Python
test and handler typing conventions.

Source: Path instructions

src/request.rs (1)

269-269: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Size the response channel for a single message.

Each request allocates an mpsc channel with ctx.channel_capacity slots. Exactly one Response is ever sent on it. Use tokio::sync::oneshot instead, or pass capacity 1. This removes a per-request allocation proportional to channel_capacity.

Also applies to: 288-288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/request.rs` at line 269, Update the response channel creation in the
request handling paths around response_sender and the corresponding second
occurrence to use a single-message channel: prefer tokio::sync::oneshot when the
sender/receiver API is compatible, otherwise set the mpsc capacity to 1 instead
of ctx.channel_capacity. Preserve the existing one-response send and receive
behavior.
src/middleware.rs (1)

20-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add doc comments to the public MiddlewareChain::execute API.

MiddlewareChain and execute are public and now expose a new signature. Add /// documentation with Args, Returns, and Example sections.

As per coding guidelines: "Use doc comments /// for public APIs in Rust" and "Include Args, Returns, and Example sections in Rust docstrings for public APIs".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/middleware.rs` around lines 20 - 37, Add Rust /// documentation to the
public MiddlewareChain type and its execute method, including Args, Returns, and
Example sections that describe the middleware execution API and its PyResult
return value. Keep the implementation unchanged and document the exposed
parameters, route handler, arguments, and keyword arguments.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib.rs`:
- Around line 505-512: Handle the `RequestBuilder::build()` error in the request
processing chain instead of calling `.unwrap()`, returning a 500 response when
body collection or multipart parsing fails. Preserve the existing
`.process(ctx).await` flow for successfully built requests.
- Around line 521-539: Update process_requests so each received ProcessRequest
is handled in a spawned task rather than awaiting call_python_handler inline,
allowing the receiver loop to continue accepting requests concurrently. Move
response construction and response_sender.send into the per-request task, while
preserving shutdown handling and existing error-to-Response conversion.

In `@src/request.rs`:
- Around line 263-282: Update handle_found_route to stop transmuting
MatchRoute<'_> into MatchRoute<'static> before constructing ProcessRequest.
Convert the route parameters into owned strings and retain only owned/shared
route metadata required by call_python_handler, such as the handler and route
sequence. Adjust ProcessRequest and downstream consumers to use these owned
values, eliminating borrowed route data from the queued request.

---

Nitpick comments:
In `@src/middleware.rs`:
- Around line 20-37: Add Rust /// documentation to the public MiddlewareChain
type and its execute method, including Args, Returns, and Example sections that
describe the middleware execution API and its PyResult return value. Keep the
implementation unchanged and document the exposed parameters, route handler,
arguments, and keyword arguments.

In `@src/request.rs`:
- Line 269: Update the response channel creation in the request handling paths
around response_sender and the corresponding second occurrence to use a
single-message channel: prefer tokio::sync::oneshot when the sender/receiver API
is compatible, otherwise set the mpsc capacity to 1 instead of
ctx.channel_capacity. Preserve the existing one-response send and receive
behavior.

In `@tests/app.py`:
- Line 9: Add the appropriate return type hint to the `main` function signature
in `tests/app.py`, following the project’s existing Python test and handler
typing conventions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 799de851-da10-436b-ac6e-6c62a4dd1c0e

📥 Commits

Reviewing files that changed from the base of the PR and between cb54f93 and 14cca46.

📒 Files selected for processing (5)
  • src/lib.rs
  • src/middleware.rs
  • src/request.rs
  • src/routing.rs
  • tests/app.py

Comment thread src/lib.rs
Comment on lines +505 to +512
RequestBuilder::new(req)
.with_app_data(&ctx.app_data)
.with_template(&ctx.template)
.build()
.await
.unwrap();
request.process(ctx).await
.unwrap()
.process(ctx)
.await

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Remove the .unwrap() on RequestBuilder::build(); a remote client can panic the connection task.

build() returns PyResult<Request> and fails on conditions that a client controls. src/request.rs lines 350-381 show two failure paths: self.req.collect().await.into_py_exception()? fails when the body read fails, for example on an aborted upload, and parse_multipart(...).into_py_exception()? fails on a malformed multipart/form-data body. Each failure now panics inside the hyper service task instead of producing a response.

Return a 500 response for the error case.

🛡️ Proposed fix
                 hyper::service::service_fn(move |req| {
                     let ctx = ctx.clone();
                     async move {
-                        RequestBuilder::new(req)
+                        let request = RequestBuilder::new(req)
                             .with_app_data(&ctx.app_data)
                             .with_template(&ctx.template)
                             .build()
-                            .await
-                            .unwrap()
-                            .process(ctx)
-                            .await
+                            .await;
+                        match request {
+                            Ok(request) => request.process(ctx).await,
+                            Err(_) => Response::from(Status::INTERNAL_SERVER_ERROR).try_into(),
+                        }
                     }
                 }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RequestBuilder::new(req)
.with_app_data(&ctx.app_data)
.with_template(&ctx.template)
.build()
.await
.unwrap();
request.process(ctx).await
.unwrap()
.process(ctx)
.await
let request = RequestBuilder::new(req)
.with_app_data(&ctx.app_data)
.with_template(&ctx.template)
.build()
.await;
match request {
Ok(request) => request.process(ctx).await,
Err(_) => Response::from(Status::INTERNAL_SERVER_ERROR).try_into(),
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib.rs` around lines 505 - 512, Handle the `RequestBuilder::build()`
error in the request processing chain instead of calling `.unwrap()`, returning
a 500 response when body collection or multipart parsing fails. Preserve the
existing `.process(ctx).await` flow for successfully built requests.

Comment thread src/lib.rs
Comment on lines 521 to 539
async fn process_requests(
&self,
mut shutdown: ShutDownSignal,
mut rx: Receiver<ProcessRequest>,
mut request_receiver: Receiver<ProcessRequest>,
) -> PyResult<()> {
loop {
tokio::select! {
Some(req) = rx.recv() => self.handle_request(req).await?,
Some(pr) = request_receiver.recv() => {
let response = call_python_handler(&pr.middlewares, &pr.match_route, &pr.request, self.is_async)
.await
.unwrap_or_else(Response::from)
.call_wrapper(&pr);
let _ = pr.response_sender.send(response).await;
},
_ = shutdown.wait() => break,
}
}
Ok(())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the previous dispatch loop and any task spawning around request processing.
rg -n -C6 'process_requests|tokio::spawn|call_python_handler' src/lib.rs
git log --oneline -5 -- src/lib.rs

Repository: j03-dev/oxapy

Length of output: 2983


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== struct and relevant fields =="
rg -n -C4 'struct .*|is_async|max_connections|channel_capacity|request_sender|ProcessRequest|send\(request' src/lib.rs

echo
echo "== process_requests surrounding code =="
sed -n '430,545p' src/lib.rs | nl -ba -v430

echo
echo "== call_python_handler implementation =="
sed -n '542,620p' src/lib.rs | nl -ba -v542

Repository: j03-dev/oxapy

Length of output: 6546


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== call_python_handler implementation =="
sed -n '542,585p' src/lib.rs

echo
echo "== request sending locations =="
rg -n -C5 'request_sender|ProcessRequest|response_receiver|recv\\(' src/lib.rs

echo
echo "== deterministic model: one process_requests task awaiting Python handler keeps loop at one in-flight request =="
python3 - <<'PY'
events = []
pr_channel = []
permitted_connections = 3
process_requests_in_flight = 0

def event(s): events.append(s)
def spawn_connection_accept():
    pr_channel.append('req')
    event("accept_and_send_request")

def process_request_batch():
    global process_requests_in_flight
    if pr_channel:
        pr_channel.pop(0)
        event("process_requests_recieve_and_await_handler_started")
        process_requests_in_flight += 1
    # one await point; while handler awaits, no next recv occurs.
    event("handler_awaits_returns")
    process_requests_in_flight -= 1
    event("process_requests_responds_recieve_next_request")

spawn_connection_accept()
spawn_connection_accept()
spawn_connection_accept()
process_request_batch()
process_request_batch()
process_request_batch()

print("requests_sent:", len([e for e in events if e == "accept_and_send_request"]))
print("requests_in_channel:", len(pr_channel))
print("process_requests_in_flight:", process_requests_in_flight)
print("events:", events)
PY

Repository: j03-dev/oxapy

Length of output: 1770


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== request sending and response waiting locations =="
rg -n -C5 'request_sender|ProcessRequest|response_receiver|recv\\(' src/lib.rs || true

echo
echo "== pyo3 detach helpers in file =="
rg -n -C4 'py.detach|block_on|future_into_py|Python::attach|into_future|allow_threads' src/lib.rs

echo
echo "== imports / no_std context hints =="
sed -n '1,80p' src/lib.rs

Repository: j03-dev/oxapy

Length of output: 4602


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all ProcessRequest/request_sender usages =="
grep -R -n "ProcessRequest\|request_sender" src || true

echo
echo "== HttpServer request handlers and channel buffering =="

# print matching context around likely request entry points and Channel/Sender fields, line by line
python3 - <<'PY'
from pathlib import Path
p = Path('src/lib.rs')
text = p.read_text().splitlines()
targets = ['request_sender', 'ProcessRequest', 'ctx.request_sender.send', 'response_receiver', 'recv(']
seen = set()
for i,l in enumerate(text, start=1):
    if any(t in l for t in targets):
        start=max(1,i-20); end=min(len(text),i+35)
        key=(start,end)
        if key in seen: continue
        seen.add(key)
        print(f"--- src/lib.rs:{i} ---")
        for n in range(start,end+1):
            print(f"{n:4}: {text[n-1]}")
PY

Repository: j03-dev/oxapy

Length of output: 16060


Spawn each request instead of awaiting the Python handler inline.

process_requests receives from the channel, then awaits call_python_handler(...) before receiving the next request. This makes channel_capacity only a queue limit and caps in-flight request concurrency to 1 for async handlers, while I/O-bound handlers block the only consumer. Spawn per-ProcessRequest, then await its response only after sending it or with per-request capacity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib.rs` around lines 521 - 539, Update process_requests so each received
ProcessRequest is handled in a spawned task rather than awaiting
call_python_handler inline, allowing the receiver loop to continue accepting
requests concurrently. Move response construction and response_sender.send into
the per-request task, while preserving shutdown handling and existing
error-to-Response conversion.

Comment thread src/request.rs
Comment on lines +263 to 282
async fn handle_found_route(
&self,
ctx: &RequestContext,
router: Arc<Router>,
ctx: &Context,
match_route: MatchRoute<'_>,
middlewares: Option<Arc<[Middleware]>>,
) -> Result<hyper::Response<Body>, hyper::http::Error> {
let (tx, rx) = tokio::sync::mpsc::channel(ctx.channel_capacity);
let (response_sender, response_receiver) = tokio::sync::mpsc::channel(ctx.channel_capacity);

let transmutate_route: MatchRoute<'static> = unsafe { std::mem::transmute(match_route) };

let process_request = ProcessRequest {
request: Arc::new(self.clone()),
router: Some(router),
match_route: Some(transmutate_route),
tx,
middlewares,
request: Arc::new(self.clone()),
response_sender,
wrapper: ctx.wrapper.clone(),
};

Self::send_and_wait_response(ctx, process_request, rx).await
Self::send_and_wait_response(&ctx.request_sender, process_request, response_receiver).await
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm which fields of match_route the dispatch loop reads, and how params are consumed.
rg -n -C6 'match_route|build_route_params' src/lib.rs
rg -n -C4 'pub type MatchRoute|struct Route\b' src/routing.rs

Repository: j03-dev/oxapy

Length of output: 2846


🏁 Script executed:

#!/bin/bash
set -e

echo "== candidate files around Request and routing fields =="
wc -l src/request.rs src/routing.rs src/lib.rs
echo

echo "== src/request.rs relevant sections =="
sed -n '220,315p' src/request.rs | cat -n
echo

echo "== src/routing.rs Route definition and matches =="
sed -n '1,80p' src/routing.rs | cat -n
echo

echo "== src/lib.rs ProcessRequest/send/response handling =="
sed -n '35,70p' src/lib.rs | cat -n
sed -n '460,545p' src/lib.rs | cat -n

Repository: j03-dev/oxapy

Length of output: 11553


🏁 Script executed:

#!/bin/bash
set -e

echo "== RequestBuilder URI handling =="
rg -n -C4 'struct RequestBuilder|impl RequestBuilder|uri|method|find\(' src/request.rs src/routing.rs src/lib.rs

echo "== matchit::Match fields/params usage =="
rg -n 'matchit::Match|match_route\.params|match_route\.value|params\.iter' src/*.rs

Repository: j03-dev/oxapy

Length of output: 14534


🌐 Web query:

matchit::Match params lifetime value borrowed from input documentation

💡 Result:

In the matchit crate, the Match<'k, 'v, V> struct and its associated Params<'k, 'v> structure utilize lifetime parameters to represent that the URL parameters (keys and values) are borrowed from the input path string used during the routing process [1][2][3]. Specifically: - The 'k lifetime represents the lifetime of the parameter keys [3]. - The 'v lifetime represents the lifetime of the parameter values [3]. - When a Router::at call is performed, the resulting Match object contains a Params instance [2]. These parameters are slices (&'v str when accessed via get) that point directly into the input path string [1][3]. Because these parameters are references to the input path, the Match object cannot outlive the input string from which the path was parsed [4]. This design allows matchit to be a zero-copy router, avoiding allocations during the matching process by simply referencing portions of the input [3][5]. If you need to store the parameters independently of the input path, you must convert them to owned types (e.g., String) or use an alternative storage mechanism, as the borrowed references are strictly bound to the lifetime of the input [4].

Citations:


Avoid storing borrowed route matches in ProcessRequest.

MatchRoute borrows self.uri.path() as route params and ctx.routers entries from Router::find; handle_found_route erases both lifetimes at src/request.rs:271 and sends the value into the global request queue. If Hyper cancels process because the connection drops, the service future is dropped while rx.recv().await is still running. A queued ProcessRequest can then survive and call_python_handler later reads params.iter(), which is invalid if the borrowed input lifetimes have ended. Remove the transmute and store only owned data needed by Python: path parameters as owned strings, plus shared route metadata such as the handler and route sequence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/request.rs` around lines 263 - 282, Update handle_found_route to stop
transmuting MatchRoute<'_> into MatchRoute<'static> before constructing
ProcessRequest. Convert the route parameters into owned strings and retain only
owned/shared route metadata required by call_python_handler, such as the handler
and route sequence. Adjust ProcessRequest and downstream consumers to use these
owned values, eliminating borrowed route data from the queued request.

@j03-dev
j03-dev merged commit 708ba6d into main Aug 3, 2026
17 checks passed
@j03-dev
j03-dev deleted the chore/clean_dode branch August 3, 2026 16:49
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