rs
1# frozen_string_literal: true
2
3module Net
4 module Hippie
5 # Rust backend integration and availability detection.
6 #
7 # The RustBackend module manages the optional high-performance Rust HTTP client
8 # backend. It provides automatic detection of Rust extension availability and
9 # environment-based enabling/disabling of the Rust backend.
10 #
11 # == Backend Selection Logic
12 #
13 # 1. Check if NET_HIPPIE_RUST environment variable is set to 'true'
14 # 2. Verify that the Rust extension (net_hippie_ext) can be loaded
15 # 3. If both conditions are met, use RustConnection
16 # 4. Otherwise, fall back to RubyConnection
17 #
18 # == Performance Benefits
19 #
20 # When enabled, the Rust backend provides:
21 # * Significantly faster HTTP requests using reqwest
22 # * Better concurrency with Tokio async runtime
23 # * Lower memory usage with zero-cost abstractions
24 # * Type safety with compile-time guarantees
25 #
26 # @since 2.0.0
27 #
28 # == Environment Configuration
29 #
30 # # Enable Rust backend
31 # ENV['NET_HIPPIE_RUST'] = 'true'
32 #
33 # # Check availability and status
34 # puts "Rust available: #{Net::Hippie::RustBackend.available?}"
35 # puts "Rust enabled: #{Net::Hippie::RustBackend.enabled?}"
36 #
37 # @see RUST_BACKEND.md Detailed setup and usage documentation
38 module RustBackend
39 @rust_available = nil
40
41 # Checks if the Rust extension is available for loading.
42 #
43 # This method attempts to require the 'net_hippie_ext' native extension
44 # and caches the result. The extension is built from Rust source code
45 # using Magnus for Ruby-Rust integration.
46 #
47 # @return [Boolean] true if Rust extension loaded successfully
48 # @since 2.0.0
49 #
50 # @example Check Rust availability
51 # if Net::Hippie::RustBackend.available?
52 # puts "Rust backend ready!"
53 # else
54 # puts "Using Ruby backend (Rust not available)"
55 # end
56 def self.available?
57 return @rust_available unless @rust_available.nil?
58
59 @rust_available = begin
60 require 'net_hippie_ext'
61 true
62 rescue LoadError
63 false
64 end
65 end
66
67 # Checks if the Rust backend is both available and enabled.
68 #
69 # Returns true only when:
70 # 1. NET_HIPPIE_RUST environment variable is set to 'true'
71 # 2. The Rust extension is available (compiled and loadable)
72 #
73 # @return [Boolean] true if Rust backend should be used
74 # @since 2.0.0
75 #
76 # @example Check if Rust backend will be used
77 # ENV['NET_HIPPIE_RUST'] = 'true'
78 # if Net::Hippie::RustBackend.enabled?
79 # puts "All HTTP requests will use Rust backend"
80 # else
81 # puts "Falling back to Ruby backend"
82 # end
83 def self.enabled?
84 ENV['NET_HIPPIE_RUST'] == 'true' && available?
85 end
86
87 # Adapter that makes Rust HTTP responses compatible with Net::HTTPResponse interface.
88 #
89 # The ResponseAdapter provides a compatibility layer between Rust HTTP responses
90 # and Ruby's Net::HTTPResponse objects. This ensures that existing code works
91 # unchanged when switching between Ruby and Rust backends.
92 #
93 # == Compatibility Features
94 #
95 # * Status code access via #code method
96 # * Response body access via #body method
97 # * Header access via #[] method
98 # * Response class detection via #class method
99 # * Type checking via #is_a? and #kind_of?
100 #
101 # @since 2.0.0
102 #
103 # == Supported Response Classes
104 #
105 # * Net::HTTPOK (200)
106 # * Net::HTTPCreated (201)
107 # * Net::HTTPRedirection (3xx)
108 # * Net::HTTPClientError (4xx)
109 # * Net::HTTPServerError (5xx)
110 #
111 # @see Net::HTTPResponse The Ruby standard library response interface
112 class ResponseAdapter
113 # Creates a new response adapter from a Rust HTTP response.
114 #
115 # @param rust_response [RustResponse] The Rust HTTP response object
116 # @since 2.0.0
117 def initialize(rust_response)
118 @rust_response = rust_response
119 @code = rust_response.code
120 @body = rust_response.body
121 end
122
123 # Returns the HTTP status code.
124 #
125 # @return [String] HTTP status code (e.g., "200", "404")
126 # @since 2.0.0
127 def code
128 @code
129 end
130
131 # Returns the response body content.
132 #
133 # @return [String] HTTP response body
134 # @since 2.0.0
135 def body
136 @body
137 end
138
139 # Retrieves a response header value by name.
140 #
141 # @param header_name [String, Symbol] Header name (case-insensitive)
142 # @return [String, nil] Header value or nil if not found
143 # @since 2.0.0
144 #
145 # @example Get content type
146 # content_type = response['Content-Type']
147 # location = response[:location]
148 def [](header_name)
149 @rust_response[header_name.to_s]
150 end
151
152 # Returns the appropriate Net::HTTP response class based on status code.
153 #
154 # Maps HTTP status codes to their corresponding Net::HTTP class constants
155 # to maintain compatibility with Ruby HTTP library expectations.
156 #
157 # @return [Class] Net::HTTP response class constant
158 # @since 2.0.0
159 #
160 # @example Check response type
161 # response.class # => Net::HTTPOK (for 200 status)
162 # response.class # => Net::HTTPNotFound (for 404 status)
163 def class
164 case @code.to_i
165 when 200
166 Net::HTTPOK
167 when 201
168 Net::HTTPCreated
169 when 300..399
170 Net::HTTPRedirection
171 when 400..499
172 Net::HTTPClientError
173 when 500..599
174 Net::HTTPServerError
175 else
176 Net::HTTPResponse
177 end
178 end
179
180 # Checks if this response is an instance of the given class.
181 #
182 # Provides compatibility with Ruby's type checking by delegating
183 # to the mapped response class while supporting normal inheritance.
184 #
185 # @param klass [Class] Class to check against
186 # @return [Boolean] true if response matches the class
187 # @since 2.0.0
188 #
189 # @example Type checking
190 # response.is_a?(Net::HTTPOK) # => true (for 200 status)
191 # response.is_a?(Net::HTTPRedirection) # => true (for 3xx status)
192 def is_a?(klass)
193 self.class == klass || super
194 end
195
196 # Alias for #is_a? to maintain Ruby compatibility.
197 #
198 # @param klass [Class] Class to check against
199 # @return [Boolean] true if response matches the class
200 # @since 2.0.0
201 def kind_of?(klass)
202 is_a?(klass)
203 end
204 end
205 end
206 end
207end