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
+38
View File
@@ -0,0 +1,38 @@
//! Verifies that every RPC method name used by this crate still exists as a
//! path in the vendored OpenAPI spec, catching typos and upstream renames.
const IMPLEMENTED_METHODS: &[&str] = &[
"auth.info",
"auth.config",
"documents.info",
"documents.list",
"documents.search",
"documents.create",
"documents.update",
"documents.delete",
"collections.list",
"collections.info",
"collections.documents",
"users.list",
"users.info",
];
#[test]
fn implemented_methods_exist_in_vendored_spec() {
let spec_raw = include_str!("../spec/spec3.json");
let spec: serde_json::Value = serde_json::from_str(spec_raw).unwrap();
let paths = spec["paths"]
.as_object()
.expect("spec should have a `paths` object");
let missing: Vec<&str> = IMPLEMENTED_METHODS
.iter()
.copied()
.filter(|method| !paths.contains_key(&format!("/{method}")))
.collect();
assert!(
missing.is_empty(),
"methods missing from vendored spec: {missing:?}"
);
}