| |
|
| |
### Rust backend (radicle-httpd)
|
| |
|
| - |
- Each route module exposes `pub fn router(ctx: Context) -> Router`;
|
| - |
handlers are private `async fn`s
|
| - |
- Handler signature:
|
| - |
`async fn handler(State(ctx): State<Context>, Path(rid): Path<RepoId>) -> impl IntoResponse`
|
| - |
- Annotate the error type on Ok: `Ok::<_, Error>(Json(data))`
|
| - |
- All response structs: `#[serde(rename_all = "camelCase")]`
|
| - |
- Optional fields: `#[serde(skip_serializing_if = "Option::is_none")]`
|
| - |
- Use `json!()` macro for inline JSON; `immutable_response()` /
|
| - |
`cached_response()` helpers for Cache-Control headers
|
| - |
- Module structure: `api/v1/` handlers, `api/json/` serialization helpers,
|
| - |
`api/query.rs` query param types, `api/error.rs` error types
|
| + |
**Module structure.** `api/v1/` handlers, `api/json/` serialization
|
| + |
helpers, `api/query.rs` query param types, `api/error.rs` error types.
|
| + |
Each route module exposes `pub fn router(ctx: Context) -> Router`;
|
| + |
handlers are private `async fn`s with signature
|
| + |
`async fn handler(State(ctx): State<Context>, Path(rid): Path<RepoId>) -> impl IntoResponse`
|
| + |
and annotate the error type on `Ok`: `Ok::<_, Error>(Json(data))`.
|
| + |
Use `immutable_response()` / `cached_response()` helpers to set
|
| + |
Cache-Control; plain `Json(..)` for dynamic responses.
|
| + |
|
| + |
- **Types end-to-end.** Use domain types (`RefString`, `Qualified`,
|
| + |
`Oid`, `Alias`, `NodeId`) throughout, including response structs —
|
| + |
they already derive `Serialize`. Converting to `String` / `&str`
|
| + |
internally signals a missing derive or a misplaced type boundary.
|
| + |
Shortnames are `RefString`, fully-qualified refs are `Qualified`.
|
| + |
- **Named response structs, not `json!()`.** Define
|
| + |
`#[derive(Serialize)]` structs at module level with
|
| + |
`#[serde(rename_all = "camelCase")]`; optional fields use
|
| + |
`#[serde(skip_serializing_if = "Option::is_none")]`. Reserve
|
| + |
`json!()` for trivial one-off payloads (e.g. the root router
|
| + |
listing, error bodies).
|
| + |
- **Don't reimplement upstream.** Before writing ref, quorum, or
|
| + |
canonical logic, check the sibling crates (`../heartwood/`,
|
| + |
`../radicle-git/` — paths in the "Radicle ecosystem" section).
|
| + |
Prefer reading materialized state (e.g. `references_glob` on a
|
| + |
pattern) over recomputing.
|
| + |
- **Separate IO from logic via single-purpose traits.** One trait per
|
| + |
IO concern (`ReadCanonicalRefs`, `PeelToCommit`, `ResolveTag`, …).
|
| + |
Business functions take `R: Trait1 + Trait2`, enabling mocks and
|
| + |
future caching. Keep one impl per operation — if `peel_to_commit`
|
| + |
is needed in multiple places, callers delegate to a single
|
| + |
implementation.
|
| + |
- **Match typed components, don't string-prefix.** Use
|
| + |
`qualified.non_empty_components()` / `refname.qualified()` over
|
| + |
`starts_with("refs/heads/")`. Slicing typed refnames is an
|
| + |
anti-pattern.
|
| + |
- **Extract shared helpers when two handlers share shape.** Field
|
| + |
drift between equivalent JSON outputs is the smell.
|
| + |
- **Skip-with-warn, don't silently succeed.** For per-item IO
|
| + |
failures: `tracing::warn!` and omit the item so clients don't
|
| + |
receive data they'll fail to resolve downstream.
|
| + |
- **Observability.**
|
| + |
`#[tracing::instrument(skip_all, fields(rid = %rid, …))]` on
|
| + |
handler-facing fns, with relevant IDs as span fields.
|
| + |
- **Rustdoc on `pub` types and fields.** State intent, don't restate
|
| + |
the name.
|
| |
|
| |
## Commit messages
|
| |
|