opendal_service_memcached/
backend.rs1use std::borrow::Cow;
19use std::sync::Arc;
20use url::Url;
21
22use opendal_core::raw::*;
23use opendal_core::*;
24
25use super::MEMCACHED_SCHEME;
26use super::config::MemcachedConfig;
27use super::core::*;
28use super::deleter::MemcachedDeleter;
29use super::reader::*;
30use super::writer::MemcachedWriter;
31
32#[doc = include_str!("docs.md")]
34#[derive(Debug, Default)]
35pub struct MemcachedBuilder {
36 pub(super) config: MemcachedConfig,
37}
38
39impl MemcachedBuilder {
40 pub fn endpoint(mut self, endpoint: &str) -> Self {
44 if !endpoint.is_empty() {
45 self.config.endpoint = Some(endpoint.to_owned());
46 }
47 self
48 }
49
50 pub fn root(mut self, root: &str) -> Self {
54 self.config.root = if root.is_empty() {
55 None
56 } else {
57 Some(root.to_string())
58 };
59
60 self
61 }
62
63 pub fn username(mut self, username: &str) -> Self {
65 self.config.username = Some(username.to_string());
66 self
67 }
68
69 pub fn password(mut self, password: &str) -> Self {
71 self.config.password = Some(password.to_string());
72 self
73 }
74
75 pub fn default_ttl(mut self, ttl: Duration) -> Self {
77 self.config.default_ttl = Some(ttl);
78 self
79 }
80
81 #[must_use]
89 pub fn connection_pool_max_size(mut self, max_size: usize) -> Self {
90 assert!(max_size > 0, "max_size must be greater than zero!");
91 self.config.connection_pool_max_size = Some(max_size);
92 self
93 }
94}
95
96impl Builder for MemcachedBuilder {
97 type Config = MemcachedConfig;
98
99 fn build(self) -> Result<impl Service> {
100 let endpoint_raw = self.config.endpoint.clone().ok_or_else(|| {
101 Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
102 .with_context("service", MEMCACHED_SCHEME)
103 })?;
104
105 let url_str = if !endpoint_raw.contains("://") {
106 Cow::Owned(format!("tcp://{}", endpoint_raw))
107 } else {
108 Cow::Borrowed(endpoint_raw.as_str())
109 };
110
111 let parsed = Url::parse(&url_str).map_err(|err| {
112 Error::new(ErrorKind::ConfigInvalid, "endpoint is invalid")
113 .with_context("service", MEMCACHED_SCHEME)
114 .with_context("endpoint", &endpoint_raw)
115 .set_source(err)
116 })?;
117
118 let endpoint = match parsed.scheme() {
119 "tcp" => {
120 let host = parsed.host_str().ok_or_else(|| {
121 Error::new(ErrorKind::ConfigInvalid, "tcp endpoint doesn't have host")
122 .with_context("service", MEMCACHED_SCHEME)
123 .with_context("endpoint", &endpoint_raw)
124 })?;
125 let port = parsed.port().ok_or_else(|| {
126 Error::new(ErrorKind::ConfigInvalid, "tcp endpoint doesn't have port")
127 .with_context("service", MEMCACHED_SCHEME)
128 .with_context("endpoint", &endpoint_raw)
129 })?;
130 Endpoint::Tcp(format!("{host}:{port}"))
131 }
132
133 #[cfg(unix)]
134 "unix" => {
135 let path = parsed.path();
136 if path.is_empty() {
137 return Err(Error::new(
138 ErrorKind::ConfigInvalid,
139 "unix endpoint doesn't have path",
140 )
141 .with_context("service", MEMCACHED_SCHEME)
142 .with_context("endpoint", &endpoint_raw));
143 }
144 Endpoint::Unix(path.to_string())
145 }
146
147 #[cfg(not(unix))]
148 "unix" => {
149 return Err(Error::new(
150 ErrorKind::ConfigInvalid,
151 "unix socket is not supported on this platform",
152 )
153 .with_context("service", MEMCACHED_SCHEME)
154 .with_context("endpoint", &endpoint_raw));
155 }
156
157 scheme => {
158 return Err(Error::new(
159 ErrorKind::ConfigInvalid,
160 "endpoint is using invalid scheme, only tcp and unix are supported",
161 )
162 .with_context("service", MEMCACHED_SCHEME)
163 .with_context("endpoint", &endpoint_raw)
164 .with_context("scheme", scheme));
165 }
166 };
167
168 let root = normalize_root(self.config.root.unwrap_or_else(|| "/".to_string()).as_str());
169
170 Ok(MemcachedBackend::new(MemcachedCore::new(
171 endpoint,
172 self.config.username,
173 self.config.password,
174 self.config.default_ttl,
175 self.config.connection_pool_max_size,
176 ))
177 .with_normalized_root(root))
178 }
179}
180
181#[derive(Clone, Debug)]
183pub struct MemcachedBackend {
184 pub(crate) core: Arc<MemcachedCore>,
185 pub(crate) root: String,
186 pub(crate) info: ServiceInfo,
187 pub(crate) capability: Capability,
188}
189
190impl MemcachedBackend {
191 pub fn new(core: MemcachedCore) -> Self {
192 let info = ServiceInfo::new(MEMCACHED_SCHEME, "/", "memcached");
193 let capability = Capability {
194 read: true,
195 stat: true,
196 write: true,
197 write_can_empty: true,
198 delete: true,
199 shared: true,
200 ..Default::default()
201 };
202
203 Self {
204 core: Arc::new(core),
205 root: "/".to_string(),
206 info,
207 capability,
208 }
209 }
210
211 fn with_normalized_root(mut self, root: String) -> Self {
212 self.info = self.info.with_root(&root);
213 self.root = root;
214 self
215 }
216}
217
218impl Service for MemcachedBackend {
219 type Reader = oio::StreamReader<MemcachedReader>;
220 type Writer = MemcachedWriter;
221 type Lister = ();
222 type Deleter = oio::OneShotDeleter<MemcachedDeleter>;
223 type Copier = ();
224
225 fn info(&self) -> ServiceInfo {
226 self.info.clone()
227 }
228
229 fn capability(&self) -> Capability {
230 self.capability
231 }
232
233 async fn create_dir(
234 &self,
235 _ctx: &OperationContext,
236 _path: &str,
237 _args: OpCreateDir,
238 ) -> Result<RpCreateDir> {
239 Err(Error::new(
240 ErrorKind::Unsupported,
241 "operation is not supported",
242 ))
243 }
244
245 async fn stat(&self, _ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
246 let p = build_abs_path(&self.root, path);
247
248 if p == build_abs_path(&self.root, "") {
249 Ok(RpStat::new(Metadata::new(EntryMode::DIR)))
250 } else {
251 let bs = self.core.get(&p).await?;
252 match bs {
253 Some(bs) => Ok(RpStat::new(
254 Metadata::new(EntryMode::FILE).with_content_length(bs.len() as u64),
255 )),
256 None => Err(Error::new(ErrorKind::NotFound, "kv not found in memcached")),
257 }
258 }
259 }
260 fn read(&self, _ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
261 let output: oio::StreamReader<MemcachedReader> = {
262 Ok(oio::StreamReader::new(MemcachedReader::new(
263 self.clone(),
264 path,
265 args,
266 )))
267 }?;
268
269 Ok(output)
270 }
271
272 fn write(&self, _ctx: &OperationContext, path: &str, _: OpWrite) -> Result<Self::Writer> {
273 let output: MemcachedWriter = {
274 let p = build_abs_path(&self.root, path);
275 Ok(MemcachedWriter::new(self.core.clone(), p))
276 }?;
277
278 Ok(output)
279 }
280
281 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
282 let output: oio::OneShotDeleter<MemcachedDeleter> = {
283 Ok(oio::OneShotDeleter::new(MemcachedDeleter::new(
284 self.core.clone(),
285 self.root.clone(),
286 )))
287 }?;
288
289 Ok(output)
290 }
291
292 fn list(&self, _ctx: &OperationContext, _path: &str, _args: OpList) -> Result<Self::Lister> {
293 Err(Error::new(
294 ErrorKind::Unsupported,
295 "operation is not supported",
296 ))
297 }
298
299 fn copy(
300 &self,
301 _ctx: &OperationContext,
302 _from: &str,
303 _to: &str,
304 _args: OpCopy,
305 _opts: OpCopier,
306 ) -> Result<Self::Copier> {
307 Err(Error::new(
308 ErrorKind::Unsupported,
309 "operation is not supported",
310 ))
311 }
312
313 async fn rename(
314 &self,
315 _ctx: &OperationContext,
316 _from: &str,
317 _to: &str,
318 _args: OpRename,
319 ) -> Result<RpRename> {
320 Err(Error::new(
321 ErrorKind::Unsupported,
322 "operation is not supported",
323 ))
324 }
325
326 async fn presign(
327 &self,
328 _ctx: &OperationContext,
329 _path: &str,
330 _args: OpPresign,
331 ) -> Result<RpPresign> {
332 Err(Error::new(
333 ErrorKind::Unsupported,
334 "operation is not supported",
335 ))
336 }
337}