unitycatalog_storage_proxy/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! Same-origin Unity Catalog **storage byte-proxy**.
3//!
4//! A browser/wasm query engine points a stock
5//! [`object_store::http::HttpStore`] at this proxy instead of reaching cloud
6//! object storage directly. The proxy authorizes the request, vends a scoped
7//! credential, and **streams object bytes** (read + write) to/from storage
8//! server-side. Because the wasm side never speaks a cloud-native protocol
9//! (SigV4 / Azure SAS / GCS auth) this makes S3/GCP work on wasm for free and
10//! removes the per-cloud CORS configuration burden — the browser only ever talks
11//! to its own origin.
12//!
13//! The crate owns the proxy *semantics* behind a narrow [port](StorageProxyBackend):
14//! the wire contract (ranged `GET` / `HEAD` / whole-object `PUT` with a
15//! server-enforced `If-Match`), the streaming HTTP mapping, and the
16//! confused-deputy path-scope guard. Any server serves the identical surface by
17//! implementing [`StorageProxyBackend`] over its own storage, credential vending,
18//! and authorization.
19//!
20//! # Wire contract
21//!
22//! ```text
23//! GET {base}/{securable}/{key} -> 200 + body + Content-Length + ETag
24//! GET {base}/{securable}/{key} Range: .. -> 206 + Content-Range (+ Accept-Ranges)
25//! HEAD {base}/{securable}/{key} -> headers only
26//! PUT {base}/{securable}/{key} [body] -> 200 + ETag (whole-object upload)
27//! ```
28//!
29//! `{securable}` is a single typed path segment — `table:<fqn>`, `vol:<fqn>`, or
30//! `path:<percent-encoded cloud URL>` (see [`Securable`]) — and `{key}` is the
31//! object key relative to that securable's root. `GET`/`HEAD` open a read-scoped
32//! store; `PUT` opens a write-scoped store. A conditional write rides an
33//! `If-Match` HTTP header the proxy relays to storage, mapping a mismatch to
34//! `412 Precondition Failed`.
35//!
36//! # Example
37//!
38//! Implement [`StorageProxyBackend`] over your own storage, then mount it with
39//! [`router_with_context`]. Here the in-memory backend from the `testing` feature
40//! stands in for a real one:
41//!
42//! ```
43//! # #[cfg(feature = "testing")] {
44//! use std::sync::Arc;
45//! use unitycatalog_storage_proxy::{ContextExtractor, router_with_context_at};
46//! use unitycatalog_storage_proxy::testing::InMemoryStorageProxyBackend;
47//!
48//! let backend = InMemoryStorageProxyBackend::table("main.default.events");
49//! let extract_cx: ContextExtractor<()> = Arc::new(|_parts| Box::pin(async { Ok(()) }));
50//! // `base = ""` yields relative routes; a host adds the `/storage-proxy` prefix
51//! // via `.nest`. Pass `"/storage-proxy"` instead to `.merge` the surface directly.
52//! let proxy: axum::Router = router_with_context_at("", Arc::new(backend), extract_cx);
53//! # let _ = proxy;
54//! # }
55//! ```
56
57#[cfg(feature = "bin")]
58pub mod auth;
59#[cfg(feature = "server")]
60pub mod backend;
61#[cfg(feature = "bin")]
62pub mod cli;
63#[cfg(feature = "client-arm")]
64pub mod client;
65#[cfg(feature = "server")]
66pub mod config;
67#[cfg(feature = "server")]
68pub mod error;
69#[cfg(feature = "server")]
70pub mod handler;
71#[cfg(feature = "server")]
72pub mod router;
73#[cfg(feature = "testing")]
74pub mod testing;
75
76#[cfg(feature = "bin")]
77pub use auth::{AuthLayer, AuthMode, ForwardedIdentity};
78#[cfg(feature = "server")]
79pub use backend::{
80 ForwardedUser, ProxyCapabilities, ProxyReq, ProxyVerb, Securable, StorageProxyBackend,
81};
82#[cfg(feature = "client-arm")]
83pub use client::UnityFactoryProxyBackend;
84#[cfg(feature = "server")]
85pub use error::{ProxyError, ProxyResult};
86#[cfg(feature = "server")]
87pub use handler::StorageProxyHandler;
88#[cfg(feature = "server")]
89pub use router::{
90 ContextExtractor, router_from_extension, router_from_extension_at, router_with_context,
91 router_with_context_at,
92};