use rmcp::ErrorData as McpError;
use schemars::JsonSchema;
use serde::Deserialize;
use smix_sdk::Selector;
use smix_selector::{Modifiers, ROLE_NAMES, role_from_name};
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SelectorParams {
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub text: Option<String>,
#[serde(default)]
pub label: Option<String>,
#[serde(default)]
pub role: Option<String>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub ocr_text: Option<String>,
}
pub fn ocr_text_of(selector: &Selector) -> Option<&str> {
match selector {
Selector::OcrText { ocr_text, .. } => Some(ocr_text),
_ => None,
}
}
impl SelectorParams {
pub fn to_selector(&self) -> Result<Selector, McpError> {
let given: Vec<&str> = [
self.id.as_ref().map(|_| "id"),
self.text.as_ref().map(|_| "text"),
self.label.as_ref().map(|_| "label"),
self.role.as_ref().map(|_| "role"),
self.ocr_text.as_ref().map(|_| "ocrText"),
]
.into_iter()
.flatten()
.collect();
match given.len() {
0 => {
return Err(McpError::invalid_params(
"name the element with exactly one of: id, text, label, role, ocrText \
(prefer id)",
None,
));
}
1 => {}
_ => {
return Err(McpError::invalid_params(
format!(
"name the element with exactly one of id, text, label, role, ocrText — got {}",
given.join(" and ")
),
None,
));
}
}
if self.name.is_some() && self.role.is_none() {
return Err(McpError::invalid_params(
"`name` narrows `role`, so it needs one — give role, or use `text` to match \
visible text on its own",
None,
));
}
if let Some(id) = &self.id {
return Ok(smix_sdk::id(id.clone()));
}
if let Some(text) = &self.text {
return Ok(smix_sdk::text(text.clone()));
}
if let Some(label) = &self.label {
return Ok(smix_sdk::label(label.clone()));
}
if let Some(role) = &self.role {
let r = role_from_name(role).ok_or_else(|| {
McpError::invalid_params(
format!("unknown role `{role}`; accepted: {ROLE_NAMES}"),
None,
)
})?;
return Ok(match &self.name {
Some(n) => smix_sdk::role_named(r, n.clone()),
None => smix_sdk::role(r),
});
}
if let Some(ocr) = &self.ocr_text {
return Ok(Selector::OcrText {
ocr_text: ocr.clone(),
locales: Vec::new(),
modifiers: Modifiers::default(),
});
}
unreachable!("exactly one field was present")
}
}