use serde::{Deserialize, Serialize};
use crate::error::{HonchoError, Result};
const MAX_DIALECTIC_QUERY_CHARS: usize = 10_000;
const QUERY_TOO_LONG_MSG: &str = "query must be at most 10000 characters";
const _: () = assert!(MAX_DIALECTIC_QUERY_CHARS == 10_000);
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ReasoningLevel {
Minimal,
#[default]
Low,
Medium,
High,
Max,
#[serde(other)]
Unknown,
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_default_reasoning_level(level: &ReasoningLevel) -> bool {
*level == ReasoningLevel::default()
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_default_bool(val: &bool) -> bool {
!val
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, bon::Builder)]
#[non_exhaustive]
#[builder(derive(Debug), on(String, into))]
#[builder(finish_fn = build)]
pub struct DialecticOptions {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
#[serde(default, skip_serializing_if = "is_default_bool")]
#[builder(default = false)]
pub stream: bool,
#[serde(default, skip_serializing_if = "is_default_reasoning_level")]
#[builder(default)]
pub reasoning_level: ReasoningLevel,
}
pub fn validate_dialectic_query(query: &str) -> Result<()> {
if query.chars().nth(MAX_DIALECTIC_QUERY_CHARS).is_some() {
return Err(HonchoError::Validation(QUERY_TOO_LONG_MSG.to_owned()));
}
if query.trim().is_empty() {
return Err(HonchoError::Validation(
"query must not be empty".to_owned(),
));
}
Ok(())
}
impl DialecticOptions {
pub fn validate(&self) -> Result<()> {
validate_dialectic_query(&self.query)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RepresentationResponse {
pub representation: String,
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic, missing_docs)]
use super::*;
use serde_json::json;
#[test]
fn reasoning_level_unknown_string_deserializes_to_unknown() {
let parsed: ReasoningLevel = serde_json::from_value(json!("some_future_variant")).unwrap();
assert_eq!(parsed, ReasoningLevel::Unknown);
}
#[test]
fn known_reasoning_levels_still_deserialize() {
let low: ReasoningLevel = serde_json::from_value(json!("low")).unwrap();
let max: ReasoningLevel = serde_json::from_value(json!("max")).unwrap();
assert_eq!(low, ReasoningLevel::Low);
assert_eq!(max, ReasoningLevel::Max);
}
#[test]
fn dialectic_payload_with_unknown_reasoning_level_deserializes() {
let parsed: DialecticOptions = serde_json::from_value(json!({
"query": "hello",
"reasoning_level": "ultra_max_2099"
}))
.unwrap();
assert_eq!(parsed.query, "hello");
assert_eq!(parsed.reasoning_level, ReasoningLevel::Unknown);
}
#[test]
fn whitespace_only_query_is_rejected() {
assert!(validate_dialectic_query(" \t\n ").is_err());
assert!(validate_dialectic_query("").is_err());
assert!(validate_dialectic_query("ok").is_ok());
}
}