mod private
{
use std::fmt;
#[ derive( Debug, Clone, PartialEq ) ]
pub struct ValidationError
{
pub field : String,
pub message : String,
pub value : Option< String >,
pub constraint : Option< String >,
}
impl fmt::Display for ValidationError
{
fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result
{
write!( f, "Validation error for field '{}' : {}", self.field, self.message )?;
if let Some( ref value ) = self.value
{
write!( f, " (value : {value})" )?;
}
if let Some( ref constraint ) = self.constraint
{
write!( f, " (constraint : {constraint})" )?;
}
Ok( () )
}
}
impl std::error::Error for ValidationError {}
impl ValidationError
{
pub fn new( field : impl Into< String >, message : impl Into< String > ) -> Self
{
Self
{
field : field.into(),
message : message.into(),
value : None,
constraint : None,
}
}
#[ must_use ]
pub fn with_value( mut self, value : impl Into< String > ) -> Self
{
self.value = Some( value.into() );
self
}
#[ must_use ]
pub fn with_constraint( mut self, constraint : impl Into< String > ) -> Self
{
self.constraint = Some( constraint.into() );
self
}
}
pub trait Validate
{
fn validate( &self ) -> Result< (), Vec< ValidationError > >;
}
pub mod validators
{
use super::ValidationError;
pub fn validate_model( model : &str ) -> Result< (), ValidationError >
{
if model.is_empty()
{
return Err(
ValidationError::new( "model", "Model name cannot be empty" )
.with_constraint( "non-empty string" )
);
}
if model.len() > 256
{
return Err(
ValidationError::new( "model", "Model name exceeds maximum length" )
.with_value( format!( "{} chars", model.len() ) )
.with_constraint( "max 256 characters" )
);
}
Ok( () )
}
pub fn validate_max_tokens( max_tokens : u32 ) -> Result< (), ValidationError >
{
if max_tokens == 0
{
return Err(
ValidationError::new( "max_tokens", "max_tokens must be positive" )
.with_value( max_tokens.to_string() )
.with_constraint( "max_tokens > 0" )
);
}
if max_tokens > 8192
{
return Err(
ValidationError::new( "max_tokens", "max_tokens exceeds reasonable limit" )
.with_value( max_tokens.to_string() )
.with_constraint( "max_tokens <= 8192" )
);
}
Ok( () )
}
pub fn validate_temperature( temperature : f32 ) -> Result< (), ValidationError >
{
if !( 0.0..=1.0 ).contains( &temperature )
{
return Err(
ValidationError::new( "temperature", "Temperature must be between 0.0 and 1.0" )
.with_value( temperature.to_string() )
.with_constraint( "0.0 <= temperature <= 1.0" )
);
}
Ok( () )
}
pub fn validate_top_p( top_p : f32 ) -> Result< (), ValidationError >
{
if !( 0.0..=1.0 ).contains( &top_p )
{
return Err(
ValidationError::new( "top_p", "top_p must be between 0.0 and 1.0" )
.with_value( top_p.to_string() )
.with_constraint( "0.0 <= top_p <= 1.0" )
);
}
Ok( () )
}
pub fn validate_top_k( top_k : u32 ) -> Result< (), ValidationError >
{
if top_k == 0
{
return Err(
ValidationError::new( "top_k", "top_k must be positive" )
.with_value( top_k.to_string() )
.with_constraint( "top_k > 0" )
);
}
if top_k > 500
{
return Err(
ValidationError::new( "top_k", "top_k exceeds reasonable limit" )
.with_value( top_k.to_string() )
.with_constraint( "top_k <= 500" )
);
}
Ok( () )
}
pub fn validate_messages_not_empty< T >( messages : &[ T ] ) -> Result< (), ValidationError >
{
if messages.is_empty()
{
return Err(
ValidationError::new( "messages", "Messages array cannot be empty" )
.with_constraint( "at least one message required" )
);
}
Ok( () )
}
pub fn validate_system_prompt( system : &str ) -> Result< (), ValidationError >
{
if system.len() > 100_000
{
return Err(
ValidationError::new( "system", "System prompt exceeds maximum length" )
.with_value( format!( "{} chars", system.len() ) )
.with_constraint( "max 100000 characters" )
);
}
Ok( () )
}
}
#[ cfg( test ) ]
mod tests
{
use super::*;
use super::validators::*;
#[ test ]
fn test_validate_model_valid()
{
assert!( validate_model( "claude-3-opus-20240229" ).is_ok() );
}
#[ test ]
fn test_validate_model_empty()
{
let result = validate_model( "" );
assert!( result.is_err() );
let err = result.unwrap_err();
assert_eq!( err.field, "model" );
}
#[ test ]
fn test_validate_model_too_long()
{
let long_model = "a".repeat( 300 );
let result = validate_model( &long_model );
assert!( result.is_err() );
}
#[ test ]
fn test_validate_max_tokens_valid()
{
assert!( validate_max_tokens( 1024 ).is_ok() );
assert!( validate_max_tokens( 4096 ).is_ok() );
}
#[ test ]
fn test_validate_max_tokens_zero()
{
assert!( validate_max_tokens( 0 ).is_err() );
}
#[ test ]
fn test_validate_max_tokens_too_large()
{
assert!( validate_max_tokens( 10_000 ).is_err() );
}
#[ test ]
fn test_validate_temperature_valid()
{
assert!( validate_temperature( 0.0 ).is_ok() );
assert!( validate_temperature( 0.5 ).is_ok() );
assert!( validate_temperature( 1.0 ).is_ok() );
}
#[ test ]
fn test_validate_temperature_invalid()
{
assert!( validate_temperature( -0.1 ).is_err() );
assert!( validate_temperature( 1.1 ).is_err() );
}
#[ test ]
fn test_validate_top_p_valid()
{
assert!( validate_top_p( 0.0 ).is_ok() );
assert!( validate_top_p( 0.9 ).is_ok() );
assert!( validate_top_p( 1.0 ).is_ok() );
}
#[ test ]
fn test_validate_top_p_invalid()
{
assert!( validate_top_p( -0.1 ).is_err() );
assert!( validate_top_p( 1.1 ).is_err() );
}
#[ test ]
fn test_validate_top_k_valid()
{
assert!( validate_top_k( 1 ).is_ok() );
assert!( validate_top_k( 40 ).is_ok() );
assert!( validate_top_k( 500 ).is_ok() );
}
#[ test ]
fn test_validate_top_k_invalid()
{
assert!( validate_top_k( 0 ).is_err() );
assert!( validate_top_k( 1000 ).is_err() );
}
#[ test ]
fn test_validate_messages_not_empty_valid()
{
let messages = vec![ "message1", "message2" ];
assert!( validate_messages_not_empty( &messages ).is_ok() );
}
#[ test ]
fn test_validate_messages_not_empty_invalid()
{
let messages : Vec< String > = vec![];
assert!( validate_messages_not_empty( &messages ).is_err() );
}
#[ test ]
fn test_validation_error_display()
{
let err = ValidationError::new( "temperature", "Invalid value" )
.with_value( "2.5" )
.with_constraint( "0.0 <= temperature <= 1.0" );
let display = format!( "{err}" );
assert!( display.contains( "temperature" ) );
assert!( display.contains( "Invalid value" ) );
assert!( display.contains( "2.5" ) );
}
}
}
crate::mod_interface!
{
exposed use
{
ValidationError,
Validate,
validators,
};
}