#-------------------------------------------------------------------------------
# Author: Keith Brings
# Copyright (C) 2018 Noizu Labs, Inc. All rights reserved.
#-------------------------------------------------------------------------------
defmodule Noizu.RuleEngine.Op.BetweenOp do
@type t :: %__MODULE__{
name: String.t | nil,
description: String.t | nil,
identifier: String.t | list | tuple, # Materialized Path.
settings: Keyword.t,
strict: boolean,
argument: any,
lower_bound: any,
upper_bound: any,
}
defstruct [
name: nil,
description: nil,
identifier: nil,
comparison_strategy: :default,
settings: [async?: :auto, throw_on_timeout?: :auto],
strict: false,
argument: nil,
lower_bound: nil,
upper_bound: nil
]
end
defimpl Noizu.RuleEngine.ScriptProtocol, for: Noizu.RuleEngine.Op.BetweenOp do
alias Noizu.RuleEngine.Helper
#-----------------
# execute!/3
#-----------------
def execute!(this, state, context), do: execute!(this, state, context, %{})
#-----------------
# execute!/4
#-----------------
def execute!(this, state, context, options) do
cond do
Enum.member?([true, :auto, :required], this.settings[:async?]) && (options[:settings] && options.settings.supports_async? == true) -> execute!(:async, this, state, context, options)
this.settings[:async?] == :required -> throw Noizu.RuleEngine.Error.Basic.new("[ScriptError] Unable to perform required async execute on #{this.__struct__} - #{identifier(this, state, context)}", 310)
true -> execute!(:sync, this, state, context, options)
end
end
#-----------------
# execute!/5
#-----------------
def execute!(:sync, this, state, context, options) do
{arg, state} = Noizu.RuleEngine.ScriptProtocol.execute!(this.argument, state, context, options)
{lb, state} = Noizu.RuleEngine.ScriptProtocol.execute!(this.lower_bound, state, context, options)
{ub, state} = Noizu.RuleEngine.ScriptProtocol.execute!(this.upper_bound, state, context, options)
perform_check!(arg, lb, ub, this, state, context, options)
end
def execute!(:async, this, state, context, options) do
yield_wait = this.settings[:timeout] || options[:timeout] || 15_000
# Async execute arguments
tasks = [
Task.async(fn -> Noizu.RuleEngine.ScriptProtocol.execute!(this.argument, state, context, options) |> elem(0) end),
Task.async(fn -> Noizu.RuleEngine.ScriptProtocol.execute!(this.lower_bound, state, context, options) |> elem(0) end),
Task.async(fn -> Noizu.RuleEngine.ScriptProtocol.execute!(this.upper_bound, state, context, options) |> elem(0) end),
]
[arg, lb, ub] = tasks
|> Task.yield_many(yield_wait)
|> Enum.map(
fn({task, res}) ->
case res do
{:ok, v} -> v
_ ->
Task.shutdown(task, yield_wait)
{:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, task}}}
end
end)
perform_check!(arg, lb, ub, this, state, context, options)
end
#-----------------
# perform_check!/7
#-----------------
defp perform_check!(arg, lb, ub, this, state, context, options) do
# Comparison Strategy
cs = this.comparison_strategy || :default
{lb_c, ub_c} = this.strict && {:">", :"<"} || {:">=", :"<="}
# Perform Comparison / Error Check
outcome = cond do
match?({:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, _}}}, arg) -> arg
match?({:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, _}}}, lb) -> lb
match?({:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, _}}}, ub) -> ub
cs == :default && this.strict -> (arg > lb && arg < ub)
cs == :default -> (arg >= lb && arg <= ub)
match?({_, _, 3}, cs) -> :erlang.apply(elem(cs, 0), elem(cs, 1), [lb_c, arg, lb]) && :erlang.apply(elem(cs, 0), elem(cs, 1), [ub_c, arg, ub])
match?({_, _, 6}, cs) -> :erlang.apply(elem(cs, 0), elem(cs, 1), [lb_c, arg, lb, state, context, options]) && :erlang.apply(elem(cs, 0), elem(cs, 1), [ub_c, arg, ub, state, context, options])
is_function(cs, 3) -> cs.(lb_c, arg, lb) && cs.(ub_c, arg, ub)
is_function(cs, 6) -> elem(cs.(lb_c, arg, lb, state, context, options), 0) && elem(cs.(ub_c, arg, ub, state, context, options), 0)
true -> throw Noizu.RuleEngine.Error.Basic.new("[ScriptError] - #{identifier(this, state, context, options)} Invalid comparison strategy #{inspect cs}", 404)
end
case outcome do
{:error, {Noizu.RuleEngine.ScriptProtocol, {:timeout, task}}} ->
throw Noizu.RuleEngine.Error.Basic.new("[ScriptError] - #{identifier(this, state, context, options)} Execute Child Task Failed to Complete #{inspect task}", 404)
_ -> {outcome, state}
end
end
#---------------------
# identifier/3
#---------------------
def identifier(this, _state, _context), do: Helper.identifier(this)
#---------------------
# identifier/4
#---------------------
def identifier(this, _state, _context, _options), do: Helper.identifier(this)
#---------------------
# render/3
#---------------------
def render(this, state, context), do: render(this, state, context, %{})
#---------------------
# render/4
#---------------------
def render(this, state, context, options) do
Helper.render_arg_list("BETWEEN", identifier(this, state, context, options), [this.argument, this.lower_bound, this.upper_bound], state, context, options)
end
end