# frozen_string_literal: true require "dry/core/constants" require "bigdecimal" require "bigdecimal/util" require "date" require "uri" module Dry module Logic module Predicates include ::Dry::Core::Constants # rubocop:disable Metrics/ModuleLength module Methods def self.uuid_format(version) ::Regexp.new(<<~FORMAT.chomp, ::Regexp::IGNORECASE) \\A[0-9A-F]{8}-[0-9A-F]{4}-#{version}[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\\z FORMAT end UUIDv1 = uuid_format(1) UUIDv2 = uuid_format(2) UUIDv3 = uuid_format(3) UUIDv4 = uuid_format(4) UUIDv5 = uuid_format(5) UUIDv6 = uuid_format(6) UUIDv7 = uuid_format(7) UUIDv8 = uuid_format(8) def [](name) method(name) end def type?(type, input) = input.is_a?(type) def nil?(input) = input.nil? alias_method :none?, :nil? def key?(name, input) = input.key?(name) def attr?(name, input) = input.respond_to?(name) def empty?(input) case input when ::String, ::Array, ::Hash then input.empty? when nil then true else false end end def filled?(input) = !empty?(input) def bool?(input) = input.equal?(true) || input.equal?(false) def date?(input) = input.is_a?(::Date) def date_time?(input) = input.is_a?(::DateTime) def time?(input) = input.is_a?(::Time) def number?(input) true if Float(input) rescue ::ArgumentError, ::TypeError false end def int?(input) = input.is_a?(::Integer) def float?(input) = input.is_a?(::Float) def decimal?(input) = input.is_a?(::BigDecimal) def str?(input) = input.is_a?(::String) def hash?(input) = input.is_a?(::Hash) def array?(input) = input.is_a?(::Array) def odd?(input) = input.odd? def even?(input) = input.even? def lt?(num, input) = input < num def gt?(num, input) = input > num def lteq?(num, input) = !gt?(num, input) def gteq?(num, input) = !lt?(num, input) def size?(size, input) case size when ::Integer then size.equal?(input.size) when ::Range, ::Array then size.include?(input.size) else raise ::ArgumentError, "+#{size}+ is not supported type for size? predicate." end end def min_size?(num, input) = input.size >= num def max_size?(num, input) = input.size <= num def bytesize?(size, input) case size when ::Integer then size.equal?(input.bytesize) when ::Range, ::Array then size.include?(input.bytesize) else raise ::ArgumentError, "+#{size}+ is not supported type for bytesize? predicate." end end def min_bytesize?(num, input) = input.bytesize >= num def max_bytesize?(num, input) = input.bytesize <= num def inclusion?(list, input) deprecated(:inclusion?, :included_in?) included_in?(list, input) end def exclusion?(list, input) deprecated(:exclusion?, :excluded_from?) excluded_from?(list, input) end def included_in?(list, input) = list.include?(input) def excluded_from?(list, input) = !list.include?(input) def includes?(value, input) if input.respond_to?(:include?) input.include?(value) else false end rescue ::TypeError false end def excludes?(value, input) = !includes?(value, input) # This overrides Object#eql? so we need to make it compatible def eql?(left, right = Undefined) return super(left) if right.equal?(Undefined) left.eql?(right) end def is?(left, right) = left.equal?(right) def not_eql?(left, right) = !left.eql?(right) def true?(value) = value.equal?(true) def false?(value) = value.equal?(false) def format?(regex, input) = !input.nil? && regex.match?(input) def case?(pattern, input) = pattern === input # rubocop:disable Style/CaseEquality def uuid_v1?(input) = format?(UUIDv1, input) def uuid_v2?(input) = format?(UUIDv2, input) def uuid_v3?(input) = format?(UUIDv3, input) def uuid_v4?(input) = format?(UUIDv4, input) def uuid_v5?(input) = format?(UUIDv5, input) def uuid_v6?(input) = format?(UUIDv6, input) def uuid_v7?(input) = format?(UUIDv7, input) def uuid_v8?(input) = format?(UUIDv8, input) if defined?(::URI::RFC2396_PARSER) def uri?(schemes, input) uri_format = ::URI::RFC2396_PARSER.make_regexp(schemes) format?(uri_format, input) end else def uri?(schemes, input) uri_format = ::URI::DEFAULT_PARSER.make_regexp(schemes) format?(uri_format, input) end end def uri_rfc3986?(input) = format?(::URI::RFC3986_Parser::RFC3986_URI, input) # This overrides Object#respond_to? so we need to make it compatible def respond_to?(method, input = Undefined) return super if input.equal?(Undefined) input.respond_to?(method) end def predicate(name, &) define_singleton_method(name, &) end def deprecated(name, in_favor_of) Core::Deprecations.warn( "#{name} predicate is deprecated and will " \ "be removed in the next major version\n" \ "Please use #{in_favor_of} predicate instead", tag: "dry-logic", uplevel: 3 ) end end extend Methods def self.included(other) super other.extend(Methods) end end # rubocop:enable Metrics/ModuleLength end end