Module: Smplkit::Flags::JsonLogicEvaluator

Defined in:
lib/smplkit/flags/client.rb

Overview

Vendored minimal JSON Logic evaluator covering the operators the smplkit platform ships in flag rules.

Stays in-tree so the Ruby SDK doesn’t depend on the json_logic gem being correct — the Java SDK followed the same pattern. Operators supported: ==, !=, <, <=, >, >=, in, var, and, or, !, if, missing, none.

Class Method Summary collapse

Class Method Details

.apply(logic, data) ⇒ Object



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/smplkit/flags/client.rb', line 365

def apply(logic, data)
  return logic unless logic.is_a?(Hash)
  return logic if logic.empty?

  op, values = logic.first
  values = [values] unless values.is_a?(Array)

  case op
  when "var"
    resolve_var(values[0], data, values[1])
  when "and"
    values.reduce(true) { |acc, v| acc && truthy?(apply(v, data)) }
  when "or"
    values.reduce(false) { |acc, v| acc || truthy?(apply(v, data)) }
  when "!"
    !truthy?(apply(values[0], data))
  when "if"
    eval_if(values, data)
  when "==", "==="
    apply(values[0], data) == apply(values[1], data)
  when "!=", "!=="
    apply(values[0], data) != apply(values[1], data)
  when "<"
    compare(values, data) { |a, b| a < b }
  when "<="
    compare(values, data) { |a, b| a <= b }
  when ">"
    compare(values, data) { |a, b| a > b }
  when ">="
    compare(values, data) { |a, b| a >= b }
  when "in"
    eval_in(values, data)
  when "missing"
    eval_missing(values, data)
  when "none"
    values_arr = apply(values[0], data) || []
    inner = values[1]
    values_arr.is_a?(Array) && values_arr.none? { |item| truthy?(apply(inner, item)) }
  else
    false
  end
end

.compare(values, data) ⇒ Object



435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/smplkit/flags/client.rb', line 435

def compare(values, data)
  applied = values.map { |v| apply(v, data) }
  return false if applied.any?(&:nil?)

  if applied.length == 2
    yield(applied[0], applied[1])
  elsif applied.length == 3
    yield(applied[0], applied[1]) && yield(applied[1], applied[2])
  else
    false
  end
rescue ArgumentError, TypeError
  false
end

.eval_if(values, data) ⇒ Object



450
451
452
453
454
455
456
457
458
# File 'lib/smplkit/flags/client.rb', line 450

def eval_if(values, data)
  i = 0
  while i + 1 < values.length
    return apply(values[i + 1], data) if truthy?(apply(values[i], data))

    i += 2
  end
  i < values.length ? apply(values[i], data) : nil
end

.eval_in(values, data) ⇒ Object



460
461
462
463
464
465
466
467
468
# File 'lib/smplkit/flags/client.rb', line 460

def eval_in(values, data)
  needle = apply(values[0], data)
  haystack = apply(values[1], data)
  return false if haystack.nil?

  haystack.include?(needle)
rescue NoMethodError, TypeError
  false
end

.eval_missing(values, data) ⇒ Object



470
471
472
473
# File 'lib/smplkit/flags/client.rb', line 470

def eval_missing(values, data)
  keys = values.is_a?(Array) ? values.flatten : [values]
  keys.reject { |k| present?(resolve_var(k, data)) }
end

.present?(value) ⇒ Boolean

Returns:

  • (Boolean)


475
476
477
# File 'lib/smplkit/flags/client.rb', line 475

def present?(value)
  !(value.nil? || value == "")
end

.resolve_var(path, data, default = nil) ⇒ Object



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/smplkit/flags/client.rb', line 418

def resolve_var(path, data, default = nil)
  return data if path.nil? || path == "" || path == []

  keys = path.is_a?(Array) ? path : path.to_s.split(".")
  keys.reduce(data) do |scope, key|
    break default if scope.nil?

    if scope.is_a?(Hash)
      scope[key] || scope[key.to_s] || scope[key.to_sym]
    elsif scope.is_a?(Array) && key.to_s =~ /\A\d+\z/
      scope[key.to_i]
    else
      default
    end
  end || default
end

.truthy?(value) ⇒ Boolean

Returns:

  • (Boolean)


408
409
410
411
412
413
414
415
416
# File 'lib/smplkit/flags/client.rb', line 408

def truthy?(value)
  return false if value.nil?
  return false if value == false
  return false if value.is_a?(Numeric) && value.zero?
  return false if value == ""
  return false if value == []

  true
end