Expand description
§rp-supabase-storage
Async Rust client for the Supabase Storage API. It manages buckets, uploads and downloads objects, and creates signed and public URLs.
§Install
[dependencies]
rp-supabase-storage = "0.2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }§Quickstart
use rp_supabase_storage::url::Url;
use rp_supabase_storage::{BucketOptions, FileOptions, ListOptions, StorageClient};
let project = Url::parse("https://abc.supabase.co/")?;
let storage = StorageClient::new(&project, "service-role-key")?;
// Create a private bucket that accepts PNG images up to 1 MiB.
let options = BucketOptions {
public: false,
file_size_limit: Some(1024 * 1024),
allowed_mime_types: Some(vec!["image/png".to_owned()]),
};
storage.create_bucket("avatars", &options).await?;
let avatars = storage.from("avatars");
// Upload an object.
let file = FileOptions {
content_type: Some("image/png".to_owned()),
cache_control: 3600,
upsert: true,
};
avatars.upload("users/1.png", vec![0_u8; 16], &file).await?;
// Share it for 60 seconds.
let link = avatars.create_signed_url("users/1.png", 60).await?;
println!("{link}");
// Download, list, and remove.
let bytes = avatars.download("users/1.png").await?;
let entries = avatars.list("users", &ListOptions::default()).await?;
avatars.remove(&["users/1.png"]).await?;Use StorageClient::new_with_client to reuse an existing reqwest::Client and its connection pool.
The default client from StorageClient::new follows redirects (up to 10) only to the same origin, so
the API key never goes to another host; a client you pass in is used unchanged.
§Auth model
Every request sends the apikey header. The Authorization header depends on the key and token:
StorageClient::newwith a legacy JWT key (anon or service role) also sendsAuthorization: Bearer <api_key>. With the service role key, the client bypasses row level security (RLS). Use it only on a server.StorageClient::newwith a new-format key (sb_publishable_...orsb_secret_...) sends noAuthorizationheader. These keys are not JWTs; the gateway checks theapikeyheader.with_access_token(user_jwt)returns a client that sends the user’s JWT as the bearer token. Storage then applies the RLS policies onstorage.objectsandstorage.bucketsfor that user. Use the anon key plus a user token in code that acts for a user.
let storage = StorageClient::new(&Url::parse("https://abc.supabase.co/")?, "anon-key")?;
let as_user = storage.with_access_token(user_jwt)?;public_url sends no request. The URL works only when the bucket is public. For private
buckets, call create_signed_url or create_signed_urls. Both use the batch signing endpoint,
because the single-object endpoint returns an invalid signature for keys with ?. The API
returns signed URLs relative to /storage/v1 with the object key unescaped; this crate rebuilds
each URL from the bucket and the encoded object path, then appends the token. Keys with ? and
spaces work. The Storage server itself rejects some characters in keys, for example #.
BucketOptions fields are always sent. On update_bucket, None removes an existing file size
limit or MIME type list. FileOptions::default() sends Cache-Control: max-age=3600.
§Object paths
Separate path segments with /, for example users/1.png. The client percent-encodes each
segment once. It rejects empty paths, empty segments (a//b, leading or trailing /), . and
.., tabs, carriage returns, and line feeds with StorageError::InvalidPath before it sends a
request.
§Errors
All operations return StorageError:
Api { status, body }: the server answered with a non-success status.bodyisApiErrorBody::Storagewith optionalstatus_code,code,error, andmessagewhen the server sent the standard Storage error JSON, elseApiErrorBody::Rawwith the body text.InvalidPath: a bucket id or object path is not valid.MissingSignedToken: a signed URL from the server has no?token=query.SignFailed { path, message }:create_signed_urlgot no URL for the object, for example because it does not exist or the caller cannot read it.Http,Json,Url,InvalidHeader,InvalidBaseUrl: transport, decoding, and input errors.
match storage.from("blobs").upload("a.bin", vec![1_u8], &FileOptions::default()).await {
Ok(_) => {}
Err(error) => match error.code() {
Some(StorageErrorCode::KeyAlreadyExists | StorageErrorCode::ResourceAlreadyExists) => {
println!("already uploaded");
}
_ => return Err(error),
},
}StorageError::code() returns the body’s machine-readable code as a StorageErrorCode. A code
this crate does not know is StorageErrorCode::Other.
Most object errors arrive as HTTP 400 with the real status in the body’s statusCode.
StorageError::api_status() returns that status, or the HTTP status when the body has none.
Bucket::remove is the bulk endpoint. It skips missing paths and paths that row level security
hides, without an error. Bucket::remove_object deletes one object and fails when nothing is
deleted: api_status() is 404 and code() is NoSuchKey for a missing object, and
api_status() is 403 and code() is AccessDenied for a denied delete.
Bucket::download reads the whole body into memory. Bucket::download_stream returns after the
headers arrive. Read the body with chunk() or into_stream(), and stop when the byte count
passes your limit:
let mut download = bucket.download_stream("blobs/1.bin").await?;
let mut body = Vec::new();
while let Some(chunk) = download.chunk().await? {
if body.len() + chunk.len() > limit {
return Ok(None);
}
body.extend_from_slice(&chunk);
}§Limits
- No resumable (TUS) uploads. Each upload sends the whole body in one request.
- No image transformations and no S3 protocol support.
- Bucket and object operations only; no analytics or vector buckets.
Re-exports§
Structs§
- Bucket
- Object operations in one bucket. Create it with
StorageClient::from. - Bucket
Info - A bucket as returned by the Storage API.
- Bucket
Options - Settings for
StorageClient::create_bucketandStorageClient::update_bucket. - File
Object - An object or folder entry. Folder entries have no id, timestamps, or metadata.
- File
Options - Settings for
Bucket::uploadandBucket::update. - List
Options - Settings for
Bucket::list.Nonefields use the server defaults (limit 100, offset 0, sorted by name ascending). - Object
Download - Body of an object download that is read in chunks. Create it with
Bucket::download_stream. The status is already checked, so only transport errors remain. - Object
Key - Key of a stored object.
- Signed
Url - One entry of
Bucket::create_signed_urls. - SortBy
- Sort order for
ListOptions. - Storage
Client - Client for the Supabase Storage API at
<project>/storage/v1. - Storage
Error Body - Standard Storage error JSON, for example
{"statusCode": "409", "code": "KeyAlreadyExists", "error": "Duplicate", "message": "..."}. Every field is optional because some responses omit them.
Enums§
- ApiError
Body - Body of a failed Storage API response.
- Path
Error - Reason for
StorageError::InvalidPath. - Sort
Column - Sortable column.
- Sort
Order - Sort direction.
- Storage
Error - Errors returned by
StorageClientandBucket. - Storage
Error Code - Machine-readable
codeof a Storage API error, from theErrorCodeenum of supabase/storage. A code this crate does not know is kept inOther.