1use mlua_swarm::core::config::CheckPolicy;
14use mlua_swarm::LegacyWorkerBindingPolicy;
15use serde::Deserialize;
16use std::net::SocketAddr;
17use std::path::{Path, PathBuf};
18
19pub fn default_config_path() -> PathBuf {
22 match std::env::var("HOME") {
23 Ok(home) => PathBuf::from(home).join(".mse").join("config.toml"),
24 Err(_) => PathBuf::from(".mse/config.toml"),
25 }
26}
27
28pub fn default_store_path() -> PathBuf {
33 match std::env::var("HOME") {
34 Ok(home) => PathBuf::from(home).join(".mse").join("store"),
35 Err(_) => PathBuf::from(".mse/store"),
36 }
37}
38
39pub fn default_task_store_path() -> PathBuf {
43 match std::env::var("HOME") {
44 Ok(home) => PathBuf::from(home)
45 .join(".mse")
46 .join("store")
47 .join("task.sqlite"),
48 Err(_) => PathBuf::from(".mse/store/task.sqlite"),
49 }
50}
51
52pub fn default_run_store_path() -> PathBuf {
55 match std::env::var("HOME") {
56 Ok(home) => PathBuf::from(home)
57 .join(".mse")
58 .join("store")
59 .join("run.sqlite"),
60 Err(_) => PathBuf::from(".mse/store/run.sqlite"),
61 }
62}
63
64pub fn default_replay_store_path() -> PathBuf {
68 match std::env::var("HOME") {
69 Ok(home) => PathBuf::from(home)
70 .join(".mse")
71 .join("store")
72 .join("replay.sqlite"),
73 Err(_) => PathBuf::from(".mse/store/replay.sqlite"),
74 }
75}
76
77#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
81#[serde(deny_unknown_fields)]
82pub struct FileConfig {
83 pub bind: Option<String>,
85 pub enable_enhance_flow: Option<bool>,
87 pub legacy_worker_binding_policy: Option<LegacyWorkerBindingPolicy>,
89 pub blueprint_ref_base: Option<PathBuf>,
91 pub blueprint_ref_includes: Option<Vec<PathBuf>>,
96 pub blueprint_strict_embed: Option<bool>,
105 pub inject_endpoint_for_worker: Option<bool>,
114 pub long_hold_warn_ms: Option<u64>,
124 pub git_store_path: Option<PathBuf>,
126 pub issue_store_path: Option<PathBuf>,
129 pub enhance_setting_store_path: Option<PathBuf>,
132 pub enhance_log_store_path: Option<PathBuf>,
135 pub output_store_path: Option<PathBuf>,
138 pub task_store_path: Option<PathBuf>,
142 pub run_store_path: Option<PathBuf>,
145 pub replay_store_path: Option<PathBuf>,
151 pub ephemeral: Option<bool>,
157 pub seed_blueprint_id: Option<String>,
159 pub default_agent_kind: Option<String>,
162 pub token_secret: Option<String>,
164 pub sync_timeout_secs: Option<u64>,
170 pub check_policy: Option<CheckPolicy>,
183}
184
185#[derive(Debug, Default, Clone)]
189pub struct CliOverrides {
190 pub bind: Option<String>,
192 pub enable_enhance_flow: Option<bool>,
194 pub legacy_worker_binding_policy: Option<LegacyWorkerBindingPolicy>,
196 pub blueprint_ref_base: Option<PathBuf>,
198 pub blueprint_ref_includes: Vec<PathBuf>,
201 pub blueprint_strict_embed: Option<bool>,
204 pub inject_endpoint_for_worker: Option<bool>,
207 pub long_hold_warn_ms: Option<u64>,
210 pub git_store_path: Option<PathBuf>,
212 pub issue_store_path: Option<PathBuf>,
214 pub enhance_setting_store_path: Option<PathBuf>,
216 pub enhance_log_store_path: Option<PathBuf>,
218 pub output_store_path: Option<PathBuf>,
220 pub task_store_path: Option<PathBuf>,
222 pub run_store_path: Option<PathBuf>,
224 pub replay_store_path: Option<PathBuf>,
226 pub ephemeral: Option<bool>,
228 pub seed_blueprint_id: Option<String>,
230 pub default_agent_kind: Option<String>,
232 pub token_secret: Option<String>,
234 pub sync_timeout_secs: Option<u64>,
236 pub check_policy: Option<CheckPolicy>,
240}
241
242#[derive(Debug, Clone, PartialEq)]
244pub struct ResolvedConfig {
245 pub bind: SocketAddr,
247 pub enable_enhance_flow: bool,
249 pub legacy_worker_binding_policy: LegacyWorkerBindingPolicy,
251 pub blueprint_ref_base: Option<PathBuf>,
253 pub blueprint_ref_includes: Vec<PathBuf>,
257 pub blueprint_strict_embed: bool,
263 pub git_store_path: PathBuf,
267 pub issue_store_path: Option<PathBuf>,
270 pub enhance_setting_store_path: Option<PathBuf>,
273 pub enhance_log_store_path: Option<PathBuf>,
276 pub output_store_path: Option<PathBuf>,
279 pub task_store_path: Option<PathBuf>,
282 pub run_store_path: Option<PathBuf>,
285 pub replay_store_path: Option<PathBuf>,
288 pub seed_blueprint_id: String,
290 pub default_agent_kind: Option<String>,
293 pub token_secret: Option<String>,
295 pub sync_timeout_secs: u64,
301 pub inject_endpoint_for_worker: bool,
307 pub long_hold_warn_ms: Option<u64>,
310 pub check_policy: CheckPolicy,
317}
318
319impl Default for ResolvedConfig {
320 fn default() -> Self {
321 Self {
322 bind: default_bind(),
323 enable_enhance_flow: false,
324 legacy_worker_binding_policy: LegacyWorkerBindingPolicy::Allow,
325 blueprint_ref_base: None,
326 blueprint_ref_includes: Vec::new(),
327 blueprint_strict_embed: false,
328 git_store_path: default_store_path(),
329 issue_store_path: None,
330 enhance_setting_store_path: None,
331 enhance_log_store_path: None,
332 output_store_path: None,
333 task_store_path: None,
334 run_store_path: None,
335 replay_store_path: None,
336 seed_blueprint_id: "main".into(),
337 default_agent_kind: None,
338 token_secret: None,
339 sync_timeout_secs: default_sync_timeout_secs(),
340 inject_endpoint_for_worker: false,
341 long_hold_warn_ms: None,
342 check_policy: CheckPolicy::default(),
343 }
344 }
345}
346
347pub fn default_sync_timeout_secs() -> u64 {
356 3600
357}
358
359fn default_bind() -> SocketAddr {
360 "127.0.0.1:7777"
361 .parse()
362 .expect("literal default bind must parse")
363}
364
365pub fn load_file_config(path: &Path) -> Result<FileConfig, String> {
370 match std::fs::read_to_string(path) {
371 Ok(text) => toml::from_str(&text)
372 .map_err(|e| format!("config file {} parse error: {e}", path.display())),
373 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(FileConfig::default()),
374 Err(e) => Err(format!("config file {} read error: {e}", path.display())),
375 }
376}
377
378pub fn resolve(cli: CliOverrides, file: FileConfig) -> Result<ResolvedConfig, String> {
381 let default = ResolvedConfig::default();
382
383 let bind = match cli.bind.or(file.bind) {
384 Some(s) => s
385 .parse::<SocketAddr>()
386 .map_err(|e| format!("bind {s:?}: {e}"))?,
387 None => default.bind,
388 };
389
390 let ephemeral = cli.ephemeral.or(file.ephemeral).unwrap_or(false);
391
392 Ok(ResolvedConfig {
393 bind,
394 enable_enhance_flow: cli
395 .enable_enhance_flow
396 .or(file.enable_enhance_flow)
397 .unwrap_or(default.enable_enhance_flow),
398 legacy_worker_binding_policy: cli
399 .legacy_worker_binding_policy
400 .or(file.legacy_worker_binding_policy)
401 .unwrap_or(default.legacy_worker_binding_policy),
402 blueprint_ref_base: cli.blueprint_ref_base.or(file.blueprint_ref_base),
403 blueprint_ref_includes: {
404 let mut merged = cli.blueprint_ref_includes;
405 merged.extend(file.blueprint_ref_includes.unwrap_or_default());
406 merged
407 },
408 blueprint_strict_embed: cli
409 .blueprint_strict_embed
410 .or(file.blueprint_strict_embed)
411 .unwrap_or(default.blueprint_strict_embed),
412 inject_endpoint_for_worker: cli
413 .inject_endpoint_for_worker
414 .or(file.inject_endpoint_for_worker)
415 .unwrap_or(default.inject_endpoint_for_worker),
416 long_hold_warn_ms: cli.long_hold_warn_ms.or(file.long_hold_warn_ms),
417 git_store_path: cli
418 .git_store_path
419 .or(file.git_store_path)
420 .unwrap_or_else(default_store_path),
421 issue_store_path: cli.issue_store_path.or(file.issue_store_path),
422 enhance_setting_store_path: cli
423 .enhance_setting_store_path
424 .or(file.enhance_setting_store_path),
425 enhance_log_store_path: cli.enhance_log_store_path.or(file.enhance_log_store_path),
426 output_store_path: cli.output_store_path.or(file.output_store_path),
427 task_store_path: cli.task_store_path.or(file.task_store_path).or_else(|| {
428 if ephemeral {
429 None
430 } else {
431 Some(default_task_store_path())
432 }
433 }),
434 run_store_path: cli.run_store_path.or(file.run_store_path).or_else(|| {
435 if ephemeral {
436 None
437 } else {
438 Some(default_run_store_path())
439 }
440 }),
441 replay_store_path: cli
442 .replay_store_path
443 .or(file.replay_store_path)
444 .or_else(|| {
445 if ephemeral {
446 None
447 } else {
448 Some(default_replay_store_path())
449 }
450 }),
451 seed_blueprint_id: cli
452 .seed_blueprint_id
453 .or(file.seed_blueprint_id)
454 .unwrap_or(default.seed_blueprint_id),
455 default_agent_kind: cli.default_agent_kind.or(file.default_agent_kind),
456 token_secret: cli.token_secret.or(file.token_secret),
457 sync_timeout_secs: cli
458 .sync_timeout_secs
459 .or(file.sync_timeout_secs)
460 .unwrap_or_else(default_sync_timeout_secs),
461 check_policy: cli
462 .check_policy
463 .or(file.check_policy)
464 .unwrap_or(default.check_policy),
465 })
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
473 fn resolve_cli_flag_wins_over_file_and_default() {
474 let cli = CliOverrides {
475 bind: Some("127.0.0.1:9999".into()),
476 ..Default::default()
477 };
478 let file = FileConfig {
479 bind: Some("127.0.0.1:8888".into()),
480 ..Default::default()
481 };
482 let resolved = resolve(cli, file).expect("resolve");
483 assert_eq!(
484 resolved.bind,
485 "127.0.0.1:9999".parse::<SocketAddr>().unwrap()
486 );
487 }
488
489 #[test]
490 fn resolve_file_wins_over_built_in_default_when_cli_absent() {
491 let cli = CliOverrides::default();
492 let file = FileConfig {
493 seed_blueprint_id: Some("from-file".into()),
494 enable_enhance_flow: Some(true),
495 ..Default::default()
496 };
497 let resolved = resolve(cli, file).expect("resolve");
498 assert_eq!(resolved.seed_blueprint_id, "from-file");
499 assert!(resolved.enable_enhance_flow);
500 }
501
502 #[test]
503 fn resolve_legacy_worker_binding_policy_uses_cli_file_default_precedence() {
504 let resolved = resolve(CliOverrides::default(), FileConfig::default()).unwrap();
505 assert_eq!(
506 resolved.legacy_worker_binding_policy,
507 LegacyWorkerBindingPolicy::Allow
508 );
509
510 let file = FileConfig {
511 legacy_worker_binding_policy: Some(LegacyWorkerBindingPolicy::Reject),
512 ..Default::default()
513 };
514 let resolved = resolve(CliOverrides::default(), file.clone()).unwrap();
515 assert_eq!(
516 resolved.legacy_worker_binding_policy,
517 LegacyWorkerBindingPolicy::Reject
518 );
519
520 let cli = CliOverrides {
521 legacy_worker_binding_policy: Some(LegacyWorkerBindingPolicy::Allow),
522 ..Default::default()
523 };
524 let resolved = resolve(cli, file).unwrap();
525 assert_eq!(
526 resolved.legacy_worker_binding_policy,
527 LegacyWorkerBindingPolicy::Allow
528 );
529 }
530
531 #[test]
532 fn resolve_built_in_default_when_cli_and_file_absent() {
533 let resolved = resolve(CliOverrides::default(), FileConfig::default()).expect("resolve");
534 assert_eq!(resolved.bind, default_bind());
535 assert_eq!(resolved.seed_blueprint_id, "main");
536 assert!(!resolved.enable_enhance_flow);
537 assert_eq!(resolved.git_store_path, default_store_path());
538 }
539
540 #[test]
541 fn resolve_git_store_path_file_overrides_default_location() {
542 let file = FileConfig {
543 git_store_path: Some(PathBuf::from("/tmp/custom-store")),
544 ..Default::default()
545 };
546 let resolved = resolve(CliOverrides::default(), file).expect("resolve");
547 assert_eq!(resolved.git_store_path, PathBuf::from("/tmp/custom-store"));
548 }
549
550 #[test]
551 fn resolve_bind_parse_error_is_propagated() {
552 let cli = CliOverrides {
553 bind: Some("not-a-valid-addr".into()),
554 ..Default::default()
555 };
556 let err = resolve(cli, FileConfig::default()).unwrap_err();
557 assert!(err.contains("not-a-valid-addr"), "unexpected error: {err}");
558 }
559
560 #[test]
561 fn load_file_config_rejects_unknown_fields() {
562 let toml_text = "bind = \"127.0.0.1:1234\"\ntypo_field = true\n";
563 let err = toml::from_str::<FileConfig>(toml_text).unwrap_err();
564 let msg = err.to_string();
565 assert!(
566 msg.contains("typo_field") || msg.contains("unknown field"),
567 "unexpected error message: {msg}"
568 );
569 }
570
571 #[test]
572 fn load_file_config_missing_file_falls_back_to_default() {
573 let path = std::path::Path::new("/nonexistent/mse-config-test-path/config.toml");
574 let cfg = load_file_config(path).expect("missing file should not error");
575 assert_eq!(cfg, FileConfig::default());
576 }
577
578 #[test]
579 fn load_file_config_parses_valid_toml() {
580 let dir = std::env::temp_dir().join(format!("server-config-test-{}", std::process::id()));
581 std::fs::create_dir_all(&dir).expect("create tmp dir");
582 let path = dir.join("config.toml");
583 std::fs::write(
584 &path,
585 "bind = \"127.0.0.1:7000\"\nenable_enhance_flow = true\nseed_blueprint_id = \"main\"\n",
586 )
587 .expect("write tmp config");
588 let cfg = load_file_config(&path).expect("parse tmp config");
589 assert_eq!(cfg.bind.as_deref(), Some("127.0.0.1:7000"));
590 assert_eq!(cfg.enable_enhance_flow, Some(true));
591 let _ = std::fs::remove_dir_all(&dir);
592 }
593
594 #[test]
595 fn resolve_task_and_run_store_path_cli_wins_over_file() {
596 let cli = CliOverrides {
597 task_store_path: Some(PathBuf::from("/tmp/cli-tasks.db")),
598 ..Default::default()
599 };
600 let file = FileConfig {
601 task_store_path: Some(PathBuf::from("/tmp/file-tasks.db")),
602 run_store_path: Some(PathBuf::from("/tmp/file-runs.db")),
603 ..Default::default()
604 };
605 let resolved = resolve(cli, file).expect("resolve");
606 assert_eq!(
607 resolved.task_store_path,
608 Some(PathBuf::from("/tmp/cli-tasks.db")),
609 "cli task_store_path must win over file"
610 );
611 assert_eq!(
612 resolved.run_store_path,
613 Some(PathBuf::from("/tmp/file-runs.db")),
614 "run_store_path falls back to file when cli is absent"
615 );
616 }
617
618 #[test]
619 fn resolve_task_and_run_store_path_default_none() {
620 let resolved = resolve(CliOverrides::default(), FileConfig::default()).expect("resolve");
621 assert_eq!(
622 resolved.task_store_path,
623 Some(default_task_store_path()),
624 "issue #35 ST1: task_store_path now persists by default"
625 );
626 assert_eq!(
627 resolved.run_store_path,
628 Some(default_run_store_path()),
629 "issue #35 ST1: run_store_path now persists by default"
630 );
631 }
632
633 #[test]
634 fn resolve_ephemeral_true_restores_in_memory_default() {
635 let cli = CliOverrides {
636 ephemeral: Some(true),
637 ..Default::default()
638 };
639 let resolved = resolve(cli, FileConfig::default()).expect("resolve");
640 assert_eq!(resolved.task_store_path, None);
641 assert_eq!(resolved.run_store_path, None);
642 }
643
644 #[test]
645 fn resolve_explicit_path_wins_over_ephemeral() {
646 let cli = CliOverrides {
647 task_store_path: Some(PathBuf::from("/tmp/explicit-tasks.db")),
648 ephemeral: Some(true),
649 ..Default::default()
650 };
651 let resolved = resolve(cli, FileConfig::default()).expect("resolve");
652 assert_eq!(
653 resolved.task_store_path,
654 Some(PathBuf::from("/tmp/explicit-tasks.db")),
655 "explicit path must win over ephemeral"
656 );
657 }
658
659 #[test]
660 fn resolve_ephemeral_from_file_config() {
661 let file = FileConfig {
662 ephemeral: Some(true),
663 ..Default::default()
664 };
665 let resolved = resolve(CliOverrides::default(), file).expect("resolve");
666 assert_eq!(resolved.task_store_path, None);
667 assert_eq!(resolved.run_store_path, None);
668 assert_eq!(resolved.replay_store_path, None);
669 }
670
671 #[test]
676 fn resolve_replay_store_path_cli_wins_over_file() {
677 let cli = CliOverrides {
678 replay_store_path: Some(PathBuf::from("/tmp/cli-replay.db")),
679 ..Default::default()
680 };
681 let file = FileConfig {
682 replay_store_path: Some(PathBuf::from("/tmp/file-replay.db")),
683 ..Default::default()
684 };
685 let resolved = resolve(cli, file).expect("resolve");
686 assert_eq!(
687 resolved.replay_store_path,
688 Some(PathBuf::from("/tmp/cli-replay.db")),
689 "cli replay_store_path must win over file"
690 );
691 }
692
693 #[test]
694 fn resolve_replay_store_path_file_wins_over_default() {
695 let file = FileConfig {
696 replay_store_path: Some(PathBuf::from("/tmp/file-replay.db")),
697 ..Default::default()
698 };
699 let resolved = resolve(CliOverrides::default(), file).expect("resolve");
700 assert_eq!(
701 resolved.replay_store_path,
702 Some(PathBuf::from("/tmp/file-replay.db")),
703 "file replay_store_path must win over built-in default"
704 );
705 }
706
707 #[test]
708 fn resolve_replay_store_path_default_persists() {
709 let resolved = resolve(CliOverrides::default(), FileConfig::default()).expect("resolve");
710 assert_eq!(
711 resolved.replay_store_path,
712 Some(default_replay_store_path()),
713 "replay_store_path persists by default (sibling of run_store_path)"
714 );
715 }
716
717 #[test]
718 fn resolve_replay_store_path_ephemeral_restores_in_memory() {
719 let cli = CliOverrides {
720 ephemeral: Some(true),
721 ..Default::default()
722 };
723 let resolved = resolve(cli, FileConfig::default()).expect("resolve");
724 assert_eq!(resolved.replay_store_path, None);
725 }
726
727 #[test]
728 fn resolve_replay_store_path_explicit_wins_over_ephemeral() {
729 let cli = CliOverrides {
730 replay_store_path: Some(PathBuf::from("/tmp/explicit-replay.db")),
731 ephemeral: Some(true),
732 ..Default::default()
733 };
734 let resolved = resolve(cli, FileConfig::default()).expect("resolve");
735 assert_eq!(
736 resolved.replay_store_path,
737 Some(PathBuf::from("/tmp/explicit-replay.db")),
738 "explicit replay path must win over ephemeral"
739 );
740 }
741
742 #[test]
747 fn resolve_sync_timeout_secs_default_when_cli_and_file_absent() {
748 let resolved = resolve(CliOverrides::default(), FileConfig::default()).expect("resolve");
749 assert_eq!(resolved.sync_timeout_secs, 3600);
750 assert_eq!(resolved.sync_timeout_secs, default_sync_timeout_secs());
751 }
752
753 #[test]
754 fn resolve_sync_timeout_secs_file_wins_over_default() {
755 let file = FileConfig {
756 sync_timeout_secs: Some(120),
757 ..Default::default()
758 };
759 let resolved = resolve(CliOverrides::default(), file).expect("resolve");
760 assert_eq!(resolved.sync_timeout_secs, 120);
761 }
762
763 #[test]
764 fn resolve_sync_timeout_secs_cli_wins_over_file() {
765 let cli = CliOverrides {
766 sync_timeout_secs: Some(45),
767 ..Default::default()
768 };
769 let file = FileConfig {
770 sync_timeout_secs: Some(120),
771 ..Default::default()
772 };
773 let resolved = resolve(cli, file).expect("resolve");
774 assert_eq!(
775 resolved.sync_timeout_secs, 45,
776 "cli sync_timeout_secs must win over file"
777 );
778 }
779
780 #[test]
785 fn resolve_check_policy_default_when_cli_and_file_absent() {
786 let resolved = resolve(CliOverrides::default(), FileConfig::default()).expect("resolve");
787 assert_eq!(
788 resolved.check_policy,
789 CheckPolicy::Warn,
790 "default check_policy must preserve pre-CheckPolicy fail-open (Warn)"
791 );
792 assert_eq!(resolved.check_policy, CheckPolicy::default());
793 }
794
795 #[test]
796 fn resolve_check_policy_file_wins_over_default() {
797 let file = FileConfig {
798 check_policy: Some(CheckPolicy::Strict),
799 ..Default::default()
800 };
801 let resolved = resolve(CliOverrides::default(), file).expect("resolve");
802 assert_eq!(
803 resolved.check_policy,
804 CheckPolicy::Strict,
805 "file check_policy must win over built-in default"
806 );
807 }
808
809 #[test]
810 fn resolve_check_policy_cli_wins_over_file() {
811 let cli = CliOverrides {
812 check_policy: Some(CheckPolicy::Silent),
813 ..Default::default()
814 };
815 let file = FileConfig {
816 check_policy: Some(CheckPolicy::Strict),
817 ..Default::default()
818 };
819 let resolved = resolve(cli, file).expect("resolve");
820 assert_eq!(
821 resolved.check_policy,
822 CheckPolicy::Silent,
823 "cli check_policy must win over file"
824 );
825 }
826
827 #[test]
832 fn resolve_blueprint_strict_embed_default_false_when_cli_and_file_absent() {
833 let resolved = resolve(CliOverrides::default(), FileConfig::default()).expect("resolve");
834 assert!(
835 !resolved.blueprint_strict_embed,
836 "default blueprint_strict_embed = false (backward-compat: linker runs server-side)"
837 );
838 }
839
840 #[test]
841 fn resolve_blueprint_strict_embed_file_wins_over_default() {
842 let file = FileConfig {
843 blueprint_strict_embed: Some(true),
844 ..Default::default()
845 };
846 let resolved = resolve(CliOverrides::default(), file).expect("resolve");
847 assert!(resolved.blueprint_strict_embed);
848 }
849
850 #[test]
851 fn resolve_blueprint_strict_embed_cli_wins_over_file() {
852 let cli = CliOverrides {
853 blueprint_strict_embed: Some(false),
854 ..Default::default()
855 };
856 let file = FileConfig {
857 blueprint_strict_embed: Some(true),
858 ..Default::default()
859 };
860 let resolved = resolve(cli, file).expect("resolve");
861 assert!(
862 !resolved.blueprint_strict_embed,
863 "cli blueprint_strict_embed=false must win over file=true"
864 );
865 }
866
867 #[test]
868 fn file_config_deserializes_blueprint_strict_embed() {
869 let toml_text = "blueprint_strict_embed = true\n";
870 let cfg: FileConfig = toml::from_str(toml_text).expect("parse");
871 assert_eq!(cfg.blueprint_strict_embed, Some(true));
872 }
873
874 #[test]
875 fn file_config_deserializes_check_policy_snake_case_literals() {
876 let toml_text = "check_policy = \"strict\"\n";
877 let cfg: FileConfig = toml::from_str(toml_text).expect("parse");
878 assert_eq!(cfg.check_policy, Some(CheckPolicy::Strict));
879
880 let toml_text = "check_policy = \"silent\"\n";
881 let cfg: FileConfig = toml::from_str(toml_text).expect("parse");
882 assert_eq!(cfg.check_policy, Some(CheckPolicy::Silent));
883
884 let toml_text = "check_policy = \"warn\"\n";
885 let cfg: FileConfig = toml::from_str(toml_text).expect("parse");
886 assert_eq!(cfg.check_policy, Some(CheckPolicy::Warn));
887 }
888}