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