opendal/services/swift/
backend.rs1use std::fmt::Debug;
19use std::fmt::Formatter;
20use std::sync::Arc;
21
22use http::Response;
23use http::StatusCode;
24use log::debug;
25
26use super::core::*;
27use super::delete::SwfitDeleter;
28use super::error::parse_error;
29use super::lister::SwiftLister;
30use super::writer::SwiftWriter;
31use super::DEFAULT_SCHEME;
32use crate::raw::*;
33use crate::services::SwiftConfig;
34use crate::*;
35impl Configurator for SwiftConfig {
36 type Builder = SwiftBuilder;
37 fn into_builder(self) -> Self::Builder {
38 SwiftBuilder { config: self }
39 }
40}
41
42#[doc = include_str!("docs.md")]
45#[doc = include_str!("compatible_services.md")]
46#[derive(Default, Clone)]
47pub struct SwiftBuilder {
48 config: SwiftConfig,
49}
50
51impl Debug for SwiftBuilder {
52 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 let mut d = f.debug_struct("SwiftBuilder");
54 d.field("config", &self.config);
55 d.finish_non_exhaustive()
56 }
57}
58
59impl SwiftBuilder {
60 pub fn endpoint(mut self, endpoint: &str) -> Self {
71 self.config.endpoint = if endpoint.is_empty() {
72 None
73 } else {
74 Some(endpoint.trim_end_matches('/').to_string())
75 };
76 self
77 }
78
79 pub fn container(mut self, container: &str) -> Self {
83 self.config.container = if container.is_empty() {
84 None
85 } else {
86 Some(container.trim_end_matches('/').to_string())
87 };
88 self
89 }
90
91 pub fn root(mut self, root: &str) -> Self {
95 self.config.root = if root.is_empty() {
96 None
97 } else {
98 Some(root.to_string())
99 };
100
101 self
102 }
103
104 pub fn token(mut self, token: &str) -> Self {
108 if !token.is_empty() {
109 self.config.token = Some(token.to_string());
110 }
111 self
112 }
113}
114
115impl Builder for SwiftBuilder {
116 type Config = SwiftConfig;
117
118 fn build(self) -> Result<impl Access> {
120 debug!("backend build started: {:?}", &self);
121
122 let root = normalize_root(&self.config.root.unwrap_or_default());
123 debug!("backend use root {root}");
124
125 let endpoint = match self.config.endpoint {
126 Some(endpoint) => {
127 if endpoint.starts_with("http") {
128 endpoint
129 } else {
130 format!("https://{endpoint}")
131 }
132 }
133 None => {
134 return Err(Error::new(
135 ErrorKind::ConfigInvalid,
136 "missing endpoint for Swift",
137 ));
138 }
139 };
140 debug!("backend use endpoint: {}", &endpoint);
141
142 let container = match self.config.container {
143 Some(container) => container,
144 None => {
145 return Err(Error::new(
146 ErrorKind::ConfigInvalid,
147 "missing container for Swift",
148 ));
149 }
150 };
151
152 let token = self.config.token.unwrap_or_default();
153
154 Ok(SwiftBackend {
155 core: Arc::new(SwiftCore {
156 info: {
157 let am = AccessorInfo::default();
158 am.set_scheme(DEFAULT_SCHEME)
159 .set_root(&root)
160 .set_native_capability(Capability {
161 stat: true,
162 read: true,
163
164 write: true,
165 write_can_empty: true,
166 write_with_user_metadata: true,
167
168 delete: true,
169
170 list: true,
171 list_with_recursive: true,
172
173 shared: true,
174
175 ..Default::default()
176 });
177 am.into()
178 },
179 root,
180 endpoint,
181 container,
182 token,
183 }),
184 })
185 }
186}
187
188#[derive(Debug, Clone)]
190pub struct SwiftBackend {
191 core: Arc<SwiftCore>,
192}
193
194impl Access for SwiftBackend {
195 type Reader = HttpBody;
196 type Writer = oio::OneShotWriter<SwiftWriter>;
197 type Lister = oio::PageLister<SwiftLister>;
198 type Deleter = oio::OneShotDeleter<SwfitDeleter>;
199
200 fn info(&self) -> Arc<AccessorInfo> {
201 self.core.info.clone()
202 }
203
204 async fn stat(&self, path: &str, _args: OpStat) -> Result<RpStat> {
205 let resp = self.core.swift_get_metadata(path).await?;
206
207 match resp.status() {
208 StatusCode::OK | StatusCode::NO_CONTENT => {
209 let headers = resp.headers();
210 let mut meta = parse_into_metadata(path, headers)?;
211 let user_meta = parse_prefixed_headers(headers, "x-object-meta-");
212 if !user_meta.is_empty() {
213 meta = meta.with_user_metadata(user_meta);
214 }
215
216 Ok(RpStat::new(meta))
217 }
218 _ => Err(parse_error(resp)),
219 }
220 }
221
222 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
223 let resp = self.core.swift_read(path, args.range(), &args).await?;
224
225 let status = resp.status();
226
227 match status {
228 StatusCode::OK | StatusCode::PARTIAL_CONTENT => Ok((RpRead::new(), resp.into_body())),
229 _ => {
230 let (part, mut body) = resp.into_parts();
231 let buf = body.to_buffer().await?;
232 Err(parse_error(Response::from_parts(part, buf)))
233 }
234 }
235 }
236
237 async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
238 let writer = SwiftWriter::new(self.core.clone(), args.clone(), path.to_string());
239
240 let w = oio::OneShotWriter::new(writer);
241
242 Ok((RpWrite::default(), w))
243 }
244
245 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
246 Ok((
247 RpDelete::default(),
248 oio::OneShotDeleter::new(SwfitDeleter::new(self.core.clone())),
249 ))
250 }
251
252 async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
253 let l = SwiftLister::new(
254 self.core.clone(),
255 path.to_string(),
256 args.recursive(),
257 args.limit(),
258 );
259
260 Ok((RpList::default(), oio::PageLister::new(l)))
261 }
262
263 async fn copy(&self, from: &str, to: &str, _args: OpCopy) -> Result<RpCopy> {
264 let resp = self.core.swift_copy(from, to).await?;
267
268 let status = resp.status();
269
270 match status {
271 StatusCode::CREATED | StatusCode::OK => Ok(RpCopy::default()),
272 _ => Err(parse_error(resp)),
273 }
274 }
275}