1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
# frozen_string_literal: true
require "graphql/client/error"
module GraphQL
class Client
# Collocation will not be enforced if a stack trace includes any of these gems.
WHITELISTED_GEM_NAMES = %w{pry byebug}
# Raised when method is called from outside the expected file scope.
class NonCollocatedCallerError < Error; end
# Enforcements collocated object access best practices.
module CollocatedEnforcement
extend self
# Public: Ignore collocated caller enforcement for the scope of the block.
def allow_noncollocated_callers
Thread.current[:query_result_caller_location_ignore] = true
yield
ensure
Thread.current[:query_result_caller_location_ignore] = nil
end
def verify_collocated_path(location, path, method = "method")
return yield if Thread.current[:query_result_caller_location_ignore]
if (location.path != path) && !(WHITELISTED_GEM_NAMES.any? { |g| location.path.include?("gems/#{g}") })
error = NonCollocatedCallerError.new("#{method} was called outside of '#{path}' https://git.io/v1syX")
error.set_backtrace(caller(2))
raise error
end
begin
Thread.current[:query_result_caller_location_ignore] = true
yield
ensure
Thread.current[:query_result_caller_location_ignore] = nil
end
end
# Internal: Decorate method with collocated caller enforcement.
#
# mod - Target Module/Class
# methods - Array of Symbol method names
# path - String filename to assert calling from
#
# Returns nothing.
def enforce_collocated_callers(mod, methods, path)
mod.prepend(Module.new do
methods.each do |method|
define_method(method) do |*args, &block|
location = caller_locations(1, 1)[0]
CollocatedEnforcement.verify_collocated_path(location, path, method) do
super(*args, &block)
end
end
end
end)
end
end
end
end
|