use crate::backend::model::ModelChoice;
use crate::protocol::{
FrontendSetting, FrontendSettingKind, FrontendSettingOption, FrontendSettingValue,
MiddlewareFeature,
};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy)]
pub struct MiddlewareManifest {
pub id: &'static str,
pub label: &'static str,
pub description: &'static str,
pub required: bool,
pub default_enabled: bool,
pub settings: &'static [MiddlewareSettingManifest],
}
impl MiddlewareManifest {
#[must_use]
pub fn feature(self, models: &[ModelChoice]) -> MiddlewareFeature {
MiddlewareFeature {
id: self.id.into(),
label: self.label.into(),
description: self.description.into(),
required: self.required,
settings: self
.settings
.iter()
.map(|setting| setting.schema(models))
.collect(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum MiddlewareSettingManifest {
Integer {
id: &'static str,
label: &'static str,
description: &'static str,
min: i64,
max: Option<i64>,
step: i64,
default: i64,
},
Select {
id: &'static str,
label: &'static str,
description: &'static str,
choices: MiddlewareSettingChoices,
unset_label: Option<&'static str>,
default: Option<&'static str>,
max_bytes: usize,
},
}
impl MiddlewareSettingManifest {
#[must_use]
pub const fn id(self) -> &'static str {
match self {
Self::Integer { id, .. } | Self::Select { id, .. } => id,
}
}
#[must_use]
pub const fn uses_model_routes(self) -> bool {
matches!(
self,
Self::Select {
choices: MiddlewareSettingChoices::ModelRoutes,
..
}
)
}
#[must_use]
pub fn default_value(self) -> Option<FrontendSettingValue> {
match self {
Self::Integer { default, .. } => Some(FrontendSettingValue::Integer(default)),
Self::Select { default, .. } => {
default.map(|value| FrontendSettingValue::String(value.into()))
}
}
}
#[must_use]
pub fn schema(self, models: &[ModelChoice]) -> FrontendSetting {
match self {
Self::Integer {
id,
label,
description,
min,
max,
step,
..
} => FrontendSetting {
id: id.into(),
label: label.into(),
description: description.into(),
kind: FrontendSettingKind::Integer { min, max, step },
},
Self::Select {
id,
label,
description,
choices,
unset_label,
..
} => FrontendSetting {
id: id.into(),
label: label.into(),
description: description.into(),
kind: FrontendSettingKind::Select {
options: choices.options(models),
unset_label: unset_label.map(str::to_string),
},
},
}
}
pub fn validate(self, middleware: &str, value: Option<&FrontendSettingValue>) -> Result<()> {
match (self, value) {
(Self::Integer { min, max, .. }, Some(FrontendSettingValue::Integer(value)))
if *value >= min && max.is_none_or(|max| *value <= max) =>
{
Ok(())
}
(Self::Integer { id, .. }, Some(FrontendSettingValue::Integer(_))) => {
Err(Error::Config(format!(
"middleware setting `{middleware}.{id}` is out of range"
)))
}
(Self::Integer { id, .. }, Some(FrontendSettingValue::String(_))) => {
Err(setting_type(middleware, id, "integer"))
}
(Self::Integer { id, .. }, None) => Err(Error::Config(format!(
"missing middleware setting `{middleware}.{id}`"
))),
(
Self::Select {
choices, max_bytes, ..
},
Some(FrontendSettingValue::String(value)),
) if !value.trim().is_empty()
&& value.len() <= max_bytes
&& match choices {
MiddlewareSettingChoices::Static(_) => choices.contains(&[], value),
MiddlewareSettingChoices::ModelRoutes => true,
} =>
{
Ok(())
}
(Self::Select { id, max_bytes, .. }, Some(FrontendSettingValue::String(_))) => {
Err(Error::Config(format!(
"middleware setting `{middleware}.{id}` must be an advertised choice of 1–{max_bytes} bytes"
)))
}
(Self::Select { id, .. }, Some(FrontendSettingValue::Integer(_))) => {
Err(setting_type(middleware, id, "string"))
}
(
Self::Select {
unset_label: Some(_),
..
},
None,
) => Ok(()),
(Self::Select { id, .. }, None) => Err(Error::Config(format!(
"missing middleware setting `{middleware}.{id}`"
))),
}
}
pub fn validate_choice(
self,
middleware: &str,
value: Option<&FrontendSettingValue>,
models: &[ModelChoice],
) -> Result<()> {
let Self::Select {
id,
choices: MiddlewareSettingChoices::ModelRoutes,
..
} = self
else {
return Ok(());
};
let Some(FrontendSettingValue::String(value)) = value else {
return Ok(());
};
if MiddlewareSettingChoices::ModelRoutes.contains(models, value) {
Ok(())
} else {
Err(Error::Config(format!(
"middleware setting `{middleware}.{id}` is not an advertised choice"
)))
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum MiddlewareSettingChoices {
Static(&'static [MiddlewareSettingChoice]),
ModelRoutes,
}
impl MiddlewareSettingChoices {
fn options(self, models: &[ModelChoice]) -> Vec<FrontendSettingOption> {
match self {
Self::Static(choices) => choices
.iter()
.map(|choice| FrontendSettingOption {
value: choice.value.into(),
label: choice.label.into(),
description: choice.description.into(),
})
.collect(),
Self::ModelRoutes => models
.iter()
.map(|choice| FrontendSettingOption {
value: choice.route.clone(),
label: choice.reasoning_effort.as_ref().map_or_else(
|| choice.group.clone(),
|effort| format!("{} · {effort}", choice.group),
),
description: format!("{} · {}", choice.model, choice.route),
})
.collect(),
}
}
fn contains(self, models: &[ModelChoice], value: &str) -> bool {
match self {
Self::Static(choices) => choices.iter().any(|choice| choice.value == value),
Self::ModelRoutes => models.iter().any(|choice| choice.route == value),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MiddlewareSettingChoice {
pub value: &'static str,
pub label: &'static str,
pub description: &'static str,
}
fn setting_type(middleware: &str, setting: &str, expected: &str) -> Error {
Error::Config(format!(
"middleware setting `{middleware}.{setting}` must be {expected}"
))
}
#[cfg(test)]
mod tests {
use super::*;
const CHOICES: &[MiddlewareSettingChoice] = &[MiddlewareSettingChoice {
value: "safe",
label: "Safe",
description: "Use the safe policy",
}];
const SETTING: MiddlewareSettingManifest = MiddlewareSettingManifest::Select {
id: "policy",
label: "Policy",
description: "Selection",
choices: MiddlewareSettingChoices::Static(CHOICES),
unset_label: None,
default: Some("safe"),
max_bytes: 16,
};
#[test]
fn select_rejects_values_not_declared_by_the_module() {
let error = SETTING
.validate(
"example",
Some(&FrontendSettingValue::String("unknown".into())),
)
.expect_err("unknown choice must fail");
assert!(error.to_string().contains("advertised choice"));
}
}