athena_cli/commands/inspect/
download.rs1use anyhow::{anyhow, Context, Result};
2use aws_sdk_s3::Client;
3use std::fs::{self, File};
4use std::io::Write;
5use std::path::{Path, PathBuf};
6use url::Url;
7
8pub async fn download_from_s3(
10 s3_client: &Client,
11 s3_url: &str,
12 output_dir: &str,
13 _query_id: &str,
14) -> Result<PathBuf> {
15 let url = Url::parse(s3_url).context(format!("Failed to parse S3 URL: {}", s3_url))?;
17 let host = url
18 .host_str()
19 .ok_or_else(|| anyhow!("Invalid S3 URL: no host in {}", s3_url))?;
20
21 let (bucket, key) = if let Some(stripped) = s3_url.strip_prefix("s3://") {
23 let parts: Vec<&str> = stripped.splitn(2, '/').collect();
24 if parts.len() < 2 {
25 return Err(anyhow!("Invalid S3 URL format (s3://): {}", s3_url));
26 }
27 (parts[0].to_string(), parts[1].to_string())
28 } else if host.ends_with(".amazonaws.com") {
29 let bucket_name = host
30 .split('.')
31 .next()
32 .ok_or_else(|| anyhow!("Invalid S3 URL: cannot extract bucket from host: {}", host))?;
33 let object_key = url.path().strip_prefix('/').unwrap_or(url.path());
34 (bucket_name.to_string(), object_key.to_string())
35 } else {
36 let path_segments = url
37 .path_segments()
38 .ok_or_else(|| anyhow!("Invalid S3 URL: no path in {}", s3_url))?
39 .collect::<Vec<_>>();
40 if path_segments.is_empty() {
41 return Err(anyhow!("Invalid S3 URL: empty path in {}", s3_url));
42 }
43 let bucket_name = path_segments[0];
44 let object_key = path_segments[1..].join("/");
45 (bucket_name.to_string(), object_key)
46 };
47
48 fs::create_dir_all(output_dir)
50 .context(format!("Failed to create output directory: {}", output_dir))?;
51
52 let filename_from_key = Path::new(&key)
54 .file_name()
55 .ok_or_else(|| anyhow!("Could not extract filename from S3 key: {}", key))?
56 .to_string_lossy()
57 .to_string();
58
59 let output_path = Path::new(output_dir).join(&filename_from_key);
61
62 let resp = s3_client
64 .get_object()
65 .bucket(&bucket)
66 .key(&key)
67 .send()
68 .await
69 .context(format!(
70 "Failed to download file from S3 bucket: {}, key: {}",
71 bucket, key
72 ))?;
73
74 let data = resp
76 .body
77 .collect()
78 .await
79 .context("Failed to read S3 object data stream")?;
80 let bytes = data.into_bytes();
81
82 let mut file = File::create(&output_path).context(format!(
84 "Failed to create output file: {}",
85 output_path.display()
86 ))?;
87 file.write_all(&bytes).context(format!(
88 "Failed to write data to file: {}",
89 output_path.display()
90 ))?;
91
92 Ok(output_path)
93}