use crate::error::SynwireError;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum OutputMode {
Native,
Tool,
Prompt,
Custom(String),
}
impl OutputMode {
pub fn fallback_chain() -> Vec<Self> {
vec![Self::Native, Self::Tool, Self::Prompt]
}
pub fn validate_compatibility(
&self,
supports_native: bool,
supports_tools: bool,
) -> Result<(), SynwireError> {
match self {
Self::Native if !supports_native => Err(SynwireError::Prompt {
message: "Provider does not support native structured output".into(),
}),
Self::Tool if !supports_tools => Err(SynwireError::Prompt {
message: "Provider does not support tool calling".into(),
}),
_ => Ok(()),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_fallback_chain_order() {
let chain = OutputMode::fallback_chain();
assert_eq!(
chain,
vec![OutputMode::Native, OutputMode::Tool, OutputMode::Prompt]
);
}
#[test]
fn test_native_rejects_unsupported() {
let result = OutputMode::Native.validate_compatibility(false, true);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("native structured output"));
}
#[test]
fn test_tool_rejects_unsupported() {
let result = OutputMode::Tool.validate_compatibility(true, false);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("tool calling"));
}
#[test]
fn test_prompt_always_compatible() {
let result = OutputMode::Prompt.validate_compatibility(false, false);
assert!(result.is_ok());
}
#[test]
fn test_native_accepts_supported() {
let result = OutputMode::Native.validate_compatibility(true, false);
assert!(result.is_ok());
}
#[test]
fn test_tool_accepts_supported() {
let result = OutputMode::Tool.validate_compatibility(false, true);
assert!(result.is_ok());
}
#[test]
fn test_custom_always_compatible() {
let result = OutputMode::Custom("my_strategy".into()).validate_compatibility(false, false);
assert!(result.is_ok());
}
}