1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
4pub enum Profile {
5 #[default]
7 Minimal,
8 Compatibility,
10 Agent,
12}
13
14#[derive(Debug, Clone, Default)]
15pub struct BackendPolicy {
16 pub profile: Profile,
17 pub preferred: Option<String>,
18 pub chain: Vec<String>,
19 pub allow_fallback: bool,
20 pub timeout_secs: f64,
21 pub enable_ocr: bool,
22 pub enable_layout: bool,
23 pub ocr_dpi: u32,
24 pub ocr_drop_score: f32,
25 pub max_pages: usize,
26}
27
28impl BackendPolicy {
29 pub fn ordered_chain(&self, default_chain: &[String]) -> Vec<String> {
31 let mut base: Vec<String> = if !self.chain.is_empty() {
32 self.chain.clone()
33 } else {
34 let mut d = default_chain.to_vec();
35 if let Some(pref) = &self.preferred {
36 if let Some(pos) = d.iter().position(|x| x == pref) {
37 d.remove(pos);
38 d.insert(0, pref.clone());
39 } else {
40 d.insert(0, pref.clone());
41 }
42 }
43 d
44 };
45 if !self.allow_fallback && base.len() > 1 {
46 base.truncate(1);
47 }
48 base
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 fn s(v: &[&str]) -> Vec<String> {
57 v.iter().map(|x| x.to_string()).collect()
58 }
59
60 #[test]
61 fn preferred_promotes() {
62 let p = BackendPolicy {
63 preferred: Some("b".into()),
64 allow_fallback: true,
65 ..Default::default()
66 };
67 assert_eq!(p.ordered_chain(&s(&["a", "b", "c"])), s(&["b", "a", "c"]));
68 }
69
70 #[test]
71 fn explicit_chain_wins() {
72 let p = BackendPolicy {
73 chain: s(&["c", "a"]),
74 allow_fallback: true,
75 ..Default::default()
76 };
77 assert_eq!(p.ordered_chain(&s(&["a", "b", "c"])), s(&["c", "a"]));
78 }
79
80 #[test]
81 fn no_fallback_truncates() {
82 let p = BackendPolicy {
83 allow_fallback: false,
84 ..Default::default()
85 };
86 assert_eq!(p.ordered_chain(&s(&["a", "b"])), s(&["a"]));
87 }
88}