jax_daemon/http_server/api/v0/bucket/
ls.rs1use axum::extract::{Json, State};
2use axum::response::{IntoResponse, Response};
3use reqwest::{Client, RequestBuilder, Url};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use common::prelude::{Link, MountError};
8
9use crate::http_server::api::client::ApiRequest;
10use crate::ServiceState;
11
12#[derive(Debug, Clone, Serialize, Deserialize, clap::Args)]
13pub struct LsRequest {
14 #[arg(long)]
16 pub bucket_id: Uuid,
17
18 #[serde(skip_serializing_if = "Option::is_none")]
20 #[arg(long)]
21 pub path: Option<String>,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
25 #[arg(long)]
26 pub deep: Option<bool>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct LsResponse {
31 pub items: Vec<PathInfo>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct PathInfo {
36 pub path: String,
37 pub name: String,
38 pub link: Link,
39 pub is_dir: bool,
40 pub mime_type: String,
41}
42
43#[axum::debug_handler]
44pub async fn handler(
45 State(state): State<ServiceState>,
46 Json(req): Json<LsRequest>,
47) -> Result<impl IntoResponse, LsError> {
48 let deep = req.deep.unwrap_or(false);
49
50 let mount = state.peer().mount_for_read(req.bucket_id).await?;
52
53 let path_str = req.path.as_deref().unwrap_or("/");
54 let path_buf = std::path::PathBuf::from(path_str);
55
56 let items = if deep {
58 mount.ls_deep(&path_buf).await?
59 } else {
60 mount.ls(&path_buf).await?
61 };
62
63 let path_infos = items
65 .into_iter()
66 .map(|(path, node_link)| {
67 let absolute_path = std::path::Path::new("/").join(&path);
69 let path_str = absolute_path.to_string_lossy().to_string();
70 let name = path
71 .file_name()
72 .map(|n| n.to_string_lossy().to_string())
73 .unwrap_or_else(|| path.to_string_lossy().to_string());
74
75 let mime_type = if node_link.is_dir() {
76 "inode/directory".to_string()
77 } else {
78 node_link
79 .data()
80 .and_then(|data| data.mime())
81 .map(|mime| mime.to_string())
82 .unwrap_or_else(|| "application/octet-stream".to_string())
83 };
84
85 PathInfo {
86 path: path_str,
87 name,
88 link: node_link.link().clone(),
89 is_dir: node_link.is_dir(),
90 mime_type,
91 }
92 })
93 .collect();
94
95 Ok((http::StatusCode::OK, Json(LsResponse { items: path_infos })).into_response())
96}
97
98#[derive(Debug, thiserror::Error)]
99pub enum LsError {
100 #[error("Mount error: {0}")]
101 Mount(#[from] MountError),
102}
103
104impl IntoResponse for LsError {
105 fn into_response(self) -> Response {
106 (
107 http::StatusCode::INTERNAL_SERVER_ERROR,
108 format!("Error: {}", self),
109 )
110 .into_response()
111 }
112}
113
114impl ApiRequest for LsRequest {
116 type Response = LsResponse;
117
118 fn build_request(self, base_url: &Url, client: &Client) -> RequestBuilder {
119 let full_url = base_url.join("/api/v0/bucket/ls").unwrap();
120 client.post(full_url).json(&self)
121 }
122}