opendal/services/azdls/
lister.rs1use std::sync::Arc;
19
20use bytes::Buf;
21use serde::Deserialize;
22use serde_json::de;
23
24use super::core::AzdlsCore;
25use super::error::parse_error;
26use crate::raw::*;
27use crate::*;
28
29pub struct AzdlsLister {
30 core: Arc<AzdlsCore>,
31
32 path: String,
33 limit: Option<usize>,
34}
35
36impl AzdlsLister {
37 pub fn new(core: Arc<AzdlsCore>, path: String, limit: Option<usize>) -> Self {
38 Self { core, path, limit }
39 }
40}
41
42impl oio::PageList for AzdlsLister {
43 async fn next_page(&self, ctx: &mut oio::PageContext) -> Result<()> {
44 let resp = self
45 .core
46 .azdls_list(&self.path, &ctx.token, self.limit)
47 .await?;
48
49 if resp.status() == http::StatusCode::NOT_FOUND {
51 ctx.done = true;
52 return Ok(());
53 }
54
55 if resp.status() != http::StatusCode::OK {
56 return Err(parse_error(resp));
57 }
58
59 if ctx.token.is_empty() && !ctx.done {
61 let e = oio::Entry::new(&self.path, Metadata::new(EntryMode::DIR));
62 ctx.entries.push_back(e);
63 }
64
65 if let Some(value) = resp.headers().get("x-ms-continuation") {
67 let value = value.to_str().map_err(|err| {
68 Error::new(ErrorKind::Unexpected, "header value is not valid string")
69 .set_source(err)
70 })?;
71 ctx.token = value.to_string();
72 } else {
73 ctx.token = "".to_string();
74 ctx.done = true;
75 }
76
77 let bs = resp.into_body();
78
79 let output: Output = de::from_reader(bs.reader()).map_err(new_json_deserialize_error)?;
80
81 for object in output.paths {
82 let mode = if &object.is_directory == "true" {
84 EntryMode::DIR
85 } else {
86 EntryMode::FILE
87 };
88
89 let meta = Metadata::new(mode)
90 .with_etag(format!("\"{}\"", &object.etag))
92 .with_content_length(object.content_length.parse().map_err(|err| {
93 Error::new(ErrorKind::Unexpected, "content length is not valid integer")
94 .set_source(err)
95 })?)
96 .with_last_modified(parse_datetime_from_rfc2822(&object.last_modified)?);
97
98 let mut path = build_rel_path(&self.core.root, &object.name);
99 if mode.is_dir() {
100 path += "/"
101 };
102
103 let de = oio::Entry::new(&path, meta);
104
105 ctx.entries.push_back(de);
106 }
107
108 Ok(())
109 }
110}
111
112#[derive(Default, Debug, Deserialize)]
118#[serde(default)]
119struct Output {
120 paths: Vec<Path>,
121}
122
123#[derive(Default, Debug, Deserialize, PartialEq, Eq)]
124#[serde(default)]
125struct Path {
126 #[serde(rename = "contentLength")]
127 content_length: String,
128 #[serde(rename = "etag")]
129 etag: String,
130 #[serde(rename = "isDirectory")]
132 is_directory: String,
133 #[serde(rename = "lastModified")]
134 last_modified: String,
135 #[serde(rename = "name")]
136 name: String,
137}
138
139#[cfg(test)]
140mod tests {
141 use bytes::Bytes;
142
143 use super::*;
144
145 #[test]
146 fn test_parse_path() {
147 let bs = Bytes::from(
148 r#"{"paths":[{"contentLength":"1977097","etag":"0x8DACF9B0061305F","group":"$superuser","lastModified":"Sat, 26 Nov 2022 10:43:05 GMT","name":"c3b3ef48-7783-4946-81bc-dc07e1728878/d4ea21d7-a533-4011-8b1f-d0e566d63725","owner":"$superuser","permissions":"rw-r-----"}]}"#,
149 );
150 let out: Output = de::from_slice(&bs).expect("must success");
151 println!("{out:?}");
152
153 assert_eq!(
154 out.paths[0],
155 Path {
156 content_length: "1977097".to_string(),
157 etag: "0x8DACF9B0061305F".to_string(),
158 is_directory: "".to_string(),
159 last_modified: "Sat, 26 Nov 2022 10:43:05 GMT".to_string(),
160 name: "c3b3ef48-7783-4946-81bc-dc07e1728878/d4ea21d7-a533-4011-8b1f-d0e566d63725"
161 .to_string()
162 }
163 );
164 }
165}