diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..626fc16 --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +- Validate before build +- Add Supervisors functionality +- Named nodes with graph prefix +- Improve test +- Improve API +- Update to GenStage diff --git a/lib/exstreme/gnode/behaviour.ex b/lib/exstreme/gnode/behaviour.ex index 8f974af..d92b484 100644 --- a/lib/exstreme/gnode/behaviour.ex +++ b/lib/exstreme/gnode/behaviour.ex @@ -19,8 +19,9 @@ defmodule Exstreme.GNode.Behaviour do #TODO use counters end - def start_link(opts \\ []) do - GenServer.start_link(__MODULE__, opts) + def start_link(params \\ []) do + nid = Keyword.get(params, :nid) + GenServer.start_link(__MODULE__, params, name: nid) end def init(params) do diff --git a/lib/exstreme/graph.ex b/lib/exstreme/graph.ex index 4252175..7f34270 100644 --- a/lib/exstreme/graph.ex +++ b/lib/exstreme/graph.ex @@ -3,8 +3,10 @@ defmodule Exstreme.Graph do """ alias __MODULE__ - @type t :: %Graph{params: [key: term], nodes: %{key: [key: term]}, connections: %{key: atom}} - defstruct params: [], nodes: %{}, connections: %{} + @typedoc """ + """ + @type t :: %Graph{name: String.t,params: [key: term], nodes: %{key: [key: term]}, connections: %{key: atom}} + defstruct name: '', params: [], nodes: %{}, connections: %{} @doc """ """ @@ -119,7 +121,7 @@ defmodule Exstreme.Graph do end @spec get_nodes_func(atom, {atom, atom}, [atom], ((atom, {atom, atom}) -> boolean)) :: [atom] - defp get_nodes_func(node, pair = {from, to}, res, func) do + defp get_nodes_func(node, {from, to} = pair, res, func) do case to do to when is_atom(to) -> {ok, add_node} = func.(node, pair) diff --git a/lib/exstreme/graph_builder.ex b/lib/exstreme/graph_builder.ex index 7c0026c..975dbd8 100644 --- a/lib/exstreme/graph_builder.ex +++ b/lib/exstreme/graph_builder.ex @@ -5,15 +5,19 @@ defmodule Exstreme.GraphBuilder do alias Exstreme.GNode.Funnel alias Exstreme.GNode.Common alias Exstreme.Graph + alias Exstreme.GraphValidator @doc """ + Builds the Supervision tree for the graph """ - @spec build(Graph.t) :: Graph.t + @spec build(Graph.t) :: Graph.t | GraphValidator.error def build(graph) do - graph - |> update_nodes_relations - |> start_nodes - |> connect_nodes + with :ok <- GraphValidator.validate(graph) do + graph + |> update_nodes_relations + |> start_nodes + |> connect_nodes + end end #private diff --git a/lib/exstreme/graph_creator.ex b/lib/exstreme/graph_creator.ex index 357528d..c046296 100644 --- a/lib/exstreme/graph_creator.ex +++ b/lib/exstreme/graph_creator.ex @@ -3,18 +3,34 @@ defmodule Exstreme.GraphCreator do """ alias Exstreme.Graph + @default_length_name 20 + + @typedoc """ +  + """ @type update_map_func :: (%{key: atom} -> %{key: atom}) @doc """ """ @spec create_graph([key: term]) :: Graph.t - def create_graph(params \\ []), do: %Graph{params: params} + def create_graph(params) do + name = + :crypto.strong_rand_bytes(@default_length_name) + |> Base.url_encode64 + |> binary_part(0, @default_length_name) + create_graph(name, params) + end + + @doc """ + """ + @spec create_graph(String.t, [key: term]) :: Graph.t + def create_graph(name, params), do: %Graph{name: name, params: params} @doc """ """ @spec create_node(Graph.t, [key: term]) :: {Graph.t, atom} - def create_node(graph = %Graph{nodes: nodes}, params \\ []) do - key = next_node_key(nodes) + def create_node(graph = %Graph{name: name, nodes: nodes}, params \\ []) do + key = next_node_key(name, nodes) new_graph = update_in(graph.nodes, &(Map.put(&1, key, params))) {new_graph, key} @@ -23,8 +39,8 @@ defmodule Exstreme.GraphCreator do @doc """ """ @spec create_broadcast(Graph.t, [key: term]) :: {Graph.t, atom} - def create_broadcast(graph = %Graph{nodes: nodes}, params \\ []) do - key = next_broadcast_key(nodes) + def create_broadcast(graph = %Graph{name: name, nodes: nodes}, params \\ []) do + key = next_broadcast_key(name, nodes) new_graph = update_in(graph.nodes, &(Map.put(&1, key, params))) {new_graph, key} @@ -33,8 +49,8 @@ defmodule Exstreme.GraphCreator do @doc """ """ @spec create_funnel(Graph.t, [key: term]) :: {Graph.t, atom} - def create_funnel(graph = %Graph{nodes: nodes}, params \\ []) do - key = next_funnel_key(nodes) + def create_funnel(graph = %Graph{name: name, nodes: nodes}, params \\ []) do + key = next_funnel_key(name, nodes) new_graph = update_in(graph.nodes, &(Map.put(&1, key, params))) {new_graph, key} @@ -153,29 +169,30 @@ defmodule Exstreme.GraphCreator do end end - @spec next_node_key(%{key: atom}) :: atom - defp next_node_key(nodes) do - next_key(nodes, "n") + @spec next_node_key(String.t, %{key: atom}) :: atom + defp next_node_key(name, nodes) do + next_key(name, nodes, "n") end - @spec next_broadcast_key(%{key: atom}) :: atom - defp next_broadcast_key(nodes) do - next_key(nodes, "b") + @spec next_broadcast_key(String.t, %{key: atom}) :: atom + defp next_broadcast_key(name, nodes) do + next_key(name, nodes, "b") end - @spec next_funnel_key(%{key: atom}) :: atom - defp next_funnel_key(nodes) do - next_key(nodes, "f") + @spec next_funnel_key(String.t, %{key: atom}) :: atom + defp next_funnel_key(name, nodes) do + next_key(name, nodes, "f") end - @spec next_key(%{key: atom}, String.t) :: atom - defp next_key(map, letter) do + @spec next_key(String.t, %{key: atom}, String.t) :: atom + defp next_key(name, map, letter) do + prefix = "#{letter}_#{name}_" count = map |> Map.keys |> Enum.map(&Atom.to_string/1) - |> Enum.filter(fn(str) -> String.starts_with?(str, letter) end) + |> Enum.filter(&(String.starts_with?(&1, prefix))) |> Enum.count - String.to_atom("#{letter}#{count + 1}") + String.to_atom("#{prefix}#{count + 1}") end end diff --git a/lib/exstreme/graph_validator.ex b/lib/exstreme/graph_validator.ex index 07d9883..06a2b20 100644 --- a/lib/exstreme/graph_validator.ex +++ b/lib/exstreme/graph_validator.ex @@ -3,9 +3,13 @@ defmodule Exstreme.GraphValidator do @moduledoc """ """ + @typedoc """ + """ + @type error :: {:error, String.t} + @doc """ """ - @spec validate(Graph.t) :: :ok | {:error, []} + @spec validate(Graph.t) :: :ok | error def validate(graph) do with :ok <- validate_must_have_connections(graph), :ok <- validate_start_nodes(graph), @@ -15,18 +19,18 @@ defmodule Exstreme.GraphValidator do # private - @spec validate_must_have_connections(Graph.t) :: :ok | {:error, []} + @spec validate_must_have_connections(Graph.t) :: :ok | error defp validate_must_have_connections(graph) do nodes_amount = Graph.count_nodes(graph) connections_amount = Graph.count_connections(graph) if nodes_amount > 0 and connections_amount == 0 do - {:error, []} + {:error, ""} else :ok end end - @spec validate_start_nodes(Graph.t) :: :ok | {:error, []} + @spec validate_start_nodes(Graph.t) :: :ok | error defp validate_start_nodes(graph) do start_nodes = Graph.find_start_node(graph) @@ -35,28 +39,28 @@ defmodule Exstreme.GraphValidator do do: :ok end - @spec validate_should_start_with_one_node([atom, ...]) :: :ok | {:error, []} + @spec validate_should_start_with_one_node([atom, ...]) :: :ok | error defp validate_should_start_with_one_node([_]), do: :ok - defp validate_should_start_with_one_node(_), do: {:error, []} + defp validate_should_start_with_one_node(_), do: {:error, ""} - @spec validate_should_start_with_node([atom, ...]) :: :ok | {:error, []} + @spec validate_should_start_with_node([atom, ...]) :: :ok | error defp validate_should_start_with_node([start_node]) do start_char = start_node |> Atom.to_string |> String.first if start_char == "n" do :ok else - {:error, []} + {:error, ""} end end - @spec validate_connectivity(Graph.t) :: :ok | {:error, []} + @spec validate_connectivity(Graph.t) :: :ok | error defp validate_connectivity(graph) do stats = Graph.connections_stats(graph) if stats[:unconnected] == nil do :ok else - {:error, []} + {:error, ""} end end end diff --git a/mix.exs b/mix.exs index 9ad9637..75c9a80 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Exstreme.Mixfile do def project do [app: :exstreme, version: "0.0.1", - elixir: "~> 1.2", + elixir: "~> 1.3", elixirc_paths: elixirc_paths(Mix.env), build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, diff --git a/mix.lock b/mix.lock index 4e40a5a..2ed836d 100644 --- a/mix.lock +++ b/mix.lock @@ -1 +1 @@ -%{"dialyxir": {:hex, :dialyxir, "0.3.3"}} +%{"dialyxir": {:hex, :dialyxir, "0.3.3", "2f8bb8ab4e17acf4086cae847bd385c0f89296d3e3448dc304c26bfbe4b46cb4", [:mix], []}} diff --git a/test/exstreme/graph_builder_test.exs b/test/exstreme/graph_builder_test.exs index fc61daa..f1954f1 100644 --- a/test/exstreme/graph_builder_test.exs +++ b/test/exstreme/graph_builder_test.exs @@ -5,7 +5,7 @@ defmodule Exstreme.GraphBuilderTest do alias Exstreme.Graph doctest Exstreme.GraphBuilder - test "creates a graph" do + test "creates a graph and check built params" do graph_built = GraphBuilder.build(create_graph) assert graph_built != create_graph Enum.each(graph_built.nodes, fn({_, params}) -> diff --git a/test/exstreme/graph_creator_test.exs b/test/exstreme/graph_creator_test.exs index 96321b7..026261c 100644 --- a/test/exstreme/graph_creator_test.exs +++ b/test/exstreme/graph_creator_test.exs @@ -5,87 +5,97 @@ defmodule Exstreme.GraphCreatorTest do alias Exstreme.Graph doctest Exstreme.GraphCreator - test "creates a valid graph struct" do - compare_graph = %Graph{ - nodes: %{n1: params, n2: params}, - connections: %{n1: :n2} - } - assert create_graph == compare_graph + describe "when the graph is valid" do + test "creates a valid graph struct" do + compare_graph = %Graph{ + name: graph_name, + nodes: %{n_demo_1: params, n_demo_2: params}, + connections: %{n_demo_1: :n_demo_2} + } + assert create_graph == compare_graph + end end - test "throws an error when adding again the relation" do - assert_raise ArgumentError, fn -> - create_graph - |> GraphCreator.add_connection(:n1, :n2) + describe "when the graph is invalid" do + test "throws an error when adding again the relation" do + assert_raise ArgumentError, fn -> + create_graph + |> GraphCreator.add_connection(:n1, :n2) + end end - end - test "throws an error when adding again a self relation" do - assert_raise FunctionClauseError, fn -> - create_graph - |> GraphCreator.add_connection(:n1, :n1) + test "throws an error when adding again a self relation" do + assert_raise FunctionClauseError, fn -> + create_graph + |> GraphCreator.add_connection(:n1, :n1) + end end - end - test "throws an error when adding a cicle relation" do - assert_raise ArgumentError, "there is already a connection like that", fn -> - create_graph - |> GraphCreator.add_connection(:n2, :n1) + test "throws an error when adding a cicle relation" do + assert_raise ArgumentError, "there is already a connection like that", fn -> + create_graph + |> GraphCreator.add_connection(:n2, :n1) + end end end - test "can create n3 and add a relation between n2 and n3" do - compare_graph = %Graph{ - nodes: %{n1: params, n2: params, n3: params}, - connections: %{n1: :n2, n2: :n3} - } + describe "adding nodes" do + test "can create n3 and add a relation between n2 and n3" do + compare_graph = %Graph{ + name: graph_name, + nodes: %{n1: params, n2: params, n3: params}, + connections: %{n1: :n2, n2: :n3} + } - {graph, n3} = GraphCreator.create_node(create_graph, params) - new_graph = GraphCreator.add_connection(graph, :n2, n3) + {graph, n3} = GraphCreator.create_node(create_graph, params) + new_graph = GraphCreator.add_connection(graph, :n2, n3) - assert new_graph == compare_graph - end + assert new_graph == compare_graph + end - test "can add a broadcast an many nodes to the broadcast" do - compare_graph = %Graph{ - nodes: %{n1: params, n2: params, b1: params_broadcast, n3: params, n4: params}, - connections: %{n1: :n2, n2: :b1, b1: [:n4, :n3]} - } + test "can add a broadcast an many nodes to the broadcast" do + compare_graph = %Graph{ + name: graph_name, + nodes: %{n1: params, n2: params, b1: params_broadcast, n3: params, n4: params}, + connections: %{n1: :n2, n2: :b1, b1: [:n4, :n3]} + } - {graph, b1} = GraphCreator.create_broadcast(create_graph, params_broadcast) - {graph, n3} = GraphCreator.create_node(graph, params) - {graph, n4} = GraphCreator.create_node(graph, params) + {graph, b1} = GraphCreator.create_broadcast(create_graph, params_broadcast) + {graph, n3} = GraphCreator.create_node(graph, params) + {graph, n4} = GraphCreator.create_node(graph, params) - new_graph = - graph - |> GraphCreator.add_connection(:n2, b1) - |> GraphCreator.add_connection(b1, n3) - |> GraphCreator.add_connection(b1, n4) + new_graph = + graph + |> GraphCreator.add_connection(:n2, b1) + |> GraphCreator.add_connection(b1, n3) + |> GraphCreator.add_connection(b1, n4) - assert new_graph == compare_graph - end + assert new_graph == compare_graph + end - test "can add a funnel" do - compare_graph = %Graph{ - nodes: %{n1: params, n2: params, b1: params_broadcast, n3: params, n4: params, f1: params_funnel, n5: params}, - connections: %{n1: :n2, n2: :b1, b1: [:n4, :n3], n3: :f1, n4: :f1, f1: :n5} - } + test "can add a funnel" do + compare_graph = %Graph{ + name: graph_name, + nodes: %{n1: params, n2: params, b1: params_broadcast, n3: params, n4: params, f1: params_funnel, n5: params}, + connections: %{n1: :n2, n2: :b1, b1: [:n4, :n3], n3: :f1, n4: :f1, f1: :n5} + } - {graph, b1} = GraphCreator.create_broadcast(create_graph, params_broadcast) - {graph, n3} = GraphCreator.create_node(graph, params) - {graph, n4} = GraphCreator.create_node(graph, params) - {graph, f1} = GraphCreator.create_funnel(graph, params_funnel) - {graph, n5} = GraphCreator.create_node(graph, params) + {graph, b1} = GraphCreator.create_broadcast(create_graph, params_broadcast) + {graph, n3} = GraphCreator.create_node(graph, params) + {graph, n4} = GraphCreator.create_node(graph, params) + {graph, f1} = GraphCreator.create_funnel(graph, params_funnel) + {graph, n5} = GraphCreator.create_node(graph, params) - new_graph = - graph - |> GraphCreator.add_connection(:n2, b1) - |> GraphCreator.add_connection(b1, n3) - |> GraphCreator.add_connection(b1, n4) - |> GraphCreator.add_connection(n3, f1) - |> GraphCreator.add_connection(n4, f1) - |> GraphCreator.add_connection(f1, n5) + new_graph = + graph + |> GraphCreator.add_connection(:n2, b1) + |> GraphCreator.add_connection(b1, n3) + |> GraphCreator.add_connection(b1, n4) + |> GraphCreator.add_connection(n3, f1) + |> GraphCreator.add_connection(n4, f1) + |> GraphCreator.add_connection(f1, n5) - assert new_graph == compare_graph + assert new_graph == compare_graph + end end end diff --git a/test/support/common.ex b/test/support/common.ex index d73c7e2..0652466 100644 --- a/test/support/common.ex +++ b/test/support/common.ex @@ -4,8 +4,10 @@ defmodule Exstreme.Common do using do quote do + def graph_name, do: "demo" + def graph_many_nodes do - graph = GraphCreator.create_graph([]) + graph = GraphCreator.create_graph(graph_name, []) {graph, n1} = GraphCreator.create_node(graph, params) {graph, n2} = GraphCreator.create_node(graph, params) {graph, b1} = GraphCreator.create_broadcast(graph, params_broadcast) @@ -27,29 +29,29 @@ defmodule Exstreme.Common do # invalid graphs def graph_one_node_no_connections do - graph = GraphCreator.create_graph([]) + graph = GraphCreator.create_graph(graph_name, []) {graph, _n1} = GraphCreator.create_node(graph, params) graph end - def graph_no_connections, do: GraphCreator.create_graph([]) + def graph_no_connections, do: GraphCreator.create_graph(graph_name, []) def graph_start_with_broadcast do - graph = GraphCreator.create_graph([]) + graph = GraphCreator.create_graph(graph_name, []) {graph, b1} = GraphCreator.create_broadcast(graph, params_broadcast) {graph, n1} = GraphCreator.create_node(graph, params) GraphCreator.add_connection(graph, b1, n1) end def graph_start_with_funnnel do - graph = GraphCreator.create_graph([]) + graph = GraphCreator.create_graph(graph_name, []) {graph, f1} = GraphCreator.create_funnel(graph, params_funnel) {graph, n1} = GraphCreator.create_node(graph, params) GraphCreator.add_connection(graph, f1, n1) end def graph_unconnected_nodes do - graph = GraphCreator.create_graph([]) + graph = GraphCreator.create_graph(graph_name, []) {graph, n1} = GraphCreator.create_node(graph, params) {graph, n2} = GraphCreator.create_node(graph, params) {graph, _n3} = GraphCreator.create_node(graph, params) @@ -57,7 +59,7 @@ defmodule Exstreme.Common do end defp create_graph do - graph = GraphCreator.create_graph([]) + graph = GraphCreator.create_graph(graph_name, []) {graph, n1} = GraphCreator.create_node(graph, params) {graph, n2} = GraphCreator.create_node(graph, params) GraphCreator.add_connection(graph, n1, n2)