module LaunchDarkly::Impl::EvaluatorOperators

Defines the behavior of all operators that can be used in feature flag rules and segment rules.

Constants

BUILTINS
NUMERIC_VERSION_COMPONENTS_REGEX

Public Class Methods

apply(op, user_value, clause_value) click to toggle source

Applies an operator to produce a boolean result.

@param op [Symbol] one of the supported LaunchDarkly operators, as a symbol @param user_value the value of the user attribute that is referenced in the current clause (left-hand

side of the expression)

@param clause_value the constant value that `user_value` is being compared to (right-hand side of the

expression)

@return [Boolean] true if the expression should be considered a match; false if it is not a match, or

if the values cannot be compared because they are of the wrong types, or if the operator is unknown
# File lib/ldclient-rb/impl/evaluator_operators.rb, line 18
def self.apply(op, user_value, clause_value)
  case op
  when :in
    user_value == clause_value
  when :startsWith
    string_op(user_value, clause_value, lambda { |a, b| a.start_with? b })
  when :endsWith
    string_op(user_value, clause_value, lambda { |a, b| a.end_with? b })
  when :contains
    string_op(user_value, clause_value, lambda { |a, b| a.include? b })
  when :matches
    string_op(user_value, clause_value, lambda { |a, b|
      begin
        re = Regexp.new b
        !re.match(a).nil?
      rescue
        false
      end
    })
  when :lessThan
    numeric_op(user_value, clause_value, lambda { |a, b| a < b })
  when :lessThanOrEqual
    numeric_op(user_value, clause_value, lambda { |a, b| a <= b })
  when :greaterThan
    numeric_op(user_value, clause_value, lambda { |a, b| a > b })
  when :greaterThanOrEqual
    numeric_op(user_value, clause_value, lambda { |a, b| a >= b })
  when :before
    date_op(user_value, clause_value, lambda { |a, b| a < b })
  when :after
    date_op(user_value, clause_value, lambda { |a, b| a > b })
  when :semVerEqual
    semver_op(user_value, clause_value, lambda { |a, b| a == b })
  when :semVerLessThan
    semver_op(user_value, clause_value, lambda { |a, b| a < b })
  when :semVerGreaterThan
    semver_op(user_value, clause_value, lambda { |a, b| a > b })
  when :segmentMatch
    # We should never reach this; it can't be evaluated based on just two parameters, because it requires
    # looking up the segment from the data store. Instead, we special-case this operator in clause_match_user.
    false
  else
    false
  end
end
user_value(user, attribute) click to toggle source

Retrieves the value of a user attribute by name.

Built-in attributes correspond to top-level properties in the user object. They are treated as strings and non-string values are coerced to strings, except for `anonymous` which is meant to be a boolean if present and is not currently coerced. This behavior is consistent with earlier versions of the Ruby SDK, but is not guaranteed to be consistent with other SDKs, since the evaluator specification is based on the strongly-typed SDKs where it is not possible for an attribute to have the wrong type.

Custom attributes correspond to properties within the `custom` property, if any, and can be of any type.

@param user [Object] the user properties @param attribute [String|Symbol] the attribute to get, for instance `:key` or `:name` or `:some_custom_attr` @return the attribute value, or nil if the attribute is unknown

# File lib/ldclient-rb/impl/evaluator_operators.rb, line 77
def self.user_value(user, attribute)
  attribute = attribute.to_sym
  if BUILTINS.include? attribute
    value = user[attribute]
    return nil if value.nil?
    (attribute == :anonymous) ? value : value.to_s
  elsif !user[:custom].nil?
    user[:custom][attribute]
  else
    nil
  end
end

Private Class Methods

add_zero_version_component(v) click to toggle source
# File lib/ldclient-rb/impl/evaluator_operators.rb, line 153
def self.add_zero_version_component(v)
  NUMERIC_VERSION_COMPONENTS_REGEX.match(v) { |m|
    m[0] + ".0" + v[m[0].length..-1]
  }
end
date_op(user_value, clause_value, fn) click to toggle source
# File lib/ldclient-rb/impl/evaluator_operators.rb, line 106
def self.date_op(user_value, clause_value, fn)
  ud = to_date(user_value)
  if !ud.nil?
    cd = to_date(clause_value)
    !cd.nil? && fn.call(ud, cd)
  else
    false
  end
end
numeric_op(user_value, clause_value, fn) click to toggle source
# File lib/ldclient-rb/impl/evaluator_operators.rb, line 102
def self.numeric_op(user_value, clause_value, fn)
  (user_value.is_a? Numeric) && (clause_value.is_a? Numeric) && fn.call(user_value, clause_value)
end
semver_op(user_value, clause_value, fn) click to toggle source
# File lib/ldclient-rb/impl/evaluator_operators.rb, line 116
def self.semver_op(user_value, clause_value, fn)
  uv = to_semver(user_value)
  if !uv.nil?
    cv = to_semver(clause_value)
    !cv.nil? && fn.call(uv, cv)
  else
    false
  end
end
string_op(user_value, clause_value, fn) click to toggle source
# File lib/ldclient-rb/impl/evaluator_operators.rb, line 98
def self.string_op(user_value, clause_value, fn)
  (user_value.is_a? String) && (clause_value.is_a? String) && fn.call(user_value, clause_value)
end
to_date(value) click to toggle source
# File lib/ldclient-rb/impl/evaluator_operators.rb, line 126
def self.to_date(value)
  if value.is_a? String
    begin
      DateTime.rfc3339(value).strftime("%Q").to_i
    rescue => e
      nil
    end
  elsif value.is_a? Numeric
    value
  else
    nil
  end
end
to_semver(value) click to toggle source
# File lib/ldclient-rb/impl/evaluator_operators.rb, line 140
def self.to_semver(value)
  if value.is_a? String
    for _ in 0..2 do
      begin
        return Semantic::Version.new(value)
      rescue ArgumentError
        value = add_zero_version_component(value)
      end
    end
  end
  nil
end