Add Outline API client library foundation

Implements a first vertical slice of the Outline RPC API (auth, documents,
collections, users) covering the structural patterns used across the whole
API: single object, paginated list, tree, create/update, delete, search.
Chosen as the basis for future CLI and GUI clients built on top of this crate.

- Handwritten client (not codegen) against the vendored OpenAPI spec, since
  Outline's API is uniformly POST /api/<resource>.<action> with JSON bodies
  and inline/anonymous schemas that generators handle poorly
- Async (reqwest + tokio) Client, cheaply cloneable, no &mut self methods
- thiserror-based Error with ErrorKind classification and boxed API error
  context; forward-compatible models (unknown fields ignored, unknown
  string-enum values preserved via a catch-all variant) since the API is
  unversioned and self-hosted instances vary in age
- Pagination via Page<T>/Paginator with next_page/collect_all/into_stream
- wiremock-based test suite plus a spec-coverage test guarding against typos
  in RPC method names
This commit is contained in:
2026-07-30 13:00:09 +02:00
commit dec8face54
38 changed files with 13760 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
//! Lists every document in a collection, following pagination automatically.
//!
//! ```text
//! OUTLINE_API_KEY=ol_api_... cargo run --example list_documents -- <collection-id>
//! ```
use std::env;
use outline::Client;
#[tokio::main]
async fn main() -> outline::Result<()> {
let collection_id = env::args()
.nth(1)
.expect("usage: list_documents <collection-id>");
let client = Client::from_env()?;
let mut documents = client
.documents()
.list()
.collection_id(collection_id)
.paginate();
while let Some(page) = documents.next_page().await? {
for document in page.items {
println!("{}\t{}", document.id, document.title);
}
}
Ok(())
}
+23
View File
@@ -0,0 +1,23 @@
//! Runs a full-text search and prints the top results with a short snippet.
//!
//! ```text
//! OUTLINE_API_KEY=ol_api_... cargo run --example search -- "hiring practices"
//! ```
use std::env;
use outline::Client;
#[tokio::main]
async fn main() -> outline::Result<()> {
let query = env::args().nth(1).expect("usage: search <query>");
let client = Client::from_env()?;
let page = client.documents().search(query).limit(10).send().await?;
for result in page.items {
let context = result.context.as_deref().unwrap_or("");
println!("{}\n {}\n", result.document.title, context);
}
Ok(())
}