#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Profile {
#[default]
Minimal,
Compatibility,
Agent,
}
#[derive(Debug, Clone, Default)]
pub struct BackendPolicy {
pub profile: Profile,
pub preferred: Option<String>,
pub chain: Vec<String>,
pub allow_fallback: bool,
pub timeout_secs: f64,
pub enable_ocr: bool,
pub enable_layout: bool,
pub ocr_dpi: u32,
pub ocr_drop_score: f32,
pub max_pages: usize,
}
impl BackendPolicy {
pub fn ordered_chain(&self, default_chain: &[String]) -> Vec<String> {
let mut base: Vec<String> = if !self.chain.is_empty() {
self.chain.clone()
} else {
let mut d = default_chain.to_vec();
if let Some(pref) = &self.preferred {
if let Some(pos) = d.iter().position(|x| x == pref) {
d.remove(pos);
d.insert(0, pref.clone());
} else {
d.insert(0, pref.clone());
}
}
d
};
if !self.allow_fallback && base.len() > 1 {
base.truncate(1);
}
base
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(v: &[&str]) -> Vec<String> {
v.iter().map(|x| x.to_string()).collect()
}
#[test]
fn preferred_promotes() {
let p = BackendPolicy {
preferred: Some("b".into()),
allow_fallback: true,
..Default::default()
};
assert_eq!(p.ordered_chain(&s(&["a", "b", "c"])), s(&["b", "a", "c"]));
}
#[test]
fn explicit_chain_wins() {
let p = BackendPolicy {
chain: s(&["c", "a"]),
allow_fallback: true,
..Default::default()
};
assert_eq!(p.ordered_chain(&s(&["a", "b", "c"])), s(&["c", "a"]));
}
#[test]
fn no_fallback_truncates() {
let p = BackendPolicy {
allow_fallback: false,
..Default::default()
};
assert_eq!(p.ordered_chain(&s(&["a", "b"])), s(&["a"]));
}
}