# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Utils # Utility methods for identifying instances that can be used interchangeably class DuckUtils class << self # Determine if the given object, or the object to which it delegates, # responds to the given method. # # @param object [Object] # @param method [Symbol] # @return [Boolean] def quacks_to? object, method return true if object.cs__respond_to?(method) return false unless object.is_a?(Delegator) object.cs__delegator_respond_to?(method) end # Most things that are closable IO's will in fact be of the IO type. That # being said, some will also be extensions of DelegateClass with IO type, # like Tempfile. We need to handle both cases. # # @param object [Object] # @return [Boolean] def closable_io? object return false unless Contrast::Utils::IOUtil.io?(object) quacks_to?(object, :closed?) end # Determine if the given Object is a Hash, or similar enough to a hash # for us to iterate on it using the #each_pair method # # @param object [Object] # @return [Boolean] def iterable_hash? object # do iterate on things believed to safely implement #each_pair return true if object.cs__is_a?(Hash) # otherwise, don't risk it false end # Determine if the given Object is a concrete implementation of # Enumerable known to be safe to call #each on. # # @param object [Object] # @return [Boolean] def iterable_enumerable? object # do iterate on things believed to safely implement #each. We're # purposefully skipping Hash and Hash-like things here as # #iterable_hash? should handle those. return true if object.cs__is_a?(Array) return true if object.cs__is_a?(Enumerator) return true if object.cs__is_a?(Hash) return true if object.cs__is_a?(Range) return true if object.cs__is_a?(Set) # otherwise, don't risk it false end end end end end