Skip to content

Commit 3974ea7

Browse files
committed
feat: Add from_uri support for all services
Signed-off-by: Xuanwo <github@xuanwo.io>
1 parent d74c8d7 commit 3974ea7

File tree

47 files changed

+2508
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2508
-0
lines changed

core/src/services/aliyun_drive/config.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,24 @@ impl Debug for AliyunDriveConfig {
7575
impl crate::Configurator for AliyunDriveConfig {
7676
type Builder = AliyunDriveBuilder;
7777

78+
fn from_uri(uri: &crate::types::OperatorUri) -> crate::Result<Self> {
79+
let mut map = uri.options().clone();
80+
81+
if let Some(drive_type) = uri.name() {
82+
if !drive_type.is_empty() {
83+
map.insert("drive_type".to_string(), drive_type.to_string());
84+
}
85+
}
86+
87+
if let Some(root) = uri.root() {
88+
if !root.is_empty() {
89+
map.insert("root".to_string(), root.to_string());
90+
}
91+
}
92+
93+
Self::from_iter(map)
94+
}
95+
7896
#[allow(deprecated)]
7997
fn into_builder(self) -> Self::Builder {
8098
AliyunDriveBuilder {
@@ -83,3 +101,36 @@ impl crate::Configurator for AliyunDriveConfig {
83101
}
84102
}
85103
}
104+
105+
#[cfg(test)]
106+
mod tests {
107+
use super::*;
108+
use crate::Configurator;
109+
use crate::types::OperatorUri;
110+
111+
#[test]
112+
fn from_uri_sets_drive_type_and_root() {
113+
let uri = OperatorUri::new(
114+
"aliyun_drive://resource/library/photos".parse().unwrap(),
115+
Vec::<(String, String)>::new(),
116+
)
117+
.unwrap();
118+
119+
let cfg = AliyunDriveConfig::from_uri(&uri).unwrap();
120+
assert_eq!(cfg.drive_type, "resource".to_string());
121+
assert_eq!(cfg.root.as_deref(), Some("library/photos"));
122+
}
123+
124+
#[test]
125+
fn from_uri_allows_missing_drive_type() {
126+
let uri = OperatorUri::new(
127+
"aliyun_drive:///documents".parse().unwrap(),
128+
Vec::<(String, String)>::new(),
129+
)
130+
.unwrap();
131+
132+
let cfg = AliyunDriveConfig::from_uri(&uri).unwrap();
133+
assert_eq!(cfg.drive_type, String::default());
134+
assert_eq!(cfg.root.as_deref(), Some("documents"));
135+
}
136+
}

core/src/services/alluxio/config.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@ impl Debug for AlluxioConfig {
5353
impl crate::Configurator for AlluxioConfig {
5454
type Builder = AlluxioBuilder;
5555

56+
fn from_uri(uri: &crate::types::OperatorUri) -> crate::Result<Self> {
57+
let authority = uri.authority().ok_or_else(|| {
58+
crate::Error::new(crate::ErrorKind::ConfigInvalid, "uri authority is required")
59+
.with_context("service", crate::Scheme::Alluxio)
60+
})?;
61+
62+
let mut map = uri.options().clone();
63+
map.insert("endpoint".to_string(), format!("http://{authority}"));
64+
65+
if let Some(root) = uri.root() {
66+
if !root.is_empty() {
67+
map.insert("root".to_string(), root.to_string());
68+
}
69+
}
70+
71+
Self::from_iter(map)
72+
}
73+
5674
#[allow(deprecated)]
5775
fn into_builder(self) -> Self::Builder {
5876
AlluxioBuilder {
@@ -61,3 +79,23 @@ impl crate::Configurator for AlluxioConfig {
6179
}
6280
}
6381
}
82+
83+
#[cfg(test)]
84+
mod tests {
85+
use super::*;
86+
use crate::Configurator;
87+
use crate::types::OperatorUri;
88+
89+
#[test]
90+
fn from_uri_sets_endpoint_and_root() {
91+
let uri = OperatorUri::new(
92+
"alluxio://127.0.0.1:39999/data/raw".parse().unwrap(),
93+
Vec::<(String, String)>::new(),
94+
)
95+
.unwrap();
96+
97+
let cfg = AlluxioConfig::from_uri(&uri).unwrap();
98+
assert_eq!(cfg.endpoint.as_deref(), Some("http://127.0.0.1:39999"));
99+
assert_eq!(cfg.root.as_deref(), Some("data/raw"));
100+
}
101+
}

core/src/services/azdls/config.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,53 @@ impl Debug for AzdlsConfig {
9292
impl crate::Configurator for AzdlsConfig {
9393
type Builder = AzdlsBuilder;
9494

95+
fn from_uri(uri: &crate::types::OperatorUri) -> crate::Result<Self> {
96+
let authority = uri.authority().ok_or_else(|| {
97+
crate::Error::new(crate::ErrorKind::ConfigInvalid, "uri authority is required")
98+
.with_context("service", crate::Scheme::Azdls)
99+
})?;
100+
101+
let mut map = uri.options().clone();
102+
map.insert("endpoint".to_string(), format!("https://{authority}"));
103+
104+
if let Some(host) = uri.name() {
105+
if let Some(account) = host.split('.').next() {
106+
if !account.is_empty() {
107+
map.entry("account_name".to_string())
108+
.or_insert_with(|| account.to_string());
109+
}
110+
}
111+
}
112+
113+
if let Some(root) = uri.root() {
114+
if let Some((filesystem, rest)) = root.split_once('/') {
115+
if filesystem.is_empty() {
116+
return Err(crate::Error::new(
117+
crate::ErrorKind::ConfigInvalid,
118+
"filesystem is required in uri path",
119+
)
120+
.with_context("service", crate::Scheme::Azdls));
121+
}
122+
map.insert("filesystem".to_string(), filesystem.to_string());
123+
if !rest.is_empty() {
124+
map.insert("root".to_string(), rest.to_string());
125+
}
126+
} else if !root.is_empty() {
127+
map.insert("filesystem".to_string(), root.to_string());
128+
}
129+
}
130+
131+
if !map.contains_key("filesystem") {
132+
return Err(crate::Error::new(
133+
crate::ErrorKind::ConfigInvalid,
134+
"filesystem is required",
135+
)
136+
.with_context("service", crate::Scheme::Azdls));
137+
}
138+
139+
Self::from_iter(map)
140+
}
141+
95142
#[allow(deprecated)]
96143
fn into_builder(self) -> Self::Builder {
97144
AzdlsBuilder {
@@ -100,3 +147,42 @@ impl crate::Configurator for AzdlsConfig {
100147
}
101148
}
102149
}
150+
151+
#[cfg(test)]
152+
mod tests {
153+
use super::*;
154+
use crate::Configurator;
155+
use crate::types::OperatorUri;
156+
157+
#[test]
158+
fn from_uri_sets_endpoint_filesystem_root_and_account() {
159+
let uri = OperatorUri::new(
160+
"azdls://account.dfs.core.windows.net/fs/data/2024"
161+
.parse()
162+
.unwrap(),
163+
Vec::<(String, String)>::new(),
164+
)
165+
.unwrap();
166+
167+
let cfg = AzdlsConfig::from_uri(&uri).unwrap();
168+
assert_eq!(
169+
cfg.endpoint.as_deref(),
170+
Some("https://account.dfs.core.windows.net")
171+
);
172+
assert_eq!(cfg.filesystem, "fs".to_string());
173+
assert_eq!(cfg.root.as_deref(), Some("data/2024"));
174+
assert_eq!(cfg.account_name.as_deref(), Some("account"));
175+
}
176+
177+
#[test]
178+
fn from_uri_accepts_filesystem_from_query() {
179+
let uri = OperatorUri::new(
180+
"azdls://account.dfs.core.windows.net".parse().unwrap(),
181+
vec![("filesystem".to_string(), "logs".to_string())],
182+
)
183+
.unwrap();
184+
185+
let cfg = AzdlsConfig::from_uri(&uri).unwrap();
186+
assert_eq!(cfg.filesystem, "logs".to_string());
187+
}
188+
}

core/src/services/azfile/config.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,53 @@ impl Debug for AzfileConfig {
6464
impl crate::Configurator for AzfileConfig {
6565
type Builder = AzfileBuilder;
6666

67+
fn from_uri(uri: &crate::types::OperatorUri) -> crate::Result<Self> {
68+
let authority = uri.authority().ok_or_else(|| {
69+
crate::Error::new(crate::ErrorKind::ConfigInvalid, "uri authority is required")
70+
.with_context("service", crate::Scheme::Azfile)
71+
})?;
72+
73+
let mut map = uri.options().clone();
74+
map.insert("endpoint".to_string(), format!("https://{authority}"));
75+
76+
if let Some(host) = uri.name() {
77+
if let Some(account) = host.split('.').next() {
78+
if !account.is_empty() {
79+
map.entry("account_name".to_string())
80+
.or_insert_with(|| account.to_string());
81+
}
82+
}
83+
}
84+
85+
if let Some(root) = uri.root() {
86+
if let Some((share, rest)) = root.split_once('/') {
87+
if share.is_empty() {
88+
return Err(crate::Error::new(
89+
crate::ErrorKind::ConfigInvalid,
90+
"share name is required in uri path",
91+
)
92+
.with_context("service", crate::Scheme::Azfile));
93+
}
94+
map.insert("share_name".to_string(), share.to_string());
95+
if !rest.is_empty() {
96+
map.insert("root".to_string(), rest.to_string());
97+
}
98+
} else if !root.is_empty() {
99+
map.insert("share_name".to_string(), root.to_string());
100+
}
101+
}
102+
103+
if !map.contains_key("share_name") {
104+
return Err(crate::Error::new(
105+
crate::ErrorKind::ConfigInvalid,
106+
"share name is required",
107+
)
108+
.with_context("service", crate::Scheme::Azfile));
109+
}
110+
111+
Self::from_iter(map)
112+
}
113+
67114
#[allow(deprecated)]
68115
fn into_builder(self) -> Self::Builder {
69116
AzfileBuilder {
@@ -72,3 +119,46 @@ impl crate::Configurator for AzfileConfig {
72119
}
73120
}
74121
}
122+
123+
#[cfg(test)]
124+
mod tests {
125+
use super::*;
126+
use crate::Configurator;
127+
use crate::types::OperatorUri;
128+
129+
#[test]
130+
fn from_uri_sets_endpoint_share_root_and_account() {
131+
let uri = OperatorUri::new(
132+
"azfile://account.file.core.windows.net/share/documents/reports"
133+
.parse()
134+
.unwrap(),
135+
Vec::<(String, String)>::new(),
136+
)
137+
.unwrap();
138+
139+
let cfg = AzfileConfig::from_uri(&uri).unwrap();
140+
assert_eq!(
141+
cfg.endpoint.as_deref(),
142+
Some("https://account.file.core.windows.net")
143+
);
144+
assert_eq!(cfg.share_name, "share".to_string());
145+
assert_eq!(cfg.root.as_deref(), Some("documents/reports"));
146+
assert_eq!(cfg.account_name.as_deref(), Some("account"));
147+
}
148+
149+
#[test]
150+
fn from_uri_accepts_share_from_query() {
151+
let uri = OperatorUri::new(
152+
"azfile://account.file.core.windows.net".parse().unwrap(),
153+
vec![("share_name".to_string(), "data".to_string())],
154+
)
155+
.unwrap();
156+
157+
let cfg = AzfileConfig::from_uri(&uri).unwrap();
158+
assert_eq!(
159+
cfg.endpoint.as_deref(),
160+
Some("https://account.file.core.windows.net")
161+
);
162+
assert_eq!(cfg.share_name, "data".to_string());
163+
}
164+
}

core/src/services/cacache/config.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use std::fmt::Debug;
1919

2020
use super::backend::CacacheBuilder;
21+
use percent_encoding::percent_decode_str;
2122
use serde::Deserialize;
2223
use serde::Serialize;
2324

@@ -30,7 +31,60 @@ pub struct CacacheConfig {
3031

3132
impl crate::Configurator for CacacheConfig {
3233
type Builder = CacacheBuilder;
34+
fn from_uri(uri: &crate::types::OperatorUri) -> crate::Result<Self> {
35+
let mut map = uri.options().clone();
36+
37+
if let Some(authority) = uri.authority() {
38+
let decoded = percent_decode_str(authority).decode_utf8_lossy();
39+
if !decoded.is_empty() {
40+
map.entry("datadir".to_string())
41+
.or_insert_with(|| decoded.to_string());
42+
}
43+
}
44+
45+
if !map.contains_key("datadir") {
46+
if let Some(root) = uri.root() {
47+
if !root.is_empty() {
48+
map.insert("datadir".to_string(), root.to_string());
49+
}
50+
}
51+
}
52+
53+
Self::from_iter(map)
54+
}
55+
3356
fn into_builder(self) -> Self::Builder {
3457
CacacheBuilder { config: self }
3558
}
3659
}
60+
61+
#[cfg(test)]
62+
mod tests {
63+
use super::*;
64+
use crate::Configurator;
65+
use crate::types::OperatorUri;
66+
67+
#[test]
68+
fn from_uri_sets_datadir_from_authority() {
69+
let uri = OperatorUri::new(
70+
"cacache://%2Fvar%2Fcache%2Fopendal".parse().unwrap(),
71+
Vec::<(String, String)>::new(),
72+
)
73+
.unwrap();
74+
75+
let cfg = CacacheConfig::from_uri(&uri).unwrap();
76+
assert_eq!(cfg.datadir.as_deref(), Some("/var/cache/opendal"));
77+
}
78+
79+
#[test]
80+
fn from_uri_falls_back_to_path() {
81+
let uri = OperatorUri::new(
82+
"cacache:///tmp/cache".parse().unwrap(),
83+
Vec::<(String, String)>::new(),
84+
)
85+
.unwrap();
86+
87+
let cfg = CacacheConfig::from_uri(&uri).unwrap();
88+
assert_eq!(cfg.datadir.as_deref(), Some("tmp/cache"));
89+
}
90+
}

0 commit comments

Comments
 (0)