mod private
{
use std::collections::HashMap;
use serde::Serialize;
use crate::error::{ OpenAIError, Result };
#[ derive( Debug, Clone ) ]
pub struct CurlGenerator
{
supported_methods : Vec< String >,
}
impl CurlGenerator
{
#[ inline ]
#[ must_use ]
pub fn new() -> Self
{
Self
{
supported_methods : vec![
"GET".to_string(),
"POST".to_string(),
"PUT".to_string(),
"DELETE".to_string(),
],
}
}
#[ inline ]
#[ must_use ]
pub fn can_generate_curl( &self ) -> bool
{
true
}
#[ inline ]
#[ must_use ]
pub fn get_supported_methods( &self ) -> &Vec< String >
{
&self.supported_methods
}
}
#[ derive( Debug, Clone ) ]
pub struct CurlRequestBuilder
{
method : String,
url : String,
headers : Vec< (String, String) >,
body : Option< String >,
}
impl CurlRequestBuilder
{
#[ inline ]
#[ must_use ]
pub fn new() -> Self
{
Self
{
method : "GET".to_string(),
url : String::new(),
headers : Vec::new(),
body : None,
}
}
#[ inline ]
#[ must_use ]
pub fn method( mut self, method : &str ) -> Self
{
self.method = method.to_string();
self
}
#[ inline ]
#[ must_use ]
pub fn url( mut self, url : &str ) -> Self
{
self.url = url.to_string();
self
}
#[ inline ]
#[ must_use ]
pub fn header( mut self, key : &str, value : &str ) -> Self
{
self.headers.push( (key.to_string(), value.to_string()) );
self
}
#[ inline ]
#[ must_use ]
pub fn body( mut self, body : &str ) -> Self
{
self.body = Some( body.to_string() );
self
}
#[ inline ]
#[ must_use ]
pub fn build( self ) -> CurlRequest
{
CurlRequest
{
method : self.method,
url : self.url,
headers : self.headers,
body : self.body,
}
}
}
#[ derive( Debug, Clone ) ]
pub struct CurlRequest
{
pub method : String,
pub url : String,
pub headers : Vec< (String, String) >,
pub body : Option< String >,
}
impl CurlRequest
{
#[ inline ]
#[ must_use ]
pub fn to_curl_command( &self ) -> String
{
let mut command = format!( "curl -X {} '{}'", self.method, self.url );
for (key, value) in &self.headers
{
command.push_str( " -H '" );
command.push_str( key );
command.push_str( ": " );
command.push_str( value );
command.push( '\'' );
}
if let Some( body ) = &self.body
{
let escaped_body = body.replace( '\"', "\\\"" );
command.push_str( " -d '" );
command.push_str( &escaped_body );
command.push( '\'' );
}
command
}
#[ inline ]
#[ must_use ]
pub fn to_curl_command_with_options( &self, options : &CurlFormatOptions ) -> String
{
let mut command = self.to_curl_command();
if options.formatting.include_verbose
{
command.push_str( " --verbose" );
}
if options.formatting.include_silent
{
command.push_str( " --silent" );
}
if options.connection.include_insecure
{
command.push_str( " --insecure" );
}
if let Some( timeout ) = options.timeout
{
command.push_str( " --max-time " );
command.push_str( &timeout.to_string() );
}
if options.formatting.pretty_print
{
command = command.replace( " -H", " \\\n -H" );
command = command.replace( " -d", " \\\n -d" );
}
command
}
#[ inline ]
#[ must_use ]
pub fn to_curl_command_safe( &self ) -> String
{
let mut safe_request = self.clone();
for (key, value) in &mut safe_request.headers
{
if key.to_lowercase().contains( "authorization" ) ||
key.to_lowercase().contains( "api-key" ) ||
key.to_lowercase().contains( "token" )
{
*value = "[REDACTED]".to_string();
}
}
safe_request.to_curl_command()
}
}
#[ derive( Debug, Clone ) ]
pub struct CurlFormatOptions
{
pub formatting : CurlFormattingOptions,
pub connection : CurlConnectionOptions,
pub timeout : Option< u32 >,
}
#[ derive( Debug, Clone ) ]
pub struct CurlFormattingOptions
{
pub pretty_print : bool,
pub include_verbose : bool,
pub include_silent : bool,
}
#[ derive( Debug, Clone ) ]
pub struct CurlConnectionOptions
{
pub include_insecure : bool,
}
impl Default for CurlFormatOptions
{
#[ inline ]
fn default() -> Self
{
Self
{
formatting : CurlFormattingOptions::default(),
connection : CurlConnectionOptions::default(),
timeout : None,
}
}
}
impl Default for CurlFormattingOptions
{
#[ inline ]
fn default() -> Self
{
Self
{
pretty_print : false,
include_verbose : false,
include_silent : false,
}
}
}
impl Default for CurlConnectionOptions
{
#[ inline ]
fn default() -> Self
{
Self
{
include_insecure : false,
}
}
}
pub trait CurlGeneration
{
type Request : Serialize;
type Error;
fn to_curl( &self, request : &Self::Request ) -> core::result::Result< String, Self::Error >;
#[ inline ]
fn to_curl_safe( &self, request : &Self::Request ) -> core::result::Result< String, Self::Error >
{
self.to_curl( request )
.map( |cmd| redact_sensitive_info( &cmd ) )
}
fn to_curl_with_headers( &self, request : &Self::Request, headers : &HashMap< String, String > ) -> core::result::Result< String, Self::Error >;
}
fn redact_sensitive_info( curl_command : &str ) -> String
{
let mut redacted = curl_command.to_string();
if let Some( start ) = redacted.find( "Bearer " )
{
if let Some( end ) = redacted[start + 7..].find( '\'' )
{
let token_end = start + 7 + end;
redacted.replace_range( start + 7..token_end, "[REDACTED]" );
}
}
let api_key_patterns = [ "sk-", "xoxb-", "xoxp-" ];
for pattern in &api_key_patterns
{
if let Some( start ) = redacted.find( pattern )
{
if let Some( end ) = redacted[start..].find( '\'' )
{
redacted.replace_range( start..start + end, "[REDACTED]" );
}
}
}
redacted
}
#[ inline ]
#[ must_use ]
pub fn build_curl_request(
method : &str,
url : &str,
headers : &[(String, String)],
body : Option< &str >
) -> CurlRequest
{
CurlRequest
{
method : method.to_string(),
url : url.to_string(),
headers : headers.to_vec(),
body : body.map( str::to_string ),
}
}
#[ inline ]
pub fn serialize_request_to_json< T : Serialize >( request : &T ) -> Result< String >
{
serde_json ::to_string( request )
.map_err( |e| error_tools::Error::from( OpenAIError::Internal( format!( "Failed to serialize request : {e}" ) ) ) )
}
impl Default for CurlGenerator
{
#[ inline ]
fn default() -> Self
{
Self::new()
}
}
impl Default for CurlRequestBuilder
{
#[ inline ]
fn default() -> Self
{
Self::new()
}
}
}
crate ::mod_interface!
{
exposed use
{
CurlGenerator,
CurlRequestBuilder,
CurlRequest,
CurlFormatOptions,
CurlFormattingOptions,
CurlConnectionOptions,
CurlGeneration,
build_curl_request,
serialize_request_to_json,
};
}