1use crate::config::advanced::FormattingStyle;
5use crate::config::advanced::auth::{AuthConfig, AuthorizationRestrictions};
6use crate::config::data_server::{DataServerConfig, DataServerEnabled};
7use crate::config::location::{Location, Locations};
8use crate::config::parser::from_path;
9use crate::config::service_info::{PackageInfo, ServiceInfo};
10use crate::config::ticket_server::TicketServerConfig;
11use crate::error::Error::{ArgParseError, ParseError, TracingError};
12use crate::error::Result;
13use crate::http::KeyPairScheme;
14use crate::storage::Backend;
15use clap::{Args as ClapArgs, Command, FromArgMatches, Parser};
16use http::header::AUTHORIZATION;
17use http::uri::Authority;
18use schemars::schema_for;
19use serde::de::Error;
20use serde::ser::SerializeSeq;
21use serde::{Deserialize, Deserializer, Serialize, Serializer};
22use std::collections::HashSet;
23use std::fmt::{Debug, Display};
24use std::io;
25use std::path::{Path, PathBuf};
26use std::str::FromStr;
27use tracing::subscriber::set_global_default;
28use tracing_subscriber::fmt::{format, layer};
29use tracing_subscriber::layer::SubscriberExt;
30use tracing_subscriber::{EnvFilter, Registry};
31
32pub mod advanced;
33pub mod data_server;
34pub mod location;
35pub mod parser;
36pub mod service_info;
37pub mod ticket_server;
38
39pub const USAGE: &str = "To configure htsget-rs use a config file or environment variables. \
41See the documentation of the htsget-config crate for more information.";
42
43#[derive(Parser, Debug)]
45#[command(author, version, about, long_about = USAGE)]
46struct Args {
47 #[arg(
48 short,
49 long,
50 env = "HTSGET_CONFIG",
51 help = "Set the location of the config file"
52 )]
53 config: Option<PathBuf>,
54 #[arg(short, long, exclusive = true, help = "Print a default config file")]
55 print_default_config: bool,
56 #[arg(
57 short = 's',
58 long,
59 exclusive = true,
60 help = "Print the response JSON schema used in the htsget auth process"
61 )]
62 print_response_schema: bool,
63}
64
65#[derive(Serialize, Deserialize, Debug, Clone)]
67#[serde(default, deny_unknown_fields)]
68pub struct Config {
69 ticket_server: TicketServerConfig,
70 data_server: DataServerEnabled,
71 service_info: ServiceInfo,
72 #[serde(alias = "location")]
73 locations: Locations,
74 formatting_style: FormattingStyle,
75 #[serde(skip_serializing)]
76 auth: Option<AuthConfig>,
77 #[serde(skip)]
78 package_info: PackageInfo,
79}
80
81impl Config {
82 pub fn new(
84 formatting_style: FormattingStyle,
85 ticket_server: TicketServerConfig,
86 data_server: DataServerEnabled,
87 service_info: ServiceInfo,
88 locations: Locations,
89 auth: Option<AuthConfig>,
90 package_info: PackageInfo,
91 ) -> Self {
92 Self {
93 formatting_style,
94 ticket_server,
95 data_server,
96 service_info,
97 locations,
98 auth,
99 package_info,
100 }
101 }
102
103 pub fn formatting_style(&self) -> FormattingStyle {
105 self.formatting_style
106 }
107
108 pub fn ticket_server(&self) -> &TicketServerConfig {
110 &self.ticket_server
111 }
112
113 pub fn ticket_server_mut(&mut self) -> &mut TicketServerConfig {
115 &mut self.ticket_server
116 }
117
118 pub fn data_server(&self) -> &DataServerEnabled {
120 &self.data_server
121 }
122
123 pub fn data_server_mut(&mut self) -> Option<&mut DataServerConfig> {
125 match &mut self.data_server {
126 DataServerEnabled::None(_) => None,
127 DataServerEnabled::Some(data_server) => Some(data_server),
128 }
129 }
130
131 pub fn package_info(&self) -> &PackageInfo {
133 &self.package_info
134 }
135
136 pub fn service_info(&self) -> &ServiceInfo {
138 &self.service_info
139 }
140
141 pub fn set_package_info(&mut self, package_info: PackageInfo) -> Result<()> {
143 self.package_info = package_info;
144
145 self
146 .service_info
147 .set_from_package_info(&self.package_info)?;
148
149 if let Some(ref mut auth) = self.auth {
150 auth.set_from_package_info(&self.package_info)?;
151 };
152 if let Some(ref mut auth) = self.ticket_server.auth {
153 auth.set_from_package_info(&self.package_info)?;
154 }
155 if let DataServerEnabled::Some(ref mut data_server_config) = self.data_server
156 && let Some(ref mut auth) = data_server_config.auth
157 {
158 auth.set_from_package_info(&self.package_info)?;
159 }
160
161 self.locations.set_from_package_info(&self.package_info)?;
162
163 Ok(())
164 }
165
166 pub fn service_info_mut(&mut self) -> &mut ServiceInfo {
168 &mut self.service_info
169 }
170
171 pub fn locations(&self) -> &[Location] {
173 self.locations.as_slice()
174 }
175
176 pub fn into_locations(self) -> Locations {
177 self.locations
178 }
179
180 pub fn parse_args_with_command(augment_args: Command) -> Result<Option<PathBuf>> {
183 let args = Args::from_arg_matches(&Args::augment_args(augment_args).get_matches())
184 .map_err(|err| ArgParseError(err.to_string()))?;
185
186 if args.config.as_ref().is_some_and(|path| !path.exists()) {
187 return Err(ParseError("config file not found".to_string()));
188 }
189
190 Ok(Self::parse_with_args(args))
191 }
192
193 pub fn parse_args() -> Option<PathBuf> {
195 Self::parse_with_args(Args::parse())
196 }
197
198 fn parse_with_args(args: Args) -> Option<PathBuf> {
199 if args.print_default_config {
200 println!(
201 "{}",
202 toml::ser::to_string_pretty(&Config::default()).unwrap()
203 );
204 None
205 } else if args.print_response_schema {
206 println!(
207 "{}",
208 serde_json::to_string_pretty(&schema_for!(AuthorizationRestrictions)).unwrap()
209 );
210 None
211 } else {
212 Some(args.config.unwrap_or_else(|| "".into()))
213 }
214 }
215
216 pub fn from_path(path: &Path) -> io::Result<Self> {
218 let mut config: Self = from_path(path)?;
219
220 if let DataServerEnabled::Some(ref mut data_server_config) = config.data_server
222 && data_server_config.auth().is_none()
223 {
224 data_server_config.set_auth(config.auth.clone());
225 }
226 if config.ticket_server().auth().is_none() {
227 config.ticket_server.set_auth(config.auth.clone());
228 }
229
230 Ok(config.validate_file_locations()?)
231 }
232
233 pub fn setup_tracing(&self) -> Result<()> {
235 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
236
237 let subscriber = Registry::default().with(env_filter);
238
239 match self.formatting_style() {
240 FormattingStyle::Full => set_global_default(subscriber.with(layer())),
241 FormattingStyle::Compact => {
242 set_global_default(subscriber.with(layer().event_format(format().compact())))
243 }
244 FormattingStyle::Pretty => {
245 set_global_default(subscriber.with(layer().event_format(format().pretty())))
246 }
247 FormattingStyle::Json => {
248 set_global_default(subscriber.with(layer().event_format(format().json())))
249 }
250 }
251 .map_err(|err| TracingError(err.to_string()))?;
252
253 Ok(())
254 }
255
256 pub fn validate_file_locations(mut self) -> Result<Self> {
258 if !self
259 .locations()
260 .iter()
261 .any(|location| location.backend().as_file().is_ok())
262 {
263 return Ok(self);
264 }
265
266 let DataServerEnabled::Some(ref mut config) = self.data_server else {
267 return Err(ParseError(
268 "must enable data server if using file locations".to_string(),
269 ));
270 };
271
272 let mut possible_paths: HashSet<_> =
273 HashSet::from_iter(self.locations.as_slice().iter().map(|location| {
274 location
275 .backend()
276 .as_file()
277 .ok()
278 .map(|file| file.local_path())
279 }));
280 possible_paths.remove(&None);
281
282 if possible_paths.len() > 1 {
283 return Err(ParseError(
284 "cannot have multiple file paths for file storage".to_string(),
285 ));
286 }
287 let local_path = possible_paths
288 .into_iter()
289 .next()
290 .flatten()
291 .ok_or_else(|| ParseError("failed to find local path from locations".to_string()))?
292 .to_string();
293
294 if config
295 .local_path()
296 .is_some_and(|path| path.to_string_lossy() != local_path)
297 {
298 return Err(ParseError(
299 "the data server local path and file storage directories must be the same".to_string(),
300 ));
301 }
302
303 config.set_local_path(Some(PathBuf::from(local_path)));
304
305 let scheme = config.tls().get_scheme();
306 let authority =
307 Authority::from_str(&config.addr().to_string()).map_err(|err| ParseError(err.to_string()))?;
308 let ticket_origin = config.ticket_origin();
309
310 self
311 .locations
312 .as_mut_slice()
313 .iter_mut()
314 .map(|location| {
315 match location.backend_mut() {
318 Backend::File(file) => {
319 if file.is_defaulted {
320 file.set_scheme(scheme);
321 file.set_authority(authority.clone());
322 file.set_ticket_origin(ticket_origin.clone())
323 }
324 }
325 #[cfg(feature = "aws")]
326 Backend::S3(_) => {}
327 #[cfg(feature = "url")]
328 Backend::Url(_) => {}
329 }
330
331 if self
333 .data_server
334 .as_data_server_config()
335 .is_ok_and(|config| config.auth().is_some())
336 {
337 location
338 .backend_mut()
339 .add_ticket_header(AUTHORIZATION.to_string());
340 }
341
342 Ok(())
343 })
344 .collect::<Result<Vec<()>>>()?;
345
346 Ok(self)
347 }
348}
349
350impl Default for Config {
351 fn default() -> Self {
352 Self {
353 formatting_style: FormattingStyle::Full,
354 ticket_server: Default::default(),
355 data_server: DataServerEnabled::Some(Default::default()),
356 service_info: Default::default(),
357 locations: Default::default(),
358 auth: Default::default(),
359 package_info: Default::default(),
360 }
361 }
362}
363
364pub(crate) fn serialize_array_display<S, T>(
365 names: &[T],
366 serializer: S,
367) -> std::result::Result<S::Ok, S::Error>
368where
369 T: Display,
370 S: Serializer,
371{
372 let mut sequence = serializer.serialize_seq(Some(names.len()))?;
373 for element in names.iter().map(|name| format!("{name}")) {
374 sequence.serialize_element(&element)?;
375 }
376 sequence.end()
377}
378
379pub(crate) fn deserialize_vec_from_str<'de, D, T>(
380 deserializer: D,
381) -> std::result::Result<Vec<T>, D::Error>
382where
383 T: FromStr,
384 T::Err: Display,
385 D: Deserializer<'de>,
386{
387 let names: Vec<String> = Deserialize::deserialize(deserializer)?;
388 names
389 .into_iter()
390 .map(|name| T::from_str(&name).map_err(Error::custom))
391 .collect()
392}
393
394#[cfg(test)]
395pub(crate) mod tests {
396 use std::fmt::Display;
397
398 use super::*;
399 use crate::config::advanced::auth::authorization::UrlOrStatic;
400 use crate::config::advanced::auth::jwt::AuthMode;
401 use crate::config::location::SimpleLocation;
402 use crate::config::parser::from_str;
403 use crate::http::tests::with_test_certificates;
404 use crate::storage::Backend;
405 use crate::types::Scheme;
406 use figment::Jail;
407 use http::Uri;
408 use http::uri::Authority;
409 use serde::de::DeserializeOwned;
410 use serde_json::json;
411
412 fn test_config<K, V, F>(contents: Option<&str>, env_variables: Vec<(K, V)>, test_fn: F)
413 where
414 K: AsRef<str>,
415 V: Display,
416 F: Fn(Config),
417 {
418 Jail::expect_with(|jail| {
419 let file = "test.toml";
420
421 if let Some(contents) = contents {
422 jail.create_file(file, contents)?;
423 }
424
425 for (key, value) in env_variables {
426 jail.set_env(key, value);
427 }
428
429 let path = Path::new(file);
430 test_fn(Config::from_path(path).map_err(|err| err.to_string())?);
431
432 test_fn(
433 from_path::<Config>(path)
434 .map_err(|err| err.to_string())?
435 .validate_file_locations()
436 .map_err(|err| err.to_string())?,
437 );
438 test_fn(
439 from_str::<Config>(contents.unwrap_or(""))
440 .map_err(|err| err.to_string())?
441 .validate_file_locations()
442 .map_err(|err| err.to_string())?,
443 );
444
445 Ok(())
446 });
447 }
448
449 pub(crate) fn test_config_from_env<K, V, F>(env_variables: Vec<(K, V)>, test_fn: F)
450 where
451 K: AsRef<str>,
452 V: Display,
453 F: Fn(Config),
454 {
455 test_config(None, env_variables, test_fn);
456 }
457
458 pub(crate) fn test_config_from_file<F>(contents: &str, test_fn: F)
459 where
460 F: Fn(Config),
461 {
462 test_config(Some(contents), Vec::<(&str, &str)>::new(), test_fn);
463 }
464
465 pub(crate) fn test_serialize_and_deserialize<T, D, F>(input: &str, expected: T, get_result: F)
466 where
467 T: Debug + PartialEq,
468 F: Fn(D) -> T,
469 D: DeserializeOwned + Serialize + Clone,
470 {
471 let config: D = toml::from_str(input).unwrap();
472 assert_eq!(expected, get_result(config.clone()));
473
474 let serialized = toml::to_string(&config).unwrap();
475 let deserialized = toml::from_str(&serialized).unwrap();
476 assert_eq!(expected, get_result(deserialized));
477 }
478
479 #[test]
480 fn config_ticket_server_addr_env() {
481 test_config_from_env(
482 vec![("HTSGET_TICKET_SERVER_ADDR", "127.0.0.1:8082")],
483 |config| {
484 assert_eq!(
485 config.ticket_server().addr(),
486 "127.0.0.1:8082".parse().unwrap()
487 );
488 },
489 );
490 }
491
492 #[test]
493 fn config_ticket_server_cors_allow_origin_env() {
494 test_config_from_env(
495 vec![("HTSGET_TICKET_SERVER_CORS_ALLOW_CREDENTIALS", true)],
496 |config| {
497 assert!(config.ticket_server().cors().allow_credentials());
498 },
499 );
500 }
501
502 #[test]
503 fn config_service_info_id_env() {
504 test_config_from_env(vec![("HTSGET_SERVICE_INFO", "{ id=id }")], |config| {
505 assert_eq!(config.service_info().as_ref().get("id"), Some(&json!("id")));
506 });
507 }
508
509 #[test]
510 fn config_data_server_addr_env() {
511 test_config_from_env(
512 vec![("HTSGET_DATA_SERVER_ADDR", "127.0.0.1:8082")],
513 |config| {
514 assert_eq!(
515 config
516 .data_server()
517 .clone()
518 .as_data_server_config()
519 .unwrap()
520 .addr(),
521 "127.0.0.1:8082".parse().unwrap()
522 );
523 },
524 );
525 }
526
527 #[test]
528 fn config_ticket_server_addr_file() {
529 test_config_from_file(r#"ticket_server.addr = "127.0.0.1:8082""#, |config| {
530 assert_eq!(
531 config.ticket_server().addr(),
532 "127.0.0.1:8082".parse().unwrap()
533 );
534 });
535 }
536
537 #[test]
538 fn config_ticket_server_cors_allow_origin_file() {
539 test_config_from_file(r#"ticket_server.cors.allow_credentials = true"#, |config| {
540 assert!(config.ticket_server().cors().allow_credentials());
541 });
542 }
543
544 #[test]
545 fn config_service_info_id_file() {
546 test_config_from_file(r#"service_info.id = "id""#, |config| {
547 assert_eq!(config.service_info().as_ref().get("id"), Some(&json!("id")));
548 });
549 }
550
551 #[test]
552 fn config_data_server_addr_file() {
553 test_config_from_file(r#"data_server.addr = "127.0.0.1:8082""#, |config| {
554 assert_eq!(
555 config
556 .data_server()
557 .clone()
558 .as_data_server_config()
559 .unwrap()
560 .addr(),
561 "127.0.0.1:8082".parse().unwrap()
562 );
563 });
564 }
565
566 #[test]
567 #[should_panic]
568 fn config_data_server_tls_no_cert() {
569 with_test_certificates(|path, _, _| {
570 let key_path = path.join("key.pem");
571
572 test_config_from_file(
573 &format!(
574 r#"
575 data_server.tls.key = "{}"
576 "#,
577 key_path.to_string_lossy().escape_default()
578 ),
579 |config| {
580 assert!(
581 config
582 .data_server()
583 .clone()
584 .as_data_server_config()
585 .unwrap()
586 .tls()
587 .is_none()
588 );
589 },
590 );
591 });
592 }
593
594 #[test]
595 fn config_data_server_tls() {
596 with_test_certificates(|path, _, _| {
597 let key_path = path.join("key.pem");
598 let cert_path = path.join("cert.pem");
599
600 test_config_from_file(
601 &format!(
602 r#"
603 data_server.tls.key = "{}"
604 data_server.tls.cert = "{}"
605 "#,
606 key_path.to_string_lossy().escape_default(),
607 cert_path.to_string_lossy().escape_default()
608 ),
609 |config| {
610 assert!(
611 config
612 .data_server()
613 .clone()
614 .as_data_server_config()
615 .unwrap()
616 .tls()
617 .is_some()
618 );
619 },
620 );
621 });
622 }
623
624 #[test]
625 fn config_data_server_tls_env() {
626 with_test_certificates(|path, _, _| {
627 let key_path = path.join("key.pem");
628 let cert_path = path.join("cert.pem");
629
630 test_config_from_env(
631 vec![
632 ("HTSGET_DATA_SERVER_TLS_KEY", key_path.to_string_lossy()),
633 ("HTSGET_DATA_SERVER_TLS_CERT", cert_path.to_string_lossy()),
634 ],
635 |config| {
636 assert!(
637 config
638 .data_server()
639 .clone()
640 .as_data_server_config()
641 .unwrap()
642 .tls()
643 .is_some()
644 );
645 },
646 );
647 });
648 }
649
650 #[test]
651 #[should_panic]
652 fn config_ticket_server_tls_no_cert() {
653 with_test_certificates(|path, _, _| {
654 let key_path = path.join("key.pem");
655
656 test_config_from_file(
657 &format!(
658 r#"
659 ticket_server.tls.key = "{}"
660 "#,
661 key_path.to_string_lossy().escape_default()
662 ),
663 |config| {
664 assert!(config.ticket_server().tls().is_none());
665 },
666 );
667 });
668 }
669
670 #[test]
671 fn config_ticket_server_tls() {
672 with_test_certificates(|path, _, _| {
673 let key_path = path.join("key.pem");
674 let cert_path = path.join("cert.pem");
675
676 test_config_from_file(
677 &format!(
678 r#"
679 ticket_server.tls.key = "{}"
680 ticket_server.tls.cert = "{}"
681 "#,
682 key_path.to_string_lossy().escape_default(),
683 cert_path.to_string_lossy().escape_default()
684 ),
685 |config| {
686 assert!(config.ticket_server().tls().is_some());
687 },
688 );
689 });
690 }
691
692 #[test]
693 fn config_ticket_server_tls_env() {
694 with_test_certificates(|path, _, _| {
695 let key_path = path.join("key.pem");
696 let cert_path = path.join("cert.pem");
697
698 test_config_from_env(
699 vec![
700 ("HTSGET_TICKET_SERVER_TLS_KEY", key_path.to_string_lossy()),
701 ("HTSGET_TICKET_SERVER_TLS_CERT", cert_path.to_string_lossy()),
702 ],
703 |config| {
704 assert!(config.ticket_server().tls().is_some());
705 },
706 );
707 });
708 }
709
710 #[test]
711 fn locations_from_data_server_config() {
712 test_config_from_file(
713 r#"
714 data_server.addr = "127.0.0.1:8080"
715 data_server.local_path = "path"
716
717 [[locations]]
718 regex = "123"
719 backend.kind = "File"
720 backend.local_path = "path"
721 "#,
722 |config| {
723 assert_eq!(config.locations().len(), 1);
724 let config = config.locations.into_inner();
725 let regex = config[0].as_regex().unwrap();
726 assert!(matches!(regex.backend(),
727 Backend::File(file) if file.local_path() == "path" && file.scheme() == Scheme::Http && file.authority() == &Authority::from_static("127.0.0.1:8080")));
728 },
729 );
730 }
731
732 #[test]
733 fn simple_locations_env() {
734 test_config_from_env(
735 vec![
736 ("HTSGET_DATA_SERVER_ADDR", "127.0.0.1:8080"),
737 (
738 "HTSGET_LOCATIONS",
739 "[ { location=file://data, prefix=bam }, { location=file://data, prefix=cram }]",
740 ),
741 ],
742 |config| {
743 assert_multiple(config);
744 },
745 );
746 }
747
748 #[test]
749 fn simple_locations() {
750 test_config_from_file(
751 r#"
752 data_server.addr = "127.0.0.1:8080"
753 data_server.local_path = "data"
754
755 locations = "file://data"
756 "#,
757 |config| {
758 assert_eq!(config.locations().len(), 1);
759 let config = config.locations.into_inner();
760 let location = config[0].as_simple().unwrap();
761 assert_eq!(location.prefix_or_id(), None);
762 assert_file_location(location, "data");
763 },
764 );
765 }
766
767 #[cfg(feature = "aws")]
768 #[test]
769 fn simple_locations_s3() {
770 test_config_from_file(
771 r#"
772 locations = "s3://bucket"
773 "#,
774 |config| {
775 assert_eq!(config.locations().len(), 1);
776 let config = config.locations.into_inner();
777 let location = config[0].as_simple().unwrap();
778 assert_eq!(location.prefix_or_id(), None);
779 assert!(matches!(location.backend(),
780 Backend::S3(s3) if s3.bucket() == "bucket"));
781 },
782 );
783 }
784
785 #[cfg(feature = "url")]
786 #[test]
787 fn simple_locations_url() {
788 test_config_from_file(
789 r#"
790 locations = "https://example.com"
791 "#,
792 |config| {
793 assert_eq!(config.locations().len(), 1);
794 let config = config.locations.into_inner();
795 let location = config[0].as_simple().unwrap();
796 assert_eq!(location.prefix_or_id(), None);
797 assert!(matches!(location.backend(),
798 Backend::Url(url) if url.url() == &"https://example.com".parse::<Uri>().unwrap()));
799 },
800 );
801 }
802
803 #[test]
804 fn simple_locations_multiple() {
805 test_config_from_file(
806 r#"
807 data_server.addr = "127.0.0.1:8080"
808 locations = [ { location = "file://data", prefix = "bam" }, { location = "file://data", prefix = "cram" }]
809 "#,
810 |config| {
811 assert_multiple(config);
812 },
813 );
814 }
815
816 #[cfg(feature = "aws")]
817 #[test]
818 fn simple_locations_multiple_mixed() {
819 test_config_from_file(
820 r#"
821 data_server.addr = "127.0.0.1:8080"
822 data_server.local_path = "data"
823 locations = [ { location = "file://data", prefix = "bam" }, { location = "file://data", prefix = "cram" }, { location = "s3://bucket", prefix = "vcf" } ]
824 "#,
825 |config| {
826 assert_eq!(config.locations().len(), 3);
827 let config = config.locations.into_inner();
828
829 let location = config[0].as_simple().unwrap();
830 assert_eq!(location.prefix_or_id().unwrap().as_prefix().unwrap(), "bam");
831 assert_file_location(location, "data");
832
833 let location = config[1].as_simple().unwrap();
834 assert_eq!(
835 location.prefix_or_id().unwrap().as_prefix().unwrap(),
836 "cram"
837 );
838 assert_file_location(location, "data");
839
840 let location = config[2].as_simple().unwrap();
841 assert_eq!(location.prefix_or_id().unwrap().as_prefix().unwrap(), "vcf");
842 assert!(matches!(location.backend(),
843 Backend::S3(s3) if s3.bucket() == "bucket"));
844 },
845 );
846 }
847
848 #[test]
849 fn config_server_auth() {
850 test_config_from_file(
851 r#"
852 ticket_server.auth.jwks_url = "https://www.example.com/"
853 ticket_server.auth.validate_issuer = ["iss1"]
854 ticket_server.auth.authorization_url = "https://www.example.com"
855 data_server.auth.jwks_url = "https://www.example.com/"
856 data_server.auth.validate_audience = ["aud1"]
857 data_server.auth.authorization_url = "https://www.example.com"
858 "#,
859 |config| {
860 let auth = config.ticket_server().auth().unwrap();
861 assert_eq!(
862 auth.auth_mode().unwrap(),
863 &AuthMode::Jwks("https://www.example.com/".parse().unwrap())
864 );
865 assert_eq!(
866 auth.validate_issuer(),
867 Some(vec!["iss1".to_string()].as_slice())
868 );
869 assert_eq!(
870 auth.authorization_url().unwrap(),
871 &UrlOrStatic::Url("https://www.example.com".parse::<Uri>().unwrap())
872 );
873 let auth = config
874 .data_server()
875 .as_data_server_config()
876 .unwrap()
877 .auth()
878 .unwrap();
879 assert_eq!(
880 auth.auth_mode().unwrap(),
881 &AuthMode::Jwks("https://www.example.com/".parse().unwrap())
882 );
883 assert_eq!(
884 auth.validate_audience(),
885 Some(vec!["aud1".to_string()].as_slice())
886 );
887 assert_eq!(
888 auth.authorization_url().unwrap(),
889 &UrlOrStatic::Url("https://www.example.com".parse::<Uri>().unwrap())
890 );
891 },
892 );
893 }
894
895 #[test]
896 fn config_server_auth_global() {
897 test_config_from_file(
898 r#"
899 auth.jwks_url = "https://www.example.com/"
900 auth.validate_audience = ["aud1"]
901 auth.authorization_url = "https://www.example.com"
902 "#,
903 |config| {
904 let auth = config.auth.unwrap();
905 assert_eq!(
906 auth.auth_mode().unwrap(),
907 &AuthMode::Jwks("https://www.example.com/".parse().unwrap())
908 );
909 assert_eq!(
910 auth.validate_audience(),
911 Some(vec!["aud1".to_string()].as_slice())
912 );
913 assert_eq!(
914 auth.authorization_url().unwrap(),
915 &UrlOrStatic::Url("https://www.example.com".parse::<Uri>().unwrap())
916 );
917 },
918 );
919 }
920
921 #[cfg(feature = "aws")]
922 #[test]
923 fn no_data_server() {
924 test_config_from_file(
925 r#"
926 data_server = "None"
927 locations = "s3://bucket"
928 "#,
929 |config| {
930 assert!(config.data_server().as_data_server_config().is_err());
931 },
932 );
933 }
934
935 fn assert_multiple(config: Config) {
936 assert_eq!(config.locations().len(), 2);
937 let config = config.locations.into_inner();
938
939 println!("{config:#?}");
940
941 let location = config[0].as_simple().unwrap();
942 assert_eq!(location.prefix_or_id().unwrap().as_prefix().unwrap(), "bam");
943 assert_file_location(location, "data");
944
945 let location = config[1].as_simple().unwrap();
946 assert_eq!(
947 location.prefix_or_id().unwrap().as_prefix().unwrap(),
948 "cram"
949 );
950 assert_file_location(location, "data");
951 }
952
953 fn assert_file_location(location: &SimpleLocation, local_path: &str) {
954 assert!(matches!(location.backend(),
955 Backend::File(file) if file.local_path() == local_path && file.scheme() == Scheme::Http && file.authority() == &Authority::from_static("127.0.0.1:8080")));
956 }
957}