Rust web gateway bridging HTTPS URLs to srcuri:// protocol for contexts where custom protocols fail (Jira, Slack, Teams).
Modes:
- Provider Passthrough (
srcuri.com/<provider-url>) - GitHub/GitLab URL → srcuri - Direct Gateway (
srcuri.com/<path>) - 1:1 https:// → srcuri://
src/routes/- HTTP handlerssrc/parsing/- Git provider URL parsingsrc/templates/- Askama HTMLsrc/tenant/- Multi-tenant configtenants/- Tenant config filesdev/- Specs
IMPORTANT: Docs, comments, markdown must be concise
- Write in the style of Strunk & White's The Elements of Style
- Don't use decorative emojis; meaningful visual info is ok
- Temporary debug emojis are acceptable
When adding a feature:
- Update
dev/features.mdunder appropriate section - Update
dev/use-cases.mdif new use case (use "As a..." format)
- Code must compile, be
rustfmt-clean, and clippy-clean (cargo clippy -- -D warnings) - Prefer small modules, minimal
pub, and explicit invariants in docs/comments - Include unit tests for non-trivial logic and edge cases
- Keep diffs minimal: don’t refactor unrelated code
- Make invalid states unrepresentable. Prefer enums/newtypes over loosely related booleans or “stringly typed” values
- Keep types small + explicit. Derive common traits when appropriate
Debug,Clone,Copy,PartialEq,Eq,Hash
- Document invariants where correctness depends on them (indexing, concurrency, perf assumptions)
- Default to borrowing in inputs
&T,&str,&[T], iterators,impl AsRef<Path>
- Only allocate when necessary, and be explicit about it
- Avoid “clone-to-fix-borrow-checker.” Only clone with a clear reason
- Use
Cow<'a, str>only when you truly need conditional ownership; otherwise use&strorString - Prefer returning
impl Iterator<Item = T>when callers can stream resultsVec<T>when callers need ownership or random accessStringonly when you must own; otherwise&str(or spans/indices)
- Use
Result<T, E>everywhere errors can occur.- No
unwrap()/expect()outside tests/examples or “impossible by invariant” code (and if so, comment why)
- No
- Prefer newtypes for identifiers and units
UserId(u64),Bytes(u64),Millis(u64)
- Avoid “boolean soup” parameters like
fn foo(a: bool, b: bool, c: bool)- Use enums/options/builders instead
- If more than ~3 parameters or many optional params
- introduce an
Optionsstruct or builder
- introduce an
- Define domain errors with thiserror
#[derive(thiserror::Error, Debug)]- Use structured variants, not raw strings
- Preserve sources with
#[source]or#[from]
- Keep mapping intentional
NotFound,Conflict,Validation,Unauthorized, etc
- Use anyhow at the boundary
anyhow::Result<T>- Add context with
.context("...")/.with_context(|| ...)
- Don’t drop sources; don’t lose context
- Convert to user-facing/API errors in one place
- Use tracing, not
println!orlog - Prefer
#[tracing::instrument(skip(...), fields(...))]on- public async entrypoints
- key operations where you need structured context
- Log policy
debugfor internal stateinfofor major lifecyclewarnfor recoverable anomalieserroronly when failing a request/task
- Don’t log secrets
- Avoid lifetime gymnastics unless needed. Start with straightforward owned/borrowed APIs
- Prefer
impl Traitreturns for simple iterators and closures - Avoid overly generic signatures; use generics when they reduce duplication without harming readability
- Prefer slices/iterators over copying collections
- Use
Vec::with_capacity()when size is known or bounded - Choose
HashMapvsBTreeMapintentionally (ordering vs speed) - Don’t introduce
SmallVec, interning,Cow, or unsafe code unless there’s a clear reason (or profiling) - If performance-sensitive, comment what is optimized and why
- Never hold a
MutexGuard/RwLockGuardacross.await - Prefer message passing (channels) over shared mutable state
- Use
tokio::syncprimitives in async code; usestd::synconly in purely sync code - Spawned tasks must
- propagate errors or log with context (don’t silently drop)
- be cancellable where appropriate
- be structured/supervised if long-lived
- Avoid I/O while holding locks
- Treat the boundary as hostile/messy
- Prefer a boundary “wire” type (DTO) that matches JSON, then convert to domain
- DTO structs:
#[derive(Serialize, Deserialize)] - Domain types enforce invariants (newtypes/enums)
- Convert DTO → domain via
TryFrom/TryInto(orFromStr) to enforce invariants once - Avoids pushing complex parsing/validation into custom
Deserializeimplementations
- Act as principal architect level developer
- Reuse existing implementations before creating new ones
- No fallback mechanisms that mask errors
- No comments restating what code says
- Remove dead code; don't comment out
- Don't memorialize code removals/changes in comments
- Write intent-revealing code
- Avoid abbreviations in variable names/methods, except where aligning with existing interfaces
- Keep functions short and single-purpose; extract helpers
- Prefer early returns for validation and error paths
- Use exhaustive
match; avoid_ =>when it hides future variants - Prefer minimal visibility
pub(crate)by default,pubonly when necessary
- Add targeted
#[allow(...)]only with a comment explaining why
- Never "fix" tests by changing expectations without valid reason
- New features need tests
- Explain why expectation changes are valid if uncertain
- Unit tests for tricky logic and edge cases
- Use table-driven tests for parsers and boundary checks
- Use
#[tokio::test]for async code - Avoid mocking SQLx internals; mock service boundaries instead
- Prefer typed state + typed extractors
- Use
State<AppState>for shared dependencies - Use
Path<T>,Query<T>,Json<T>,Form<T>with#[derive(Deserialize)]
- Use
- Validate inputs at the boundary.
- Convert DTO → domain early (
TryFrom), return structured errors - Keep handlers thin: parse/validate → call service → map to response
- Convert DTO → domain early (
- Define one error type that implements
IntoResponse.- Map domain errors + external errors into a single
ApiError - Return appropriate HTTP status codes; avoid leaking internal details
- Add per-request context with
tracing(request id, user id)
- Map domain errors + external errors into a single
- Use middleware intentionally.
TraceLayer(tower-http) for request spans- Timeouts and body limits (
RequestBodyLimitLayer,TimeoutLayer) where relevant
- Don’t block the runtime.
- No CPU-heavy work in handlers; use
spawn_blockingif needed
- No CPU-heavy work in handlers; use
- Route organization
- Group routes by feature; keep router construction separate from handlers
- Prefer
Router::nest("/v1", ...)and clear modules
- Use compile-time checked queries.
- Prefer
query!/query_as!(or#[sqlx::query_file]) for correctness - If you must use dynamic SQL, confine it to small, well-reviewed helpers
- Prefer
- Parameterize everything.
- Never string-concatenate user input into SQL
- Use a connection pool via app state.
PgPool/MySqlPool/SqlitePool. Don’t create pools per request
- Transactions are explicit and short-lived.
- Use
pool.begin().await? - Pass
&mut Transaction<'_, DB>through repo/service functions - Don’t hold a transaction across network calls or long
.awaitchains
- Use
- Model DB rows separately from domain types.
- Use
#[derive(sqlx::FromRow)]for row structs, then convert to domain - Keep DB nullability explicit (
Option<T>)
- Use
- Avoid N+1 queries.
- Use joins, batching, or
IN (...)with arrays
- Use joins, batching, or
- Use structured errors.
- Map
sqlx::Errorto domain/API errors at the boundary; preserve sources - Handle “not found” intentionally (
fetch_optional→ 404 mapping)
- Map
- Migrations
- Use
sqlx migrate run/ embedded migrations consistently; keep changes reviewed and reproducible
- Use
- App
StateholdsPool+ config + clients; handlers never create pools - Handlers call services; services call repositories; SQLx stays in the repository layer
- Commits: imperative, concise (e.g., "Fix path detection")
- Never commit secrets or local paths