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
[]
= "0.2"
= { = "1", = ["macros", "rt-multi-thread"] }
Quickstart
use Url;
use ;
# async
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.
# use ;
#
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.
# use ;
# async
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:
# use ;
# async
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.