oxyroute 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oxyroute-0.1.0/.cursor/rules/git-workflow.mdc +26 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/PRIORITIES.md +41 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/README.md +67 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/01.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/02.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/03.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/04.md +19 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/05.md +17 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/06.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/07.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/08.md +17 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/09.md +16 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/10.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/11.md +19 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/12.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/13.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/14.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/15.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/16.md +11 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/17.md +11 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/18.md +17 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/19.md +15 -0
- oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/20.md +11 -0
- oxyroute-0.1.0/.github/ISSUE_TEMPLATE/1_feature_request.yml +29 -0
- oxyroute-0.1.0/.github/ISSUE_TEMPLATE/2_bug_report.yml +39 -0
- oxyroute-0.1.0/.github/ISSUE_TEMPLATE/config.yml +12 -0
- oxyroute-0.1.0/.github/pull_request_template.md +18 -0
- oxyroute-0.1.0/.github/workflows/ci.yml +75 -0
- oxyroute-0.1.0/.github/workflows/release-pypi.yml +135 -0
- oxyroute-0.1.0/.gitignore +56 -0
- oxyroute-0.1.0/CONTRIBUTING.md +63 -0
- oxyroute-0.1.0/Cargo.lock +824 -0
- oxyroute-0.1.0/Cargo.toml +35 -0
- oxyroute-0.1.0/LICENSE +21 -0
- oxyroute-0.1.0/PKG-INFO +133 -0
- oxyroute-0.1.0/README.md +96 -0
- oxyroute-0.1.0/docs/asgi.md +28 -0
- oxyroute-0.1.0/docs/dependencies.md +52 -0
- oxyroute-0.1.0/docs/development-workflow.md +53 -0
- oxyroute-0.1.0/docs/development.md +69 -0
- oxyroute-0.1.0/docs/handlers.md +60 -0
- oxyroute-0.1.0/docs/index.md +52 -0
- oxyroute-0.1.0/docs/installation.md +55 -0
- oxyroute-0.1.0/docs/jwt.md +48 -0
- oxyroute-0.1.0/docs/openapi.md +37 -0
- oxyroute-0.1.0/docs/routing.md +31 -0
- oxyroute-0.1.0/docs/rsgi.md +61 -0
- oxyroute-0.1.0/examples/rsgi_app.py +21 -0
- oxyroute-0.1.0/examples/rsgi_lifespan_app.py +55 -0
- oxyroute-0.1.0/oxyroute/__init__.py +8 -0
- oxyroute-0.1.0/oxyroute/app.py +308 -0
- oxyroute-0.1.0/oxyroute/asgi.py +227 -0
- oxyroute-0.1.0/oxyroute/response.py +24 -0
- oxyroute-0.1.0/pyproject.toml +72 -0
- oxyroute-0.1.0/scripts/create-github-issues.sh +65 -0
- oxyroute-0.1.0/src/dispatch.rs +711 -0
- oxyroute-0.1.0/src/lib.rs +355 -0
- oxyroute-0.1.0/src/params.rs +158 -0
- oxyroute-0.1.0/src/response.rs +171 -0
- oxyroute-0.1.0/src/schema.rs +11 -0
- oxyroute-0.1.0/src/state.rs +141 -0
- oxyroute-0.1.0/src/token.rs +211 -0
- oxyroute-0.1.0/tests/fixtures/rsa/private_pkcs8.pem +28 -0
- oxyroute-0.1.0/tests/fixtures/rsa/public_pkcs8.pem +9 -0
- oxyroute-0.1.0/tests/test_405.py +61 -0
- oxyroute-0.1.0/tests/test_asgi.py +65 -0
- oxyroute-0.1.0/tests/test_dep_chain.py +74 -0
- oxyroute-0.1.0/tests/test_granian_e2e.py +163 -0
- oxyroute-0.1.0/tests/test_handler_errors_500.py +77 -0
- oxyroute-0.1.0/tests/test_head_options.py +94 -0
- oxyroute-0.1.0/tests/test_jwt_cookie.py +93 -0
- oxyroute-0.1.0/tests/test_jwt_parity.py +30 -0
- oxyroute-0.1.0/tests/test_jwt_route_iss_aud.py +99 -0
- oxyroute-0.1.0/tests/test_jwt_rs256.py +55 -0
- oxyroute-0.1.0/tests/test_middleware.py +44 -0
- oxyroute-0.1.0/tests/test_openapi.py +148 -0
- oxyroute-0.1.0/tests/test_oxyjwt_docs.py +27 -0
- oxyroute-0.1.0/tests/test_query_decode.py +25 -0
- oxyroute-0.1.0/tests/test_rsgi_lifespan.py +31 -0
- oxyroute-0.1.0/uv.lock +720 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Feature branches and PRs for GitHub issues — never land feature work by committing straight to dev/main
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git and GitHub workflow (OxyRoute)
|
|
7
|
+
|
|
8
|
+
When implementing a **feature, fix, or issue** (including backlog / issue numbers in `.github/ISSUE_BACKLOG`):
|
|
9
|
+
|
|
10
|
+
1. **Branch**
|
|
11
|
+
- Do **not** commit or push feature work directly to `dev` or `main`.
|
|
12
|
+
- Create a branch from the current base (usually `dev`): `git fetch origin` then `git checkout -b <branch>`.
|
|
13
|
+
- Prefer names like: `feat/<short-slug>`, or `issue-<N>-<short-slug>` (e.g. `issue-20-head-options`).
|
|
14
|
+
|
|
15
|
+
2. **Pull request**
|
|
16
|
+
- Push the branch: `git push -u origin <branch>`.
|
|
17
|
+
- Open a **PR** into `dev` (or the repo’s default integration branch).
|
|
18
|
+
- In the PR title or description, **link the issue**: `Closes #N` / `Fixes #N` / `Ref #N` as appropriate so GitHub closes or tracks the work.
|
|
19
|
+
|
|
20
|
+
3. **Before coding**
|
|
21
|
+
- Confirm whether an issue already exists; if the user only gave a topic, match it to a backlog file or open an issue, then use the same branch+PR flow.
|
|
22
|
+
|
|
23
|
+
4. **Exceptions**
|
|
24
|
+
- Tiny doc typo or rule-only changes may still use a small branch+PR, or a single “chore:” PR if the team agrees — default is **always branch + PR** for any code or test change.
|
|
25
|
+
|
|
26
|
+
This applies to all agents and humans working in this repo.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Priority and sequencing (OxyRoute backlog)
|
|
2
|
+
|
|
3
|
+
This file tracks **priority tiers** for the 20 items in [bodies/](bodies/); it does not replace the GitHub milestone—use it when triaging and when batch-creating issues.
|
|
4
|
+
|
|
5
|
+
## P0 (do first: correctness, safety, and coverage)
|
|
6
|
+
|
|
7
|
+
| # | File | Rationale |
|
|
8
|
+
|---|------|-----------|
|
|
9
|
+
| 2 | [02.md](bodies/02.md) | **Query decoding** — many clients break without percent-decoding. |
|
|
10
|
+
| 3 | [03.md](bodies/03.md) | **500 and logging** — unhandled `PyErr` and unclear production behavior. |
|
|
11
|
+
| 12 | [12.md](bodies/12.md) | **E2E Granian** — the main integration path (RSGI) is not validated with a real server in CI. |
|
|
12
|
+
|
|
13
|
+
## P1 (next: API and responses)
|
|
14
|
+
|
|
15
|
+
| # | File | Rationale |
|
|
16
|
+
|---|------|-----------|
|
|
17
|
+
| 1 | [01.md](bodies/01.md) | **PATCH** — Rust already supports it; Python surface is missing. |
|
|
18
|
+
| 5 | [05.md](bodies/05.md) | **DI chains + request context** — unlocks real dependency patterns. |
|
|
19
|
+
| 6 | [06.md](bodies/06.md) | **JWT `aud` / `iss` / leeway** — expected for production auth. |
|
|
20
|
+
| 10 | [10.md](bodies/10.md) | **Structured `Response` (headers, status)** — required for many APIs. |
|
|
21
|
+
|
|
22
|
+
## Research and architecture (heavier; schedule after P0/P1)
|
|
23
|
+
|
|
24
|
+
- **8, 15, 17** — asymmetric JWT / PyO3 upgrade / ASGI hardening: see [bodies/08.md](bodies/08.md), [15.md](bodies/15.md), [17.md](bodies/17.md).
|
|
25
|
+
|
|
26
|
+
## CI, release, and documentation
|
|
27
|
+
|
|
28
|
+
- **13, 14, 16** — clippy/CI, PyPI on tag, OpenAPI doc/test consistency.
|
|
29
|
+
|
|
30
|
+
## Broader feature set
|
|
31
|
+
|
|
32
|
+
- **4, 7, 9, 11, 18, 19, 20** — performance snapshot, cookies, Pydantic OpenAPI, middleware, lifespan, 405, HEAD/OPTIONS.
|
|
33
|
+
|
|
34
|
+
## Roadmap phasing (summary)
|
|
35
|
+
|
|
36
|
+
1. **Hardening:** P0 items + Issue **13** (clippy in CI) where feasible.
|
|
37
|
+
2. **API surface:** P1 (1, 10) + **20** (HEAD/OPTIONS) as needed.
|
|
38
|
+
3. **Auth and docs shape:** 6, 9, 16.
|
|
39
|
+
4. **Scale and DI:** 4, 5.
|
|
40
|
+
|
|
41
|
+
[← Back to README](README.md)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# GitHub issue backlog (batch-ready)
|
|
2
|
+
|
|
3
|
+
This directory holds **20 issue bodies** ([bodies/](bodies/)) and a [PRIORITIES.md](PRIORITIES.md) file. They were generated from the OxyRoute roadmap for **QueryaHub/OxyRoute**.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
1. [GitHub CLI](https://cli.github.com/) installed (`gh`).
|
|
8
|
+
2. Authenticate: `gh auth login` (the repository cannot create issues in your project without a logged-in `gh` or a `GH_TOKEN` with `issues:write`).
|
|
9
|
+
|
|
10
|
+
## One-shot: create the milestone, labels, and all issues
|
|
11
|
+
|
|
12
|
+
From the **repository root**:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
./scripts/create-github-issues.sh
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The script:
|
|
19
|
+
|
|
20
|
+
- Ensures you are logged into `gh`.
|
|
21
|
+
- Creates labels (`P0`, `P1`, `research`, `enhancement`, `tech-debt`, `ci`, `documentation`, `test`) if missing.
|
|
22
|
+
- Creates milestone **`v0.2.0`** if missing.
|
|
23
|
+
- Opens **20 issues** with titles, bodies, labels, and the milestone (see the script for the exact mapping).
|
|
24
|
+
|
|
25
|
+
**Warning:** Running the script twice will **duplicate** issues. If you need idempotency, check open issues on GitHub first or add guards (not included).
|
|
26
|
+
|
|
27
|
+
## Manual: single issue
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
gh issue create --title "feat: expose PATCH routes on Python App" \
|
|
31
|
+
--body-file .github/ISSUE_BACKLOG/bodies/01.md \
|
|
32
|
+
-l enhancement -l P1 -m v0.2.0
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Table of issues
|
|
36
|
+
|
|
37
|
+
| # | Body file | Title (for `gh issue create`) | Labels (suggested) | Milestone |
|
|
38
|
+
|---|-----------|---------------------------------|--------------------|-----------|
|
|
39
|
+
| 1 | [bodies/01.md](bodies/01.md) | feat: expose PATCH routes on Python App | `enhancement`, `P1` | v0.2.0 |
|
|
40
|
+
| 2 | [bodies/02.md](bodies/02.md) | fix: apply URL decoding to query string keys and values | `bug`, `P0` | v0.2.0 |
|
|
41
|
+
| 3 | [bodies/03.md](bodies/03.md) | feat: map Python exceptions in dispatch to HTTP 500 with safe body | `enhancement`, `P0` | v0.2.0 |
|
|
42
|
+
| 4 | [bodies/04.md](bodies/04.md) | perf: reduce lock contention on route match (RWLock or route snapshot) | `tech-debt`, `enhancement` | v0.2.0 |
|
|
43
|
+
| 5 | [bodies/05.md](bodies/05.md) | feat: pass request context into dependencies and support dependency chains | `enhancement`, `P1` | v0.2.0 |
|
|
44
|
+
| 6 | [bodies/06.md](bodies/06.md) | feat: extend JWT Validation with iss/aud and optional leeway | `enhancement`, `P1` | v0.2.0 |
|
|
45
|
+
| 7 | [bodies/07.md](bodies/07.md) | feat: read JWT from Cookie header for require_jwt | `enhancement` | v0.2.0 |
|
|
46
|
+
| 8 | [bodies/08.md](bodies/08.md) | research+feat: JWK/PEM-based JWT verify in Rust (align with oxyjwt) | `enhancement`, `research` | v0.2.0 |
|
|
47
|
+
| 9 | [bodies/09.md](bodies/09.md) | feat: add optional Pydantic / JSON schema hooks for OpenAPI body | `enhancement` | v0.2.0 |
|
|
48
|
+
| 10 | [bodies/10.md](bodies/10.md) | feat: structured Response object for headers and status | `enhancement`, `P1` | v0.2.0 |
|
|
49
|
+
| 11 | [bodies/11.md](bodies/11.md) | feat: optional middleware chain in run_rsgi before route match | `enhancement` | v0.2.0 |
|
|
50
|
+
| 12 | [bodies/12.md](bodies/12.md) | test: e2e HTTP against granian --interface rsgi | `test`, `P0` | v0.2.0 |
|
|
51
|
+
| 13 | [bodies/13.md](bodies/13.md) | ci: add cargo clippy and Rust unit tests to workflow | `ci` | v0.2.0 |
|
|
52
|
+
| 14 | [bodies/14.md](bodies/14.md) | ci: PyPI publish on version tag (trusted publishing) | `ci` | v0.2.0 |
|
|
53
|
+
| 15 | [bodies/15.md](bodies/15.md) | tech-debt: upgrade pyo3 0.21 -> current and validate Granian RSGI | `tech-debt`, `research` | v0.2.0 |
|
|
54
|
+
| 16 | [bodies/16.md](bodies/16.md) | docs+test: clarify include_openapi vs set_openapi_served and 404 for /openapi.json | `documentation`, `test` | v0.2.0 |
|
|
55
|
+
| 17 | [bodies/17.md](bodies/17.md) | fix: review ASGI protocol bridge (run_coroutine_threadsafe) under load | `bug`, `research` | v0.2.0 |
|
|
56
|
+
| 18 | [bodies/18.md](bodies/18.md) | feat: pass app state / lifespan for shared resources (optional design) | `enhancement` | v0.2.0 |
|
|
57
|
+
| 19 | [bodies/19.md](bodies/19.md) | feat: return 405 when path exists for another method | `enhancement` | v0.2.0 |
|
|
58
|
+
| 20 | [bodies/20.md](bodies/20.md) | feat: register HEAD/OPTIONS and sensible defaults | `enhancement` | v0.2.0 |
|
|
59
|
+
|
|
60
|
+
## Priority tier (P0 / P1)
|
|
61
|
+
|
|
62
|
+
See [PRIORITIES.md](PRIORITIES.md) for the recommended order: **P0** = 2, 3, 12; **P1** = 1, 5, 6, 10.
|
|
63
|
+
|
|
64
|
+
## Documentation
|
|
65
|
+
|
|
66
|
+
- [docs/index.md](../../docs/index.md) — main English docs for contributors and users.
|
|
67
|
+
- [CONTRIBUTING.md](../../CONTRIBUTING.md) — how to build and test locally.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [`src/state.rs`](https://github.com/QueryaHub/OxyRoute/blob/main/src/state.rs) already has `st.patch: Mutex<Router<usize>>` and `map_method_router` supports `"PATCH"`. [`oxyroute/app.py`](https://github.com/QueryaHub/OxyRoute/blob/main/oxyroute/app.py) does not add a `patch()` decorator, so users cannot register PATCH without calling native `add_route` from Python.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Add `def patch(self, path, *, ...)` mirroring `put` (default `read_json_body=True`). Register with `add_route` method `"PATCH"`. Extend [docs/routing.md](https://github.com/QueryaHub/OxyRoute/blob/main/docs/routing.md) and [README](https://github.com/QueryaHub/OxyRoute/blob/main/README.md) if needed.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- `pytest` with `@app.patch("/x")` and httpx/ASGI or a small in-process RSGI test if you add a test helper.
|
|
12
|
+
|
|
13
|
+
## Research / notes
|
|
14
|
+
|
|
15
|
+
- None beyond matchit; ensure OpenAPI in [`lib.rs`](https://github.com/QueryaHub/OxyRoute/blob/main/src/lib.rs) `openapi_add_path` still receives `PATCH` lowercase.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Problem
|
|
2
|
+
|
|
3
|
+
- [`parse_query`](https://github.com/QueryaHub/OxyRoute/blob/main/src/params.rs) splits on `&` and `=` but never applies **percent-decoding** ([RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) query; [WHATWG URLSearchParams](https://url.spec.whatwg.org/#urlencoded-parsing) often maps `+` to space in `application/x-www-form-urlencoded`). Current code breaks for `?q=hello%20world` and non-ASCII.
|
|
4
|
+
|
|
5
|
+
## Options
|
|
6
|
+
|
|
7
|
+
- (1) `percent_encoding` or `form_urlencoded` crate in `parse_query`; (2) minimal decode for `%XX` and optional `+` → space behind a flag. Unit tests in **Rust** (`#[cfg(test)]` in `params.rs` or `params_tests.rs`) with hex edge cases and UTF-8.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- Tests pass; document behavior in [docs/handlers.md](https://github.com/QueryaHub/OxyRoute/blob/main/docs/handlers.md) for the `query` dict.
|
|
12
|
+
|
|
13
|
+
## Research
|
|
14
|
+
|
|
15
|
+
- Criterion: must not double-decode; empty values; duplicate keys (last-wins vs multi-map—**document** current `HashMap` last-wins or open a follow-up for `MultiMap`).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [`run_rsgi`](https://github.com/QueryaHub/OxyRoute/blob/main/src/dispatch.rs) uses `?` on `handler.call` and dependency calls. Uncaught `PyErr` may surface as a generic RSGI error depending on Granian. Production APIs need a **500** with optional JSON `{"error":"…"}` and **no stack trace** in the response body when `OXYROUTE_DEBUG=0`.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Wrap handler and dependency resolution in `match` on `Result`. On `Err`, use `tracing` or `log` (or Python `logging` via GIL) with request method/path. Response: `text/plain` or `application/json` 500, configurable. Consider `Trace-Id` header from scope if Granian provides it.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- `pytest` that raises in a handler and asserts 500 and body policy.
|
|
12
|
+
|
|
13
|
+
## Research
|
|
14
|
+
|
|
15
|
+
- `pyo3` exception types to string sanitization; avoid leaking PII from exception messages.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- `state.lock()` is taken in multiple places per request. [`AppState`](https://github.com/QueryaHub/OxyRoute/blob/main/src/state.rs) bundles routes plus five `Mutex<Router<usize>>` and an `openapi` mutex.
|
|
4
|
+
|
|
5
|
+
## Direction A
|
|
6
|
+
|
|
7
|
+
- After `freeze()`, build an **`Arc<CompiledRoutes>`** (clones of routers or a single matchit structure per method) behind `ArcSwap` or `OnceLock` and read with no write path.
|
|
8
|
+
|
|
9
|
+
## Direction B
|
|
10
|
+
|
|
11
|
+
- `parking_lot::RwLock` for `openapi` vs `routes`—measure with Criterion microbenches or `wrk` on a hello handler.
|
|
12
|
+
|
|
13
|
+
## Acceptance criteria
|
|
14
|
+
|
|
15
|
+
- No functional regression; benchmark numbers in the PR description or `benches/`.
|
|
16
|
+
|
|
17
|
+
## Research
|
|
18
|
+
|
|
19
|
+
- [matchit](https://docs.rs/matchit) `Router` and `Sync`—today wrapped in `Mutex`. Consider matchit 0.8 migration if applicable.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [dispatch.rs](https://github.com/QueryaHub/OxyRoute/blob/main/src/dispatch.rs) (dependency section) calls each factory with **no arguments**. FastAPI-style `Depends` usually receives typed values from prior deps.
|
|
4
|
+
|
|
5
|
+
## Design sketch
|
|
6
|
+
|
|
7
|
+
1. Introduce an opaque `RequestContext` in Rust (method, path, headers, parsed query pointer) and pass to factories as a single `PyObject` or kwargs dict.
|
|
8
|
+
2. At `freeze()`, build a **DAG** of dep names, validate no cycles, compute topological order.
|
|
9
|
+
3. Per request, walk the order and pass previously resolved values into the next `call`.
|
|
10
|
+
|
|
11
|
+
## Acceptance criteria
|
|
12
|
+
|
|
13
|
+
- Test with `dependencies=[("a", a), ("b", b)]` where `b` receives `a` (signature introspection in Python, or an explicit convention).
|
|
14
|
+
|
|
15
|
+
## Research
|
|
16
|
+
|
|
17
|
+
- `inspect.signature` from Rust via PyO3 is expensive; consider registering arity at `add_route` or a static convention (`def b(a):`).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [`jsonwebtoken::Validation`](https://docs.rs/jsonwebtoken/latest/jsonwebtoken/struct.Validation.html) in [dispatch.rs](https://github.com/QueryaHub/OxyRoute/blob/main/src/dispatch.rs) sets `algorithms` and `validate_nbf` but not `iss` / `aud` from the route.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Add optional `jwt_issuer: Option<String>`, `jwt_audience: Option<String>` to `add_route` and Python decorators. Map to `val.validate_iss` / `val.validate_aud` in jsonwebtoken 9. Add `leeway: u64` for clock skew.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- Pytest with tokens where `aud` / `iss` are wrong → 401; parity test with oxyjwt where applicable.
|
|
12
|
+
|
|
13
|
+
## Research
|
|
14
|
+
|
|
15
|
+
- jsonwebtoken vs [OxyJWT](https://github.com/QueryaHub/OxyJWT) claim validation defaults—keep golden vectors in [tests/test_jwt_parity.py](https://github.com/QueryaHub/OxyRoute/blob/main/tests/test_jwt_parity.py).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [`extract_bearer`](https://github.com/QueryaHub/OxyRoute/blob/main/src/token.rs) only inspects `Authorization`. Many SPAs use `access_token=…` in a cookie.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Add `jwt_cookie: Option<String>` (cookie name). In dispatch, if Bearer is missing, parse the `Cookie` header (Rust: `cookie` crate or string split) and read the token.
|
|
8
|
+
|
|
9
|
+
## Security
|
|
10
|
+
|
|
11
|
+
- `Secure` / `HttpOnly` are browser flags; document server-side CORS. CSRF: out of scope or separate issue.
|
|
12
|
+
|
|
13
|
+
## Acceptance criteria
|
|
14
|
+
|
|
15
|
+
- Unit test and one integration test.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [`parse_algorithm`](https://github.com/QueryaHub/OxyRoute/blob/main/src/lib.rs) is HMAC-only. A shared Rust path with [OxyJWT](https://github.com/QueryaHub/OxyJWT) or `jsonwebtoken` with `DecodingKey::from_rsa_pem` was discussed in the project roadmap.
|
|
4
|
+
|
|
5
|
+
## Phases
|
|
6
|
+
|
|
7
|
+
1. Prototype decode with `RS256` using PEM from route or environment.
|
|
8
|
+
2. Compare tokens with `oxyjwt.decode` in dev tests.
|
|
9
|
+
3. Consider a `path` / `git` dependency on the OxyJWT library crate if it exposes a `lib` target (inspect upstream `Cargo.toml`).
|
|
10
|
+
|
|
11
|
+
## Acceptance criteria
|
|
12
|
+
|
|
13
|
+
- Document supported algorithms; reject unsupported algorithms at route registration time.
|
|
14
|
+
|
|
15
|
+
## Research
|
|
16
|
+
|
|
17
|
+
- `jsonwebtoken` 9 RSA/EC API; key rotation via JWKS (heavier—separate issue).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [`openapi_add_path`](https://github.com/QueryaHub/OxyRoute/blob/main/src/lib.rs) produces a bare `200` with no `content`. Users expect `application/json` schemas for bodies.
|
|
4
|
+
|
|
5
|
+
## Options
|
|
6
|
+
|
|
7
|
+
1. **Python-only:** at route time, pass optional `body_model=MyPydanticModel` to the decorator; Rust only stores a JSON Schema string generated in Python (`model_json_schema()`) in `RouteEntry`; merge into the OpenAPI document on `freeze()`.
|
|
8
|
+
2. **Rust-only:** stay minimal. Prefer (1) to avoid reimplementing Pydantic in Rust.
|
|
9
|
+
|
|
10
|
+
## Acceptance criteria
|
|
11
|
+
|
|
12
|
+
- `/openapi.json` includes `requestBody` for at least one POST route.
|
|
13
|
+
|
|
14
|
+
## Research
|
|
15
|
+
|
|
16
|
+
- Pydantic v2 JSON schema `$defs`; size of inline vs `$ref` in OAS 3.0.3.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- Return path only allows dict `{status, body}` for custom status, fixed content types in [dispatch.rs](https://github.com/QueryaHub/OxyRoute/blob/main/src/dispatch.rs) (see response mapping).
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Python class or `TypedDict`, e.g. `Response(body=, status=, headers=)`, serialized in dispatch to `response_bytes` / `response_str` with a header list. Optional `Set-Cookie` list.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- Test that sets `content-type: application/json` and a custom header on the response.
|
|
12
|
+
|
|
13
|
+
## Research
|
|
14
|
+
|
|
15
|
+
- RSGI header format in Granian (list of pairs, bytes or str)—must match [response.rs](https://github.com/QueryaHub/OxyRoute/blob/main/src/response.rs) `build_headers_ct`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- No hook for metrics, CORS, or auth that runs **before** the router (except per-route JWT).
|
|
4
|
+
|
|
5
|
+
## Design
|
|
6
|
+
|
|
7
|
+
- `App.add_middleware(f)` where `f(scope, protocol) -> Result<ControlFlow, …>` in Rust, or a list of `Py<PyAny>` pre-handlers. Short-circuit to a response (e.g. 204 for OPTIONS preflight).
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
- High complexity; start with a **single** optional Python callable for an MVP.
|
|
12
|
+
|
|
13
|
+
## Acceptance criteria
|
|
14
|
+
|
|
15
|
+
- Test that a CORS preflight can return 204 without hitting the route handler.
|
|
16
|
+
|
|
17
|
+
## Research
|
|
18
|
+
|
|
19
|
+
- CORS: `Access-Control-Request-Method` / `Origin` ([Fetch](https://fetch.spec.whatwg.org/)).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [tests/test_asgi.py](https://github.com/QueryaHub/OxyRoute/blob/main/tests/test_asgi.py) uses the ASGI transport; the RSGI path is not exercised end-to-end with a real HTTP server.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- In CI, `subprocess` to start `granian` on `127.0.0.1:<port>` with a minimal `app` module, `httpx` `GET /`, assert 200, then terminate. Use a sync `httpx` client with retry on port bind races.
|
|
8
|
+
|
|
9
|
+
## Flakiness
|
|
10
|
+
|
|
11
|
+
- Port allocation: use a random high port, retries, or `pytest` fixtures; document Windows caveats if the job is Linux-only at first.
|
|
12
|
+
|
|
13
|
+
## Acceptance criteria
|
|
14
|
+
|
|
15
|
+
- Green on Linux at minimum; mark optional for Windows if flaky.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- Clippy is run manually; [params.rs](https://github.com/QueryaHub/OxyRoute/blob/main/src/params.rs) will gain `#[test]` for query decoding (see related issue on percent-decoding).
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Add a CI step: `cargo clippy -- -D warnings` (or a documented allowlist). Add `cargo test` for new Rust unit tests. Cache `~/.cargo` and `target` with [Swatinem/rust-cache](https://github.com/Swatinem/rust-cache) or `actions/cache`.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- CI fails on a clippy warning when using `-D warnings` (or an explicit allowlist in `clippy.toml` with justification).
|
|
12
|
+
|
|
13
|
+
## Research
|
|
14
|
+
|
|
15
|
+
- `cross` for ARM if you add more target triples later.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [pyproject.toml](https://github.com/QueryaHub/OxyRoute/blob/main/pyproject.toml) has `version = "0.1.0"`; there is no release workflow.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- GitHub Action on `v*` tags: `maturin build --release` + `maturin upload` (OIDC to PyPI) per the [Maturin distribution guide](https://www.maturin.rs/distribution). Optional: `CARGO_INCREMENTAL=0` for more reproducible builds.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- Dry run against TestPyPI first; then production with trusted publishing.
|
|
12
|
+
|
|
13
|
+
## Research
|
|
14
|
+
|
|
15
|
+
- [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) and package name alignment on PyPI.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- PyO3 0.21 is aging; the GIL, `Bound` API, and `pyo3_asyncio` story may have moved ([pyo3-async-runtimes](https://github.com/PyO3/pyo3-async-runtimes) or follow-on crates).
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Read [PyO3 migration guides](https://pyo3.rs/v0.22.0/migration), bump `pyo3` and the asyncio integration, fix compile errors, run the full pytest suite.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- No regression on `handle_rsgi` and on ASGI `__call__` behavior.
|
|
12
|
+
|
|
13
|
+
## Research
|
|
14
|
+
|
|
15
|
+
- Granian’s Python RSGI expectations (e.g. sync `response_str` on protocol).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- When `include_openapi` is false, [GET /openapi.json](https://github.com/QueryaHub/OxyRoute/blob/main/src/dispatch.rs) can fall through; users may get 404 on a normal route or miss OpenAPI. Behavior should be exact and tested.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Document the interaction between `include_openapi`, `set_openapi_served`, and registration order in [docs/openapi.md](https://github.com/QueryaHub/OxyRoute/blob/main/docs/openapi.md). Add a pytest for `include_openapi=False`.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- Documentation updated; pytest covers disabled OpenAPI.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [oxyroute/asgi.py](https://github.com/QueryaHub/OxyRoute/blob/main/oxyroute/asgi.py) uses `asyncio.run_coroutine_threadsafe` and blocking `fut.result()` from sync `response_*` when native code calls into Python from a Tokio worker. Risk of deadlock under mixed load is documented; needs hardening.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Reproduce with concurrent `httpx` requests. If deadlock appears, use a one-shot `asyncio.Queue` in the ASGI task to receive response actions without cross-thread `result()`, or an `anyio` / `trio` bridge.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- Stress test: 50 concurrent requests without hang; optional `uvicorn` subprocess smoke test.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [`__rsgi_init__`](https://github.com/QueryaHub/OxyRoute/blob/main/oxyroute/app.py) is a no-op. Users need a place to run `create_engine`, Redis client setup, etc.
|
|
4
|
+
|
|
5
|
+
## Options
|
|
6
|
+
|
|
7
|
+
1. Subclass `App` and set attributes in `__rsgi_init__` (if Granian calls it in the same process as workers).
|
|
8
|
+
2. **Factory** pattern: `app = create_app()` per worker, documented in docs.
|
|
9
|
+
3. Native `State` on the Rust `App` (larger change).
|
|
10
|
+
|
|
11
|
+
## Acceptance criteria
|
|
12
|
+
|
|
13
|
+
- Example in `examples/` with an `asyncio` pool or similar; document caveats for multi-process Granian.
|
|
14
|
+
|
|
15
|
+
## Research
|
|
16
|
+
|
|
17
|
+
- How Granian workers fork or spawn relative to RSGI init.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- [dispatch.rs](https://github.com/QueryaHub/OxyRoute/blob/main/src/dispatch.rs) returns 404 on no match; it does not distinguish "path exists for another method" without scanning all method routers.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- On miss, either iterate other methods’ routers for the same path (expensive) or at `freeze()` build a `HashSet` / map from path pattern to allowed methods. If another method has a route for that path → **405** with an `Allow` header listing methods.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- GET on a POST-only path returns 405 and `Allow: POST` (or the correct list).
|
|
12
|
+
|
|
13
|
+
## Research
|
|
14
|
+
|
|
15
|
+
- HTTP [RFC 9110 §15.5.6](https://www.rfc-editor.org/rfc/rfc9110.html#name-405-method-not-allowed) and the `Allow` header.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
- Browsers and crawlers use `HEAD`; CORS preflight uses `OPTIONS`. [state.rs](https://github.com/QueryaHub/OxyRoute/blob/main/src/state.rs) has no `head` / `options` `Router` today—extend with new routers or map HEAD to GET without a body.
|
|
4
|
+
|
|
5
|
+
## Work
|
|
6
|
+
|
|
7
|
+
- Extend `map_method_router`, `add_route` in Python, `App.head` (strip body from GET?), `App.options` (CORS or 204). Overlaps with the middleware / CORS issue for full preflight.
|
|
8
|
+
|
|
9
|
+
## Acceptance criteria
|
|
10
|
+
|
|
11
|
+
- `curl -I` and OPTIONS tests.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Suggest a new feature or an improvement to OxyRoute
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Please read the [docs](https://github.com/QueryaHub/OxyRoute/blob/main/docs/index.md) and check [.github/ISSUE_BACKLOG/PRIORITIES.md](https://github.com/QueryaHub/OxyRoute/blob/main/.github/ISSUE_BACKLOG/PRIORITIES.md) for related plans.
|
|
9
|
+
- type: textarea
|
|
10
|
+
id: problem
|
|
11
|
+
attributes:
|
|
12
|
+
label: Problem or use case
|
|
13
|
+
description: What are you trying to do? What is missing?
|
|
14
|
+
validations:
|
|
15
|
+
required: true
|
|
16
|
+
- type: textarea
|
|
17
|
+
id: proposal
|
|
18
|
+
attributes:
|
|
19
|
+
label: Proposed solution
|
|
20
|
+
description: Optional design sketch, API shape, or links.
|
|
21
|
+
validations:
|
|
22
|
+
required: false
|
|
23
|
+
- type: textarea
|
|
24
|
+
id: context
|
|
25
|
+
attributes:
|
|
26
|
+
label: Context
|
|
27
|
+
description: RSGI vs ASGI, Python version, Granian version, etc.
|
|
28
|
+
validations:
|
|
29
|
+
required: false
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Report incorrect behavior, crashes, or documentation mistakes
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
For RSGI/Granian behavior, linking to a **minimal** `app` module and the exact `granian` command line helps a lot.
|
|
9
|
+
- type: textarea
|
|
10
|
+
id: what
|
|
11
|
+
attributes:
|
|
12
|
+
label: What happened?
|
|
13
|
+
validations:
|
|
14
|
+
required: true
|
|
15
|
+
- type: textarea
|
|
16
|
+
id: expected
|
|
17
|
+
attributes:
|
|
18
|
+
label: What did you expect?
|
|
19
|
+
validations:
|
|
20
|
+
required: true
|
|
21
|
+
- type: textarea
|
|
22
|
+
id: versions
|
|
23
|
+
attributes:
|
|
24
|
+
label: Versions
|
|
25
|
+
description: Python, oxyroute (git / pip), granian, OS.
|
|
26
|
+
value: |
|
|
27
|
+
- Python:
|
|
28
|
+
- oxyroute:
|
|
29
|
+
- granian (if used):
|
|
30
|
+
- OS:
|
|
31
|
+
validations:
|
|
32
|
+
required: true
|
|
33
|
+
- type: textarea
|
|
34
|
+
id: repro
|
|
35
|
+
attributes:
|
|
36
|
+
label: Reproduction
|
|
37
|
+
description: Code, shell commands, and whether the failure is RSGI or ASGI.
|
|
38
|
+
validations:
|
|
39
|
+
required: false
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests
|
|
2
|
+
blank_issues_enabled: true
|
|
3
|
+
contact_links:
|
|
4
|
+
- name: OxyRoute documentation
|
|
5
|
+
url: https://github.com/QueryaHub/OxyRoute/blob/main/docs/index.md
|
|
6
|
+
about: RSGI, routing, handlers, JWT, OpenAPI, and local development.
|
|
7
|
+
- name: Issue backlog and priorities
|
|
8
|
+
url: https://github.com/QueryaHub/OxyRoute/blob/main/.github/ISSUE_BACKLOG/PRIORITIES.md
|
|
9
|
+
about: P0 / P1 ordering and pre-written issue bodies in bodies/.
|
|
10
|
+
- name: Open an issue
|
|
11
|
+
url: https://github.com/QueryaHub/OxyRoute/issues/new/choose
|
|
12
|
+
about: Bug reports, features, and questions that fit a GitHub issue.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
- What this PR does (1–3 sentences).
|
|
4
|
+
- If it closes a ticket: `Closes #N` (replace N).
|
|
5
|
+
|
|
6
|
+
## Base branch
|
|
7
|
+
|
|
8
|
+
- [ ] This PR targets **`dev`**, not `main` (unless maintainers asked for a hotfix).
|
|
9
|
+
|
|
10
|
+
## Checklist
|
|
11
|
+
|
|
12
|
+
- [ ] `cargo build` (and `cargo clippy` if you touched Rust)
|
|
13
|
+
- [ ] `maturin develop` + tests as in [docs/development.md](docs/development.md) (e.g. pytest from a clean cwd / installed wheel)
|
|
14
|
+
- [ ] No unrelated drive-by refactors; commits are [atomic and scoped](https://github.com/QueryaHub/OxyRoute/blob/main/docs/development-workflow.md)
|
|
15
|
+
|
|
16
|
+
## Note on `.github/ISSUE_BACKLOG/`
|
|
17
|
+
|
|
18
|
+
If you only touch issue template files under [`.github/ISSUE_BACKLOG/`](.github/ISSUE_BACKLOG/) (not application code), say so in the summary. Prefer a **separate** PR for backlog-only edits when possible.
|