use super::backend::{BackendError, BackendResult};
use super::config::ClientConfig;
use reqwest::{Body, Client as HttpClient, Method, RequestBuilder, Response};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
pub mod storage_routes {
pub const CATALOGS: &str = "/storage/catalogs";
pub const CATALOG_BY_ID: &str = "/storage/catalogs/{id}";
pub const CREDENTIALS: &str = "/storage/credentials";
pub const FILE_UPLOAD_URL: &str = "/storage/files/upload-url";
pub const FILE_UPLOAD_URLS: &str = "/storage/files/upload-urls";
pub const FILE_LIST: &str = "/storage/files/list";
pub const FILE_SEARCH: &str = "/storage/files/search";
pub const FILE_BY_ID: &str = "/storage/files/{file_id}";
pub const FILE_CONFIRM_UPLOAD: &str = "/storage/files/{file_id}/confirm-upload";
pub const FILE_UPLOAD_BINARY: &str = "/storage/files/{file_id}/upload";
pub const FILE_COPY: &str = "/storage/files/{file_id}/copy";
pub const FILE_RESTORE: &str = "/storage/files/{file_id}/restore";
pub const FILE_PURGE: &str = "/storage/files/{file_id}/purge";
pub const FILE_URL: &str = "/storage/files/{file_id}/url";
pub const FILE_PUBLIC_URL: &str = "/storage/files/{file_id}/public-url";
pub const FILE_PROXY_URL: &str = "/storage/files/{file_id}/proxy-url";
pub const FILE_PROXY: &str = "/storage/files/{file_id}/proxy";
pub const FILE_VISIBILITY: &str = "/storage/files/{file_id}/visibility";
pub const FILE_DELETE_MANY: &str = "/storage/files/delete-many";
pub const FILE_UPDATE_MANY: &str = "/storage/files/update-many";
pub const FILE_VISIBILITY_MANY: &str = "/storage/files/visibility-many";
pub const FILE_VERSIONS: &str = "/storage/files/{file_id}/versions";
pub const FILE_VERSION_RESTORE: &str = "/storage/files/{file_id}/versions/{version_id}/restore";
pub const FILE_VERSION_DELETE: &str = "/storage/files/{file_id}/versions/{version_id}";
pub const FILE_RETENTION: &str = "/storage/files/{file_id}/retention";
pub const FOLDER_LIST: &str = "/storage/folders/list";
pub const FOLDER_TREE: &str = "/storage/folders/tree";
pub const FOLDER_DELETE: &str = "/storage/folders/delete";
pub const FOLDER_MOVE: &str = "/storage/folders/move";
pub const PERMISSION_LIST: &str = "/storage/permissions/list";
pub const PERMISSION_GRANT: &str = "/storage/permissions/grant";
pub const PERMISSION_REVOKE: &str = "/storage/permissions/revoke";
pub const PERMISSION_CHECK: &str = "/storage/permissions/check";
pub const OBJECTS: &str = "/storage/objects";
pub const OBJECT_HEAD: &str = "/storage/objects/head";
pub const OBJECT_EXISTS: &str = "/storage/objects/exists";
pub const OBJECT_VALIDATE: &str = "/storage/objects/validate";
pub const OBJECT_UPDATE: &str = "/storage/objects/update";
pub const OBJECT_COPY: &str = "/storage/objects/copy";
pub const OBJECT_URL: &str = "/storage/objects/url";
pub const OBJECT_PUBLIC_URL: &str = "/storage/objects/public-url";
pub const OBJECT_DELETE: &str = "/storage/objects/delete";
pub const OBJECT_UPLOAD_URL: &str = "/storage/objects/upload-url";
pub const OBJECT_POST_POLICY: &str = "/storage/objects/post-policy";
pub const OBJECT_VERSIONS: &str = "/storage/objects/versions";
pub const OBJECT_VERSION_RESTORE: &str = "/storage/objects/versions/restore";
pub const OBJECT_VERSION_DELETE: &str = "/storage/objects/versions/delete";
pub const OBJECT_FOLDER_CREATE: &str = "/storage/objects/folder";
pub const OBJECT_FOLDER_DELETE: &str = "/storage/objects/folder/delete";
pub const OBJECT_FOLDER_RENAME: &str = "/storage/objects/folder/rename";
pub const BUCKET_LIST: &str = "/storage/buckets/list";
pub const BUCKET_CREATE: &str = "/storage/buckets/create";
pub const BUCKET_DELETE: &str = "/storage/buckets/delete";
pub const BUCKET_LIFECYCLE: &str = "/storage/buckets/lifecycle";
pub const BUCKET_LIFECYCLE_SET: &str = "/storage/buckets/lifecycle/set";
pub const BUCKET_LIFECYCLE_DELETE: &str = "/storage/buckets/lifecycle/delete";
pub const BUCKET_POLICY: &str = "/storage/buckets/policy";
pub const BUCKET_POLICY_SET: &str = "/storage/buckets/policy/set";
pub const BUCKET_POLICY_DELETE: &str = "/storage/buckets/policy/delete";
pub const BUCKET_PUBLIC_ACCESS: &str = "/storage/buckets/public-access";
pub const BUCKET_PUBLIC_ACCESS_SET: &str = "/storage/buckets/public-access/set";
pub const BUCKET_PUBLIC_ACCESS_DELETE: &str = "/storage/buckets/public-access/delete";
pub const BUCKET_CORS: &str = "/storage/buckets/cors";
pub const BUCKET_CORS_SET: &str = "/storage/buckets/cors/set";
pub const BUCKET_CORS_DELETE: &str = "/storage/buckets/cors/delete";
pub const MULTIPART_CREATE: &str = "/storage/multipart/create";
pub const MULTIPART_SIGN_PART: &str = "/storage/multipart/sign-part";
pub const MULTIPART_COMPLETE: &str = "/storage/multipart/complete";
pub const MULTIPART_ABORT: &str = "/storage/multipart/abort";
pub const MULTIPART_LIST_PARTS: &str = "/storage/multipart/list-parts";
pub const AUDIT_LIST: &str = "/storage/audit/list";
}
pub type StorageValue = Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AthenaStorageEnvelope<T> {
pub status: String,
pub message: String,
pub data: T,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StorageServerSideEncryptionOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub server_side_encryption: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sse: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ssekms_key_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kms_key_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bucket_key_enabled: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct S3ConnectionConfig {
pub endpoint: String,
pub region: String,
pub access_key_id: String,
pub secret_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageObjectBaseRequest {
#[serde(flatten)]
pub connection: S3ConnectionConfig,
pub bucket: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageObjectRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
pub key: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StorageFileFilters {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offset: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visibility: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bucket: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_prefix: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ManagedFileRecord {
#[serde(flatten)]
pub fields: BTreeMap<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PresignedFileUrlResponse {
pub file_id: String,
pub bucket: String,
pub storage_key: String,
pub purpose: String,
pub url: String,
#[serde(default)]
pub headers: BTreeMap<String, String>,
pub expires_at: String,
pub expires_at_epoch_seconds: i64,
pub expires_in: u64,
pub cache_hit: bool,
pub cache_layer: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageUploadUrlResponse {
pub file: ManagedFileRecord,
pub upload: PresignedFileUrlResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageBatchUploadUrlResponse {
pub files: Vec<StorageUploadUrlResponse>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageListFilesResponse {
pub files: Vec<ManagedFileRecord>,
pub count: usize,
#[serde(default)]
pub total: Option<usize>,
#[serde(default)]
pub limit: Option<usize>,
#[serde(default)]
pub offset: Option<usize>,
#[serde(default)]
pub next_offset: Option<usize>,
#[serde(default)]
pub has_more: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageFileMutationResponse {
pub file: ManagedFileRecord,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageFileMutationManyResponse {
pub files: Vec<ManagedFileRecord>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageFolderMutationResponse {
pub s3_id: String,
pub prefix: String,
pub processed_files: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoragePermissionListResponse {
pub permissions: Vec<Value>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoragePermissionCheckResponse {
pub allowed: bool,
pub permission: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageAuditListResponse {
pub events: Vec<Value>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateStorageUploadUrlRequest {
pub s3_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bucket: Option<String>,
pub storage_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size_bytes: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub public: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visibility: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
#[serde(flatten)]
pub encryption: StorageServerSideEncryptionOptions,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateStorageUploadUrlsRequest {
pub files: Vec<CreateStorageUploadUrlRequest>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListStorageFilesRequest {
pub s3_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
#[serde(flatten)]
pub filters: StorageFileFilters,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UpdateStorageFileRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub storage_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bucket: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size_bytes: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checksum_sha256: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visibility: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SetStorageFileVisibilityRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub public: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visibility: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteManyStorageFilesRequest {
pub file_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateManyStorageFilesRequest {
pub file_ids: Vec<String>,
#[serde(flatten)]
pub update: UpdateStorageFileRequest,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SetManyStorageFileVisibilityRequest {
pub file_ids: Vec<String>,
#[serde(flatten)]
pub visibility: SetStorageFileVisibilityRequest,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfirmStorageUploadRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size_bytes: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checksum_sha256: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchStorageFilesRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub s3_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
#[serde(flatten)]
pub filters: StorageFileFilters,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CopyStorageFileRequest {
pub storage_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bucket: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visibility: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
#[serde(flatten)]
pub encryption: StorageServerSideEncryptionOptions,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListStorageFoldersRequest {
pub s3_id: String,
pub prefix: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MoveStorageFolderRequest {
pub s3_id: String,
pub from_prefix: String,
pub to_prefix: String,
}
pub type DeleteStorageFolderRequest = ListStorageFoldersRequest;
pub type TreeStorageFoldersRequest = ListStorageFoldersRequest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoragePermissionListRequest {
pub file_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoragePermissionGrantRequest {
pub file_id: String,
pub principal_type: String,
pub principal_id: String,
pub permission: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoragePermissionRevokeRequest {
pub file_id: String,
pub principal_type: String,
pub principal_id: String,
pub permission: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoragePermissionCheckRequest {
pub file_id: String,
pub permission: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageMultipartCreateRequest {
pub file_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(flatten)]
pub encryption: StorageServerSideEncryptionOptions,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageMultipartSignPartRequest {
pub file_id: String,
pub upload_id: String,
pub part_number: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageMultipartCompletePartInput {
pub part_number: i32,
pub etag: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageMultipartCompleteRequest {
pub file_id: String,
pub upload_id: String,
pub parts: Vec<StorageMultipartCompletePartInput>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageMultipartUploadRequest {
pub file_id: String,
pub upload_id: String,
}
pub type StorageMultipartAbortRequest = StorageMultipartUploadRequest;
pub type StorageMultipartListPartsRequest = StorageMultipartUploadRequest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageObjectValidateRequest {
#[serde(flatten)]
pub object: StorageObjectRequest,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checksum_sha256: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub etag: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageObjectCopyRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
pub source_key: String,
pub destination_key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub destination_bucket: Option<String>,
#[serde(flatten)]
pub encryption: StorageServerSideEncryptionOptions,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageObjectPublicUrlRequest {
#[serde(flatten)]
pub object: StorageObjectRequest,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub public_base_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub force_path_style: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageListObjectsRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub delimiter: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub continuation_token: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_keys: Option<i32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StorageUpdateObjectFields {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub acl: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_control: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_disposition: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_encoding: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<BTreeMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageUpdateObjectRequest {
#[serde(flatten)]
pub object: StorageObjectRequest,
#[serde(flatten)]
pub fields: StorageUpdateObjectFields,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoragePresignUploadRequest {
#[serde(flatten)]
pub object: StorageObjectRequest,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(flatten)]
pub encryption: StorageServerSideEncryptionOptions,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageObjectVersionListRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_keys: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_marker: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version_id_marker: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub delimiter: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageObjectVersionMutationRequest {
#[serde(flatten)]
pub object: StorageObjectRequest,
pub version_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageSignedPostPolicyRequest {
#[serde(flatten)]
pub object: StorageObjectRequest,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_size: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_size: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_in: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub public_base_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub force_path_style: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub success_action_status: Option<String>,
#[serde(flatten)]
pub encryption: StorageServerSideEncryptionOptions,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageObjectFolderCreateRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
pub prefix: String,
}
pub type StorageObjectFolderDeleteRequest = StorageObjectFolderCreateRequest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageObjectFolderRenameRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
pub from_prefix: String,
pub to_prefix: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageBucketCorsRuleInput {
pub allowed_origins: Vec<String>,
pub allowed_methods: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_headers: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub expose_headers: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_age_seconds: Option<u32>,
}
pub type StorageBucketRequest = StorageObjectBaseRequest;
pub type StorageBucketCorsRequest = StorageObjectBaseRequest;
pub type StorageBucketLifecycleRequest = StorageObjectBaseRequest;
pub type StorageBucketPolicyRequest = StorageObjectBaseRequest;
pub type StoragePublicAccessBlockRequest = StorageObjectBaseRequest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageSetBucketCorsRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
pub rules: Vec<StorageBucketCorsRuleInput>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageBucketLifecycleRuleInput {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expiration_days: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expired_object_delete_marker: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub noncurrent_version_expiration_days: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub abort_incomplete_multipart_upload_days: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageSetBucketLifecycleRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
pub rules: Vec<StorageBucketLifecycleRuleInput>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageSetBucketPolicyRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
pub policy: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageSetPublicAccessBlockRequest {
#[serde(flatten)]
pub base: StorageObjectBaseRequest,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub block_public_acls: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ignore_public_acls: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub block_public_policy: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub restrict_public_buckets: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageFileRetentionRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub retain_until: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub retain_until_date: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bypass_governance: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StorageAuditQueryRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offset: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct AthenaStorageClient {
base_url: String,
api_key: Option<String>,
client_name: Option<String>,
http: HttpClient,
}
impl AthenaStorageClient {
pub(crate) fn from_config(config: &ClientConfig) -> Self {
Self {
base_url: normalize_base_url(&config.connection.url),
api_key: config.connection.key.clone(),
client_name: config.client_name.clone(),
http: HttpClient::new(),
}
}
pub fn catalog(&self) -> StorageCatalogNamespace<'_> {
StorageCatalogNamespace { client: self }
}
pub fn credentials(&self) -> StorageCredentialsNamespace<'_> {
StorageCredentialsNamespace { client: self }
}
pub fn file(&self) -> StorageFileNamespace<'_> {
StorageFileNamespace { client: self }
}
pub fn folder(&self) -> StorageFolderNamespace<'_> {
StorageFolderNamespace { client: self }
}
pub fn permission(&self) -> StoragePermissionNamespace<'_> {
StoragePermissionNamespace { client: self }
}
pub fn object(&self) -> StorageObjectNamespace<'_> {
StorageObjectNamespace { client: self }
}
pub fn bucket(&self) -> StorageBucketNamespace<'_> {
StorageBucketNamespace { client: self }
}
pub fn multipart(&self) -> StorageMultipartNamespace<'_> {
StorageMultipartNamespace { client: self }
}
pub fn audit(&self) -> StorageAuditNamespace<'_> {
StorageAuditNamespace { client: self }
}
pub fn route(&self, path: &str) -> String {
format!("{}/{}", self.base_url, path.trim_start_matches('/'))
}
async fn raw<T>(&self, method: Method, path: &str, body: Option<Value>) -> BackendResult<T>
where
T: DeserializeOwned,
{
let request = self.request(method.clone(), path)?;
let request = if let Some(body) = body {
request.json(&body)
} else {
request
};
let response = request.send().await.map_err(storage_http_error)?;
decode_json_response(response, path).await
}
async fn athena<T>(&self, method: Method, path: &str, body: Option<Value>) -> BackendResult<T>
where
T: DeserializeOwned,
{
let envelope: AthenaStorageEnvelope<T> = self.raw(method, path, body).await?;
Ok(envelope.data)
}
async fn binary(
&self,
method: Method,
path: &str,
headers: Option<BTreeMap<String, String>>,
) -> BackendResult<Response> {
let mut request = self.request(method, path)?;
if let Some(headers) = headers {
for (name, value) in headers {
request = request.header(name, value);
}
}
let response = request.send().await.map_err(storage_http_error)?;
if response.status().is_success() {
return Ok(response);
}
let status = response.status();
let text = response.text().await.unwrap_or_default();
Err(BackendError::Generic(format!(
"storage request {path} failed with status {status}: {text}"
)))
}
async fn upload_body<T>(
&self,
path: &str,
body: Body,
headers: Option<BTreeMap<String, String>>,
) -> BackendResult<T>
where
T: DeserializeOwned,
{
let mut request = self.request(Method::PUT, path)?.body(body);
if let Some(headers) = headers {
for (name, value) in headers {
request = request.header(name, value);
}
}
let response = request.send().await.map_err(storage_http_error)?;
let envelope: AthenaStorageEnvelope<T> = decode_json_response(response, path).await?;
Ok(envelope.data)
}
fn request(&self, method: Method, path: &str) -> BackendResult<RequestBuilder> {
let api_key = self.api_key.as_deref().ok_or_else(|| {
BackendError::Generic(
"Athena storage requests require an API key on the client config".to_string(),
)
})?;
let mut request = self
.http
.request(method, self.route(path))
.header("x-athena-key", api_key)
.header("apikey", api_key)
.bearer_auth(api_key);
if let Some(client_name) = self
.client_name
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
request = request.header("x-athena-client", client_name);
}
Ok(request)
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageCatalogNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageCatalogNamespace<'_> {
pub async fn list(&self) -> BackendResult<Value> {
self.client
.raw(Method::GET, storage_routes::CATALOGS, None)
.await
}
pub async fn create(&self, input: Value) -> BackendResult<Value> {
self.client
.raw(Method::POST, storage_routes::CATALOGS, Some(input))
.await
}
pub async fn update(&self, id: &str, input: Value) -> BackendResult<Value> {
self.client
.raw(
Method::PATCH,
&path_param(storage_routes::CATALOG_BY_ID, "id", id),
Some(input),
)
.await
}
pub async fn delete(&self, id: &str) -> BackendResult<Value> {
self.client
.raw(
Method::DELETE,
&path_param(storage_routes::CATALOG_BY_ID, "id", id),
None,
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageCredentialsNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageCredentialsNamespace<'_> {
pub async fn list(&self) -> BackendResult<Value> {
self.client
.raw(Method::GET, storage_routes::CREDENTIALS, None)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageFileNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageFileNamespace<'_> {
pub async fn upload_url(
&self,
input: CreateStorageUploadUrlRequest,
) -> BackendResult<StorageUploadUrlResponse> {
self.client
.athena(
Method::POST,
storage_routes::FILE_UPLOAD_URL,
Some(to_value(input)?),
)
.await
}
pub async fn upload_urls(
&self,
input: CreateStorageUploadUrlsRequest,
) -> BackendResult<StorageBatchUploadUrlResponse> {
self.client
.athena(
Method::POST,
storage_routes::FILE_UPLOAD_URLS,
Some(to_value(input)?),
)
.await
}
pub async fn list(
&self,
input: ListStorageFilesRequest,
) -> BackendResult<StorageListFilesResponse> {
self.client
.athena(
Method::POST,
storage_routes::FILE_LIST,
Some(to_value(input)?),
)
.await
}
pub async fn search(
&self,
input: SearchStorageFilesRequest,
) -> BackendResult<StorageListFilesResponse> {
self.client
.athena(
Method::POST,
storage_routes::FILE_SEARCH,
Some(to_value(input)?),
)
.await
}
pub async fn get(&self, file_id: &str) -> BackendResult<StorageFileMutationResponse> {
self.client
.athena(
Method::GET,
&file_path(storage_routes::FILE_BY_ID, file_id),
None,
)
.await
}
pub async fn url(
&self,
file_id: &str,
purpose: Option<&str>,
) -> BackendResult<PresignedFileUrlResponse> {
self.client
.athena(
Method::GET,
&append_purpose(&file_path(storage_routes::FILE_URL, file_id), purpose),
None,
)
.await
}
pub async fn proxy_url(&self, file_id: &str, purpose: Option<&str>) -> BackendResult<Value> {
self.client
.athena(
Method::GET,
&append_purpose(&file_path(storage_routes::FILE_PROXY_URL, file_id), purpose),
None,
)
.await
}
pub async fn proxy(
&self,
file_id: &str,
purpose: Option<&str>,
headers: Option<BTreeMap<String, String>>,
) -> BackendResult<Response> {
self.client
.binary(
Method::GET,
&append_purpose(&file_path(storage_routes::FILE_PROXY, file_id), purpose),
headers,
)
.await
}
pub async fn proxy_bytes(
&self,
file_id: &str,
purpose: Option<&str>,
headers: Option<BTreeMap<String, String>>,
) -> BackendResult<Vec<u8>> {
let response = self.proxy(file_id, purpose, headers).await?;
response
.bytes()
.await
.map(|bytes| bytes.to_vec())
.map_err(storage_http_error)
}
pub async fn update(
&self,
file_id: &str,
input: UpdateStorageFileRequest,
) -> BackendResult<StorageFileMutationResponse> {
self.client
.athena(
Method::PATCH,
&file_path(storage_routes::FILE_BY_ID, file_id),
Some(to_value(input)?),
)
.await
}
pub async fn delete(&self, file_id: &str) -> BackendResult<StorageFileMutationResponse> {
self.client
.athena(
Method::DELETE,
&file_path(storage_routes::FILE_BY_ID, file_id),
None,
)
.await
}
pub async fn confirm_upload(
&self,
file_id: &str,
input: ConfirmStorageUploadRequest,
) -> BackendResult<StorageFileMutationResponse> {
self.client
.athena(
Method::POST,
&file_path(storage_routes::FILE_CONFIRM_UPLOAD, file_id),
Some(to_value(input)?),
)
.await
}
pub async fn upload_binary(
&self,
file_id: &str,
body: impl Into<Body>,
headers: Option<BTreeMap<String, String>>,
) -> BackendResult<StorageFileMutationResponse> {
self.client
.upload_body(
&file_path(storage_routes::FILE_UPLOAD_BINARY, file_id),
body.into(),
headers,
)
.await
}
pub async fn copy(
&self,
file_id: &str,
input: CopyStorageFileRequest,
) -> BackendResult<StorageFileMutationResponse> {
self.client
.athena(
Method::POST,
&file_path(storage_routes::FILE_COPY, file_id),
Some(to_value(input)?),
)
.await
}
pub async fn restore(&self, file_id: &str) -> BackendResult<StorageFileMutationResponse> {
self.client
.athena(
Method::POST,
&file_path(storage_routes::FILE_RESTORE, file_id),
Some(Value::Object(Default::default())),
)
.await
}
pub async fn purge(&self, file_id: &str) -> BackendResult<StorageFileMutationResponse> {
self.client
.athena(
Method::DELETE,
&file_path(storage_routes::FILE_PURGE, file_id),
None,
)
.await
}
pub async fn public_url(&self, file_id: &str) -> BackendResult<Value> {
self.client
.athena(
Method::GET,
&file_path(storage_routes::FILE_PUBLIC_URL, file_id),
None,
)
.await
}
pub async fn set_visibility(
&self,
file_id: &str,
input: SetStorageFileVisibilityRequest,
) -> BackendResult<StorageFileMutationResponse> {
self.client
.athena(
Method::PATCH,
&file_path(storage_routes::FILE_VISIBILITY, file_id),
Some(to_value(input)?),
)
.await
}
pub async fn delete_many(
&self,
input: DeleteManyStorageFilesRequest,
) -> BackendResult<StorageFileMutationManyResponse> {
self.client
.athena(
Method::POST,
storage_routes::FILE_DELETE_MANY,
Some(to_value(input)?),
)
.await
}
pub async fn update_many(
&self,
input: UpdateManyStorageFilesRequest,
) -> BackendResult<StorageFileMutationManyResponse> {
self.client
.athena(
Method::POST,
storage_routes::FILE_UPDATE_MANY,
Some(to_value(input)?),
)
.await
}
pub async fn set_visibility_many(
&self,
input: SetManyStorageFileVisibilityRequest,
) -> BackendResult<StorageFileMutationManyResponse> {
self.client
.athena(
Method::POST,
storage_routes::FILE_VISIBILITY_MANY,
Some(to_value(input)?),
)
.await
}
pub async fn versions(&self, file_id: &str) -> BackendResult<Value> {
self.client
.athena(
Method::GET,
&file_path(storage_routes::FILE_VERSIONS, file_id),
None,
)
.await
}
pub async fn restore_version(&self, file_id: &str, version_id: &str) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
&file_version_path(storage_routes::FILE_VERSION_RESTORE, file_id, version_id),
Some(Value::Object(Default::default())),
)
.await
}
pub async fn delete_version(&self, file_id: &str, version_id: &str) -> BackendResult<Value> {
self.client
.athena(
Method::DELETE,
&file_version_path(storage_routes::FILE_VERSION_DELETE, file_id, version_id),
None,
)
.await
}
pub async fn get_retention(
&self,
file_id: &str,
version_id: Option<&str>,
) -> BackendResult<Value> {
let path = append_query(
&file_path(storage_routes::FILE_RETENTION, file_id),
&[("version_id", version_id)],
);
self.client.athena(Method::GET, &path, None).await
}
pub async fn set_retention(
&self,
file_id: &str,
input: StorageFileRetentionRequest,
) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
&file_path(storage_routes::FILE_RETENTION, file_id),
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageFolderNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageFolderNamespace<'_> {
pub async fn list(&self, input: ListStorageFoldersRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::FOLDER_LIST,
Some(to_value(input)?),
)
.await
}
pub async fn tree(&self, input: TreeStorageFoldersRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::FOLDER_TREE,
Some(to_value(input)?),
)
.await
}
pub async fn delete(
&self,
input: DeleteStorageFolderRequest,
) -> BackendResult<StorageFolderMutationResponse> {
self.client
.athena(
Method::POST,
storage_routes::FOLDER_DELETE,
Some(to_value(input)?),
)
.await
}
pub async fn move_folder(
&self,
input: MoveStorageFolderRequest,
) -> BackendResult<StorageFolderMutationResponse> {
self.client
.athena(
Method::POST,
storage_routes::FOLDER_MOVE,
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StoragePermissionNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StoragePermissionNamespace<'_> {
pub async fn list(
&self,
input: StoragePermissionListRequest,
) -> BackendResult<StoragePermissionListResponse> {
self.client
.athena(
Method::POST,
storage_routes::PERMISSION_LIST,
Some(to_value(input)?),
)
.await
}
pub async fn grant(&self, input: StoragePermissionGrantRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::PERMISSION_GRANT,
Some(to_value(input)?),
)
.await
}
pub async fn revoke(&self, input: StoragePermissionRevokeRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::PERMISSION_REVOKE,
Some(to_value(input)?),
)
.await
}
pub async fn check(
&self,
input: StoragePermissionCheckRequest,
) -> BackendResult<StoragePermissionCheckResponse> {
self.client
.athena(
Method::POST,
storage_routes::PERMISSION_CHECK,
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageObjectNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageObjectNamespace<'_> {
pub fn folder(&self) -> StorageObjectFolderNamespace<'_> {
StorageObjectFolderNamespace {
client: self.client,
}
}
pub async fn list(&self, input: StorageListObjectsRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECTS,
Some(to_value(input)?),
)
.await
}
pub async fn head(&self, input: StorageObjectRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_HEAD,
Some(to_value(input)?),
)
.await
}
pub async fn exists(&self, input: StorageObjectRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_EXISTS,
Some(to_value(input)?),
)
.await
}
pub async fn validate(&self, input: StorageObjectValidateRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_VALIDATE,
Some(to_value(input)?),
)
.await
}
pub async fn update(&self, input: StorageUpdateObjectRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_UPDATE,
Some(to_value(input)?),
)
.await
}
pub async fn copy(&self, input: StorageObjectCopyRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_COPY,
Some(to_value(input)?),
)
.await
}
pub async fn url(&self, input: StorageObjectRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_URL,
Some(to_value(input)?),
)
.await
}
pub async fn public_url(&self, input: StorageObjectPublicUrlRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_PUBLIC_URL,
Some(to_value(input)?),
)
.await
}
pub async fn delete(&self, input: StorageObjectRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_DELETE,
Some(to_value(input)?),
)
.await
}
pub async fn upload_url(&self, input: StoragePresignUploadRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_UPLOAD_URL,
Some(to_value(input)?),
)
.await
}
pub async fn post_policy(&self, input: StorageSignedPostPolicyRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_POST_POLICY,
Some(to_value(input)?),
)
.await
}
pub async fn versions(&self, input: StorageObjectVersionListRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_VERSIONS,
Some(to_value(input)?),
)
.await
}
pub async fn restore_version(
&self,
input: StorageObjectVersionMutationRequest,
) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_VERSION_RESTORE,
Some(to_value(input)?),
)
.await
}
pub async fn delete_version(
&self,
input: StorageObjectVersionMutationRequest,
) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_VERSION_DELETE,
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageObjectFolderNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageObjectFolderNamespace<'_> {
pub async fn create(&self, input: StorageObjectFolderCreateRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_FOLDER_CREATE,
Some(to_value(input)?),
)
.await
}
pub async fn delete(&self, input: StorageObjectFolderDeleteRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_FOLDER_DELETE,
Some(to_value(input)?),
)
.await
}
pub async fn rename(&self, input: StorageObjectFolderRenameRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::OBJECT_FOLDER_RENAME,
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageBucketNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageBucketNamespace<'_> {
pub async fn list(&self, input: S3ConnectionConfig) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_LIST,
Some(to_value(input)?),
)
.await
}
pub async fn create(&self, input: StorageBucketRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_CREATE,
Some(to_value(input)?),
)
.await
}
pub async fn delete(&self, input: StorageBucketRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_DELETE,
Some(to_value(input)?),
)
.await
}
pub fn lifecycle(&self) -> StorageBucketLifecycleNamespace<'_> {
StorageBucketLifecycleNamespace {
client: self.client,
}
}
pub fn policy(&self) -> StorageBucketPolicyNamespace<'_> {
StorageBucketPolicyNamespace {
client: self.client,
}
}
pub fn public_access(&self) -> StorageBucketPublicAccessNamespace<'_> {
StorageBucketPublicAccessNamespace {
client: self.client,
}
}
pub fn cors(&self) -> StorageBucketCorsNamespace<'_> {
StorageBucketCorsNamespace {
client: self.client,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageBucketLifecycleNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageBucketLifecycleNamespace<'_> {
pub async fn get(&self, input: StorageBucketLifecycleRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_LIFECYCLE,
Some(to_value(input)?),
)
.await
}
pub async fn set(&self, input: StorageSetBucketLifecycleRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_LIFECYCLE_SET,
Some(to_value(input)?),
)
.await
}
pub async fn delete(&self, input: StorageBucketLifecycleRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_LIFECYCLE_DELETE,
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageBucketPolicyNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageBucketPolicyNamespace<'_> {
pub async fn get(&self, input: StorageBucketPolicyRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_POLICY,
Some(to_value(input)?),
)
.await
}
pub async fn set(&self, input: StorageSetBucketPolicyRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_POLICY_SET,
Some(to_value(input)?),
)
.await
}
pub async fn delete(&self, input: StorageBucketPolicyRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_POLICY_DELETE,
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageBucketPublicAccessNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageBucketPublicAccessNamespace<'_> {
pub async fn get(&self, input: StoragePublicAccessBlockRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_PUBLIC_ACCESS,
Some(to_value(input)?),
)
.await
}
pub async fn set(&self, input: StorageSetPublicAccessBlockRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_PUBLIC_ACCESS_SET,
Some(to_value(input)?),
)
.await
}
pub async fn delete(&self, input: StoragePublicAccessBlockRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_PUBLIC_ACCESS_DELETE,
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageBucketCorsNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageBucketCorsNamespace<'_> {
pub async fn get(&self, input: StorageBucketCorsRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_CORS,
Some(to_value(input)?),
)
.await
}
pub async fn set(&self, input: StorageSetBucketCorsRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_CORS_SET,
Some(to_value(input)?),
)
.await
}
pub async fn delete(&self, input: StorageBucketCorsRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::BUCKET_CORS_DELETE,
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageMultipartNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageMultipartNamespace<'_> {
pub async fn create(&self, input: StorageMultipartCreateRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::MULTIPART_CREATE,
Some(to_value(input)?),
)
.await
}
pub async fn sign_part(&self, input: StorageMultipartSignPartRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::MULTIPART_SIGN_PART,
Some(to_value(input)?),
)
.await
}
pub async fn complete(
&self,
input: StorageMultipartCompleteRequest,
) -> BackendResult<StorageFileMutationResponse> {
self.client
.athena(
Method::POST,
storage_routes::MULTIPART_COMPLETE,
Some(to_value(input)?),
)
.await
}
pub async fn abort(&self, input: StorageMultipartAbortRequest) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::MULTIPART_ABORT,
Some(to_value(input)?),
)
.await
}
pub async fn list_parts(
&self,
input: StorageMultipartListPartsRequest,
) -> BackendResult<Value> {
self.client
.athena(
Method::POST,
storage_routes::MULTIPART_LIST_PARTS,
Some(to_value(input)?),
)
.await
}
}
#[derive(Debug, Clone, Copy)]
pub struct StorageAuditNamespace<'a> {
client: &'a AthenaStorageClient,
}
impl StorageAuditNamespace<'_> {
pub async fn list(
&self,
input: StorageAuditQueryRequest,
) -> BackendResult<StorageAuditListResponse> {
self.client
.athena(
Method::POST,
storage_routes::AUDIT_LIST,
Some(to_value(input)?),
)
.await
}
}
fn normalize_base_url(url: &str) -> String {
url.trim().trim_end_matches('/').to_string()
}
fn file_path(route: &str, file_id: &str) -> String {
path_param(route, "file_id", file_id)
}
fn file_version_path(route: &str, file_id: &str, version_id: &str) -> String {
path_param(&file_path(route, file_id), "version_id", version_id)
}
fn path_param(route: &str, name: &str, value: &str) -> String {
route.replace(&format!("{{{name}}}"), &percent_encode(value))
}
fn append_purpose(path: &str, purpose: Option<&str>) -> String {
append_query(path, &[("purpose", purpose)])
}
fn append_query(path: &str, pairs: &[(&str, Option<&str>)]) -> String {
let mut first = true;
let mut out = path.to_string();
for (name, value) in pairs {
let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) else {
continue;
};
out.push(if first { '?' } else { '&' });
first = false;
out.push_str(&percent_encode(name));
out.push('=');
out.push_str(&percent_encode(value));
}
out
}
fn percent_encode(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for byte in value.bytes() {
let ch = byte as char;
if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.' | '~') {
out.push(ch);
} else {
out.push_str(&format!("%{byte:02X}"));
}
}
out
}
fn to_value<T: Serialize>(value: T) -> BackendResult<Value> {
serde_json::to_value(value).map_err(|error| {
BackendError::Generic(format!("failed to encode storage request: {error}"))
})
}
fn storage_http_error(error: reqwest::Error) -> BackendError {
BackendError::Generic(format!("storage request failed: {error}"))
}
async fn decode_json_response<T>(response: Response, path: &str) -> BackendResult<T>
where
T: DeserializeOwned,
{
let status = response.status();
let text = response.text().await.map_err(storage_http_error)?;
if !status.is_success() {
return Err(BackendError::Generic(format!(
"storage request {path} failed with status {status}: {text}"
)));
}
serde_json::from_str(&text).map_err(|error| {
BackendError::Generic(format!(
"storage request {path} returned invalid JSON: {error}; body={text}"
))
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_replaces_path_params() {
assert_eq!(
file_version_path(storage_routes::FILE_VERSION_RESTORE, "file 1", "v/1"),
"/storage/files/file%201/versions/v%2F1/restore"
);
}
#[test]
fn query_builder_encodes_purpose() {
assert_eq!(
append_purpose("/storage/files/file_1/proxy", Some("stream bytes")),
"/storage/files/file_1/proxy?purpose=stream%20bytes"
);
}
#[test]
fn upload_request_flattens_sse_fields() {
let payload = to_value(CreateStorageUploadUrlRequest {
s3_id: "s3_1".to_string(),
bucket: None,
storage_key: "docs/file.pdf".to_string(),
name: None,
original_name: None,
resource_id: None,
mime_type: None,
content_type: Some("application/pdf".to_string()),
size_bytes: None,
file_id: None,
public: None,
visibility: None,
metadata: None,
encryption: StorageServerSideEncryptionOptions {
server_side_encryption: Some("AES256".to_string()),
sse: None,
ssekms_key_id: None,
kms_key_id: None,
bucket_key_enabled: Some(true),
},
})
.unwrap();
assert_eq!(payload["server_side_encryption"], "AES256");
assert_eq!(payload["bucket_key_enabled"], true);
}
}