Compare commits
3
Commits
bff34dff15
...
176667d038
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
176667d038 | ||
|
|
9494717c9e | ||
|
|
f559824c05 |
@@ -0,0 +1,35 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt, clippy
|
||||||
|
- run: cargo fmt --check
|
||||||
|
- run: cargo clippy --all-features --all-targets -- -D warnings
|
||||||
|
|
||||||
|
test:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt, clippy
|
||||||
|
- run: cargo fmt --check
|
||||||
|
- run: cargo clippy --all-features --all-targets -- -D warnings
|
||||||
|
- run: cargo test --all-features
|
||||||
|
- run: cargo doc --no-deps --all-features
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
components: rustfmt, clippy
|
|
||||||
- run: cargo fmt --check
|
|
||||||
- run: cargo clippy --all-features --all-targets -- -D warnings
|
|
||||||
- run: cargo test --all-features
|
|
||||||
- run: cargo doc --no-deps --all-features
|
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
Two crates:
|
||||||
|
|
||||||
|
- **`outline-sdk`** (root, lib name `outline`) — an async Rust client library for the
|
||||||
|
[Outline](https://www.getoutline.com) knowledge base API. Pure API layer, no GUI/CLI code.
|
||||||
|
- **`cli/`** (`outline-cli`) — a minimal terminal client (`login`, `search`, `get`) built on
|
||||||
|
top of the library via a path dependency. **Separate Cargo workspace** with its own
|
||||||
|
`Cargo.lock` — commands run from the root do not build/test the CLI, and vice versa.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
Run from the repo root (`outline-sdk`):
|
||||||
|
```sh
|
||||||
|
cargo fmt --check
|
||||||
|
cargo clippy --all-features --all-targets -- -D warnings
|
||||||
|
cargo test --all-features
|
||||||
|
cargo doc --no-deps --all-features
|
||||||
|
```
|
||||||
|
Run a single test: `cargo test --all-features <test_name>` (e.g. `cargo test --all-features normalizes_bare_host`).
|
||||||
|
|
||||||
|
Live smoke tests against the real API are `#[ignore]`d by default (see `tests/live.rs`):
|
||||||
|
```sh
|
||||||
|
OUTLINE_API_KEY=ol_api_... cargo test -- --ignored
|
||||||
|
```
|
||||||
|
|
||||||
|
Run from `cli/` (separate workspace, `cd cli` first):
|
||||||
|
```sh
|
||||||
|
cargo build --release # binary at target/release/outline
|
||||||
|
cargo fmt --check
|
||||||
|
cargo clippy --all-features --all-targets -- -D warnings
|
||||||
|
```
|
||||||
|
|
||||||
|
CI (`.github/workflows/ci.yml`) runs fmt check, clippy (`-D warnings`), `cargo test --all-features`,
|
||||||
|
and `cargo doc` for the root crate only.
|
||||||
|
|
||||||
|
## Architecture (root crate)
|
||||||
|
|
||||||
|
The Outline API is **RPC-style**: every endpoint is `POST /api/<resource>.<action>` with a JSON
|
||||||
|
body and a `{ok, data, pagination, policies}` envelope — there's no REST-y resource routing to
|
||||||
|
reason about. The whole crate is built around that one shape:
|
||||||
|
|
||||||
|
- **`client.rs`** — `Client` holds a `reqwest::Client`, base URL, and `Auth`, wrapped in an `Arc`
|
||||||
|
(cheap to clone, shared connection pool). All real HTTP happens through `Client::send`, which
|
||||||
|
three thin wrappers sit on top of: `rpc` (single object → `data`), `rpc_list` (list → `Page`),
|
||||||
|
`rpc_ok` (fire-and-forget, e.g. `delete`). Every endpoint is declared as a `const MethodDef`
|
||||||
|
(name + `idempotent` flag) at the top of its `api/*.rs` file and passed into one of these three.
|
||||||
|
- **`envelope.rs`** — the `{data, pagination, policies}` wrapper every success response is
|
||||||
|
deserialized into before being unwrapped by `Client::rpc`/`rpc_list`.
|
||||||
|
- **`api/`** — one module per resource (`auth`, `documents`, `collections`, `users`), each exposed
|
||||||
|
as `client.<resource>()` returning a `*Api<'a>` struct borrowing the client. List/search/create/
|
||||||
|
update calls return a **builder** (e.g. `ListDocuments`, `SearchDocuments`) with chained setter
|
||||||
|
methods and a terminal `.send()` (single page) or `.paginate()` (see below). Adding a new
|
||||||
|
endpoint means: add a `const MethodDef`, add a method on the resource's `*Api` struct, and (for
|
||||||
|
list/search) add a params struct implementing `Paginated` plus a builder.
|
||||||
|
- **`page.rs`** — pagination is generic over any params type implementing `Paginated` (i.e.
|
||||||
|
embeds `PageParams` via `#[serde(flatten)]`). `Paginator::next_page` advances `offset` itself
|
||||||
|
using the returned item count (a page shorter than the requested limit means done) — it does
|
||||||
|
**not** trust the API's `nextPath` hint, since that field isn't part of the published spec.
|
||||||
|
`paginate()` also gets a `into_stream()` under the `stream` feature.
|
||||||
|
- **`error.rs`** — single `Error` enum (`Config`, `Transport`, `Decode`, `Api`). `Error::kind()`
|
||||||
|
gives a coarse `ErrorKind` (maps HTTP status *and* the API's own `error` string code, since
|
||||||
|
Outline doesn't always use the "right" status code) for match-free handling; `is_retryable()`
|
||||||
|
and `retry_after()` build on top of it.
|
||||||
|
- **`models/`** — response types, deliberately decoupled from `Client` so they can be held
|
||||||
|
independently of a live connection (e.g. in GUI app state). Two forward-compatibility patterns
|
||||||
|
used throughout, because the API is unversioned and self-hosted instances vary in age:
|
||||||
|
- `Id` is a transparent `String` newtype, not `uuid::Uuid` — an unexpected id format from an
|
||||||
|
older/newer server must not fail deserialization of a whole response.
|
||||||
|
- String enums carry an `Unknown(UnknownVariant)` variant via `#[serde(untagged)]` instead of
|
||||||
|
`#[serde(other)]`, so an unrecognized value round-trips instead of causing a hard failure.
|
||||||
|
- **`rate_limit.rs`** — parses `RateLimit-*`/`Retry-After` response headers; attached to
|
||||||
|
`Error::Api` so callers can back off correctly.
|
||||||
|
- **`spec/spec3.json`** — a vendored copy of Outline's OpenAPI spec. `tests/spec_coverage.rs`
|
||||||
|
asserts every implemented RPC method name still exists as a path in it, catching typos and
|
||||||
|
upstream renames without hitting the network.
|
||||||
|
|
||||||
|
Feature flags of note: `stream` (pagination as a `futures_core::Stream`), `rustls-tls` /
|
||||||
|
`native-tls` (mutually exclusive TLS backend choice), `uuid` (`Id::as_uuid()`), `tracing`,
|
||||||
|
`multipart`.
|
||||||
|
|
||||||
|
## Architecture (`cli/`)
|
||||||
|
|
||||||
|
- **`cli.rs`** — clap `Parser`/`Subcommand` definitions only; no logic.
|
||||||
|
- **`commands/`** — one module per subcommand, dispatched from `commands/mod.rs`.
|
||||||
|
- **`config.rs`** — non-secret settings (currently just `base_url`) in
|
||||||
|
`~/.config/outline-cli/config.toml`. Precedence for the effective base URL:
|
||||||
|
`--base-url`/`$OUTLINE_URL` > saved config > Outline Cloud default.
|
||||||
|
- **`secret.rs`** — the *only* module that talks to the FreeDesktop Secret Service (GNOME
|
||||||
|
Keyring/KWallet) for the API key. The API key is never written to the config file. Swapping
|
||||||
|
the secret backend later means rewriting only this file.
|
||||||
|
- Rendered documents are cached in `~/.cache/outline-cli/` (mode `0600`) rather than `/tmp`,
|
||||||
|
since documents may be confidential.
|
||||||
|
|
||||||
|
## Testing conventions
|
||||||
|
|
||||||
|
- `tests/common/mod.rs::client_for` spins up a `wiremock::MockServer` and returns a `Client`
|
||||||
|
pointed at it with a dummy API key — the standard fixture for every non-live integration test.
|
||||||
|
- `tests/fixtures/` holds canned JSON response bodies.
|
||||||
|
- `tests/live.rs` is the only suite that hits the real API; it's `#[ignore]`d and requires
|
||||||
|
`OUTLINE_API_KEY`.
|
||||||
|
- Unit tests for pure logic (e.g. base-URL normalization in `builder.rs`, config precedence in
|
||||||
|
`cli/src/config.rs`) live inline in `#[cfg(test)] mod tests` next to the code they test, not
|
||||||
|
under `tests/`.
|
||||||
Reference in New Issue
Block a user