opendal 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (566) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +89 -56
  3. data/Cargo.toml +1 -1
  4. data/DEPENDENCIES.rust.tsv +55 -41
  5. data/LICENSE +201 -0
  6. data/LICENSE-MPL-2.0.txt +373 -0
  7. data/NOTICE +5 -0
  8. data/core/CHANGELOG.md +175 -0
  9. data/core/Cargo.lock +225 -298
  10. data/core/Cargo.toml +100 -94
  11. data/core/DEPENDENCIES.rust.tsv +44 -73
  12. data/core/benches/metadata/README.md +21 -0
  13. data/core/benches/metadata/main.rs +541 -0
  14. data/core/benches/vs_fs/Cargo.toml +1 -1
  15. data/core/benches/vs_s3/Cargo.toml +1 -1
  16. data/core/core/Cargo.toml +7 -2
  17. data/core/core/src/blocking/delete.rs +5 -1
  18. data/core/core/src/blocking/operator.rs +12 -0
  19. data/core/core/src/blocking/read/std_reader.rs +25 -0
  20. data/core/core/src/docs/concepts.rs +70 -94
  21. data/core/core/src/docs/internals/accessor.rs +62 -293
  22. data/core/core/src/docs/internals/layer.rs +43 -59
  23. data/core/core/src/docs/internals/mod.rs +21 -23
  24. data/core/core/src/docs/mod.rs +10 -6
  25. data/core/core/src/docs/performance/concurrent_write.md +92 -55
  26. data/core/core/src/docs/performance/http_optimization.md +120 -104
  27. data/core/core/src/docs/performance/mod.rs +8 -6
  28. data/core/core/src/docs/rfcs/5485_conditional_reader.md +4 -0
  29. data/core/core/src/docs/rfcs/7182_restore_api.md +139 -557
  30. data/core/core/src/docs/rfcs/7945_limited_read.md +210 -0
  31. data/core/core/src/docs/rfcs/7948_s3_provider_presets.md +254 -0
  32. data/core/core/src/docs/rfcs/8109_writer_copy_from.md +330 -0
  33. data/core/core/src/docs/rfcs/8145_conflict_error_kind.md +210 -0
  34. data/core/core/src/docs/rfcs/8147_write_if_not_changed.md +233 -0
  35. data/core/core/src/docs/rfcs/8188_compose_api.md +292 -0
  36. data/core/core/src/docs/rfcs/8194_compact_metadata_and_operation_args.md +444 -0
  37. data/core/core/src/docs/rfcs/README.md +14 -3
  38. data/core/core/src/docs/rfcs/mod.rs +46 -22
  39. data/core/core/src/docs/specs/README.md +34 -0
  40. data/core/core/src/docs/specs/conditional-operations.md +156 -0
  41. data/core/core/src/docs/specs/metadata.md +63 -0
  42. data/core/core/src/docs/specs/mod.rs +26 -0
  43. data/core/core/src/docs/upgrade.md +114 -1
  44. data/core/core/src/layers/capability_override.rs +15 -2
  45. data/core/core/src/layers/complete.rs +347 -43
  46. data/core/core/src/layers/correctness_check.rs +718 -51
  47. data/core/core/src/layers/error_context.rs +76 -2
  48. data/core/core/src/layers/simulate.rs +91 -28
  49. data/core/core/src/lib.rs +1 -0
  50. data/core/core/src/raw/accessor.rs +91 -15
  51. data/core/core/src/raw/enum_utils.rs +7 -0
  52. data/core/core/src/raw/http_util/header.rs +22 -17
  53. data/core/core/src/raw/oio/compose/api.rs +92 -0
  54. data/core/core/src/raw/oio/compose/mod.rs +19 -0
  55. data/core/core/src/raw/oio/copy/api.rs +3 -3
  56. data/core/core/src/raw/oio/copy/block_copy.rs +32 -4
  57. data/core/core/src/raw/oio/copy/multipart_copy.rs +36 -7
  58. data/core/core/src/raw/oio/entry.rs +48 -22
  59. data/core/core/src/raw/oio/list/flat_list.rs +1 -1
  60. data/core/core/src/raw/oio/list/hierarchy_list.rs +3 -2
  61. data/core/core/src/raw/oio/mod.rs +3 -0
  62. data/core/core/src/raw/oio/read/position_read.rs +1 -1
  63. data/core/core/src/raw/oio/write/api.rs +40 -0
  64. data/core/core/src/raw/oio/write/append_write.rs +4 -4
  65. data/core/core/src/raw/oio/write/block_write.rs +2 -2
  66. data/core/core/src/raw/oio/write/multipart_write.rs +181 -48
  67. data/core/core/src/raw/oio/write/position_write.rs +1 -1
  68. data/core/core/src/raw/operation.rs +6 -0
  69. data/core/core/src/raw/ops.rs +1191 -456
  70. data/core/core/src/raw/rps.rs +11 -0
  71. data/core/core/src/raw/time.rs +14 -3
  72. data/core/core/src/services/memory/backend.rs +5 -10
  73. data/core/core/src/services/memory/lister.rs +4 -4
  74. data/core/core/src/services/memory/writer.rs +7 -6
  75. data/core/core/src/types/buffer.rs +102 -12
  76. data/core/core/src/types/bytes_range.rs +20 -1
  77. data/core/core/src/types/capability.rs +65 -0
  78. data/core/core/src/types/compact.rs +329 -0
  79. data/core/core/src/types/compose/composer.rs +183 -0
  80. data/core/core/src/types/compose/input.rs +57 -0
  81. data/core/core/src/types/compose/mod.rs +23 -0
  82. data/core/core/src/types/context/read.rs +16 -11
  83. data/core/core/src/types/context/write.rs +150 -2
  84. data/core/core/src/types/copy.rs +5 -3
  85. data/core/core/src/types/delete/deleter.rs +20 -12
  86. data/core/core/src/types/delete/input.rs +22 -64
  87. data/core/core/src/types/delete/mod.rs +0 -1
  88. data/core/core/src/types/entry.rs +8 -14
  89. data/core/core/src/types/error.rs +28 -11
  90. data/core/core/src/types/metadata.rs +709 -322
  91. data/core/core/src/types/mod.rs +9 -1
  92. data/core/core/src/types/operator/operator.rs +278 -29
  93. data/core/core/src/types/operator/operator_futures.rs +466 -105
  94. data/core/core/src/types/options.rs +631 -158
  95. data/core/core/src/types/read/futures_async_reader.rs +663 -50
  96. data/core/core/src/types/read/reader.rs +23 -5
  97. data/core/core/src/types/write/futures_async_writer.rs +27 -0
  98. data/core/core/src/types/write/writer.rs +47 -5
  99. data/core/http-transports/reqwest/Cargo.toml +1 -1
  100. data/core/layers/async-backtrace/Cargo.toml +2 -2
  101. data/core/layers/async-backtrace/src/lib.rs +21 -2
  102. data/core/layers/await-tree/Cargo.toml +2 -2
  103. data/core/layers/await-tree/src/lib.rs +25 -2
  104. data/core/layers/capability-check/Cargo.toml +2 -2
  105. data/core/layers/capability-check/src/lib.rs +98 -10
  106. data/core/layers/chaos/Cargo.toml +2 -2
  107. data/core/layers/chaos/src/lib.rs +18 -3
  108. data/core/layers/concurrent-limit/Cargo.toml +3 -3
  109. data/core/layers/concurrent-limit/src/lib.rs +53 -13
  110. data/core/layers/dtrace/Cargo.toml +2 -2
  111. data/core/layers/dtrace/src/lib.rs +29 -2
  112. data/core/layers/fastmetrics/Cargo.toml +3 -3
  113. data/core/layers/fastrace/Cargo.toml +2 -2
  114. data/core/layers/fastrace/src/lib.rs +28 -2
  115. data/core/layers/foyer/Cargo.toml +2 -2
  116. data/core/layers/foyer/src/full.rs +16 -19
  117. data/core/layers/foyer/src/lib.rs +67 -14
  118. data/core/layers/hotpath/Cargo.toml +2 -2
  119. data/core/layers/hotpath/src/lib.rs +20 -2
  120. data/core/layers/immutable-index/Cargo.toml +2 -2
  121. data/core/layers/immutable-index/src/lib.rs +48 -24
  122. data/core/layers/logging/Cargo.toml +2 -2
  123. data/core/layers/logging/src/lib.rs +46 -2
  124. data/core/layers/metrics/Cargo.toml +3 -3
  125. data/core/layers/mime-guess/Cargo.toml +2 -2
  126. data/core/layers/mime-guess/src/lib.rs +31 -4
  127. data/core/layers/observe-metrics-common/Cargo.toml +2 -2
  128. data/core/layers/observe-metrics-common/src/lib.rs +55 -2
  129. data/core/layers/otelmetrics/Cargo.toml +3 -3
  130. data/core/layers/oteltrace/Cargo.toml +2 -2
  131. data/core/layers/oteltrace/src/lib.rs +27 -2
  132. data/core/layers/prometheus/Cargo.toml +3 -3
  133. data/core/layers/prometheus-client/Cargo.toml +3 -3
  134. data/core/layers/retry/Cargo.toml +4 -4
  135. data/core/layers/retry/src/lib.rs +76 -19
  136. data/core/layers/route/Cargo.toml +3 -3
  137. data/core/layers/route/src/lib.rs +34 -7
  138. data/core/layers/tail-cut/Cargo.toml +2 -2
  139. data/core/layers/tail-cut/src/lib.rs +28 -2
  140. data/core/layers/throttle/Cargo.toml +2 -2
  141. data/core/layers/throttle/src/lib.rs +19 -2
  142. data/core/layers/timeout/Cargo.toml +3 -3
  143. data/core/layers/timeout/src/lib.rs +38 -13
  144. data/core/layers/tracing/Cargo.toml +2 -2
  145. data/core/layers/tracing/src/lib.rs +24 -3
  146. data/core/services/aliyun-drive/Cargo.toml +2 -2
  147. data/core/services/aliyun-drive/src/backend.rs +34 -24
  148. data/core/services/aliyun-drive/src/core.rs +143 -54
  149. data/core/services/aliyun-drive/src/lister.rs +19 -18
  150. data/core/services/aliyun-drive/src/reader.rs +4 -1
  151. data/core/services/aliyun-drive/src/writer.rs +3 -3
  152. data/core/services/alluxio/Cargo.toml +1 -1
  153. data/core/services/alluxio/src/backend.rs +1 -1
  154. data/core/services/alluxio/src/core.rs +113 -94
  155. data/core/services/alluxio/src/reader.rs +7 -3
  156. data/core/services/alluxio/src/writer.rs +2 -2
  157. data/core/services/azblob/Cargo.toml +2 -2
  158. data/core/services/azblob/src/backend.rs +17 -9
  159. data/core/services/azblob/src/copier.rs +41 -15
  160. data/core/services/azblob/src/core.rs +372 -135
  161. data/core/services/azblob/src/deleter.rs +41 -18
  162. data/core/services/azblob/src/lister.rs +16 -10
  163. data/core/services/azblob/src/reader.rs +4 -1
  164. data/core/services/azblob/src/writer.rs +48 -15
  165. data/core/services/azdls/Cargo.toml +3 -3
  166. data/core/services/azdls/src/backend.rs +45 -15
  167. data/core/services/azdls/src/config.rs +4 -1
  168. data/core/services/azdls/src/core.rs +276 -87
  169. data/core/services/azdls/src/deleter.rs +13 -4
  170. data/core/services/azdls/src/lister.rs +18 -12
  171. data/core/services/azdls/src/reader.rs +6 -2
  172. data/core/services/azdls/src/writer.rs +44 -23
  173. data/core/services/azfile/Cargo.toml +2 -2
  174. data/core/services/azfile/src/backend.rs +24 -8
  175. data/core/services/azfile/src/core.rs +195 -77
  176. data/core/services/azfile/src/deleter.rs +8 -1
  177. data/core/services/azfile/src/lister.rs +20 -14
  178. data/core/services/azfile/src/reader.rs +5 -2
  179. data/core/services/azfile/src/writer.rs +27 -15
  180. data/core/services/azure-common/Cargo.toml +1 -1
  181. data/core/services/b2/Cargo.toml +2 -2
  182. data/core/services/b2/src/backend.rs +30 -11
  183. data/core/services/b2/src/core.rs +134 -120
  184. data/core/services/b2/src/deleter.rs +1 -1
  185. data/core/services/b2/src/lister.rs +5 -2
  186. data/core/services/b2/src/reader.rs +5 -2
  187. data/core/services/b2/src/writer.rs +29 -12
  188. data/core/services/cacache/Cargo.toml +1 -1
  189. data/core/services/cacache/src/backend.rs +4 -5
  190. data/core/services/cacache/src/reader.rs +4 -1
  191. data/core/services/cacache/src/writer.rs +6 -2
  192. data/core/services/cloudflare-kv/Cargo.toml +1 -1
  193. data/core/services/cloudflare-kv/src/backend.rs +26 -15
  194. data/core/services/cloudflare-kv/src/config.rs +13 -2
  195. data/core/services/cloudflare-kv/src/core.rs +62 -56
  196. data/core/services/cloudflare-kv/src/deleter.rs +12 -3
  197. data/core/services/cloudflare-kv/src/lister.rs +66 -17
  198. data/core/services/cloudflare-kv/src/reader.rs +14 -9
  199. data/core/services/cloudflare-kv/src/writer.rs +10 -7
  200. data/core/services/compfs/Cargo.toml +1 -1
  201. data/core/services/compfs/src/backend.rs +11 -7
  202. data/core/services/compfs/src/lister.rs +7 -7
  203. data/core/services/compfs/src/writer.rs +1 -1
  204. data/core/services/cos/Cargo.toml +1 -1
  205. data/core/services/cos/src/backend.rs +129 -16
  206. data/core/services/cos/src/core.rs +198 -80
  207. data/core/services/cos/src/deleter.rs +4 -1
  208. data/core/services/cos/src/lister.rs +42 -27
  209. data/core/services/cos/src/reader.rs +6 -2
  210. data/core/services/cos/src/writer.rs +45 -21
  211. data/core/services/d1/Cargo.toml +1 -1
  212. data/core/services/d1/src/backend.rs +6 -5
  213. data/core/services/d1/src/core.rs +79 -63
  214. data/core/services/d1/src/reader.rs +4 -1
  215. data/core/services/d1/src/writer.rs +6 -2
  216. data/core/services/dashmap/Cargo.toml +1 -1
  217. data/core/services/dashmap/src/backend.rs +2 -2
  218. data/core/services/dashmap/src/reader.rs +4 -1
  219. data/core/services/dashmap/src/writer.rs +11 -8
  220. data/core/services/dbfs/Cargo.toml +1 -1
  221. data/core/services/dbfs/src/backend.rs +21 -15
  222. data/core/services/dbfs/src/core.rs +65 -60
  223. data/core/services/dbfs/src/deleter.rs +4 -1
  224. data/core/services/dbfs/src/lister.rs +10 -10
  225. data/core/services/dbfs/src/writer.rs +6 -3
  226. data/core/services/dropbox/Cargo.toml +2 -2
  227. data/core/services/dropbox/src/backend.rs +30 -23
  228. data/core/services/dropbox/src/core.rs +65 -63
  229. data/core/services/dropbox/src/deleter.rs +1 -1
  230. data/core/services/dropbox/src/lister.rs +17 -8
  231. data/core/services/dropbox/src/reader.rs +4 -1
  232. data/core/services/dropbox/src/writer.rs +13 -10
  233. data/core/services/etcd/Cargo.toml +1 -1
  234. data/core/services/etcd/src/backend.rs +8 -5
  235. data/core/services/etcd/src/core.rs +5 -11
  236. data/core/services/etcd/src/lister.rs +6 -1
  237. data/core/services/etcd/src/reader.rs +4 -1
  238. data/core/services/etcd/src/writer.rs +6 -3
  239. data/core/services/foundationdb/Cargo.toml +1 -1
  240. data/core/services/foundationdb/src/backend.rs +6 -5
  241. data/core/services/foundationdb/src/reader.rs +4 -1
  242. data/core/services/foundationdb/src/writer.rs +6 -2
  243. data/core/services/foyer/Cargo.toml +3 -3
  244. data/core/services/foyer/src/backend.rs +6 -5
  245. data/core/services/foyer/src/reader.rs +4 -1
  246. data/core/services/foyer/src/writer.rs +6 -2
  247. data/core/services/fs/Cargo.toml +1 -1
  248. data/core/services/fs/src/backend.rs +4 -3
  249. data/core/services/fs/src/core.rs +44 -26
  250. data/core/services/fs/src/lister.rs +9 -6
  251. data/core/services/fs/src/writer.rs +20 -13
  252. data/core/services/ftp/Cargo.toml +4 -6
  253. data/core/services/ftp/src/backend.rs +9 -5
  254. data/core/services/ftp/src/core.rs +9 -7
  255. data/core/services/ftp/src/lister.rs +7 -8
  256. data/core/services/ftp/src/reader.rs +3 -3
  257. data/core/services/ftp/src/writer.rs +3 -3
  258. data/core/services/gcs/Cargo.toml +4 -2
  259. data/core/services/gcs/src/backend.rs +53 -7
  260. data/core/services/gcs/src/composer.rs +705 -0
  261. data/core/services/gcs/src/copier.rs +14 -4
  262. data/core/services/gcs/src/core.rs +828 -124
  263. data/core/services/gcs/src/deleter.rs +33 -18
  264. data/core/services/gcs/src/lib.rs +1 -0
  265. data/core/services/gcs/src/lister.rs +21 -12
  266. data/core/services/gcs/src/reader.rs +12 -5
  267. data/core/services/gcs/src/writer.rs +632 -10
  268. data/core/services/gcs-grpc/Cargo.toml +58 -0
  269. data/core/services/gcs-grpc/README.md +19 -0
  270. data/core/services/gcs-grpc/proto/README.md +23 -0
  271. data/core/services/gcs-grpc/proto/google/storage/v2/storage.proto +171 -0
  272. data/core/services/gcs-grpc/proto/upstream-descriptor.bin +0 -0
  273. data/core/services/gcs-grpc/src/backend.rs +449 -0
  274. data/core/services/gcs-grpc/src/config.rs +116 -0
  275. data/core/services/gcs-grpc/src/copier.rs +98 -0
  276. data/core/services/gcs-grpc/src/core.rs +298 -0
  277. data/core/services/gcs-grpc/src/deleter.rs +65 -0
  278. data/core/services/gcs-grpc/src/docs.md +19 -0
  279. data/core/services/gcs-grpc/src/generated/google.storage.v2.rs +541 -0
  280. data/core/services/gcs-grpc/src/generated/mod.rs +24 -0
  281. data/core/services/gcs-grpc/src/lib.rs +43 -0
  282. data/core/services/gcs-grpc/src/lister.rs +125 -0
  283. data/core/services/gcs-grpc/src/reader.rs +140 -0
  284. data/core/services/gcs-grpc/src/writer.rs +780 -0
  285. data/core/services/gdrive/Cargo.toml +2 -2
  286. data/core/services/gdrive/src/backend.rs +123 -69
  287. data/core/services/gdrive/src/core.rs +87 -69
  288. data/core/services/gdrive/src/deleter.rs +8 -2
  289. data/core/services/gdrive/src/lister.rs +38 -26
  290. data/core/services/gdrive/src/path_index.rs +2 -2
  291. data/core/services/gdrive/src/reader.rs +9 -3
  292. data/core/services/gdrive/src/writer.rs +13 -7
  293. data/core/services/ghac/Cargo.toml +2 -2
  294. data/core/services/ghac/src/backend.rs +5 -2
  295. data/core/services/ghac/src/core.rs +59 -37
  296. data/core/services/ghac/src/reader.rs +5 -2
  297. data/core/services/ghac/src/writer.rs +16 -6
  298. data/core/services/github/Cargo.toml +1 -1
  299. data/core/services/github/src/backend.rs +16 -8
  300. data/core/services/github/src/core.rs +88 -76
  301. data/core/services/github/src/lister.rs +17 -15
  302. data/core/services/github/src/reader.rs +5 -2
  303. data/core/services/github/src/writer.rs +12 -6
  304. data/core/services/goosefs/Cargo.toml +2 -2
  305. data/core/services/goosefs/src/backend.rs +2 -2
  306. data/core/services/goosefs/src/core.rs +291 -208
  307. data/core/services/goosefs/src/docs.md +3 -0
  308. data/core/services/goosefs/src/lister.rs +2 -3
  309. data/core/services/goosefs/src/reader.rs +7 -3
  310. data/core/services/goosefs/src/writer.rs +21 -10
  311. data/core/services/goosefs/tests/safe_conditional_put.rs +37 -0
  312. data/core/services/gridfs/Cargo.toml +2 -2
  313. data/core/services/gridfs/src/backend.rs +12 -8
  314. data/core/services/gridfs/src/core.rs +1 -1
  315. data/core/services/gridfs/src/reader.rs +4 -1
  316. data/core/services/gridfs/src/writer.rs +6 -2
  317. data/core/services/hdfs/Cargo.toml +1 -1
  318. data/core/services/hdfs/src/backend.rs +1 -1
  319. data/core/services/hdfs/src/core.rs +7 -4
  320. data/core/services/hdfs/src/lister.rs +7 -9
  321. data/core/services/hdfs/src/writer.rs +5 -1
  322. data/core/services/hdfs-native/Cargo.toml +1 -1
  323. data/core/services/hdfs-native/src/backend.rs +1 -1
  324. data/core/services/hdfs-native/src/core.rs +44 -50
  325. data/core/services/hdfs-native/src/lister.rs +8 -10
  326. data/core/services/hdfs-native/src/writer.rs +5 -1
  327. data/core/services/hf/Cargo.toml +4 -3
  328. data/core/services/hf/src/backend.rs +4 -3
  329. data/core/services/hf/src/core.rs +907 -153
  330. data/core/services/hf/src/lister.rs +16 -6
  331. data/core/services/hf/src/reader.rs +377 -57
  332. data/core/services/hf/src/writer.rs +6 -2
  333. data/core/services/http/Cargo.toml +3 -3
  334. data/core/services/http/src/backend.rs +8 -6
  335. data/core/services/http/src/core.rs +57 -35
  336. data/core/services/http/src/reader.rs +5 -2
  337. data/core/services/ipfs/Cargo.toml +1 -1
  338. data/core/services/ipfs/src/backend.rs +20 -9
  339. data/core/services/ipfs/src/core.rs +43 -34
  340. data/core/services/ipfs/src/reader.rs +5 -2
  341. data/core/services/ipmfs/Cargo.toml +1 -1
  342. data/core/services/ipmfs/src/backend.rs +17 -8
  343. data/core/services/ipmfs/src/core.rs +66 -60
  344. data/core/services/ipmfs/src/deleter.rs +5 -2
  345. data/core/services/ipmfs/src/lister.rs +15 -12
  346. data/core/services/ipmfs/src/reader.rs +5 -2
  347. data/core/services/ipmfs/src/writer.rs +6 -3
  348. data/core/services/koofr/Cargo.toml +2 -2
  349. data/core/services/koofr/src/backend.rs +46 -17
  350. data/core/services/koofr/src/core.rs +64 -48
  351. data/core/services/koofr/src/deleter.rs +4 -1
  352. data/core/services/koofr/src/lister.rs +11 -10
  353. data/core/services/koofr/src/reader.rs +5 -2
  354. data/core/services/koofr/src/writer.rs +13 -7
  355. data/core/services/lakefs/Cargo.toml +1 -1
  356. data/core/services/lakefs/src/backend.rs +25 -8
  357. data/core/services/lakefs/src/core.rs +79 -73
  358. data/core/services/lakefs/src/deleter.rs +4 -1
  359. data/core/services/lakefs/src/lister.rs +17 -14
  360. data/core/services/lakefs/src/reader.rs +5 -2
  361. data/core/services/lakefs/src/writer.rs +11 -10
  362. data/core/services/memcached/Cargo.toml +1 -1
  363. data/core/services/memcached/src/backend.rs +57 -7
  364. data/core/services/memcached/src/config.rs +13 -2
  365. data/core/services/memcached/src/core.rs +11 -1
  366. data/core/services/memcached/src/reader.rs +4 -1
  367. data/core/services/memcached/src/writer.rs +6 -2
  368. data/core/services/mini_moka/Cargo.toml +1 -1
  369. data/core/services/mini_moka/src/backend.rs +8 -8
  370. data/core/services/mini_moka/src/reader.rs +4 -1
  371. data/core/services/mini_moka/src/writer.rs +11 -7
  372. data/core/services/moka/Cargo.toml +1 -1
  373. data/core/services/moka/src/backend.rs +7 -7
  374. data/core/services/moka/src/reader.rs +4 -1
  375. data/core/services/moka/src/writer.rs +10 -6
  376. data/core/services/mongodb/Cargo.toml +2 -2
  377. data/core/services/mongodb/src/backend.rs +7 -6
  378. data/core/services/mongodb/src/core.rs +1 -1
  379. data/core/services/mongodb/src/reader.rs +4 -1
  380. data/core/services/mongodb/src/writer.rs +6 -2
  381. data/core/services/monoiofs/Cargo.toml +2 -2
  382. data/core/services/monoiofs/src/backend.rs +16 -10
  383. data/core/services/monoiofs/src/core.rs +8 -9
  384. data/core/services/monoiofs/src/reader.rs +8 -10
  385. data/core/services/monoiofs/src/writer.rs +19 -24
  386. data/core/services/mysql/Cargo.toml +2 -2
  387. data/core/services/mysql/src/backend.rs +7 -6
  388. data/core/services/mysql/src/core.rs +1 -1
  389. data/core/services/mysql/src/lister.rs +7 -3
  390. data/core/services/mysql/src/reader.rs +4 -1
  391. data/core/services/mysql/src/writer.rs +6 -2
  392. data/core/services/obs/Cargo.toml +1 -1
  393. data/core/services/obs/src/backend.rs +29 -11
  394. data/core/services/obs/src/core.rs +81 -82
  395. data/core/services/obs/src/deleter.rs +4 -1
  396. data/core/services/obs/src/lister.rs +12 -5
  397. data/core/services/obs/src/reader.rs +5 -2
  398. data/core/services/obs/src/writer.rs +40 -19
  399. data/core/services/onedrive/Cargo.toml +2 -2
  400. data/core/services/onedrive/src/backend.rs +30 -21
  401. data/core/services/onedrive/src/config.rs +2 -2
  402. data/core/services/onedrive/src/core.rs +191 -90
  403. data/core/services/onedrive/src/deleter.rs +5 -2
  404. data/core/services/onedrive/src/graph_model.rs +10 -51
  405. data/core/services/onedrive/src/lister.rs +60 -24
  406. data/core/services/onedrive/src/reader.rs +17 -2
  407. data/core/services/onedrive/src/writer.rs +26 -15
  408. data/core/services/opfs/Cargo.toml +1 -1
  409. data/core/services/opfs/src/backend.rs +6 -13
  410. data/core/services/opfs/src/core.rs +33 -37
  411. data/core/services/opfs/src/lister.rs +4 -4
  412. data/core/services/opfs/src/writer.rs +1 -3
  413. data/core/services/oss/Cargo.toml +1 -1
  414. data/core/services/oss/src/backend.rs +40 -8
  415. data/core/services/oss/src/core.rs +192 -92
  416. data/core/services/oss/src/deleter.rs +16 -6
  417. data/core/services/oss/src/lister.rs +43 -27
  418. data/core/services/oss/src/reader.rs +6 -2
  419. data/core/services/oss/src/writer.rs +40 -22
  420. data/core/services/pcloud/Cargo.toml +1 -1
  421. data/core/services/pcloud/src/backend.rs +40 -6
  422. data/core/services/pcloud/src/core.rs +80 -66
  423. data/core/services/pcloud/src/deleter.rs +8 -1
  424. data/core/services/pcloud/src/lister.rs +4 -1
  425. data/core/services/pcloud/src/reader.rs +5 -2
  426. data/core/services/pcloud/src/writer.rs +6 -3
  427. data/core/services/persy/Cargo.toml +1 -1
  428. data/core/services/persy/src/backend.rs +6 -5
  429. data/core/services/persy/src/core.rs +60 -15
  430. data/core/services/persy/src/reader.rs +4 -1
  431. data/core/services/persy/src/writer.rs +6 -2
  432. data/core/services/postgresql/Cargo.toml +2 -2
  433. data/core/services/postgresql/src/backend.rs +7 -6
  434. data/core/services/postgresql/src/core.rs +1 -1
  435. data/core/services/postgresql/src/reader.rs +4 -1
  436. data/core/services/postgresql/src/writer.rs +6 -2
  437. data/core/services/redb/Cargo.toml +1 -1
  438. data/core/services/redb/src/backend.rs +6 -5
  439. data/core/services/redb/src/reader.rs +4 -1
  440. data/core/services/redb/src/writer.rs +6 -2
  441. data/core/services/redis/Cargo.toml +1 -1
  442. data/core/services/redis/src/backend.rs +18 -8
  443. data/core/services/redis/src/config.rs +13 -2
  444. data/core/services/redis/src/reader.rs +8 -4
  445. data/core/services/redis/src/writer.rs +6 -2
  446. data/core/services/rocksdb/Cargo.toml +1 -1
  447. data/core/services/rocksdb/src/backend.rs +6 -5
  448. data/core/services/rocksdb/src/lister.rs +6 -5
  449. data/core/services/rocksdb/src/reader.rs +4 -1
  450. data/core/services/rocksdb/src/writer.rs +6 -2
  451. data/core/services/s3/Cargo.toml +5 -4
  452. data/core/services/s3/src/backend.rs +995 -39
  453. data/core/services/s3/src/compatible_services.md +57 -0
  454. data/core/services/s3/src/config.rs +16 -0
  455. data/core/services/s3/src/copier.rs +190 -55
  456. data/core/services/s3/src/core.rs +299 -190
  457. data/core/services/s3/src/deleter.rs +26 -7
  458. data/core/services/s3/src/docs.md +57 -0
  459. data/core/services/s3/src/lister.rs +55 -36
  460. data/core/services/s3/src/reader.rs +22 -2
  461. data/core/services/s3/src/writer.rs +154 -32
  462. data/core/services/seafile/Cargo.toml +2 -2
  463. data/core/services/seafile/src/backend.rs +3 -3
  464. data/core/services/seafile/src/core.rs +105 -79
  465. data/core/services/seafile/src/lister.rs +11 -8
  466. data/core/services/seafile/src/reader.rs +5 -2
  467. data/core/services/seafile/src/writer.rs +6 -3
  468. data/core/services/sftp/Cargo.toml +1 -1
  469. data/core/services/sftp/src/backend.rs +19 -4
  470. data/core/services/sftp/src/core.rs +51 -46
  471. data/core/services/sftp/src/lister.rs +4 -4
  472. data/core/services/sftp/src/writer.rs +1 -1
  473. data/core/services/sled/Cargo.toml +1 -1
  474. data/core/services/sled/src/backend.rs +6 -5
  475. data/core/services/sled/src/core.rs +28 -6
  476. data/core/services/sled/src/lister.rs +6 -5
  477. data/core/services/sled/src/reader.rs +4 -1
  478. data/core/services/sled/src/writer.rs +6 -2
  479. data/core/services/sqlite/Cargo.toml +2 -2
  480. data/core/services/sqlite/src/backend.rs +12 -7
  481. data/core/services/sqlite/src/core.rs +3 -3
  482. data/core/services/sqlite/src/reader.rs +4 -1
  483. data/core/services/sqlite/src/writer.rs +7 -3
  484. data/core/services/surrealdb/Cargo.toml +2 -2
  485. data/core/services/surrealdb/src/backend.rs +7 -6
  486. data/core/services/surrealdb/src/core.rs +1 -1
  487. data/core/services/surrealdb/src/reader.rs +4 -1
  488. data/core/services/surrealdb/src/writer.rs +6 -2
  489. data/core/services/swift/Cargo.toml +1 -1
  490. data/core/services/swift/src/backend.rs +38 -8
  491. data/core/services/swift/src/core.rs +63 -67
  492. data/core/services/swift/src/deleter.rs +8 -2
  493. data/core/services/swift/src/lister.rs +11 -8
  494. data/core/services/swift/src/reader.rs +5 -2
  495. data/core/services/swift/src/writer.rs +17 -8
  496. data/core/services/tikv/Cargo.toml +2 -2
  497. data/core/services/tikv/src/backend.rs +7 -6
  498. data/core/services/tikv/src/core.rs +31 -14
  499. data/core/services/tikv/src/reader.rs +4 -1
  500. data/core/services/tikv/src/writer.rs +6 -2
  501. data/core/services/tos/Cargo.toml +2 -2
  502. data/core/services/tos/src/backend.rs +45 -21
  503. data/core/services/tos/src/copier.rs +44 -17
  504. data/core/services/tos/src/core.rs +147 -117
  505. data/core/services/tos/src/deleter.rs +9 -3
  506. data/core/services/tos/src/docs.md +1 -1
  507. data/core/services/tos/src/lister.rs +43 -28
  508. data/core/services/tos/src/reader.rs +5 -2
  509. data/core/services/tos/src/writer.rs +37 -16
  510. data/core/services/upyun/Cargo.toml +1 -1
  511. data/core/services/upyun/src/backend.rs +30 -7
  512. data/core/services/upyun/src/core.rs +122 -87
  513. data/core/services/upyun/src/deleter.rs +4 -1
  514. data/core/services/upyun/src/lister.rs +10 -10
  515. data/core/services/upyun/src/reader.rs +5 -2
  516. data/core/services/upyun/src/writer.rs +19 -7
  517. data/core/services/vercel-artifacts/Cargo.toml +1 -1
  518. data/core/services/vercel-artifacts/src/backend.rs +6 -3
  519. data/core/services/vercel-artifacts/src/core.rs +32 -27
  520. data/core/services/vercel-artifacts/src/reader.rs +5 -2
  521. data/core/services/vercel-artifacts/src/writer.rs +6 -3
  522. data/core/services/vercel-blob/Cargo.toml +1 -1
  523. data/core/services/vercel-blob/src/backend.rs +23 -6
  524. data/core/services/vercel-blob/src/core.rs +92 -81
  525. data/core/services/vercel-blob/src/lister.rs +12 -3
  526. data/core/services/vercel-blob/src/reader.rs +5 -2
  527. data/core/services/vercel-blob/src/writer.rs +19 -7
  528. data/core/services/webdav/Cargo.toml +2 -2
  529. data/core/services/webdav/src/backend.rs +25 -5
  530. data/core/services/webdav/src/core.rs +116 -59
  531. data/core/services/webdav/src/deleter.rs +4 -1
  532. data/core/services/webdav/src/lister.rs +4 -1
  533. data/core/services/webdav/src/reader.rs +6 -2
  534. data/core/services/webdav/src/writer.rs +17 -7
  535. data/core/services/webhdfs/Cargo.toml +2 -2
  536. data/core/services/webhdfs/src/backend.rs +24 -11
  537. data/core/services/webhdfs/src/core.rs +99 -98
  538. data/core/services/webhdfs/src/deleter.rs +5 -2
  539. data/core/services/webhdfs/src/lister.rs +21 -9
  540. data/core/services/webhdfs/src/reader.rs +5 -2
  541. data/core/services/webhdfs/src/writer.rs +49 -14
  542. data/core/services/yandex-disk/Cargo.toml +1 -1
  543. data/core/services/yandex-disk/src/backend.rs +28 -6
  544. data/core/services/yandex-disk/src/core.rs +97 -79
  545. data/core/services/yandex-disk/src/deleter.rs +4 -1
  546. data/core/services/yandex-disk/src/lister.rs +25 -9
  547. data/core/services/yandex-disk/src/reader.rs +5 -2
  548. data/core/services/yandex-disk/src/writer.rs +6 -3
  549. data/core/src/lib.rs +5 -0
  550. data/core/testkit/Cargo.toml +4 -4
  551. data/core/testkit/src/utils.rs +53 -8
  552. data/core/tests/behavior/async_compose.rs +297 -0
  553. data/core/tests/behavior/async_copy.rs +162 -1
  554. data/core/tests/behavior/async_delete.rs +364 -3
  555. data/core/tests/behavior/async_read.rs +86 -0
  556. data/core/tests/behavior/async_restore.rs +162 -0
  557. data/core/tests/behavior/async_stat.rs +90 -0
  558. data/core/tests/behavior/async_write.rs +330 -2
  559. data/core/tests/behavior/main.rs +4 -0
  560. data/core/upgrade.md +114 -1
  561. data/src/capability.rs +0 -1
  562. data/src/io.rs +0 -1
  563. data/src/lister.rs +0 -1
  564. data/src/metadata.rs +0 -1
  565. data/src/operator_info.rs +0 -1
  566. metadata +44 -2
@@ -19,15 +19,21 @@ use std::sync::Arc;
19
19
 
20
20
  use bytes::Buf;
21
21
  use http::StatusCode;
22
+ use http::header::LOCATION;
23
+ use http::header::RANGE;
22
24
 
23
25
  use super::core::CompleteMultipartUploadRequestPart;
26
+ use super::core::ErrorContext;
24
27
  use super::core::GcsCore;
25
28
  use super::core::InitiateMultipartUploadResult;
29
+ use super::core::constants::X_GOOG_GENERATION;
26
30
  use super::core::parse_error;
27
31
  use opendal_core::raw::*;
28
32
  use opendal_core::*;
29
33
 
30
- pub type GcsWriters = oio::MultipartWriter<GcsWriter>;
34
+ pub type GcsWriters = TwoWays<oio::MultipartWriter<GcsWriter>, GcsConditionalWriter>;
35
+
36
+ const RESUMABLE_CHUNK_SIZE: usize = 8 * 1024 * 1024;
31
37
 
32
38
  pub struct GcsWriter {
33
39
  core: Arc<GcsCore>,
@@ -67,7 +73,11 @@ impl oio::MultipartWrite for GcsWriter {
67
73
  GcsCore::build_metadata_from_object_response(&self.path, resp.into_body())?;
68
74
  Ok(metadata)
69
75
  }
70
- _ => Err(parse_error(resp)),
76
+ _ => Err(parse_error(
77
+ ErrorContext::new(ServiceOperation("InsertObject"))
78
+ .with_caller_condition(self.op.is_conditional()),
79
+ resp,
80
+ )),
71
81
  }
72
82
  }
73
83
 
@@ -78,7 +88,10 @@ impl oio::MultipartWrite for GcsWriter {
78
88
  .await?;
79
89
 
80
90
  if !resp.status().is_success() {
81
- return Err(parse_error(resp));
91
+ return Err(parse_error(
92
+ ErrorContext::new(ServiceOperation("CreateMultipartUpload")),
93
+ resp,
94
+ ));
82
95
  }
83
96
 
84
97
  let buf = resp.into_body();
@@ -103,7 +116,10 @@ impl oio::MultipartWrite for GcsWriter {
103
116
  .await?;
104
117
 
105
118
  if !resp.status().is_success() {
106
- return Err(parse_error(resp));
119
+ return Err(parse_error(
120
+ ErrorContext::new(ServiceOperation("UploadPart")),
121
+ resp,
122
+ ));
107
123
  }
108
124
 
109
125
  let etag = parse_etag(resp.headers())?
@@ -142,12 +158,23 @@ impl oio::MultipartWrite for GcsWriter {
142
158
  .await?;
143
159
 
144
160
  if !resp.status().is_success() {
145
- return Err(parse_error(resp));
161
+ return Err(parse_error(
162
+ ErrorContext::new(ServiceOperation("CompleteMultipartUpload")),
163
+ resp,
164
+ ));
165
+ }
166
+ let mut metadata = if self.path.ends_with('/') {
167
+ MetadataBuilder::dir()
168
+ } else {
169
+ MetadataBuilder::unknown()
170
+ };
171
+ if let Some(etag) = parse_etag(resp.headers())? {
172
+ metadata.etag(etag);
173
+ }
174
+ if let Some(generation) = parse_header_to_str(resp.headers(), X_GOOG_GENERATION)? {
175
+ metadata.version(generation);
146
176
  }
147
- // we don't extract metadata from `CompleteMultipartUploadResult`, since we only need the `ETag` from it.
148
- // However, the `ETag` differs from the `ETag` obtained through the `stat` operation.
149
- // refer to: https://cloud.google.com/storage/docs/metadata#etags
150
- Ok(Metadata::default())
177
+ Ok(metadata.build())
151
178
  }
152
179
 
153
180
  async fn abort_part(&self, upload_id: &str) -> Result<()> {
@@ -158,7 +185,602 @@ impl oio::MultipartWrite for GcsWriter {
158
185
  match resp.status() {
159
186
  // gcs returns code 204 if abort succeeds.
160
187
  StatusCode::NO_CONTENT => Ok(()),
161
- _ => Err(parse_error(resp)),
188
+ _ => Err(parse_error(
189
+ ErrorContext::new(ServiceOperation("AbortMultipartUpload")),
190
+ resp,
191
+ )),
192
+ }
193
+ }
194
+ }
195
+
196
+ pub struct GcsConditionalWriter {
197
+ core: Arc<GcsCore>,
198
+ ctx: OperationContext,
199
+ path: String,
200
+ op: OpWrite,
201
+ session_uri: Option<String>,
202
+ offset: u64,
203
+ pending: oio::QueueBuf,
204
+ finalize_state: FinalizeState,
205
+ }
206
+
207
+ enum ResumableUploadStatus {
208
+ Incomplete(u64),
209
+ Complete(Box<Metadata>),
210
+ }
211
+
212
+ #[derive(Eq, PartialEq)]
213
+ enum FinalizeState {
214
+ Ready,
215
+ CheckStatus,
216
+ }
217
+
218
+ impl GcsConditionalWriter {
219
+ pub fn new(core: Arc<GcsCore>, ctx: OperationContext, path: &str, op: OpWrite) -> Self {
220
+ Self {
221
+ core,
222
+ ctx,
223
+ path: path.to_string(),
224
+ op,
225
+ session_uri: None,
226
+ offset: 0,
227
+ pending: oio::QueueBuf::new(),
228
+ finalize_state: FinalizeState::Ready,
229
+ }
230
+ }
231
+
232
+ async fn ensure_session(&mut self) -> Result<&str> {
233
+ if self.session_uri.is_none() {
234
+ let resp = self
235
+ .core
236
+ .gcs_initiate_resumable_upload(&self.ctx, &self.path, &self.op)
237
+ .await?;
238
+ if !resp.status().is_success() {
239
+ return Err(parse_error(
240
+ ErrorContext::new(ServiceOperation("InitiateResumableUpload"))
241
+ .with_caller_condition(self.op.is_conditional()),
242
+ resp,
243
+ ));
244
+ }
245
+ let location = parse_header_to_str(resp.headers(), LOCATION)?
246
+ .ok_or_else(|| {
247
+ Error::new(
248
+ ErrorKind::Unexpected,
249
+ "GCS resumable upload response is missing Location",
250
+ )
251
+ })?
252
+ .to_string();
253
+ self.session_uri = Some(location);
254
+ }
255
+ Ok(self.session_uri.as_deref().unwrap())
256
+ }
257
+
258
+ fn persisted_offset(resp: &http::Response<Buffer>) -> Result<u64> {
259
+ let Some(range) = parse_header_to_str(resp.headers(), RANGE)? else {
260
+ return Ok(0);
261
+ };
262
+ let end = range
263
+ .strip_prefix("bytes=")
264
+ .and_then(|v| v.rsplit_once('-'))
265
+ .map(|(_, end)| end)
266
+ .ok_or_else(|| {
267
+ Error::new(
268
+ ErrorKind::Unexpected,
269
+ "invalid Range header in GCS resumable upload response",
270
+ )
271
+ .with_context("range", range)
272
+ })?;
273
+ end.parse::<u64>().map(|v| v + 1).map_err(|err| {
274
+ Error::new(
275
+ ErrorKind::Unexpected,
276
+ "invalid Range header in GCS resumable upload response",
277
+ )
278
+ .with_context("range", range)
279
+ .set_source(err)
280
+ })
281
+ }
282
+
283
+ async fn query_status(&mut self, total: u64) -> Result<ResumableUploadStatus> {
284
+ let session_uri = self.ensure_session().await?.to_string();
285
+ let resp = self
286
+ .core
287
+ .gcs_query_resumable_upload(&self.ctx, &session_uri, total)
288
+ .await?;
289
+
290
+ match resp.status() {
291
+ StatusCode::PERMANENT_REDIRECT => {
292
+ let persisted = Self::persisted_offset(&resp)?;
293
+ Ok(ResumableUploadStatus::Incomplete(persisted))
294
+ }
295
+ StatusCode::OK | StatusCode::CREATED => {
296
+ let metadata =
297
+ GcsCore::build_metadata_from_object_response(&self.path, resp.into_body())?;
298
+ Ok(ResumableUploadStatus::Complete(Box::new(metadata)))
299
+ }
300
+ _ => Err(parse_error(
301
+ ErrorContext::new(ServiceOperation("QueryResumableUpload")),
302
+ resp,
303
+ )),
162
304
  }
163
305
  }
306
+
307
+ async fn upload_chunk(&mut self, body: Buffer) -> Result<()> {
308
+ let start = self.offset;
309
+ let end = start + body.len() as u64;
310
+ let session_uri = self.ensure_session().await?.to_string();
311
+ let resp = self
312
+ .core
313
+ .gcs_upload_resumable_chunk(&self.ctx, &session_uri, start, body, None)
314
+ .await?;
315
+
316
+ if resp.status() == StatusCode::PERMANENT_REDIRECT {
317
+ let persisted = Self::persisted_offset(&resp)?;
318
+ if persisted < start || persisted > end {
319
+ return Err(Error::new(
320
+ ErrorKind::Unexpected,
321
+ "GCS resumable upload reported invalid persisted range",
322
+ )
323
+ .with_context("start", start)
324
+ .with_context("end", end)
325
+ .with_context("persisted", persisted)
326
+ .set_temporary());
327
+ }
328
+ if persisted < end {
329
+ // GCS ignores an already persisted prefix when the same chunk
330
+ // is retried, so advance only after the full chunk is acknowledged.
331
+ return Err(Error::new(
332
+ ErrorKind::Unexpected,
333
+ "GCS resumable upload persisted only part of a chunk",
334
+ )
335
+ .with_context("start", start)
336
+ .with_context("end", end)
337
+ .with_context("persisted", persisted)
338
+ .set_temporary());
339
+ }
340
+ self.offset = end;
341
+ return Ok(());
342
+ }
343
+
344
+ if resp.status() == StatusCode::OK || resp.status() == StatusCode::CREATED {
345
+ return Err(Error::new(
346
+ ErrorKind::Unexpected,
347
+ "GCS finalized a resumable upload before the last chunk",
348
+ ));
349
+ }
350
+
351
+ Err(parse_error(
352
+ ErrorContext::new(ServiceOperation("UploadResumableChunk"))
353
+ .with_caller_condition(self.op.is_conditional()),
354
+ resp,
355
+ ))
356
+ }
357
+
358
+ async fn flush_pending(&mut self) -> Result<()> {
359
+ while self.pending.len() > RESUMABLE_CHUNK_SIZE {
360
+ let mut chunk = self.pending.clone().collect();
361
+ chunk.truncate(RESUMABLE_CHUNK_SIZE);
362
+ self.upload_chunk(chunk).await?;
363
+ self.pending.advance(RESUMABLE_CHUNK_SIZE);
364
+ }
365
+ Ok(())
366
+ }
367
+ }
368
+
369
+ impl oio::Write for GcsConditionalWriter {
370
+ async fn write(&mut self, body: Buffer) -> Result<()> {
371
+ self.flush_pending().await?;
372
+ self.pending.push(body);
373
+ Ok(())
374
+ }
375
+
376
+ async fn close(&mut self) -> Result<Metadata> {
377
+ self.flush_pending().await?;
378
+ let body = self.pending.clone().collect();
379
+ let start = self.offset;
380
+ let total = start + body.len() as u64;
381
+
382
+ if self.finalize_state == FinalizeState::CheckStatus {
383
+ match self.query_status(total).await? {
384
+ ResumableUploadStatus::Incomplete(persisted) => {
385
+ if persisted < start || persisted > total {
386
+ return Err(Error::new(
387
+ ErrorKind::Unexpected,
388
+ "GCS resumable upload reported invalid persisted range",
389
+ )
390
+ .with_context("start", start)
391
+ .with_context("end", total)
392
+ .with_context("persisted", persisted)
393
+ .set_temporary());
394
+ }
395
+ self.finalize_state = FinalizeState::Ready;
396
+ }
397
+ ResumableUploadStatus::Complete(metadata) => {
398
+ self.finalize_state = FinalizeState::Ready;
399
+ self.offset = total;
400
+ self.pending.advance(body.len());
401
+ return Ok(*metadata);
402
+ }
403
+ }
404
+ }
405
+
406
+ let session_uri = self.ensure_session().await?.to_string();
407
+ self.finalize_state = FinalizeState::CheckStatus;
408
+ let resp = self
409
+ .core
410
+ .gcs_upload_resumable_chunk(&self.ctx, &session_uri, start, body.clone(), Some(total))
411
+ .await?;
412
+
413
+ if resp.status() == StatusCode::PERMANENT_REDIRECT {
414
+ let persisted = Self::persisted_offset(&resp)?;
415
+ if persisted < start || persisted > total {
416
+ return Err(Error::new(
417
+ ErrorKind::Unexpected,
418
+ "GCS resumable upload reported invalid persisted range",
419
+ )
420
+ .with_context("start", start)
421
+ .with_context("end", total)
422
+ .with_context("persisted", persisted)
423
+ .set_temporary());
424
+ }
425
+ self.finalize_state = FinalizeState::Ready;
426
+ return Err(Error::new(
427
+ ErrorKind::Unexpected,
428
+ "GCS did not finalize the last resumable upload chunk",
429
+ )
430
+ .with_context("persisted", persisted)
431
+ .set_temporary());
432
+ }
433
+
434
+ if resp.status() == StatusCode::OK || resp.status() == StatusCode::CREATED {
435
+ let metadata =
436
+ GcsCore::build_metadata_from_object_response(&self.path, resp.into_body())?;
437
+ self.finalize_state = FinalizeState::Ready;
438
+ self.offset = total;
439
+ self.pending.advance(body.len());
440
+ return Ok(metadata);
441
+ }
442
+
443
+ let err = parse_error(
444
+ ErrorContext::new(ServiceOperation("UploadResumableChunk"))
445
+ .with_caller_condition(self.op.is_conditional()),
446
+ resp,
447
+ );
448
+ if !err.is_temporary() {
449
+ self.finalize_state = FinalizeState::Ready;
450
+ }
451
+ Err(err)
452
+ }
453
+
454
+ async fn abort(&mut self) -> Result<()> {
455
+ let Some(session_uri) = self.session_uri.as_deref() else {
456
+ self.pending.clear();
457
+ return Ok(());
458
+ };
459
+ let resp = self
460
+ .core
461
+ .gcs_cancel_resumable_upload(&self.ctx, session_uri)
462
+ .await?;
463
+ if resp.status().is_success()
464
+ || resp.status().as_u16() == 499
465
+ || resp.status() == StatusCode::NOT_FOUND
466
+ || resp.status() == StatusCode::GONE
467
+ {
468
+ self.session_uri = None;
469
+ self.pending.clear();
470
+ Ok(())
471
+ } else {
472
+ Err(parse_error(
473
+ ErrorContext::new(ServiceOperation("CancelResumableUpload")),
474
+ resp,
475
+ ))
476
+ }
477
+ }
478
+ }
479
+
480
+ #[cfg(test)]
481
+ mod tests {
482
+ use std::sync::Mutex;
483
+
484
+ use bytes::Bytes;
485
+ use futures::stream;
486
+ use http::Request;
487
+ use http::Response;
488
+ use http::header::CONTENT_LENGTH;
489
+ use opendal_core::raw::oio::Write as _;
490
+ use reqsign_core::Context;
491
+ use reqsign_core::ProvideCredentialChain;
492
+ use reqsign_core::Signer;
493
+ use reqsign_google::RequestSigner;
494
+ use reqsign_google::TokenCredentialProvider;
495
+
496
+ use super::*;
497
+
498
+ #[derive(Default)]
499
+ struct MockState {
500
+ uploaded: Vec<u8>,
501
+ ranges: Vec<String>,
502
+ fail_chunk_response: bool,
503
+ fail_final_response: bool,
504
+ fail_final_response_partially: bool,
505
+ completed: bool,
506
+ }
507
+
508
+ #[derive(Clone, Default)]
509
+ struct MockResumableTransport {
510
+ state: Arc<Mutex<MockState>>,
511
+ }
512
+
513
+ impl MockResumableTransport {
514
+ fn fail_chunk_response(&self) {
515
+ self.state.lock().unwrap().fail_chunk_response = true;
516
+ }
517
+
518
+ fn fail_final_response(&self) {
519
+ self.state.lock().unwrap().fail_final_response = true;
520
+ }
521
+
522
+ fn fail_final_response_partially(&self) {
523
+ self.state.lock().unwrap().fail_final_response_partially = true;
524
+ }
525
+
526
+ fn uploaded(&self) -> Vec<u8> {
527
+ self.state.lock().unwrap().uploaded.clone()
528
+ }
529
+
530
+ fn ranges(&self) -> Vec<String> {
531
+ self.state.lock().unwrap().ranges.clone()
532
+ }
533
+
534
+ fn response(
535
+ status: StatusCode,
536
+ range: Option<String>,
537
+ body: Option<String>,
538
+ ) -> Result<Response<HttpBody>> {
539
+ let body = body.unwrap_or_default();
540
+ let size = body.len() as u64;
541
+ let mut response = Response::builder()
542
+ .status(status)
543
+ .header(CONTENT_LENGTH, size);
544
+ if let Some(range) = range {
545
+ response = response.header(RANGE, range);
546
+ }
547
+ let stream = if body.is_empty() {
548
+ stream::iter(Vec::<Result<Buffer>>::new())
549
+ } else {
550
+ stream::iter(vec![Ok(Buffer::from(Bytes::from(body)))])
551
+ };
552
+ Ok(response
553
+ .body(HttpBody::new(stream, Some(size)))
554
+ .expect("mock response must build"))
555
+ }
556
+
557
+ fn completed_response(size: usize) -> Result<Response<HttpBody>> {
558
+ Self::response(
559
+ StatusCode::OK,
560
+ None,
561
+ Some(format!(r#"{{"size":"{size}","generation":"1"}}"#)),
562
+ )
563
+ }
564
+ }
565
+
566
+ impl HttpTransport for MockResumableTransport {
567
+ async fn fetch(&self, req: Request<Buffer>) -> Result<Response<HttpBody>> {
568
+ if req.method() == http::Method::POST {
569
+ return Ok(Response::builder()
570
+ .status(StatusCode::OK)
571
+ .header(LOCATION, "https://upload.example/session")
572
+ .header(CONTENT_LENGTH, 0)
573
+ .body(HttpBody::new(
574
+ stream::iter(Vec::<Result<Buffer>>::new()),
575
+ Some(0),
576
+ ))
577
+ .expect("mock response must build"));
578
+ }
579
+
580
+ assert_eq!(req.method(), http::Method::PUT);
581
+ assert_eq!(req.uri(), "https://upload.example/session");
582
+
583
+ let content_range = req
584
+ .headers()
585
+ .get("content-range")
586
+ .unwrap()
587
+ .to_str()
588
+ .unwrap()
589
+ .to_string();
590
+ let body = req.body().to_bytes();
591
+ let mut state = self.state.lock().unwrap();
592
+ state.ranges.push(content_range.clone());
593
+
594
+ if body.is_empty() {
595
+ if content_range == "bytes */0" && state.uploaded.is_empty() {
596
+ state.completed = true;
597
+ return Self::completed_response(0);
598
+ }
599
+ if state.completed {
600
+ return Self::completed_response(state.uploaded.len());
601
+ }
602
+ let range = (!state.uploaded.is_empty())
603
+ .then(|| format!("bytes=0-{}", state.uploaded.len() - 1));
604
+ return Self::response(StatusCode::PERMANENT_REDIRECT, range, None);
605
+ }
606
+
607
+ let value = content_range.strip_prefix("bytes ").unwrap();
608
+ let (range, total) = value.split_once('/').unwrap();
609
+ let (start, end) = range.split_once('-').unwrap();
610
+ let start = start.parse::<usize>().unwrap();
611
+ let end = end.parse::<usize>().unwrap();
612
+ assert_eq!(end + 1 - start, body.len());
613
+ assert!(start <= state.uploaded.len());
614
+ let persisted = state.uploaded.len() - start;
615
+ assert!(persisted <= body.len());
616
+ assert_eq!(&state.uploaded[start..], &body[..persisted]);
617
+ let remaining = &body[persisted..];
618
+
619
+ if total == "*" {
620
+ if state.fail_chunk_response {
621
+ state.fail_chunk_response = false;
622
+ state
623
+ .uploaded
624
+ .extend_from_slice(&remaining[..remaining.len() / 2]);
625
+ return Err(
626
+ Error::new(ErrorKind::Unexpected, "mock lost chunk response")
627
+ .set_temporary(),
628
+ );
629
+ }
630
+ state.uploaded.extend_from_slice(remaining);
631
+ let range = Some(format!("bytes=0-{}", state.uploaded.len() - 1));
632
+ return Self::response(StatusCode::PERMANENT_REDIRECT, range, None);
633
+ }
634
+
635
+ if state.fail_final_response_partially {
636
+ state.fail_final_response_partially = false;
637
+ state
638
+ .uploaded
639
+ .extend_from_slice(&remaining[..remaining.len() / 2]);
640
+ return Err(
641
+ Error::new(ErrorKind::Unexpected, "mock lost partial final response")
642
+ .set_temporary(),
643
+ );
644
+ }
645
+ state.uploaded.extend_from_slice(remaining);
646
+ assert_eq!(total.parse::<usize>().unwrap(), state.uploaded.len());
647
+ state.completed = true;
648
+ if state.fail_final_response {
649
+ state.fail_final_response = false;
650
+ return Err(
651
+ Error::new(ErrorKind::Unexpected, "mock lost final response").set_temporary(),
652
+ );
653
+ }
654
+ Self::completed_response(state.uploaded.len())
655
+ }
656
+ }
657
+
658
+ fn test_writer(transport: MockResumableTransport) -> GcsConditionalWriter {
659
+ let sign_ctx = Context::new();
660
+ let signer = Signer::new(
661
+ sign_ctx.clone(),
662
+ ProvideCredentialChain::new().push(TokenCredentialProvider::new("test-token")),
663
+ RequestSigner::new("storage"),
664
+ );
665
+ let core = Arc::new(GcsCore {
666
+ info: ServiceInfo::new("gcs", "/", "test-bucket"),
667
+ capability: Capability::default(),
668
+ endpoint: "https://storage.googleapis.com".to_string(),
669
+ bucket: "test-bucket".to_string(),
670
+ root: "/".to_string(),
671
+ signer,
672
+ sign_ctx,
673
+ predefined_acl: None,
674
+ default_storage_class: None,
675
+ skip_signature: true,
676
+ });
677
+ let ctx = OperationContext::new().with_http_transport(HttpTransporter::new(transport));
678
+ let (op, _) = OpWrite::from_options(
679
+ &Capability::default(),
680
+ options::WriteOptions {
681
+ if_not_exists: true,
682
+ ..Default::default()
683
+ },
684
+ )
685
+ .unwrap();
686
+ GcsConditionalWriter::new(core, ctx, "object", op)
687
+ }
688
+
689
+ #[tokio::test]
690
+ async fn retry_write_resends_interrupted_chunk_before_accepting_input() {
691
+ let transport = MockResumableTransport::default();
692
+ transport.fail_chunk_response();
693
+ let mut writer = test_writer(transport.clone());
694
+ let mut expected = vec![7; RESUMABLE_CHUNK_SIZE + 3];
695
+ let body = Buffer::from(Bytes::copy_from_slice(&expected));
696
+ let next = Bytes::from_static(b"next");
697
+
698
+ writer.write(body).await.unwrap();
699
+ let err = writer.write(Buffer::from(next.clone())).await.unwrap_err();
700
+ assert!(err.is_temporary());
701
+ writer.write(Buffer::from(next.clone())).await.unwrap();
702
+ let metadata = writer.close().await.unwrap();
703
+
704
+ expected.extend_from_slice(&next);
705
+ assert_eq!(transport.uploaded(), expected);
706
+ assert_eq!(
707
+ metadata.content_length(),
708
+ (RESUMABLE_CHUNK_SIZE + 3 + next.len()) as u64
709
+ );
710
+ assert_eq!(
711
+ transport.ranges(),
712
+ vec![
713
+ format!("bytes 0-{}/*", RESUMABLE_CHUNK_SIZE - 1),
714
+ format!("bytes 0-{}/*", RESUMABLE_CHUNK_SIZE - 1),
715
+ format!(
716
+ "bytes {}-{}/{}",
717
+ RESUMABLE_CHUNK_SIZE,
718
+ RESUMABLE_CHUNK_SIZE + 2 + next.len(),
719
+ RESUMABLE_CHUNK_SIZE + 3 + next.len()
720
+ ),
721
+ ]
722
+ );
723
+ }
724
+
725
+ #[tokio::test]
726
+ async fn retry_close_reconciles_lost_final_response() {
727
+ let transport = MockResumableTransport::default();
728
+ transport.fail_final_response();
729
+ let mut writer = test_writer(transport.clone());
730
+ let expected = b"hello".to_vec();
731
+ writer
732
+ .write(Buffer::from(Bytes::copy_from_slice(&expected)))
733
+ .await
734
+ .unwrap();
735
+
736
+ let err = writer.close().await.unwrap_err();
737
+ assert!(err.is_temporary());
738
+ let metadata = writer.close().await.unwrap();
739
+
740
+ assert_eq!(transport.uploaded(), expected);
741
+ assert_eq!(metadata.content_length(), 5);
742
+ assert_eq!(
743
+ transport.ranges(),
744
+ vec!["bytes 0-4/5".to_string(), "bytes */5".to_string()]
745
+ );
746
+ }
747
+
748
+ #[tokio::test]
749
+ async fn retry_close_resends_after_incomplete_status() {
750
+ let transport = MockResumableTransport::default();
751
+ transport.fail_final_response_partially();
752
+ let mut writer = test_writer(transport.clone());
753
+ let expected = b"hello".to_vec();
754
+ writer
755
+ .write(Buffer::from(Bytes::copy_from_slice(&expected)))
756
+ .await
757
+ .unwrap();
758
+
759
+ let err = writer.close().await.unwrap_err();
760
+ assert!(err.is_temporary());
761
+ let metadata = writer.close().await.unwrap();
762
+
763
+ assert_eq!(transport.uploaded(), expected);
764
+ assert_eq!(metadata.content_length(), 5);
765
+ assert_eq!(
766
+ transport.ranges(),
767
+ vec![
768
+ "bytes 0-4/5".to_string(),
769
+ "bytes */5".to_string(),
770
+ "bytes 0-4/5".to_string(),
771
+ ]
772
+ );
773
+ }
774
+
775
+ #[tokio::test]
776
+ async fn close_empty_object_uses_resumable_upload() {
777
+ let transport = MockResumableTransport::default();
778
+ let mut writer = test_writer(transport.clone());
779
+
780
+ let metadata = writer.close().await.unwrap();
781
+
782
+ assert_eq!(metadata.content_length(), 0);
783
+ assert_eq!(transport.uploaded(), Vec::<u8>::new());
784
+ assert_eq!(transport.ranges(), vec!["bytes */0".to_string()]);
785
+ }
164
786
  }