opendal/services/swift/
backend.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::Debug;
19use std::sync::Arc;
20
21use http::Response;
22use http::StatusCode;
23use log::debug;
24
25use super::SWIFT_SCHEME;
26use super::core::*;
27use super::deleter::SwfitDeleter;
28use super::error::parse_error;
29use super::lister::SwiftLister;
30use super::writer::SwiftWriter;
31use crate::raw::*;
32use crate::services::SwiftConfig;
33use crate::*;
34
35/// [OpenStack Swift](https://docs.openstack.org/api-ref/object-store/#)'s REST API support.
36/// For more information about swift-compatible services, refer to [Compatible Services](#compatible-services).
37#[doc = include_str!("docs.md")]
38#[doc = include_str!("compatible_services.md")]
39#[derive(Debug, Default)]
40pub struct SwiftBuilder {
41    pub(super) config: SwiftConfig,
42}
43
44impl SwiftBuilder {
45    /// Set the remote address of this backend
46    ///
47    /// Endpoints should be full uri, e.g.
48    ///
49    /// - `http://127.0.0.1:8080/v1/AUTH_test`
50    /// - `http://192.168.66.88:8080/swift/v1`
51    /// - `https://openstack-controller.example.com:8080/v1/ccount`
52    ///
53    /// If user inputs endpoint without scheme, we will
54    /// prepend `https://` to it.
55    pub fn endpoint(mut self, endpoint: &str) -> Self {
56        self.config.endpoint = if endpoint.is_empty() {
57            None
58        } else {
59            Some(endpoint.trim_end_matches('/').to_string())
60        };
61        self
62    }
63
64    /// Set container of this backend.
65    ///
66    /// All operations will happen under this container. It is required. e.g. `snapshots`
67    pub fn container(mut self, container: &str) -> Self {
68        self.config.container = if container.is_empty() {
69            None
70        } else {
71            Some(container.trim_end_matches('/').to_string())
72        };
73        self
74    }
75
76    /// Set root of this backend.
77    ///
78    /// All operations will happen under this root.
79    pub fn root(mut self, root: &str) -> Self {
80        self.config.root = if root.is_empty() {
81            None
82        } else {
83            Some(root.to_string())
84        };
85
86        self
87    }
88
89    /// Set the token of this backend.
90    ///
91    /// Default to empty string.
92    pub fn token(mut self, token: &str) -> Self {
93        if !token.is_empty() {
94            self.config.token = Some(token.to_string());
95        }
96        self
97    }
98}
99
100impl Builder for SwiftBuilder {
101    type Config = SwiftConfig;
102
103    /// Build a SwiftBackend.
104    fn build(self) -> Result<impl Access> {
105        debug!("backend build started: {:?}", &self);
106
107        let root = normalize_root(&self.config.root.unwrap_or_default());
108        debug!("backend use root {root}");
109
110        let endpoint = match self.config.endpoint {
111            Some(endpoint) => {
112                if endpoint.starts_with("http") {
113                    endpoint
114                } else {
115                    format!("https://{endpoint}")
116                }
117            }
118            None => {
119                return Err(Error::new(
120                    ErrorKind::ConfigInvalid,
121                    "missing endpoint for Swift",
122                ));
123            }
124        };
125        debug!("backend use endpoint: {}", &endpoint);
126
127        let container = match self.config.container {
128            Some(container) => container,
129            None => {
130                return Err(Error::new(
131                    ErrorKind::ConfigInvalid,
132                    "missing container for Swift",
133                ));
134            }
135        };
136
137        let token = self.config.token.unwrap_or_default();
138
139        Ok(SwiftBackend {
140            core: Arc::new(SwiftCore {
141                info: {
142                    let am = AccessorInfo::default();
143                    am.set_scheme(SWIFT_SCHEME)
144                        .set_root(&root)
145                        .set_native_capability(Capability {
146                            stat: true,
147                            read: true,
148
149                            write: true,
150                            write_can_empty: true,
151                            write_with_user_metadata: true,
152
153                            delete: true,
154
155                            list: true,
156                            list_with_recursive: true,
157
158                            shared: true,
159
160                            ..Default::default()
161                        });
162                    am.into()
163                },
164                root,
165                endpoint,
166                container,
167                token,
168            }),
169        })
170    }
171}
172
173/// Backend for Swift service
174#[derive(Debug, Clone)]
175pub struct SwiftBackend {
176    core: Arc<SwiftCore>,
177}
178
179impl Access for SwiftBackend {
180    type Reader = HttpBody;
181    type Writer = oio::OneShotWriter<SwiftWriter>;
182    type Lister = oio::PageLister<SwiftLister>;
183    type Deleter = oio::OneShotDeleter<SwfitDeleter>;
184
185    fn info(&self) -> Arc<AccessorInfo> {
186        self.core.info.clone()
187    }
188
189    async fn stat(&self, path: &str, _args: OpStat) -> Result<RpStat> {
190        let resp = self.core.swift_get_metadata(path).await?;
191
192        match resp.status() {
193            StatusCode::OK | StatusCode::NO_CONTENT => {
194                let headers = resp.headers();
195                let mut meta = parse_into_metadata(path, headers)?;
196                let user_meta = parse_prefixed_headers(headers, "x-object-meta-");
197                if !user_meta.is_empty() {
198                    meta = meta.with_user_metadata(user_meta);
199                }
200
201                Ok(RpStat::new(meta))
202            }
203            _ => Err(parse_error(resp)),
204        }
205    }
206
207    async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
208        let resp = self.core.swift_read(path, args.range(), &args).await?;
209
210        let status = resp.status();
211
212        match status {
213            StatusCode::OK | StatusCode::PARTIAL_CONTENT => Ok((RpRead::new(), resp.into_body())),
214            _ => {
215                let (part, mut body) = resp.into_parts();
216                let buf = body.to_buffer().await?;
217                Err(parse_error(Response::from_parts(part, buf)))
218            }
219        }
220    }
221
222    async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
223        let writer = SwiftWriter::new(self.core.clone(), args.clone(), path.to_string());
224
225        let w = oio::OneShotWriter::new(writer);
226
227        Ok((RpWrite::default(), w))
228    }
229
230    async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
231        Ok((
232            RpDelete::default(),
233            oio::OneShotDeleter::new(SwfitDeleter::new(self.core.clone())),
234        ))
235    }
236
237    async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
238        let l = SwiftLister::new(
239            self.core.clone(),
240            path.to_string(),
241            args.recursive(),
242            args.limit(),
243        );
244
245        Ok((RpList::default(), oio::PageLister::new(l)))
246    }
247
248    async fn copy(&self, from: &str, to: &str, _args: OpCopy) -> Result<RpCopy> {
249        // cannot copy objects larger than 5 GB.
250        // Reference: https://docs.openstack.org/api-ref/object-store/#copy-object
251        let resp = self.core.swift_copy(from, to).await?;
252
253        let status = resp.status();
254
255        match status {
256            StatusCode::CREATED | StatusCode::OK => Ok(RpCopy::default()),
257            _ => Err(parse_error(resp)),
258        }
259    }
260}