lib/structs.ex

defmodule Condition do
  defstruct [:path, :operator, :value]
end

defmodule ConditionSet do
  defstruct [:all, :any]
end

defmodule Rule do
  defstruct [:id, :conditions]

  def new(%{"id"=>id, "conditions"=>conditions}) when is_map(conditions) do
    %Rule{id: id, conditions: to_condition_or_set(conditions)}
  end

  defp to_condition_or_set(map_or_list) do
    case map_or_list do
      %{"path" => _path, "operator" => _operator, "value" => _value} ->
        struct(Condition, map_or_list)

      %{"all" => list} when is_list(list) ->
        %ConditionSet{all: Enum.map(list, &to_condition_or_set(&1))}

      %{"any" => list} when is_list(list) ->
        %ConditionSet{any: Enum.map(list, &to_condition_or_set(&1))}

      _ ->
        raise ArgumentError, "invalid condition or set"
    end
  end
end

"""
example1 = %Rule{
  id: "1",
  conditions: %Condition{
    path: "/operator_test",
    operator: "equals",
    value: "abc"
  }
}

example2 = %Rule{
  id: "2",
  conditions: %ConditionSet{
    all: [
      %Condition{
        path: "/operator_test",
        operator: "equals",
        value: "one"
      },
      %Condition{
        path: "/path/0/test",
        operator: "equals",
        value: "two"
      }
    ]
  }
}

example3 = %Rule{
  id: "3",
  conditions: %ConditionSet{
    any: [
      %Condition{
        path: "/path/0/test",
        operator: "equals",
        value: "one"
      },
      %Condition{
        path: "/path/1/test",
        operator: "equals",
        value: "one"
      }
    ]
  }
}

example4 = %Rule{
  id: "4",
  conditions: %ConditionSet{
    any: [
      %Condition{
        path: "/path/0/test",
        operator: "equals",
        value: "one"
      },
      %ConditionSet{
        all: [
          %Condition{
            path: "/path/0/test",
            operator: "equals",
            value: "one"
          },
          %Condition{
            path: "/path/1/test",
            operator: "equals",
            value: "two"
          }
        ]
      }
    ]
  }
}
"""