1use crate::database::DatabaseAdapter;
11
12#[derive(Clone)]
14pub enum Datasource {
15 Database(DatabaseAdapter),
17 Api {
19 base_url: String,
20 headers: Vec<(String, String)>,
21 },
22}
23
24#[derive(Debug, PartialEq)]
26pub enum DsnTarget<'a> {
27 Collection(&'a str),
29 Path(&'a str),
31 Root,
33}
34
35pub fn parse_dsn(url: &str) -> Option<(&str, DsnTarget<'_>)> {
56 let rest = url.strip_prefix("dsn:")?;
57 if rest.is_empty() {
58 return None;
59 }
60
61 if let Some(slash_pos) = rest.find('/') {
63 let name = &rest[..slash_pos];
64 if name.is_empty() {
65 return None;
66 }
67 let path = &rest[slash_pos..]; return Some((name, DsnTarget::Path(path)));
69 }
70
71 if let Some(dot_pos) = rest.find('.') {
73 let name = &rest[..dot_pos];
74 let collection = &rest[dot_pos + 1..];
75 if name.is_empty() || collection.is_empty() {
76 return None;
77 }
78 return Some((name, DsnTarget::Collection(collection)));
79 }
80
81 Some((rest, DsnTarget::Root))
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_parse_dsn_collection() {
91 let (name, target) = parse_dsn("dsn:users.profiles").unwrap();
92 assert_eq!(name, "users");
93 assert_eq!(target, DsnTarget::Collection("profiles"));
94 }
95
96 #[test]
97 fn test_parse_dsn_path() {
98 let (name, target) = parse_dsn("dsn:inventory/products").unwrap();
99 assert_eq!(name, "inventory");
100 assert_eq!(target, DsnTarget::Path("/products"));
101 }
102
103 #[test]
104 fn test_parse_dsn_nested_path() {
105 let (name, target) = parse_dsn("dsn:api/v2/users/active").unwrap();
106 assert_eq!(name, "api");
107 assert_eq!(target, DsnTarget::Path("/v2/users/active"));
108 }
109
110 #[test]
111 fn test_parse_dsn_root() {
112 let (name, target) = parse_dsn("dsn:mydb").unwrap();
113 assert_eq!(name, "mydb");
114 assert_eq!(target, DsnTarget::Root);
115 }
116
117 #[test]
118 fn test_parse_dsn_not_dsn_prefix() {
119 assert!(parse_dsn("local:posts").is_none());
120 assert!(parse_dsn("https://example.com").is_none());
121 }
122
123 #[test]
124 fn test_parse_dsn_empty_name() {
125 assert!(parse_dsn("dsn:").is_none());
126 assert!(parse_dsn("dsn:/path").is_none());
127 assert!(parse_dsn("dsn:.collection").is_none());
128 }
129
130 #[test]
131 fn test_parse_dsn_empty_collection() {
132 assert!(parse_dsn("dsn:name.").is_none());
133 }
134
135 #[test]
136 fn test_parse_dsn_path_takes_priority_over_dot() {
137 let (name, target) = parse_dsn("dsn:api/users.json").unwrap();
139 assert_eq!(name, "api");
140 assert_eq!(target, DsnTarget::Path("/users.json"));
141 }
142
143 #[test]
144 fn test_parse_dsn_trailing_slash() {
145 let (name, target) = parse_dsn("dsn:api/").unwrap();
146 assert_eq!(name, "api");
147 assert_eq!(target, DsnTarget::Path("/"));
148 }
149
150 #[test]
151 fn test_parse_dsn_just_prefix() {
152 assert!(parse_dsn("dsn:").is_none());
153 }
154
155 #[test]
156 fn test_parse_dsn_non_dsn_url() {
157 assert!(parse_dsn("http://example.com").is_none());
158 assert!(parse_dsn("").is_none());
159 }
160
161 #[test]
162 fn test_parse_dsn_deep_collection_name() {
163 let (name, target) = parse_dsn("dsn:db.schema.table").unwrap();
165 assert_eq!(name, "db");
166 assert_eq!(target, DsnTarget::Collection("schema.table"));
167 }
168}