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