From cc612e755b5bda1bfe67d478c62798a19f3147b5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 8 May 2026 21:18:50 +0530 Subject: [PATCH] fix: SafeFetch dependency loading (#14408) The SafeFetch spec suite was failing in CI with `NameError: uninitialized constant SafeFetch::Fetcher` across every example that exercised `SafeFetch.fetch`. From a product perspective, this made the external-file fetch path look unreliable even though the failure happened before any network validation, SSRF protection, content-type checks, or tempfile handling could run. The symptom pointed to a load-order issue rather than an actual fetch behavior regression. `SafeFetch.fetch` referenced `Fetcher` from the top-level module, but that nested class was not guaranteed to be loaded in every test execution path before the method was invoked. This change keeps the existing SafeFetch split between the public API and the implementation classes, but makes the public entry point responsible for loading the implementation it needs before use. That is intentionally smaller than folding all of the fetcher logic into `lib/safe_fetch.rb`; the separate files still keep the request option parsing and streaming implementation readable, while the public API no longer depends on Rails or the test runner having loaded nested constants in a particular order. The file also now uses a single `SafeFetch` module declaration. That removes the awkward reopen pattern and makes the dependency boundary easier to see: constants and errors are defined first, then the public `fetch` method loads and delegates to the implementation classes. --- lib/safe_fetch.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/safe_fetch.rb b/lib/safe_fetch.rb index d664dcf6a..7f89c03c4 100644 --- a/lib/safe_fetch.rb +++ b/lib/safe_fetch.rb @@ -22,16 +22,11 @@ module SafeFetch class FileTooLargeError < Error; end class UnsupportedContentTypeError < Error; end class UnsupportedMethodError < Error; end -end -require_relative 'safe_fetch/request_options' -require_relative 'safe_fetch/fetcher' - -module SafeFetch def self.fetch(url, **, &) raise ArgumentError, 'block required' unless block_given? - Fetcher.new(RequestOptions.new(url: url, **)).fetch(&) + SafeFetch::Fetcher.new(SafeFetch::RequestOptions.new(url: url, **)).fetch(&) rescue SsrfFilter::InvalidUriScheme, URI::InvalidURIError => e raise InvalidUrlError, e.message rescue SsrfFilter::Error, Resolv::ResolvError => e