1pub mod auth_cache;
14pub use auth_cache::AuthCache;
15
16pub mod token;
17pub use token::{ResolvedToken, TokenOrigin, resolve_token};
18
19use crate::diagnostic::Diagnostic;
20
21const SHORTCUTS: &[(&str, &str)] = &[
29 ("prod", "https://api.dasch.swiss"),
30 ("stage", "https://api.stage.dasch.swiss"),
31 ("dev", "https://api.dev.dasch.swiss"),
32 ("demo", "https://api.demo.dasch.swiss"),
33 ("rdu", "https://api.rdu.dasch.swiss"),
34 ("ls-prod", "https://api.ls-prod-server.dasch.swiss"),
35 ("ls-test", "https://api.ls-test-server.dasch.swiss"),
36 ("local", "http://0.0.0.0:3333"),
37];
38
39#[derive(Debug, Clone)]
41pub struct Config {
42 pub server: String,
44}
45
46impl Config {
47 pub fn resolve(server: Option<&str>) -> Result<Self, Diagnostic> {
53 match server {
54 None => Err(Diagnostic::Usage(
55 "no server specified. Provide one via --server <prod|dev|…|URL>, \
56the DSP_SERVER environment variable, or a .env file in the current directory. \
57See `dsp docs connecting` for details."
58 .to_string(),
59 )),
60 Some(s) => {
61 let lower = s.to_ascii_lowercase();
64 let url = SHORTCUTS
65 .iter()
66 .find(|(name, _)| *name == lower)
67 .map(|(_, url)| *url)
68 .unwrap_or(s);
69
70 tracing::debug!(server = url, "resolved server");
71
72 Ok(Config {
73 server: url.to_string(),
74 })
75 }
76 }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::diagnostic::Diagnostic;
84
85 #[test]
86 fn resolve_with_literal_url() {
87 let cfg = Config::resolve(Some("https://api.example.org")).unwrap();
88 assert_eq!(cfg.server, "https://api.example.org");
89 }
90
91 #[test]
92 fn resolve_with_known_shortcut_prod() {
93 let cfg = Config::resolve(Some("prod")).unwrap();
94 assert_eq!(cfg.server, "https://api.dasch.swiss");
95 }
96
97 #[test]
98 fn resolve_with_known_shortcut_local() {
99 let cfg = Config::resolve(Some("local")).unwrap();
100 assert_eq!(cfg.server, "http://0.0.0.0:3333");
101 }
102
103 #[test]
104 fn resolve_with_unknown_word_passes_through() {
105 let cfg = Config::resolve(Some("staging-experiment")).unwrap();
106 assert_eq!(cfg.server, "staging-experiment");
107 }
108
109 #[test]
110 fn resolve_with_known_shortcut_dev() {
111 let cfg = Config::resolve(Some("dev")).unwrap();
112 assert_eq!(cfg.server, "https://api.dev.dasch.swiss");
113 }
114
115 #[test]
116 fn resolve_with_known_shortcut_demo() {
117 let cfg = Config::resolve(Some("demo")).unwrap();
118 assert_eq!(cfg.server, "https://api.demo.dasch.swiss");
119 }
120
121 #[test]
122 fn resolve_shortcut_is_case_insensitive() {
123 assert_eq!(
125 Config::resolve(Some("PROD")).unwrap().server,
126 "https://api.dasch.swiss"
127 );
128 assert_eq!(
129 Config::resolve(Some("Dev")).unwrap().server,
130 "https://api.dev.dasch.swiss"
131 );
132 }
133
134 #[test]
135 fn resolve_literal_url_preserves_case() {
136 let cfg = Config::resolve(Some("https://API.Example.ORG/Path")).unwrap();
138 assert_eq!(cfg.server, "https://API.Example.ORG/Path");
139 }
140
141 #[test]
142 fn resolve_with_none_returns_usage_diagnostic() {
143 let err = Config::resolve(None).unwrap_err();
144 assert!(matches!(err, Diagnostic::Usage(_)));
145 }
146
147 #[test]
148 fn missing_server_message_mentions_all_three_paths() {
149 let err = Config::resolve(None).unwrap_err();
150 let msg = err.to_string();
151 assert!(msg.contains("--server"), "missing --server in: {msg}");
152 assert!(msg.contains("DSP_SERVER"), "missing DSP_SERVER in: {msg}");
153 assert!(msg.contains(".env"), "missing .env in: {msg}");
154 }
155
156 #[test]
157 fn shortcut_and_canonical_url_resolve_identically() {
158 let via_shortcut = Config::resolve(Some("dev")).unwrap();
161 let via_url = Config::resolve(Some("https://api.dev.dasch.swiss")).unwrap();
162 assert_eq!(
163 via_shortcut.server, via_url.server,
164 "shortcut 'dev' and its URL must resolve to the same string for \
165cache key lookups to work"
166 );
167 }
168}