opendal 0.1.12 → 0.1.13

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +31 -36
  3. data/Cargo.toml +1 -1
  4. data/DEPENDENCIES.rust.tsv +25 -35
  5. data/README.md +1 -1
  6. data/core/CHANGELOG.md +4 -0
  7. data/core/Cargo.lock +128 -187
  8. data/core/Cargo.toml +95 -95
  9. data/core/DEPENDENCIES.rust.tsv +259 -289
  10. data/core/LICENSE +16 -0
  11. data/core/benches/vs_fs/Cargo.toml +1 -1
  12. data/core/benches/vs_s3/Cargo.toml +1 -1
  13. data/core/core/CHANGELOG.md +4 -0
  14. data/core/core/Cargo.toml +1 -1
  15. data/core/core/LICENSE +16 -0
  16. data/core/core/src/docs/upgrade.md +8 -0
  17. data/core/core/src/lib.rs +1 -3
  18. data/core/core/src/raw/futures_util.rs +26 -21
  19. data/core/core/src/raw/http_util/error.rs +9 -0
  20. data/core/core/src/raw/ops.rs +1 -0
  21. data/core/core/src/types/http_transport/mod.rs +30 -2
  22. data/core/core/src/types/http_transport/uri.rs +166 -0
  23. data/core/core/src/types/metadata.rs +1 -0
  24. data/core/core/src/types/mod.rs +2 -0
  25. data/core/http-transports/reqwest/Cargo.toml +1 -1
  26. data/core/http-transports/reqwest/LICENSE +16 -0
  27. data/core/http-transports/reqwest/src/lib.rs +46 -4
  28. data/core/layers/async-backtrace/Cargo.toml +2 -2
  29. data/core/layers/async-backtrace/LICENSE +16 -0
  30. data/core/layers/await-tree/Cargo.toml +2 -2
  31. data/core/layers/await-tree/LICENSE +16 -0
  32. data/core/layers/capability-check/Cargo.toml +2 -2
  33. data/core/layers/capability-check/LICENSE +16 -0
  34. data/core/layers/chaos/Cargo.toml +2 -2
  35. data/core/layers/chaos/LICENSE +16 -0
  36. data/core/layers/concurrent-limit/Cargo.toml +3 -3
  37. data/core/layers/concurrent-limit/LICENSE +16 -0
  38. data/core/layers/dtrace/Cargo.toml +2 -2
  39. data/core/layers/dtrace/LICENSE +16 -0
  40. data/core/layers/fastmetrics/Cargo.toml +3 -3
  41. data/core/layers/fastmetrics/LICENSE +16 -0
  42. data/core/layers/fastrace/Cargo.toml +2 -2
  43. data/core/layers/fastrace/LICENSE +16 -0
  44. data/core/layers/foyer/Cargo.toml +2 -2
  45. data/core/layers/foyer/LICENSE +16 -0
  46. data/core/layers/hotpath/Cargo.toml +2 -2
  47. data/core/layers/hotpath/LICENSE +16 -0
  48. data/core/layers/immutable-index/Cargo.toml +2 -2
  49. data/core/layers/immutable-index/LICENSE +16 -0
  50. data/core/layers/logging/Cargo.toml +2 -2
  51. data/core/layers/logging/LICENSE +16 -0
  52. data/core/layers/metrics/Cargo.toml +3 -3
  53. data/core/layers/metrics/LICENSE +16 -0
  54. data/core/layers/mime-guess/Cargo.toml +2 -2
  55. data/core/layers/mime-guess/LICENSE +16 -0
  56. data/core/layers/observe-metrics-common/Cargo.toml +1 -1
  57. data/core/layers/observe-metrics-common/LICENSE +16 -0
  58. data/core/layers/otelmetrics/Cargo.toml +3 -3
  59. data/core/layers/otelmetrics/LICENSE +16 -0
  60. data/core/layers/oteltrace/Cargo.toml +2 -2
  61. data/core/layers/oteltrace/LICENSE +16 -0
  62. data/core/layers/prometheus/Cargo.toml +3 -3
  63. data/core/layers/prometheus/LICENSE +16 -0
  64. data/core/layers/prometheus-client/Cargo.toml +3 -3
  65. data/core/layers/prometheus-client/LICENSE +16 -0
  66. data/core/layers/retry/Cargo.toml +4 -4
  67. data/core/layers/retry/LICENSE +16 -0
  68. data/core/layers/route/Cargo.toml +3 -3
  69. data/core/layers/route/LICENSE +16 -0
  70. data/core/layers/tail-cut/Cargo.toml +2 -2
  71. data/core/layers/tail-cut/LICENSE +16 -0
  72. data/core/layers/throttle/Cargo.toml +2 -2
  73. data/core/layers/throttle/LICENSE +16 -0
  74. data/core/layers/timeout/Cargo.toml +3 -3
  75. data/core/layers/timeout/LICENSE +16 -0
  76. data/core/layers/tracing/Cargo.toml +2 -2
  77. data/core/layers/tracing/LICENSE +16 -0
  78. data/core/services/aliyun-drive/Cargo.toml +2 -2
  79. data/core/services/aliyun-drive/LICENSE +16 -0
  80. data/core/services/alluxio/Cargo.toml +1 -1
  81. data/core/services/alluxio/LICENSE +16 -0
  82. data/core/services/azblob/Cargo.toml +2 -2
  83. data/core/services/azblob/LICENSE +16 -0
  84. data/core/services/azblob/src/backend.rs +21 -7
  85. data/core/services/azblob/src/config.rs +4 -1
  86. data/core/services/azdls/Cargo.toml +3 -3
  87. data/core/services/azdls/LICENSE +16 -0
  88. data/core/services/azfile/Cargo.toml +2 -2
  89. data/core/services/azfile/LICENSE +16 -0
  90. data/core/services/azure-common/Cargo.toml +1 -1
  91. data/core/services/azure-common/LICENSE +16 -0
  92. data/core/services/b2/Cargo.toml +2 -2
  93. data/core/services/b2/LICENSE +16 -0
  94. data/core/services/cacache/Cargo.toml +1 -1
  95. data/core/services/cacache/LICENSE +16 -0
  96. data/core/services/cloudflare-kv/Cargo.toml +1 -1
  97. data/core/services/cloudflare-kv/LICENSE +16 -0
  98. data/core/services/compfs/Cargo.toml +1 -1
  99. data/core/services/compfs/LICENSE +16 -0
  100. data/core/services/compfs/src/core.rs +27 -11
  101. data/core/services/cos/Cargo.toml +1 -1
  102. data/core/services/cos/LICENSE +16 -0
  103. data/core/services/d1/Cargo.toml +1 -1
  104. data/core/services/d1/LICENSE +16 -0
  105. data/core/services/dashmap/Cargo.toml +1 -1
  106. data/core/services/dashmap/LICENSE +16 -0
  107. data/core/services/dbfs/Cargo.toml +1 -1
  108. data/core/services/dbfs/LICENSE +16 -0
  109. data/core/services/dropbox/Cargo.toml +2 -2
  110. data/core/services/dropbox/LICENSE +16 -0
  111. data/core/services/etcd/Cargo.toml +2 -2
  112. data/core/services/etcd/LICENSE +16 -0
  113. data/core/services/etcd/src/core.rs +3 -3
  114. data/core/services/foundationdb/Cargo.toml +1 -1
  115. data/core/services/foundationdb/LICENSE +16 -0
  116. data/core/services/foyer/Cargo.toml +3 -3
  117. data/core/services/foyer/LICENSE +16 -0
  118. data/core/services/fs/Cargo.toml +1 -1
  119. data/core/services/fs/LICENSE +16 -0
  120. data/core/services/fs/src/core.rs +69 -10
  121. data/core/services/ftp/Cargo.toml +2 -2
  122. data/core/services/ftp/LICENSE +16 -0
  123. data/core/services/ftp/src/core.rs +1 -1
  124. data/core/services/ftp/src/reader.rs +1 -1
  125. data/core/services/ftp/src/writer.rs +1 -1
  126. data/core/services/gcs/Cargo.toml +3 -2
  127. data/core/services/gcs/LICENSE +16 -0
  128. data/core/services/gcs/src/composer.rs +11 -21
  129. data/core/services/gcs-grpc/Cargo.toml +1 -1
  130. data/core/services/gcs-grpc/proto/google/storage/v2/storage.proto +17 -18
  131. data/core/services/gdrive/Cargo.toml +3 -2
  132. data/core/services/gdrive/LICENSE +16 -0
  133. data/core/services/gdrive/src/docs.md +2 -0
  134. data/core/services/gdrive/src/path_index.rs +109 -18
  135. data/core/services/ghac/Cargo.toml +2 -2
  136. data/core/services/ghac/LICENSE +16 -0
  137. data/core/services/github/Cargo.toml +1 -1
  138. data/core/services/github/LICENSE +16 -0
  139. data/core/services/goosefs/Cargo.toml +13 -4
  140. data/core/services/goosefs/LICENSE +16 -0
  141. data/core/services/goosefs/README.md +11 -0
  142. data/core/services/goosefs/src/backend.rs +3 -5
  143. data/core/services/goosefs/src/core.rs +1 -1
  144. data/core/services/goosefs/src/docs.md +44 -3
  145. data/core/services/goosefs/src/reader.rs +22 -89
  146. data/core/services/gridfs/Cargo.toml +2 -2
  147. data/core/services/gridfs/LICENSE +16 -0
  148. data/core/services/hdfs/Cargo.toml +1 -1
  149. data/core/services/hdfs/LICENSE +16 -0
  150. data/core/services/hdfs-native/Cargo.toml +1 -1
  151. data/core/services/hdfs-native/LICENSE +16 -0
  152. data/core/services/hf/Cargo.toml +3 -3
  153. data/core/services/hf/LICENSE +16 -0
  154. data/core/services/hf/src/backend.rs +77 -9
  155. data/core/services/hf/src/config.rs +58 -0
  156. data/core/services/hf/src/core.rs +695 -318
  157. data/core/services/hf/src/docs.md +29 -0
  158. data/core/services/hf/src/lister.rs +3 -3
  159. data/core/services/hf/src/reader.rs +128 -143
  160. data/core/services/http/Cargo.toml +3 -3
  161. data/core/services/http/LICENSE +16 -0
  162. data/core/services/ipfs/Cargo.toml +1 -1
  163. data/core/services/ipfs/LICENSE +16 -0
  164. data/core/services/ipmfs/Cargo.toml +1 -1
  165. data/core/services/ipmfs/LICENSE +16 -0
  166. data/core/services/koofr/Cargo.toml +2 -2
  167. data/core/services/koofr/LICENSE +16 -0
  168. data/core/services/lakefs/Cargo.toml +1 -1
  169. data/core/services/lakefs/LICENSE +16 -0
  170. data/core/services/memcached/Cargo.toml +2 -2
  171. data/core/services/memcached/LICENSE +16 -0
  172. data/core/services/memcached/src/core.rs +3 -3
  173. data/core/services/mini_moka/Cargo.toml +1 -1
  174. data/core/services/mini_moka/LICENSE +16 -0
  175. data/core/services/moka/Cargo.toml +1 -1
  176. data/core/services/moka/LICENSE +16 -0
  177. data/core/services/mongodb/Cargo.toml +2 -2
  178. data/core/services/mongodb/LICENSE +16 -0
  179. data/core/services/monoiofs/Cargo.toml +2 -2
  180. data/core/services/monoiofs/LICENSE +16 -0
  181. data/core/services/monoiofs/src/core.rs +27 -11
  182. data/core/services/mysql/Cargo.toml +2 -2
  183. data/core/services/mysql/LICENSE +16 -0
  184. data/core/services/obs/Cargo.toml +1 -1
  185. data/core/services/obs/LICENSE +16 -0
  186. data/core/services/onedrive/Cargo.toml +2 -2
  187. data/core/services/onedrive/LICENSE +16 -0
  188. data/core/services/opfs/Cargo.toml +1 -1
  189. data/core/services/opfs/LICENSE +16 -0
  190. data/core/services/oss/Cargo.toml +1 -1
  191. data/core/services/oss/LICENSE +16 -0
  192. data/core/services/pcloud/Cargo.toml +1 -1
  193. data/core/services/pcloud/LICENSE +16 -0
  194. data/core/services/persy/Cargo.toml +1 -1
  195. data/core/services/persy/LICENSE +16 -0
  196. data/core/services/postgresql/Cargo.toml +2 -2
  197. data/core/services/postgresql/LICENSE +16 -0
  198. data/core/services/redb/Cargo.toml +1 -1
  199. data/core/services/redb/LICENSE +16 -0
  200. data/core/services/redis/Cargo.toml +2 -2
  201. data/core/services/redis/LICENSE +16 -0
  202. data/core/services/redis/src/core.rs +1 -1
  203. data/core/services/rocksdb/Cargo.toml +1 -1
  204. data/core/services/rocksdb/LICENSE +16 -0
  205. data/core/services/s3/Cargo.toml +1 -1
  206. data/core/services/s3/LICENSE +16 -0
  207. data/core/services/seafile/Cargo.toml +4 -2
  208. data/core/services/seafile/LICENSE +16 -0
  209. data/core/services/seafile/src/backend.rs +2 -3
  210. data/core/services/seafile/src/core.rs +189 -86
  211. data/core/services/seafile/src/docs.md +2 -0
  212. data/core/services/sftp/Cargo.toml +2 -2
  213. data/core/services/sftp/LICENSE +16 -0
  214. data/core/services/sftp/src/core.rs +1 -1
  215. data/core/services/sftp/src/reader.rs +1 -1
  216. data/core/services/sled/Cargo.toml +1 -1
  217. data/core/services/sled/LICENSE +16 -0
  218. data/core/services/sqlite/Cargo.toml +2 -2
  219. data/core/services/sqlite/LICENSE +16 -0
  220. data/core/services/surrealdb/Cargo.toml +2 -2
  221. data/core/services/surrealdb/LICENSE +16 -0
  222. data/core/services/swift/Cargo.toml +1 -1
  223. data/core/services/swift/LICENSE +16 -0
  224. data/core/services/tikv/Cargo.toml +2 -2
  225. data/core/services/tikv/LICENSE +16 -0
  226. data/core/services/tos/Cargo.toml +1 -1
  227. data/core/services/tos/LICENSE +16 -0
  228. data/core/services/upyun/Cargo.toml +1 -1
  229. data/core/services/upyun/LICENSE +16 -0
  230. data/core/services/vercel-artifacts/Cargo.toml +1 -1
  231. data/core/services/vercel-artifacts/LICENSE +16 -0
  232. data/core/services/vercel-blob/Cargo.toml +1 -1
  233. data/core/services/vercel-blob/LICENSE +16 -0
  234. data/core/services/webdav/Cargo.toml +1 -2
  235. data/core/services/webdav/LICENSE +16 -0
  236. data/core/services/webhdfs/Cargo.toml +2 -2
  237. data/core/services/webhdfs/LICENSE +16 -0
  238. data/core/services/yandex-disk/Cargo.toml +1 -1
  239. data/core/services/yandex-disk/LICENSE +16 -0
  240. data/core/src/lib.rs +1 -3
  241. data/core/testkit/Cargo.toml +4 -4
  242. data/core/testkit/LICENSE +16 -0
  243. data/core/upgrade.md +8 -0
  244. metadata +3 -2
@@ -32,7 +32,7 @@ version = { workspace = true }
32
32
  all-features = true
33
33
 
34
34
  [dependencies]
35
- opendal-core = { path = "../../core", version = "0.59.1", default-features = false }
35
+ opendal-core = { path = "../../core", version = "0.59.2", default-features = false }
36
36
 
37
37
  bytes = { workspace = true }
38
38
  futures = { workspace = true }
@@ -199,3 +199,19 @@
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
201
  limitations under the License.
202
+
203
+
204
+ ------------------------------------------------------------------------------------
205
+ This product bundles third-party components under other open source licenses.
206
+ This section summarizes those components and their licenses. See the referenced
207
+ license files for text of these licenses.
208
+
209
+
210
+ Creative Commons Zero v1.0 Universal
211
+ ------------------------------------
212
+
213
+ bindings/dotnet/.gitignore
214
+
215
+ The above file is derived from VisualStudio.gitignore in
216
+ https://github.com/github/gitignore. Its license text is in
217
+ bindings/dotnet/LICENSE-CC0-1.0.txt.
@@ -36,5 +36,5 @@ bytes = { workspace = true }
36
36
  futures = { workspace = true }
37
37
  hdfs-native = { version = "0.14" }
38
38
  log = { workspace = true }
39
- opendal-core = { path = "../../core", version = "0.59.1", default-features = false }
39
+ opendal-core = { path = "../../core", version = "0.59.2", default-features = false }
40
40
  serde = { workspace = true, features = ["derive"] }
@@ -199,3 +199,19 @@
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
201
  limitations under the License.
202
+
203
+
204
+ ------------------------------------------------------------------------------------
205
+ This product bundles third-party components under other open source licenses.
206
+ This section summarizes those components and their licenses. See the referenced
207
+ license files for text of these licenses.
208
+
209
+
210
+ Creative Commons Zero v1.0 Universal
211
+ ------------------------------------
212
+
213
+ bindings/dotnet/.gitignore
214
+
215
+ The above file is derived from VisualStudio.gitignore in
216
+ https://github.com/github/gitignore. Its license text is in
217
+ bindings/dotnet/LICENSE-CC0-1.0.txt.
@@ -32,12 +32,12 @@ version = { workspace = true }
32
32
  all-features = true
33
33
 
34
34
  [dependencies]
35
- asyncband = { workspace = true }
35
+ asyncband = { workspace = true, features = ["mutex", "once-cell"] }
36
36
  bytes = { workspace = true }
37
37
  hf-xet = "1.6.0"
38
38
  http = { workspace = true }
39
39
  log = { workspace = true }
40
- opendal-core = { path = "../../core", version = "0.59.1", default-features = false }
40
+ opendal-core = { path = "../../core", version = "0.59.2", default-features = false }
41
41
  percent-encoding = "2"
42
42
  serde = { workspace = true, features = ["derive"] }
43
43
  serde_json = { workspace = true }
@@ -45,6 +45,6 @@ serde_json = { workspace = true }
45
45
  [dev-dependencies]
46
46
  base64 = { workspace = true }
47
47
  futures = { workspace = true }
48
- opendal-http-transport-reqwest = { path = "../../http-transports/reqwest", version = "0.59.1" }
48
+ opendal-http-transport-reqwest = { path = "../../http-transports/reqwest", version = "0.59.2" }
49
49
  serde_json = { workspace = true }
50
50
  tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
@@ -199,3 +199,19 @@
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
201
  limitations under the License.
202
+
203
+
204
+ ------------------------------------------------------------------------------------
205
+ This product bundles third-party components under other open source licenses.
206
+ This section summarizes those components and their licenses. See the referenced
207
+ license files for text of these licenses.
208
+
209
+
210
+ Creative Commons Zero v1.0 Universal
211
+ ------------------------------------
212
+
213
+ bindings/dotnet/.gitignore
214
+
215
+ The above file is derived from VisualStudio.gitignore in
216
+ https://github.com/github/gitignore. Its license text is in
217
+ bindings/dotnet/LICENSE-CC0-1.0.txt.
@@ -133,6 +133,22 @@ impl HfBuilder {
133
133
  self
134
134
  }
135
135
 
136
+ /// Enable caching of resolved HTTP download addresses and XET file metadata.
137
+ ///
138
+ /// Defaults to `false`: every range resolves through the Hub in either
139
+ /// download mode. Set to `true` to share resolve results across readers on
140
+ /// the same backend. HTTP addresses refresh near expiry.
141
+ ///
142
+ /// Enable this only when previously written files are not modified. Changed
143
+ /// files can remain invisible while cached results are reused, including
144
+ /// changes from other clients or a floating repository revision. Issued
145
+ /// download URLs can remain usable until expiry after Hub permissions change.
146
+ /// Separate authorization identities must use separately constructed backends.
147
+ pub fn enable_resolve_cache(mut self, enabled: bool) -> Self {
148
+ self.config.enable_resolve_cache = enabled;
149
+ self
150
+ }
151
+
136
152
  /// Set the Hub base URL.
137
153
  ///
138
154
  /// Configure this when your organization uses a
@@ -146,12 +162,18 @@ impl HfBuilder {
146
162
  self
147
163
  }
148
164
 
165
+ /// Resolve the Hub base URL: an explicit config value wins, then
166
+ /// `HF_ENDPOINT`, then the public Hub. A trailing slash is trimmed
167
+ /// because every URL is built by appending `/api/...` to this, and HF
168
+ /// answers the resulting `//api/...` with a 404.
149
169
  fn hf_endpoint(&self) -> String {
150
170
  self.config
151
171
  .endpoint
152
172
  .clone()
153
173
  .or_else(|| std::env::var("HF_ENDPOINT").ok())
154
174
  .unwrap_or_else(|| "https://huggingface.co".to_string())
175
+ .trim_end_matches('/')
176
+ .to_string()
155
177
  }
156
178
 
157
179
  /// Resolve the download mode: an explicit config value wins; otherwise a set,
@@ -260,16 +282,10 @@ impl Builder for HfBuilder {
260
282
  let repo = HfRepo::new(repo_type, repo_id, Some(revision.clone()));
261
283
  debug!("backend repo uri: {:?}", repo.uri(&root, ""));
262
284
 
285
+ let mut core = HfCore::build(info, capability, repo, root, token, endpoint, download_mode)?;
286
+ core.enable_resolve_cache = self.config.enable_resolve_cache;
263
287
  Ok(HfBackend {
264
- core: Arc::new(HfCore::build(
265
- info,
266
- capability,
267
- repo,
268
- root,
269
- token,
270
- endpoint,
271
- download_mode,
272
- )?),
288
+ core: Arc::new(core),
273
289
  })
274
290
  }
275
291
  }
@@ -435,6 +451,19 @@ pub(super) mod test_utils {
435
451
  finish_operator(op)
436
452
  }
437
453
 
454
+ /// Same public dataset as [`mbpp_operator`], but with the repo id cased
455
+ /// differently from the canonical `google-research-datasets/mbpp`. HF
456
+ /// answers every request for it with a `307` to the canonical URL.
457
+ pub fn miscased_mbpp_operator() -> Operator {
458
+ let op = Operator::new(
459
+ HfBuilder::default()
460
+ .repo_type("dataset")
461
+ .repo_id("Google-Research-Datasets/MBPP"),
462
+ )
463
+ .unwrap();
464
+ finish_operator(op)
465
+ }
466
+
438
467
  pub fn testing_dataset_core() -> Arc<HfCore> {
439
468
  let repo_id = std::env::var("HF_OPENDAL_DATASET").expect("HF_OPENDAL_DATASET must be set");
440
469
  let token = std::env::var("HF_OPENDAL_TOKEN").expect("HF_OPENDAL_TOKEN must be set");
@@ -538,6 +567,18 @@ mod tests {
538
567
  assert_eq!(result.as_deref(), Some("file-token"));
539
568
  }
540
569
 
570
+ #[test]
571
+ fn hf_endpoint_trims_trailing_slash() {
572
+ let _guard = ENV_LOCK.lock().unwrap();
573
+ unsafe { std::env::remove_var("HF_ENDPOINT") };
574
+ assert_eq!(
575
+ HfBuilder::default()
576
+ .endpoint("https://hub.example.com/")
577
+ .hf_endpoint(),
578
+ "https://hub.example.com"
579
+ );
580
+ }
581
+
541
582
  #[test]
542
583
  fn hf_endpoint_returns_default() {
543
584
  let _guard = ENV_LOCK.lock().unwrap();
@@ -600,6 +641,33 @@ mod tests {
600
641
  assert_eq!(mode, HfDownloadMode::Xet);
601
642
  }
602
643
 
644
+ #[tokio::test]
645
+ async fn build_resolve_cache_requires_opt_in() -> Result<()> {
646
+ use super::super::core::test_utils::MockHttpTransport;
647
+
648
+ for enabled in [None, Some(false), Some(true)] {
649
+ let mut builder = HfBuilder::default()
650
+ .repo_type("model")
651
+ .repo_id("org/repo")
652
+ .download_mode("xet");
653
+ if let Some(enabled) = enabled {
654
+ builder = builder.enable_resolve_cache(enabled);
655
+ }
656
+ let transport = MockHttpTransport::new();
657
+ let ctx = OperationContext::new()
658
+ .with_http_transport(HttpTransporter::new(transport.clone()));
659
+ let op = Operator::new(builder)?.with_context(ctx);
660
+ for reads in 1..=2 {
661
+ assert_eq!(op.read("plain.txt").await?.to_vec(), b"hello");
662
+ assert_eq!(
663
+ transport.request_count(),
664
+ reads + usize::from(enabled == Some(true))
665
+ );
666
+ }
667
+ }
668
+ Ok(())
669
+ }
670
+
603
671
  #[test]
604
672
  fn build_accepts_datasets_alias() {
605
673
  HfBuilder::default()
@@ -60,6 +60,13 @@ pub struct HfConfig {
60
60
  ///
61
61
  /// See <https://huggingface.co/docs/huggingface_hub/package_reference/environment_variables#hfhubdisablexet>.
62
62
  pub download_mode: Option<HfDownloadMode>,
63
+ /// Enable caching of resolved HTTP download addresses and XET file metadata.
64
+ ///
65
+ /// Defaults to `false`. Set to `true` to share resolve results across readers
66
+ /// on the same backend. Changed files may remain invisible while cached
67
+ /// results are reused. See [`HfBuilder::enable_resolve_cache`] for freshness
68
+ /// semantics.
69
+ pub enable_resolve_cache: bool,
63
70
  }
64
71
 
65
72
  impl Debug for HfConfig {
@@ -73,6 +80,7 @@ impl Debug for HfConfig {
73
80
  .field("revision", &self.revision)
74
81
  .field("root", &self.root)
75
82
  .field("download_mode", &self.download_mode)
83
+ .field("enable_resolve_cache", &self.enable_resolve_cache)
76
84
  .finish_non_exhaustive()
77
85
  }
78
86
  }
@@ -106,6 +114,19 @@ impl opendal_core::Configurator for HfConfig {
106
114
  .map(|s| HfDownloadMode::parse(s))
107
115
  .transpose()?;
108
116
 
117
+ let enable_resolve_cache = opts
118
+ .get("enable_resolve_cache")
119
+ .map(|value| value.parse::<bool>())
120
+ .transpose()
121
+ .map_err(|err| {
122
+ opendal_core::Error::new(
123
+ opendal_core::ErrorKind::ConfigInvalid,
124
+ "enable_resolve_cache must be true or false",
125
+ )
126
+ .set_source(err)
127
+ })?
128
+ .unwrap_or_default();
129
+
109
130
  if !path.is_empty() {
110
131
  // Full URI like "hf://datasets/user/repo@rev/path"
111
132
  let parsed = HfUri::parse(&path)?;
@@ -117,6 +138,7 @@ impl opendal_core::Configurator for HfConfig {
117
138
  token: opts.get("token").cloned(),
118
139
  endpoint: opts.get("endpoint").cloned(),
119
140
  download_mode,
141
+ enable_resolve_cache,
120
142
  })
121
143
  } else {
122
144
  // Bare scheme from via_iter, all config is in options.
@@ -138,6 +160,7 @@ impl opendal_core::Configurator for HfConfig {
138
160
  token: opts.get("token").cloned(),
139
161
  endpoint: opts.get("endpoint").cloned(),
140
162
  download_mode,
163
+ enable_resolve_cache,
141
164
  })
142
165
  }
143
166
  }
@@ -222,4 +245,39 @@ mod tests {
222
245
  let cfg = HfConfig::from_uri(&uri).unwrap();
223
246
  assert_eq!(cfg.download_mode.unwrap_or_default(), HfDownloadMode::Xet);
224
247
  }
248
+
249
+ #[test]
250
+ fn from_uri_resolve_cache_options() {
251
+ for scheme in ["hf://datasets/user/repo", "huggingface"] {
252
+ for (value, expected) in [(None, false), (Some("false"), false), (Some("true"), true)] {
253
+ let mut options = vec![("repo_type", "dataset"), ("repo_id", "user/repo")];
254
+ if let Some(value) = value {
255
+ options.push(("enable_resolve_cache", value));
256
+ }
257
+ let uri = OperatorUri::new(
258
+ scheme,
259
+ options
260
+ .into_iter()
261
+ .map(|(key, value)| (key.to_string(), value.to_string())),
262
+ )
263
+ .unwrap();
264
+ assert_eq!(
265
+ HfConfig::from_uri(&uri).unwrap().enable_resolve_cache,
266
+ expected
267
+ );
268
+ }
269
+
270
+ let uri = OperatorUri::new(
271
+ scheme,
272
+ [("enable_resolve_cache".to_string(), "invalid".to_string())],
273
+ )
274
+ .unwrap();
275
+ let err = HfConfig::from_uri(&uri).unwrap_err();
276
+ assert_eq!(err.kind(), opendal_core::ErrorKind::ConfigInvalid);
277
+ assert!(
278
+ err.to_string()
279
+ .contains("enable_resolve_cache must be true or false")
280
+ );
281
+ }
282
+ }
225
283
  }