Shared GET path used by both [`WorkspaceFileSystem::read_text`] and [`WorkspaceFileSystemExt::read_text_with_version`]. Returns `(content, etag)`. The ETag is the opaque version token used by compare-and-swap writes. Refuses responses without an ETag — every S3-compatible service must return one for a successful GET; absence indicates a misconfigured endpoint.
(&self, path: &WorkspacePath)
| 428 | /// S3-compatible service must return one for a successful GET; absence |
| 429 | /// indicates a misconfigured endpoint. |
| 430 | async fn get_object_text(&self, path: &WorkspacePath) -> WorkspaceResult<(String, String)> { |
| 431 | let key = self.key_for(path); |
| 432 | let start = std::time::Instant::now(); |
| 433 | let send_result = self |
| 434 | .client |
| 435 | .get_object() |
| 436 | .bucket(&self.bucket) |
| 437 | .key(&key) |
| 438 | .send() |
| 439 | .await; |
| 440 | emit_s3_call_event( |
| 441 | "s3.get_object", |
| 442 | &self.bucket, |
| 443 | &key, |
| 444 | send_result |
| 445 | .as_ref() |
| 446 | .ok() |
| 447 | .and_then(|r| r.content_length()) |
| 448 | .unwrap_or(0) |
| 449 | .max(0) as u64, |
| 450 | send_result.is_ok(), |
| 451 | start.elapsed(), |
| 452 | ); |
| 453 | let resp = send_result.map_err(|e| classify_get_error(&self.bucket, &key, e))?; |
| 454 | |
| 455 | validate_content_length( |
| 456 | resp.content_length(), |
| 457 | self.max_read_bytes, |
| 458 | &self.bucket, |
| 459 | &key, |
| 460 | )?; |
| 461 | |
| 462 | let etag = resp |
| 463 | .e_tag() |
| 464 | .map(|s| s.to_string()) |
| 465 | .ok_or_else(|| { |
| 466 | anyhow!( |
| 467 | "S3 object s3://{}/{} returned no ETag; cannot use compare-and-swap writes against this endpoint", |
| 468 | self.bucket, |
| 469 | key |
| 470 | ) |
| 471 | })?; |
| 472 | |
| 473 | let bytes = resp |
| 474 | .body |
| 475 | .collect() |
| 476 | .await |
| 477 | .map_err(|e| { |
| 478 | anyhow!( |
| 479 | "Failed to read S3 object body s3://{}/{}: {}", |
| 480 | self.bucket, |
| 481 | key, |
| 482 | e |
| 483 | ) |
| 484 | })? |
| 485 | .into_bytes(); |
| 486 | |
| 487 | let content = String::from_utf8(bytes.to_vec()).map_err(|e| { |
no test coverage detected