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.
Files changed (80) hide show
  1. oxyroute-0.1.0/.cursor/rules/git-workflow.mdc +26 -0
  2. oxyroute-0.1.0/.github/ISSUE_BACKLOG/PRIORITIES.md +41 -0
  3. oxyroute-0.1.0/.github/ISSUE_BACKLOG/README.md +67 -0
  4. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/01.md +15 -0
  5. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/02.md +15 -0
  6. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/03.md +15 -0
  7. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/04.md +19 -0
  8. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/05.md +17 -0
  9. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/06.md +15 -0
  10. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/07.md +15 -0
  11. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/08.md +17 -0
  12. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/09.md +16 -0
  13. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/10.md +15 -0
  14. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/11.md +19 -0
  15. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/12.md +15 -0
  16. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/13.md +15 -0
  17. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/14.md +15 -0
  18. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/15.md +15 -0
  19. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/16.md +11 -0
  20. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/17.md +11 -0
  21. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/18.md +17 -0
  22. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/19.md +15 -0
  23. oxyroute-0.1.0/.github/ISSUE_BACKLOG/bodies/20.md +11 -0
  24. oxyroute-0.1.0/.github/ISSUE_TEMPLATE/1_feature_request.yml +29 -0
  25. oxyroute-0.1.0/.github/ISSUE_TEMPLATE/2_bug_report.yml +39 -0
  26. oxyroute-0.1.0/.github/ISSUE_TEMPLATE/config.yml +12 -0
  27. oxyroute-0.1.0/.github/pull_request_template.md +18 -0
  28. oxyroute-0.1.0/.github/workflows/ci.yml +75 -0
  29. oxyroute-0.1.0/.github/workflows/release-pypi.yml +135 -0
  30. oxyroute-0.1.0/.gitignore +56 -0
  31. oxyroute-0.1.0/CONTRIBUTING.md +63 -0
  32. oxyroute-0.1.0/Cargo.lock +824 -0
  33. oxyroute-0.1.0/Cargo.toml +35 -0
  34. oxyroute-0.1.0/LICENSE +21 -0
  35. oxyroute-0.1.0/PKG-INFO +133 -0
  36. oxyroute-0.1.0/README.md +96 -0
  37. oxyroute-0.1.0/docs/asgi.md +28 -0
  38. oxyroute-0.1.0/docs/dependencies.md +52 -0
  39. oxyroute-0.1.0/docs/development-workflow.md +53 -0
  40. oxyroute-0.1.0/docs/development.md +69 -0
  41. oxyroute-0.1.0/docs/handlers.md +60 -0
  42. oxyroute-0.1.0/docs/index.md +52 -0
  43. oxyroute-0.1.0/docs/installation.md +55 -0
  44. oxyroute-0.1.0/docs/jwt.md +48 -0
  45. oxyroute-0.1.0/docs/openapi.md +37 -0
  46. oxyroute-0.1.0/docs/routing.md +31 -0
  47. oxyroute-0.1.0/docs/rsgi.md +61 -0
  48. oxyroute-0.1.0/examples/rsgi_app.py +21 -0
  49. oxyroute-0.1.0/examples/rsgi_lifespan_app.py +55 -0
  50. oxyroute-0.1.0/oxyroute/__init__.py +8 -0
  51. oxyroute-0.1.0/oxyroute/app.py +308 -0
  52. oxyroute-0.1.0/oxyroute/asgi.py +227 -0
  53. oxyroute-0.1.0/oxyroute/response.py +24 -0
  54. oxyroute-0.1.0/pyproject.toml +72 -0
  55. oxyroute-0.1.0/scripts/create-github-issues.sh +65 -0
  56. oxyroute-0.1.0/src/dispatch.rs +711 -0
  57. oxyroute-0.1.0/src/lib.rs +355 -0
  58. oxyroute-0.1.0/src/params.rs +158 -0
  59. oxyroute-0.1.0/src/response.rs +171 -0
  60. oxyroute-0.1.0/src/schema.rs +11 -0
  61. oxyroute-0.1.0/src/state.rs +141 -0
  62. oxyroute-0.1.0/src/token.rs +211 -0
  63. oxyroute-0.1.0/tests/fixtures/rsa/private_pkcs8.pem +28 -0
  64. oxyroute-0.1.0/tests/fixtures/rsa/public_pkcs8.pem +9 -0
  65. oxyroute-0.1.0/tests/test_405.py +61 -0
  66. oxyroute-0.1.0/tests/test_asgi.py +65 -0
  67. oxyroute-0.1.0/tests/test_dep_chain.py +74 -0
  68. oxyroute-0.1.0/tests/test_granian_e2e.py +163 -0
  69. oxyroute-0.1.0/tests/test_handler_errors_500.py +77 -0
  70. oxyroute-0.1.0/tests/test_head_options.py +94 -0
  71. oxyroute-0.1.0/tests/test_jwt_cookie.py +93 -0
  72. oxyroute-0.1.0/tests/test_jwt_parity.py +30 -0
  73. oxyroute-0.1.0/tests/test_jwt_route_iss_aud.py +99 -0
  74. oxyroute-0.1.0/tests/test_jwt_rs256.py +55 -0
  75. oxyroute-0.1.0/tests/test_middleware.py +44 -0
  76. oxyroute-0.1.0/tests/test_openapi.py +148 -0
  77. oxyroute-0.1.0/tests/test_oxyjwt_docs.py +27 -0
  78. oxyroute-0.1.0/tests/test_query_decode.py +25 -0
  79. oxyroute-0.1.0/tests/test_rsgi_lifespan.py +31 -0
  80. 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.