rusty_cat/upload_trait.rs
1use bytes::Bytes;
2
3use crate::http_breakpoint::UploadResumeInfo;
4use crate::{MeowError, TransferTask};
5use async_trait::async_trait;
6
7/// Context for upload prepare stage.
8#[derive(Debug, Clone, Copy)]
9pub struct UploadPrepareCtx<'a> {
10 /// HTTP client used for requests.
11 pub client: &'a reqwest::Client,
12 /// Immutable task snapshot.
13 pub task: &'a TransferTask,
14 /// Locally confirmed uploaded offset in bytes.
15 ///
16 /// Range: `>= 0`.
17 pub local_offset: u64,
18}
19
20/// Context for upload chunk stage.
21///
22/// [`UploadChunkCtx::chunk`] is a [`bytes::Bytes`] handle. Cloning it is an
23/// O(1) refcount bump, and passing it into [`reqwest::Body`] never copies the
24/// underlying buffer. This lets protocol implementations send the same chunk
25/// multiple times (for example during retries) without re-allocating.
26#[derive(Debug, Clone)]
27pub struct UploadChunkCtx<'a> {
28 /// HTTP client used for requests.
29 pub client: &'a reqwest::Client,
30 /// Immutable task snapshot.
31 pub task: &'a TransferTask,
32 /// Raw bytes for the current chunk.
33 ///
34 /// `Bytes` is cheap to clone and can be converted into `reqwest::Body`
35 /// without copying. Avoid calling [`bytes::Bytes::to_vec`] on hot paths.
36 pub chunk: Bytes,
37 /// Start offset of this chunk in the full file.
38 ///
39 /// Range: `>= 0`.
40 pub offset: u64,
41}
42
43/// Custom breakpoint upload protocol.
44///
45/// Implementors are responsible for request construction and response parsing.
46/// The executor handles file I/O, chunking, retries, progress, and scheduling.
47///
48/// # Examples
49///
50/// ```no_run
51/// use async_trait::async_trait;
52/// use rusty_cat::api::{
53/// BreakpointUpload, MeowError, UploadChunkCtx, UploadPrepareCtx, UploadResumeInfo,
54/// };
55///
56/// struct MyUploadProtocol;
57///
58/// #[async_trait]
59/// impl BreakpointUpload for MyUploadProtocol {
60/// async fn prepare(&self, _ctx: UploadPrepareCtx<'_>) -> Result<UploadResumeInfo, MeowError> {
61/// Ok(UploadResumeInfo::default())
62/// }
63///
64/// async fn upload_chunk(&self, ctx: UploadChunkCtx<'_>) -> Result<UploadResumeInfo, MeowError> {
65/// let _ = (ctx.task.file_name(), ctx.offset);
66/// Ok(UploadResumeInfo {
67/// completed_file_id: None,
68/// next_byte: Some(ctx.offset + ctx.chunk.len() as u64),
69/// provider_upload_id: None,
70/// })
71/// }
72/// }
73/// ```
74///
75/// # Executor integration contract
76///
77/// - If [`UploadResumeInfo::completed_file_id`] is `Some`, the executor treats
78/// the upload as fully completed and stops sending further chunks.
79/// - [`UploadResumeInfo::next_byte`] is a server-suggested next offset; the
80/// executor merges it with local offset before continuing.
81/// - When computed next offset reaches `task.total_size()`, the executor calls
82/// [`BreakpointUpload::complete_upload`] unless completion was already
83/// indicated by `completed_file_id`.
84/// - When user cancels an upload task, executor calls
85/// [`BreakpointUpload::abort_upload`].
86#[async_trait]
87pub trait BreakpointUpload: Send + Sync {
88 /// Prepare stage before first chunk upload.
89 ///
90 /// Typical responsibilities include creating upload session and querying
91 /// already uploaded offset on remote side.
92 ///
93 /// # Parameters
94 ///
95 /// - `client`: Shared HTTP client used by executor.
96 /// - `task`: Upload task snapshot with URL/method/headers/file metadata.
97 /// - `local_offset`: Locally confirmed uploaded offset.
98 ///
99 /// # Returns
100 ///
101 /// - `Ok(info)`: Server resume info used by executor to compute next offset.
102 /// - `Err`: Prepare failed and task enters error path.
103 ///
104 /// # Errors
105 ///
106 /// Return [`MeowError`] when remote session creation/checkpoint probing
107 /// fails, request signing fails, or protocol payload parsing fails.
108 ///
109 /// # Examples
110 ///
111 /// ```no_run
112 /// use rusty_cat::api::UploadPrepareCtx;
113 ///
114 /// fn read_prepare_ctx(ctx: UploadPrepareCtx<'_>) {
115 /// let _ = (ctx.task.url(), ctx.local_offset);
116 /// }
117 /// ```
118 async fn prepare(&self, ctx: UploadPrepareCtx<'_>) -> Result<UploadResumeInfo, MeowError>;
119
120 /// Uploads a single chunk.
121 ///
122 /// Executor guarantees chunk bounds are valid and chunk bytes match the
123 /// provided `offset`.
124 ///
125 /// # Errors
126 ///
127 /// Return [`MeowError`] when chunk request fails, server rejects the chunk,
128 /// or protocol response parsing fails.
129 ///
130 /// # Examples
131 ///
132 /// ```no_run
133 /// use rusty_cat::api::UploadChunkCtx;
134 ///
135 /// fn read_chunk_ctx(ctx: &UploadChunkCtx<'_>) {
136 /// // `ctx.chunk` is a `bytes::Bytes`; cloning is a cheap refcount bump.
137 /// let _ = (ctx.offset, ctx.chunk.len(), ctx.task.total_size());
138 /// }
139 /// ```
140 async fn upload_chunk(&self, ctx: UploadChunkCtx<'_>) -> Result<UploadResumeInfo, MeowError>;
141
142 /// Finalization step after all chunk bytes are uploaded.
143 ///
144 /// Typical use case: multipart-complete API calls. Return value is an
145 /// optional provider-defined payload that will be forwarded to
146 /// `MeowClient::try_enqueue` complete callback.
147 ///
148 /// Default implementation is a no-op (`Ok(None)`).
149 ///
150 /// # Errors
151 ///
152 /// Implementations should return [`MeowError`] if final commit/merge API
153 /// fails.
154 async fn complete_upload(
155 &self,
156 _client: &reqwest::Client,
157 _task: &TransferTask,
158 ) -> Result<Option<String>, MeowError> {
159 Ok(None)
160 }
161
162 /// Abort/cleanup hook called when user cancels an upload task.
163 ///
164 /// Typical use case: abort multipart session or remove temporary objects.
165 /// Default implementation is a no-op.
166 ///
167 /// # Errors
168 ///
169 /// Implementations should return [`MeowError`] when cleanup or abort API
170 /// calls fail.
171 async fn abort_upload(
172 &self,
173 _client: &reqwest::Client,
174 _task: &TransferTask,
175 ) -> Result<(), MeowError> {
176 Ok(())
177 }
178}