use axum::http::StatusCode;
use axum::response::Response;
use crate::ir::ChatRequest;
use super::compact::{openai_error_response, openai_error_response_with_param};
const MAX_MESSAGES_PER_CHAT_REQUEST: usize = 32_768;
#[allow(clippy::result_large_err)]
pub(super) fn validate_input(req: &ChatRequest) -> Result<(), Response> {
if req.messages.is_empty() {
return Err(openai_error_response_with_param(
StatusCode::BAD_REQUEST,
"messages must contain at least one message".into(),
Some("messages"),
None,
));
}
if req.messages.len() > MAX_MESSAGES_PER_CHAT_REQUEST {
return Err(openai_error_response_with_param(
StatusCode::BAD_REQUEST,
format!("messages array exceeds maximum length ({MAX_MESSAGES_PER_CHAT_REQUEST})"),
Some("messages"),
None,
));
}
if let Some(t) = req.sampling.temperature
&& (!(0.0..=2.0).contains(&t))
{
return Err(openai_error_response_with_param(
StatusCode::BAD_REQUEST,
"temperature must be between 0 and 2".into(),
Some("temperature"),
None,
));
}
if let Some(p) = req.sampling.top_p
&& (p <= 0.0 || p > 1.0)
{
return Err(openai_error_response_with_param(
StatusCode::BAD_REQUEST,
"top_p must be between 0 (exclusive) and 1".into(),
Some("top_p"),
None,
));
}
if req.max_tokens == 0 {
return Err(openai_error_response_with_param(
StatusCode::BAD_REQUEST,
"max_tokens must be at least 1".into(),
Some("max_tokens"),
None,
));
}
if req.n == 0 || req.n > 128 {
return Err(openai_error_response_with_param(
StatusCode::BAD_REQUEST,
format!("n must be between 1 and 128, got {}", req.n),
Some("n"),
None,
));
}
if let Some(crate::tool_parser::ToolChoice::Mode(ref s)) = req.tool_choice {
if !["auto", "none", "required"].contains(&s.as_str()) {
return Err(openai_error_response(
StatusCode::BAD_REQUEST,
format!(
"Invalid tool_choice value: '{s}'. Must be 'auto', 'none', 'required', or a function object."
),
));
}
if s == "required" && req.tools.is_empty() {
return Err(openai_error_response(
StatusCode::BAD_REQUEST,
"tool_choice is 'required' but no tools were provided".into(),
));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn request_with_message_count(message_count: usize) -> ChatRequest {
ChatRequest {
model: "test".into(),
messages: vec![crate::ir::Message::synthetic_system(String::new()); message_count],
tools: Vec::new(),
tool_choice: None,
sampling: crate::ir::SamplingParams::default(),
max_tokens: 1,
min_tokens: 0,
stop: Vec::new(),
stream: false,
n: 1,
response_format: None,
thinking: crate::ir::ThinkingDirective::Unspecified,
reasoning_effort: None,
preserve_thinking: None,
template_thinking: None,
repetition_detection: None,
adapter: None,
src_lang: None,
tgt_lang: None,
num_beams: None,
length_penalty: None,
early_stopping: None,
logit_bias: Vec::new(),
top_logprobs: None,
seed: None,
timeout_secs: None,
return_token_ids: false,
lookup_tenant: None,
}
}
#[test]
fn accepts_the_32768_message_limit() {
assert!(validate_input(&request_with_message_count(MAX_MESSAGES_PER_CHAT_REQUEST)).is_ok());
}
#[test]
fn rejects_more_than_32768_messages() {
let response = validate_input(&request_with_message_count(
MAX_MESSAGES_PER_CHAT_REQUEST + 1,
))
.unwrap_err();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
}