api_gemini 0.5.0

Gemini's API for accessing large language models (LLMs).
Documentation
//! Request/response compression support for bandwidth optimization.
//!
//! This module provides HTTP compression using gzip, deflate, or brotli algorithms.
//! Compression can significantly reduce bandwidth usage for large payloads like
//! multimodal content, batch embeddings, and long conversation histories.

use std::io::{ Read, Write };
use flate2::{ Compression as FlateLevel, read::{ GzDecoder, DeflateDecoder }, write::{ GzEncoder, DeflateEncoder } };
use crate::error::Error;

/// Compression algorithm to use for requests/responses
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub enum CompressionAlgorithm
{
  /// GZIP compression (RFC 1952)
  Gzip,
  /// DEFLATE compression (RFC 1951)
  Deflate,
  /// Brotli compression (RFC 7932)
  Brotli,
  /// No compression
  None,
}

impl CompressionAlgorithm
{
  /// Get the Content-Encoding header value for this algorithm
  #[ must_use ]
  pub fn content_encoding( &self ) -> Option< &'static str >
  {
    match self
    {
      Self::Gzip => Some( "gzip" ),
      Self::Deflate => Some( "deflate" ),
      Self::Brotli => Some( "br" ),
      Self::None => None,
    }
  }
}

impl Default for CompressionAlgorithm
{
  #[ inline ]
  fn default() -> Self
  {
    Self::Gzip
  }
}

/// Configuration for compression behavior
#[ derive( Debug, Clone ) ]
pub struct CompressionConfig
{
  /// Compression algorithm to use
  pub algorithm : CompressionAlgorithm,
  /// Compression level (0-9 for gzip/deflate, 0-11 for brotli)
  pub level : u32,
  /// Minimum payload size to compress (bytes)
  /// Payloads smaller than this won't be compressed
  pub min_size : usize,
}

impl CompressionConfig
{
  /// Create default compression configuration
  ///
  /// Uses gzip compression at level 6 with 1KB minimum size
  #[ must_use ]
  pub fn new() -> Self
  {
    Self {
      algorithm : CompressionAlgorithm::Gzip,
      level : 6,
      min_size : 1024,
    }
  }

  /// Set compression algorithm
  #[ must_use ]
  pub fn algorithm( mut self, algorithm : CompressionAlgorithm ) -> Self
  {
    self.algorithm = algorithm;
    self
  }

  /// Set compression level
  ///
  /// - For gzip/deflate : 0-9 (0=none, 1=fastest, 9=best compression)
  /// - For brotli : 0-11 (0=none, 1=fastest, 11=best compression)
  #[ must_use ]
  pub fn level( mut self, level : u32 ) -> Self
  {
    self.level = level;
    self
  }

  /// Set minimum payload size to compress
  #[ must_use ]
  pub fn min_size( mut self, min_size : usize ) -> Self
  {
    self.min_size = min_size;
    self
  }
}

impl Default for CompressionConfig
{
  #[ inline ]
  fn default() -> Self
  {
    Self::new()
  }
}

/// Compress data using the specified configuration
///
/// # Errors
///
/// Returns error if compression fails
pub fn compress( data : &[ u8 ], config : &CompressionConfig ) -> Result< Vec< u8 >, Error >
{
  // Skip compression for small payloads
  if data.len() < config.min_size
  {
    return Ok( data.to_vec() );
  }

  match config.algorithm
  {
    CompressionAlgorithm::Gzip => {
      let mut encoder = GzEncoder::new( Vec::new(), FlateLevel::new( config.level ) );
      encoder.write_all( data )
        .map_err( | e | Error::ConfigurationError( format!( "Gzip compression failed : {}", e ) ) )?;
      encoder.finish()
        .map_err( | e | Error::ConfigurationError( format!( "Gzip compression finish failed : {}", e ) ) )
    },
    CompressionAlgorithm::Deflate => {
      let mut encoder = DeflateEncoder::new( Vec::new(), FlateLevel::new( config.level ) );
      encoder.write_all( data )
        .map_err( | e | Error::ConfigurationError( format!( "Deflate compression failed : {}", e ) ) )?;
      encoder.finish()
        .map_err( | e | Error::ConfigurationError( format!( "Deflate compression finish failed : {}", e ) ) )
    },
    CompressionAlgorithm::Brotli => {
      use brotli::enc::BrotliEncoderParams;
      let params = BrotliEncoderParams {
        quality : config.level as i32,
        ..Default::default()
      };
      let mut output = Vec::new();
      {
        let mut compressor = brotli::CompressorWriter::with_params(
          &mut output,
          4096,
          &params
        );
        compressor.write_all( data )
          .map_err( | e | Error::ConfigurationError( format!( "Brotli compression failed : {}", e ) ) )?;
      }
      Ok( output )
    },
    CompressionAlgorithm::None => Ok( data.to_vec() ),
  }
}

/// Decompress data using the specified algorithm
///
/// # Errors
///
/// Returns error if decompression fails
pub fn decompress( data : &[ u8 ], algorithm : CompressionAlgorithm ) -> Result< Vec< u8 >, Error >
{
  match algorithm
  {
    CompressionAlgorithm::Gzip => {
      let mut decoder = GzDecoder::new( data );
      let mut output = Vec::new();
      decoder.read_to_end( &mut output )
        .map_err( | e | Error::ConfigurationError( format!( "Gzip decompression failed : {}", e ) ) )?;
      Ok( output )
    },
    CompressionAlgorithm::Deflate => {
      let mut decoder = DeflateDecoder::new( data );
      let mut output = Vec::new();
      decoder.read_to_end( &mut output )
        .map_err( | e | Error::ConfigurationError( format!( "Deflate decompression failed : {}", e ) ) )?;
      Ok( output )
    },
    CompressionAlgorithm::Brotli => {
      let mut output = Vec::new();
      {
        let mut decompressor = brotli::Decompressor::new( data, 4096 );
        decompressor.read_to_end( &mut output )
          .map_err( | e | Error::ConfigurationError( format!( "Brotli decompression failed : {}", e ) ) )?;
      }
      Ok( output )
    },
    CompressionAlgorithm::None => Ok( data.to_vec() ),
  }
}

#[ cfg( test ) ]
mod tests
{
  use super::*;

  #[ test ]
  fn test_compression_config_defaults()
  {
    let config = CompressionConfig::new();
    assert_eq!( config.algorithm, CompressionAlgorithm::Gzip );
    assert_eq!( config.level, 6 );
    assert_eq!( config.min_size, 1024 );
  }

  #[ test ]
  fn test_compression_config_builder()
  {
    let config = CompressionConfig::new()
      .algorithm( CompressionAlgorithm::Brotli )
      .level( 9 )
      .min_size( 2048 );

    assert_eq!( config.algorithm, CompressionAlgorithm::Brotli );
    assert_eq!( config.level, 9 );
    assert_eq!( config.min_size, 2048 );
  }

  #[ test ]
  fn test_gzip_compression_roundtrip()
  {
    // Use repetitive data that compresses well
    let data = "Hello, World! This is a test of gzip compression. ".repeat( 50 );
    let data_bytes = data.as_bytes();
    let config = CompressionConfig::new()
      .algorithm( CompressionAlgorithm::Gzip )
      .min_size( 0 );

    let compressed = compress( data_bytes, &config ).unwrap();
    assert!( compressed.len() < data_bytes.len(), "Compressed data should be smaller" );

    let decompressed = decompress( &compressed, CompressionAlgorithm::Gzip ).unwrap();
    assert_eq!( &decompressed[ .. ], data_bytes );
  }

  #[ test ]
  fn test_deflate_compression_roundtrip()
  {
    // Use repetitive data that compresses well
    let data = "Hello, World! This is a test of deflate compression. ".repeat( 50 );
    let data_bytes = data.as_bytes();
    let config = CompressionConfig::new()
      .algorithm( CompressionAlgorithm::Deflate )
      .min_size( 0 );

    let compressed = compress( data_bytes, &config ).unwrap();
    assert!( compressed.len() < data_bytes.len(), "Compressed data should be smaller" );

    let decompressed = decompress( &compressed, CompressionAlgorithm::Deflate ).unwrap();
    assert_eq!( &decompressed[ .. ], data_bytes );
  }

  #[ test ]
  fn test_brotli_compression_roundtrip()
  {
    // Use repetitive data that compresses well
    let data = "Hello, World! This is a test of brotli compression. ".repeat( 50 );
    let data_bytes = data.as_bytes();
    let config = CompressionConfig::new()
      .algorithm( CompressionAlgorithm::Brotli )
      .level( 6 )
      .min_size( 0 );

    let compressed = compress( data_bytes, &config ).unwrap();
    assert!( compressed.len() < data_bytes.len(), "Compressed data should be smaller" );

    let decompressed = decompress( &compressed, CompressionAlgorithm::Brotli ).unwrap();
    assert_eq!( &decompressed[ .. ], data_bytes );
  }

  #[ test ]
  fn test_min_size_threshold()
  {
    let small_data = b"Hi";
    let config = CompressionConfig::new().min_size( 1024 );

    let result = compress( small_data, &config ).unwrap();
    assert_eq!( &result[ .. ], small_data, "Small data should not be compressed" );
  }

  #[ test ]
  fn test_content_encoding_header()
  {
    assert_eq!( CompressionAlgorithm::Gzip.content_encoding(), Some( "gzip" ) );
    assert_eq!( CompressionAlgorithm::Deflate.content_encoding(), Some( "deflate" ) );
    assert_eq!( CompressionAlgorithm::Brotli.content_encoding(), Some( "br" ) );
    assert_eq!( CompressionAlgorithm::None.content_encoding(), None );
  }
}