%% @doc A %% @end -module(boto_server). -behaviour(gen_server). -vsn(1). -export([child_spec/1, graph/0, start_link/1, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, format_status/1]). %% @doc A %% @end child_spec(args) -> #{id => boto_server, start => {boto_server, start_link, [args]}, shutdown => brutal_kil, restart => permanent, type => worker}. %% @private graph() -> gen_server:call(boto_server, graph). %% @doc A %% @end start_link(Args) -> gen_server:start_link({local, boto_server}, boto_server, Args, []). %% @private init(Args) -> Graph = boto_graph:start(), Table = ets:new(boto_resolvers_config, [set, protected]), State = #{graph => Graph, resolvers => Table}, ResolverList = proplists:get_value(resolvers, Args), Resolvers = lists:flatmap(fun get_resolver/1, ResolverList), boto_graph:add_vertex(Graph, noarg), [create_vertices(R, Graph) || R <- Resolvers], [create_edges(R, Graph) || R <- Resolvers], [store_resolvers(R, Table) || R <- Resolvers], {ok, State}. %% @private handle_call(graph, _Ref, State) -> {reply, maps:get(graph, State), State}. %% @private handle_cast(_M, State) -> {noreply, State}. %% @private handle_info(_M, State) -> {noreply, State}. %% @private terminate(_R, _S) -> normal. %% @private code_change(_Vsn, State, _Extra) -> {ok, State}. %% @private format_status(Status) -> Status. get_resolver(Mod) when is_atom(Mod) -> Exports = apply(Mod, module_info, [exports]), case lists:member({resolver_init, 0}, Exports) of true -> ModConfig = apply(Mod, resolver_init, []), [{{Mod, Name}, Config} || {Name, Config} <- list_wrap(ModConfig)]; false -> %TODO: prepare error message [] end; get_resolver(_Mod) -> %TODO: prepare error message []. create_vertices({_Resolver, Config}, Graph) -> Input = proplists:get_value(input, Config), [add_vertex(I, Graph) || I <- list_wrap(Input)], Output = proplists:get_value(output, Config), [add_vertex(O, Graph) || O <- list_wrap(Output)]. add_vertex({Vertex, Nested}, Graph) -> boto_graph:add_vertex(Graph, Vertex), lists:foreach(fun(I) -> add_vertex(I, Graph) end, Nested); add_vertex(Vertex, Graph) -> boto_graph:add_vertex(Graph, Vertex). create_edges({Resolver, Config}, Graph) -> Outputs = proplists:get_value(output, Config), case proplists:get_value(input, Config) of nil -> ok; [] -> lists:foreach(fun(O) -> add_edge(O, noarg, Resolver, Graph) end, Outputs); Inputs -> [add_edge(O, I, Resolver, Graph) || O <- Outputs, I <- Inputs] end. add_edge({Output, Nested}, Input, Resolver, Graph) -> boto_graph:add_edge(Graph, Input, Output, Resolver), [add_edge(N, Output, Resolver, Graph) || N <- Nested]; add_edge(Output, Input, Resolver, Graph) -> boto_graph:add_edge(Graph, Input, Output, Resolver). store_resolvers({Resolver, Config}, Table) -> ets:insert(Table, {Resolver, Config}). list_wrap(nil) -> []; list_wrap(List) when is_list(List) -> List; list_wrap(Other) -> [Other].