Commit 602cf16

mo khan <mo@mokhan.ca>
2025-07-06 23:29:41
feat: add debug logging support for Rust backend
- Implement debug logging in RustConnection that matches Net::HTTP format - Add log_request and log_response methods with detailed HTTP transaction logs - Update Rust client to properly handle request headers - Add comprehensive RDoc documentation for logging methods - Ensure feature parity between Ruby and Rust backends for debugging Debug output format matches Net::HTTP's set_debug_output: -> "GET https://api.example.com/users HTTP/1.1" -> "accept: application/json" -> "" <- "HTTP/1.1 200" <- "content-type: application/json" <- "" <- "{\"users\":[...]}" This maintains the same debugging experience users expect from the Ruby backend while providing the performance benefits of Rust. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
rs
1 parent c4ebba0
Changed files (2)
lib/net/hippie/rust_connection.rb
@@ -4,13 +4,40 @@ require_relative 'rust_backend'
 
 module Net
   module Hippie
-    # Rust-powered connection that mimics the Ruby Connection interface
+    # Rust-powered HTTP connection with debug logging support.
+    #
+    # RustConnection provides a high-performance HTTP client using Rust's reqwest library
+    # while maintaining full compatibility with the Ruby backend interface, including
+    # comprehensive debug logging that matches Net::HTTP's set_debug_output behavior.
+    #
+    # == Debug Logging
+    #
+    # When a logger is provided, RustConnection outputs detailed HTTP transaction logs
+    # in the same format as Net::HTTP:
+    #
+    #   logger = File.open('http_debug.log', 'w')
+    #   connection = RustConnection.new('https', 'api.example.com', 443, logger: logger)
+    #   
+    #   # Output format:
+    #   # -> "GET https://api.example.com/users HTTP/1.1"
+    #   # -> "accept: application/json"
+    #   # -> "user-agent: net/hippie 2.0.0"
+    #   # -> ""
+    #   # <- "HTTP/1.1 200"
+    #   # <- "content-type: application/json"
+    #   # <- ""
+    #   # <- "{\"users\":[...]}"
+    #
+    # @since 2.0.0
+    # @see RubyConnection The Ruby/net-http implementation
+    # @see Connection The backend abstraction layer
     class RustConnection
       def initialize(scheme, host, port, options = {})
         @scheme = scheme
         @host = host
         @port = port
         @options = options
+        @logger = options[:logger]
         
         # Create the Rust client (simplified version for now)
         @rust_client = Net::Hippie::RustClient.new
@@ -18,13 +45,21 @@ module Net
 
       def run(request)
         url = build_url_for(request.path)
-        headers = {} # Simplified for now
+        headers = extract_headers(request)
         body = request.body || ''
         method = extract_method(request)
 
+        # Debug logging (mimics Net::HTTP's set_debug_output behavior)
+        log_request(method, url, headers, body) if @logger
+
         begin
           rust_response = @rust_client.public_send(method.downcase, url, headers, body)
-          RustBackend::ResponseAdapter.new(rust_response)
+          response = RustBackend::ResponseAdapter.new(rust_response)
+          
+          # Debug log response
+          log_response(response) if @logger
+          
+          response
         rescue => e
           # Map Rust errors to Ruby equivalents
           raise map_rust_error(e)
@@ -52,6 +87,66 @@ module Net
         request.class.name.split('::').last.sub('HTTP', '').downcase
       end
 
+      # Logs HTTP request details in Net::HTTP debug format.
+      #
+      # Outputs request line, headers, and body using the same format as
+      # Net::HTTP's set_debug_output for consistent debugging experience.
+      #
+      # @param method [String] HTTP method (GET, POST, etc.)
+      # @param url [String] Complete request URL
+      # @param headers [Hash] Request headers
+      # @param body [String] Request body content
+      # @since 2.0.0
+      def log_request(method, url, headers, body)
+        # Format similar to Net::HTTP's debug output
+        @logger << "-> \"#{method.upcase} #{url} HTTP/1.1\"\n"
+        
+        # Log headers
+        headers.each do |key, value|
+          @logger << "-> \"#{key.downcase}: #{value}\"\n"
+        end
+        
+        @logger << "-> \"\"\n"  # Empty line
+        
+        # Log body if present
+        if body && !body.empty?
+          @logger << "-> \"#{body}\"\n"
+        end
+        
+        @logger.flush if @logger.respond_to?(:flush)
+      end
+
+      # Logs HTTP response details in Net::HTTP debug format.
+      #
+      # Outputs response status, headers, and body (truncated if large) using
+      # the same format as Net::HTTP's set_debug_output.
+      #
+      # @param response [RustBackend::ResponseAdapter] HTTP response object
+      # @since 2.0.0
+      def log_response(response)
+        # Format similar to Net::HTTP's debug output
+        @logger << "<- \"HTTP/1.1 #{response.code}\"\n"
+        
+        # Log some common response headers if available
+        %w[content-type content-length location server date].each do |header|
+          value = response[header]
+          if value
+            @logger << "<- \"#{header}: #{value}\"\n"
+          end
+        end
+        
+        @logger << "<- \"\"\n"  # Empty line
+        
+        # Log response body (truncated if too long)
+        body = response.body
+        if body && !body.empty?
+          display_body = body.length > 1000 ? "#{body[0...1000]}...[truncated]" : body
+          @logger << "<- \"#{display_body}\"\n"
+        end
+        
+        @logger.flush if @logger.respond_to?(:flush)
+      end
+
       def map_rust_error(error)
         case error.message
         when /Net::ReadTimeout/
src/lib.rs
@@ -1,4 +1,4 @@
-use magnus::{define_module, function, method, Error, Module, Object, Value, class};
+use magnus::{define_module, function, method, Error, Module, Object, Value, class, RHash, TryConvert};
 use magnus::value::ReprValue;
 use reqwest::{Client, Method, Response};
 use std::collections::HashMap;
@@ -59,7 +59,7 @@ impl RustClient {
         &self,
         method_str: String,
         url: String,
-        _headers: Value,  // Simplified - ignore headers for now
+        headers: Value,
         body: String,
     ) -> Result<RustResponse, Error> {
         let method = match method_str.to_uppercase().as_str() {
@@ -74,6 +74,15 @@ impl RustClient {
         self.runtime.block_on(async {
             let mut request_builder = self.client.request(method, &url);
 
+            // Add headers if provided
+            if let Ok(headers_hash) = RHash::from_value(headers) {
+                for (key, value) in headers_hash {
+                    if let (Ok(key_str), Ok(value_str)) = (String::try_convert(key), String::try_convert(value)) {
+                        request_builder = request_builder.header(&key_str, &value_str);
+                    }
+                }
+            }
+
             // Add body if not empty
             if !body.is_empty() {
                 request_builder = request_builder.body(body);