rs
  1# frozen_string_literal: true
  2
  3require 'base64'
  4require 'json'
  5require 'logger'
  6require 'net/http'
  7require 'openssl'
  8
  9require 'net/hippie/version'
 10require 'net/hippie/client'
 11require 'net/hippie/connection'
 12require 'net/hippie/content_type_mapper'
 13require 'net/hippie/rust_backend'
 14
 15module Net
 16  # Net::Hippie is a lightweight wrapper around Ruby's net/http library that simplifies 
 17  # HTTP requests with JSON-first defaults and optional high-performance Rust backend.
 18  #
 19  # @since 0.1.0
 20  #
 21  # == Features
 22  #
 23  # * JSON-first API with automatic content-type handling
 24  # * Built-in retry logic with exponential backoff
 25  # * Connection pooling and reuse
 26  # * TLS/SSL support with client certificates
 27  # * Optional Rust backend for enhanced performance (v2.0+)
 28  # * Automatic redirect following
 29  # * Comprehensive error handling
 30  #
 31  # == Basic Usage
 32  #
 33  #   # Simple GET request
 34  #   response = Net::Hippie.get('https://api.github.com/users/octocat')
 35  #   data = JSON.parse(response.body)
 36  #
 37  #   # POST with JSON body
 38  #   response = Net::Hippie.post('https://httpbin.org/post', 
 39  #                               body: { name: 'hippie', version: '2.0' })
 40  #
 41  # == Rust Backend (v2.0+)
 42  #
 43  #   # Enable high-performance Rust backend
 44  #   ENV['NET_HIPPIE_RUST'] = 'true'
 45  #   response = Net::Hippie.get('https://api.example.com') # Uses Rust!
 46  #
 47  # @see Client The main client class for advanced usage
 48  # @see https://github.com/xlgmokha/net-hippie Documentation and examples
 49  module Hippie
 50    # List of network-related exceptions that should trigger automatic retries.
 51    # These errors typically indicate transient network issues that may resolve
 52    # on subsequent attempts.
 53    #
 54    # @since 0.2.7
 55    CONNECTION_ERRORS = [
 56      EOFError,
 57      Errno::ECONNREFUSED,
 58      Errno::ECONNRESET,
 59      Errno::ECONNRESET,
 60      Errno::EHOSTUNREACH,
 61      Errno::EINVAL,
 62      Net::OpenTimeout,
 63      Net::ProtocolError,
 64      Net::ReadTimeout,
 65      OpenSSL::OpenSSLError,
 66      OpenSSL::SSL::SSLError,
 67      SocketError,
 68      Timeout::Error
 69    ].freeze
 70
 71    # Gets the current logger instance.
 72    # Defaults to a null logger (no output) if not explicitly set.
 73    #
 74    # @return [Logger, nil] The current logger instance
 75    # @since 1.2.0
 76    #
 77    # @example
 78    #   Net::Hippie.logger = Logger.new(STDOUT)
 79    #   logger = Net::Hippie.logger
 80    def self.logger
 81      @logger ||= Logger.new(nil)
 82    end
 83
 84    # Sets the logger for HTTP request debugging and error reporting.
 85    #
 86    # @param logger [Logger, nil] Logger instance or nil to disable logging
 87    # @return [Logger, nil] The assigned logger
 88    # @since 1.2.0
 89    #
 90    # @example Enable debug logging
 91    #   Net::Hippie.logger = Logger.new(STDERR)
 92    #   Net::Hippie.logger.level = Logger::DEBUG
 93    #
 94    # @example Disable logging
 95    #   Net::Hippie.logger = nil
 96    def self.logger=(logger)
 97      @logger = logger
 98    end
 99
100    # Gets the default SSL verification mode for HTTPS connections.
101    #
102    # @return [Integer] OpenSSL verification mode constant
103    # @since 0.2.3
104    def self.verify_mode
105      @verify_mode ||= OpenSSL::SSL::VERIFY_PEER
106    end
107
108    # Sets the default SSL verification mode for HTTPS connections.
109    #
110    # @param mode [Integer] OpenSSL verification mode constant
111    # @return [Integer] The assigned verification mode
112    # @since 0.2.3
113    #
114    # @example Disable SSL verification (not recommended for production)
115    #   Net::Hippie.verify_mode = OpenSSL::SSL::VERIFY_NONE
116    def self.verify_mode=(mode)
117      @verify_mode = mode
118    end
119
120    # Generates a Basic Authentication header value.
121    #
122    # @param username [String] The username for authentication
123    # @param password [String] The password for authentication
124    # @return [String] Base64-encoded Basic auth header value
125    # @since 0.2.1
126    #
127    # @example
128    #   auth_header = Net::Hippie.basic_auth('user', 'pass')
129    #   response = Net::Hippie.get('https://api.example.com', 
130    #                              headers: { 'Authorization' => auth_header })
131    def self.basic_auth(username, password)
132      "Basic #{::Base64.strict_encode64("#{username}:#{password}")}"
133    end
134
135    # Generates a Bearer Token authentication header value.
136    #
137    # @param token [String] The bearer token for authentication
138    # @return [String] Bearer auth header value
139    # @since 0.2.1
140    #
141    # @example
142    #   auth_header = Net::Hippie.bearer_auth('your-api-token')
143    #   response = Net::Hippie.get('https://api.example.com',
144    #                              headers: { 'Authorization' => auth_header })
145    def self.bearer_auth(token)
146      "Bearer #{token}"
147    end
148
149    # Delegates HTTP method calls to the default client with automatic retry.
150    # Supports all HTTP methods available on the Client class (get, post, put, etc.).
151    #
152    # @param symbol [Symbol] The HTTP method name to call
153    # @param args [Array] Arguments to pass to the HTTP method
154    # @return [Net::HTTPResponse] The HTTP response from the request
155    # @raise [Net::ReadTimeout, Net::OpenTimeout] When request times out
156    # @raise [Errno::ECONNREFUSED] When connection is refused
157    # @since 1.0.0
158    #
159    # @example GET request
160    #   response = Net::Hippie.get('https://api.github.com/users/octocat')
161    #
162    # @example POST request
163    #   response = Net::Hippie.post('https://httpbin.org/post', body: { key: 'value' })
164    #
165    # @see Client#get, Client#post, Client#put, Client#patch, Client#delete
166    def self.method_missing(symbol, *args)
167      default_client.with_retry(retries: 3) do |client|
168        client.public_send(symbol, *args)
169      end || super
170    end
171
172    # Checks if the module responds to HTTP method calls by delegating to Client.
173    #
174    # @param name [Symbol] The method name to check
175    # @param _include_private [Boolean] Whether to include private methods (ignored)
176    # @return [Boolean] True if the method is supported
177    # @since 1.0.0
178    def self.respond_to_missing?(name, _include_private = false)
179      Client.public_instance_methods.include?(name.to_sym) || super
180    end
181
182    # Gets the shared default client instance used for module-level HTTP calls.
183    # The client is configured with automatic redirects and uses the module logger.
184    #
185    # @return [Client] The default client instance
186    # @since 1.0.0
187    #
188    # @example Access the default client directly
189    #   client = Net::Hippie.default_client
190    #   client.get('https://api.example.com')
191    def self.default_client
192      @default_client ||= Client.new(follow_redirects: 3, logger: logger)
193    end
194  end
195end