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(_) | Backend::JsonPath(_) => {}
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 #[allow(clippy::result_large_err)]
419 Jail::expect_with(|jail| {
420 let file = "test.toml";
421
422 if let Some(contents) = contents {
423 jail.create_file(file, contents)?;
424 }
425
426 for (key, value) in env_variables {
427 jail.set_env(key, value);
428 }
429
430 let path = Path::new(file);
431 test_fn(Config::from_path(path).map_err(|err| err.to_string())?);
432
433 test_fn(
434 from_path::<Config>(path)
435 .map_err(|err| err.to_string())?
436 .validate_file_locations()
437 .map_err(|err| err.to_string())?,
438 );
439 test_fn(
440 from_str::<Config>(contents.unwrap_or(""))
441 .map_err(|err| err.to_string())?
442 .validate_file_locations()
443 .map_err(|err| err.to_string())?,
444 );
445
446 Ok(())
447 });
448 }
449
450 pub(crate) fn test_config_from_env<K, V, F>(env_variables: Vec<(K, V)>, test_fn: F)
451 where
452 K: AsRef<str>,
453 V: Display,
454 F: Fn(Config),
455 {
456 test_config(None, env_variables, test_fn);
457 }
458
459 pub(crate) fn test_config_from_file<F>(contents: &str, test_fn: F)
460 where
461 F: Fn(Config),
462 {
463 test_config(Some(contents), Vec::<(&str, &str)>::new(), test_fn);
464 }
465
466 pub(crate) fn test_serialize_and_deserialize<T, D, F>(input: &str, expected: T, get_result: F)
467 where
468 T: Debug + PartialEq,
469 F: Fn(D) -> T,
470 D: DeserializeOwned + Serialize + Clone,
471 {
472 let config: D = toml::from_str(input).unwrap();
473 assert_eq!(expected, get_result(config.clone()));
474
475 let serialized = toml::to_string(&config).unwrap();
476 let deserialized = toml::from_str(&serialized).unwrap();
477 assert_eq!(expected, get_result(deserialized));
478 }
479
480 #[test]
481 fn config_ticket_server_addr_env() {
482 test_config_from_env(
483 vec![("HTSGET_TICKET_SERVER_ADDR", "127.0.0.1:8082")],
484 |config| {
485 assert_eq!(
486 config.ticket_server().addr(),
487 "127.0.0.1:8082".parse().unwrap()
488 );
489 },
490 );
491 }
492
493 #[test]
494 fn config_ticket_server_cors_allow_origin_env() {
495 test_config_from_env(
496 vec![("HTSGET_TICKET_SERVER_CORS_ALLOW_CREDENTIALS", true)],
497 |config| {
498 assert!(config.ticket_server().cors().allow_credentials());
499 },
500 );
501 }
502
503 #[test]
504 fn config_service_info_id_env() {
505 test_config_from_env(vec![("HTSGET_SERVICE_INFO", "{ id=id }")], |config| {
506 assert_eq!(config.service_info().as_ref().get("id"), Some(&json!("id")));
507 });
508 }
509
510 #[test]
511 fn config_data_server_addr_env() {
512 test_config_from_env(
513 vec![("HTSGET_DATA_SERVER_ADDR", "127.0.0.1:8082")],
514 |config| {
515 assert_eq!(
516 config
517 .data_server()
518 .clone()
519 .as_data_server_config()
520 .unwrap()
521 .addr(),
522 "127.0.0.1:8082".parse().unwrap()
523 );
524 },
525 );
526 }
527
528 #[test]
529 fn config_ticket_server_addr_file() {
530 test_config_from_file(r#"ticket_server.addr = "127.0.0.1:8082""#, |config| {
531 assert_eq!(
532 config.ticket_server().addr(),
533 "127.0.0.1:8082".parse().unwrap()
534 );
535 });
536 }
537
538 #[test]
539 fn config_ticket_server_cors_allow_origin_file() {
540 test_config_from_file(r#"ticket_server.cors.allow_credentials = true"#, |config| {
541 assert!(config.ticket_server().cors().allow_credentials());
542 });
543 }
544
545 #[test]
546 fn config_service_info_id_file() {
547 test_config_from_file(r#"service_info.id = "id""#, |config| {
548 assert_eq!(config.service_info().as_ref().get("id"), Some(&json!("id")));
549 });
550 }
551
552 #[test]
553 fn config_data_server_addr_file() {
554 test_config_from_file(r#"data_server.addr = "127.0.0.1:8082""#, |config| {
555 assert_eq!(
556 config
557 .data_server()
558 .clone()
559 .as_data_server_config()
560 .unwrap()
561 .addr(),
562 "127.0.0.1:8082".parse().unwrap()
563 );
564 });
565 }
566
567 #[test]
568 #[should_panic]
569 fn config_data_server_tls_no_cert() {
570 with_test_certificates(|path, _, _| {
571 let key_path = path.join("key.pem");
572
573 test_config_from_file(
574 &format!(
575 r#"
576 data_server.tls.key = "{}"
577 "#,
578 key_path.to_string_lossy().escape_default()
579 ),
580 |config| {
581 assert!(
582 config
583 .data_server()
584 .clone()
585 .as_data_server_config()
586 .unwrap()
587 .tls()
588 .is_none()
589 );
590 },
591 );
592 });
593 }
594
595 #[test]
596 fn config_data_server_tls() {
597 with_test_certificates(|path, _, _| {
598 let key_path = path.join("key.pem");
599 let cert_path = path.join("cert.pem");
600
601 test_config_from_file(
602 &format!(
603 r#"
604 data_server.tls.key = "{}"
605 data_server.tls.cert = "{}"
606 "#,
607 key_path.to_string_lossy().escape_default(),
608 cert_path.to_string_lossy().escape_default()
609 ),
610 |config| {
611 assert!(
612 config
613 .data_server()
614 .clone()
615 .as_data_server_config()
616 .unwrap()
617 .tls()
618 .is_some()
619 );
620 },
621 );
622 });
623 }
624
625 #[test]
626 fn config_data_server_tls_env() {
627 with_test_certificates(|path, _, _| {
628 let key_path = path.join("key.pem");
629 let cert_path = path.join("cert.pem");
630
631 test_config_from_env(
632 vec![
633 ("HTSGET_DATA_SERVER_TLS_KEY", key_path.to_string_lossy()),
634 ("HTSGET_DATA_SERVER_TLS_CERT", cert_path.to_string_lossy()),
635 ],
636 |config| {
637 assert!(
638 config
639 .data_server()
640 .clone()
641 .as_data_server_config()
642 .unwrap()
643 .tls()
644 .is_some()
645 );
646 },
647 );
648 });
649 }
650
651 #[test]
652 #[should_panic]
653 fn config_ticket_server_tls_no_cert() {
654 with_test_certificates(|path, _, _| {
655 let key_path = path.join("key.pem");
656
657 test_config_from_file(
658 &format!(
659 r#"
660 ticket_server.tls.key = "{}"
661 "#,
662 key_path.to_string_lossy().escape_default()
663 ),
664 |config| {
665 assert!(config.ticket_server().tls().is_none());
666 },
667 );
668 });
669 }
670
671 #[test]
672 fn config_ticket_server_tls() {
673 with_test_certificates(|path, _, _| {
674 let key_path = path.join("key.pem");
675 let cert_path = path.join("cert.pem");
676
677 test_config_from_file(
678 &format!(
679 r#"
680 ticket_server.tls.key = "{}"
681 ticket_server.tls.cert = "{}"
682 "#,
683 key_path.to_string_lossy().escape_default(),
684 cert_path.to_string_lossy().escape_default()
685 ),
686 |config| {
687 assert!(config.ticket_server().tls().is_some());
688 },
689 );
690 });
691 }
692
693 #[test]
694 fn config_ticket_server_tls_env() {
695 with_test_certificates(|path, _, _| {
696 let key_path = path.join("key.pem");
697 let cert_path = path.join("cert.pem");
698
699 test_config_from_env(
700 vec![
701 ("HTSGET_TICKET_SERVER_TLS_KEY", key_path.to_string_lossy()),
702 ("HTSGET_TICKET_SERVER_TLS_CERT", cert_path.to_string_lossy()),
703 ],
704 |config| {
705 assert!(config.ticket_server().tls().is_some());
706 },
707 );
708 });
709 }
710
711 #[test]
712 fn locations_from_data_server_config() {
713 test_config_from_file(
714 r#"
715 data_server.addr = "127.0.0.1:8080"
716 data_server.local_path = "path"
717
718 [[locations]]
719 regex = "123"
720 backend.kind = "File"
721 backend.local_path = "path"
722 "#,
723 |config| {
724 assert_eq!(config.locations().len(), 1);
725 let config = config.locations.into_inner();
726 let regex = config[0].as_regex().unwrap();
727 assert!(matches!(regex.backend(),
728 Backend::File(file) if file.local_path() == "path" && file.scheme() == Scheme::Http && file.authority() == &Authority::from_static("127.0.0.1:8080")));
729 },
730 );
731 }
732
733 #[test]
734 fn simple_locations_env() {
735 test_config_from_env(
736 vec![
737 ("HTSGET_DATA_SERVER_ADDR", "127.0.0.1:8080"),
738 (
739 "HTSGET_LOCATIONS",
740 "[ { location=file://data, prefix=bam }, { location=file://data, prefix=cram }]",
741 ),
742 ],
743 |config| {
744 assert_multiple(config);
745 },
746 );
747 }
748
749 #[test]
750 fn simple_locations() {
751 test_config_from_file(
752 r#"
753 data_server.addr = "127.0.0.1:8080"
754 data_server.local_path = "data"
755
756 locations = "file://data"
757 "#,
758 |config| {
759 assert_eq!(config.locations().len(), 1);
760 let config = config.locations.into_inner();
761 let location = config[0].as_simple().unwrap();
762 assert_eq!(location.prefix_or_id(), None);
763 assert_file_location(location, "data");
764 },
765 );
766 }
767
768 #[cfg(feature = "aws")]
769 #[test]
770 fn simple_locations_s3() {
771 test_config_from_file(
772 r#"
773 locations = "s3://bucket"
774 "#,
775 |config| {
776 assert_eq!(config.locations().len(), 1);
777 let config = config.locations.into_inner();
778 let location = config[0].as_simple().unwrap();
779 assert_eq!(location.prefix_or_id(), None);
780 assert!(matches!(location.backend(),
781 Backend::S3(s3) if s3.bucket() == "bucket"));
782 },
783 );
784 }
785
786 #[cfg(feature = "url")]
787 #[test]
788 fn simple_locations_url() {
789 test_config_from_file(
790 r#"
791 locations = "https://example.com"
792 "#,
793 |config| {
794 assert_eq!(config.locations().len(), 1);
795 let config = config.locations.into_inner();
796 let location = config[0].as_simple().unwrap();
797 assert_eq!(location.prefix_or_id(), None);
798 assert!(matches!(location.backend(),
799 Backend::Url(url) if url.url() == &"https://example.com".parse::<Uri>().unwrap()));
800 },
801 );
802 }
803
804 #[test]
805 fn simple_locations_multiple() {
806 test_config_from_file(
807 r#"
808 data_server.addr = "127.0.0.1:8080"
809 locations = [ { location = "file://data", prefix = "bam" }, { location = "file://data", prefix = "cram" }]
810 "#,
811 |config| {
812 assert_multiple(config);
813 },
814 );
815 }
816
817 #[cfg(feature = "aws")]
818 #[test]
819 fn simple_locations_multiple_mixed() {
820 test_config_from_file(
821 r#"
822 data_server.addr = "127.0.0.1:8080"
823 data_server.local_path = "data"
824 locations = [ { location = "file://data", prefix = "bam" }, { location = "file://data", prefix = "cram" }, { location = "s3://bucket", prefix = "vcf" } ]
825 "#,
826 |config| {
827 assert_eq!(config.locations().len(), 3);
828 let config = config.locations.into_inner();
829
830 let location = config[0].as_simple().unwrap();
831 assert_eq!(location.prefix_or_id().unwrap().as_prefix().unwrap(), "bam");
832 assert_file_location(location, "data");
833
834 let location = config[1].as_simple().unwrap();
835 assert_eq!(
836 location.prefix_or_id().unwrap().as_prefix().unwrap(),
837 "cram"
838 );
839 assert_file_location(location, "data");
840
841 let location = config[2].as_simple().unwrap();
842 assert_eq!(location.prefix_or_id().unwrap().as_prefix().unwrap(), "vcf");
843 assert!(matches!(location.backend(),
844 Backend::S3(s3) if s3.bucket() == "bucket"));
845 },
846 );
847 }
848
849 #[test]
850 fn config_server_auth() {
851 test_config_from_file(
852 r#"
853 ticket_server.auth.jwks_url = "https://www.example.com/"
854 ticket_server.auth.validate_issuer = ["iss1"]
855 ticket_server.auth.authorization_url = "https://www.example.com"
856 data_server.auth.jwks_url = "https://www.example.com/"
857 data_server.auth.validate_audience = ["aud1"]
858 data_server.auth.authorization_url = "https://www.example.com"
859 "#,
860 |config| {
861 let auth = config.ticket_server().auth().unwrap();
862 assert_eq!(
863 auth.auth_mode().unwrap(),
864 &AuthMode::Jwks("https://www.example.com/".parse().unwrap())
865 );
866 assert_eq!(
867 auth.validate_issuer(),
868 Some(vec!["iss1".to_string()].as_slice())
869 );
870 assert_eq!(
871 auth.authorization_url().unwrap(),
872 &UrlOrStatic::Url("https://www.example.com".parse::<Uri>().unwrap())
873 );
874 let auth = config
875 .data_server()
876 .as_data_server_config()
877 .unwrap()
878 .auth()
879 .unwrap();
880 assert_eq!(
881 auth.auth_mode().unwrap(),
882 &AuthMode::Jwks("https://www.example.com/".parse().unwrap())
883 );
884 assert_eq!(
885 auth.validate_audience(),
886 Some(vec!["aud1".to_string()].as_slice())
887 );
888 assert_eq!(
889 auth.authorization_url().unwrap(),
890 &UrlOrStatic::Url("https://www.example.com".parse::<Uri>().unwrap())
891 );
892 },
893 );
894 }
895
896 #[test]
897 fn config_server_auth_global() {
898 test_config_from_file(
899 r#"
900 auth.jwks_url = "https://www.example.com/"
901 auth.validate_audience = ["aud1"]
902 auth.authorization_url = "https://www.example.com"
903 "#,
904 |config| {
905 let auth = config.auth.unwrap();
906 assert_eq!(
907 auth.auth_mode().unwrap(),
908 &AuthMode::Jwks("https://www.example.com/".parse().unwrap())
909 );
910 assert_eq!(
911 auth.validate_audience(),
912 Some(vec!["aud1".to_string()].as_slice())
913 );
914 assert_eq!(
915 auth.authorization_url().unwrap(),
916 &UrlOrStatic::Url("https://www.example.com".parse::<Uri>().unwrap())
917 );
918 },
919 );
920 }
921
922 #[cfg(feature = "aws")]
923 #[test]
924 fn no_data_server() {
925 test_config_from_file(
926 r#"
927 data_server = "None"
928 locations = "s3://bucket"
929 "#,
930 |config| {
931 assert!(config.data_server().as_data_server_config().is_err());
932 },
933 );
934 }
935
936 fn assert_multiple(config: Config) {
937 assert_eq!(config.locations().len(), 2);
938 let config = config.locations.into_inner();
939
940 println!("{config:#?}");
941
942 let location = config[0].as_simple().unwrap();
943 assert_eq!(location.prefix_or_id().unwrap().as_prefix().unwrap(), "bam");
944 assert_file_location(location, "data");
945
946 let location = config[1].as_simple().unwrap();
947 assert_eq!(
948 location.prefix_or_id().unwrap().as_prefix().unwrap(),
949 "cram"
950 );
951 assert_file_location(location, "data");
952 }
953
954 fn assert_file_location(location: &SimpleLocation, local_path: &str) {
955 assert!(matches!(location.backend(),
956 Backend::File(file) if file.local_path() == local_path && file.scheme() == Scheme::Http && file.authority() == &Authority::from_static("127.0.0.1:8080")));
957 }
958}