Skip to content

Commit

Permalink
Isolate state management (elixir-grpc#19)
Browse files Browse the repository at this point in the history
* interface service module, and move agent struct to state

* merge lookup into state

* state mutation goes to state, and un-nest Builder.Util

* code cleanup and organization

* cleanup and simplify
  • Loading branch information
mjheilmann authored Dec 26, 2023
1 parent cec8410 commit a86e804
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 315 deletions.
21 changes: 10 additions & 11 deletions lib/grpc_reflection/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,57 +22,56 @@ defmodule GrpcReflection.Server do
quote do
@cfg {__MODULE__, unquote(services)}

alias GrpcReflection.Service

@doc """
Get the current list of configured services
"""
@spec list_services :: list(binary)
def list_services do
GrpcReflection.Service.Agent.list_services(@cfg)
Service.list_services(@cfg)
end

@doc """
Get the reflection reponse containing the given symbol, if it is exposed by a configured service
"""
@spec get_by_symbol(binary()) :: {:ok, GrpcReflection.descriptor_t()} | {:error, binary}
def get_by_symbol(symbol) do
GrpcReflection.Service.Agent.get_by_symbol(@cfg, symbol)
Service.get_by_symbol(@cfg, symbol)
end

@doc """
Get the reflection response for the named file, if it is exposed by a configured service
"""
@spec get_by_filename(binary()) :: {:ok, GrpcReflection.descriptor_t()} | {:error, binary}
def get_by_filename(filename) do
GrpcReflection.Service.Agent.get_by_filename(@cfg, filename)
Service.get_by_filename(@cfg, filename)
end

@doc """
Get the extension numbers for the given type, if it is exposed by a configured service
"""
@spec get_extension_numbers_by_type(module()) :: {:ok, list(integer())} | {:error, binary}
def get_extension_numbers_by_type(mod) do
GrpcReflection.Service.Agent.get_extension_numbers_by_type(@cfg, mod)
Service.get_extension_numbers_by_type(@cfg, mod)
end

@doc """
Get the reflection response for the given extension, if it is exposed by a configured service
"""
@spec get_by_extension(binary()) :: {:ok, GrpcReflection.descriptor_t()} | {:error, binary}
def get_by_extension(containing_type) do
GrpcReflection.Service.Agent.get_by_extension(@cfg, containing_type)
Service.get_by_extension(@cfg, containing_type)
end

@doc """
A runtime configuration option for setting the services
"""
@spec put_services(list(module())) :: :ok | {:error, binary()}
def put_services(services) do
case GrpcReflection.Service.Builder.build_reflection_tree(services) do
%GrpcReflection.Service.Agent{} = state ->
GrpcReflection.Service.Agent.put_state(@cfg, state)

err ->
err
case Service.build_reflection_tree(services) do
{:ok, state} -> Service.put_state(@cfg, state)
err -> err
end
end

Expand Down
18 changes: 18 additions & 0 deletions lib/grpc_reflection/service.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule GrpcReflection.Service do
@moduledoc """
Primary interface to internal reflection state and logic
"""

alias GrpcReflection.Service.Agent
alias GrpcReflection.Service.Builder

defdelegate build_reflection_tree(services), to: Builder

defdelegate put_state(cfg, state), to: Agent

defdelegate list_services(cfg), to: Agent
defdelegate get_by_symbol(cfg, symbol), to: Agent
defdelegate get_by_filename(cfg, filename), to: Agent
defdelegate get_by_extension(cfg, containing_type), to: Agent
defdelegate get_extension_numbers_by_type(cfg, mod), to: Agent
end
35 changes: 13 additions & 22 deletions lib/grpc_reflection/service/agent.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,57 @@ defmodule GrpcReflection.Service.Agent do
require Logger

alias GrpcReflection.Service.Builder
alias GrpcReflection.Service.Lookup
alias GrpcReflection.Service.State

defstruct services: [], files: %{}, symbols: %{}, extensions: %{}

@type descriptor_t :: GrpcReflection.Server.descriptor_t()
@type cfg_t :: {atom(), list(atom)}
@type t :: %__MODULE__{
services: list(module()),
files: %{optional(binary()) => descriptor_t()},
symbols: %{optional(binary()) => descriptor_t()},
extensions: %{optional(binary()) => list(integer())}
}

def start_link(_, opts) do
name = Keyword.get(opts, :name)
services = Keyword.get(opts, :services)

case Builder.build_reflection_tree(services) do
%__MODULE__{} = state ->
{:ok, state} ->
Agent.start_link(fn -> state end, name: name)

err ->
Logger.error("Failed to build reflection tree: #{inspect(err)}")
Agent.start_link(fn -> %__MODULE__{} end, name: name)
Agent.start_link(fn -> %State{} end, name: name)
end
end

@spec list_services(cfg_t()) :: list(binary)
def list_services(cfg) do
name = start_agent_on_first_call(cfg)
Agent.get(name, &Lookup.lookup_services/1)
Agent.get(name, &State.lookup_services/1)
end

@spec get_by_symbol(cfg_t(), binary()) :: {:ok, descriptor_t()} | {:error, binary}
@spec get_by_symbol(cfg_t(), binary()) :: {:ok, State.descriptor_t()} | {:error, binary}
def get_by_symbol(cfg, symbol) do
name = start_agent_on_first_call(cfg)
Agent.get(name, &Lookup.lookup_symbol(symbol, &1))
Agent.get(name, &State.lookup_symbol(symbol, &1))
end

@spec get_by_filename(cfg_t(), binary()) :: {:ok, descriptor_t()} | {:error, binary}
@spec get_by_filename(cfg_t(), binary()) :: {:ok, State.descriptor_t()} | {:error, binary}
def get_by_filename(cfg, filename) do
name = start_agent_on_first_call(cfg)
Agent.get(name, &Lookup.lookup_filename(filename, &1))
Agent.get(name, &State.lookup_filename(filename, &1))
end

@spec get_by_extension(cfg_t(), binary()) :: {:ok, descriptor_t()} | {:error, binary}
@spec get_by_extension(cfg_t(), binary()) :: {:ok, State.descriptor_t()} | {:error, binary}
def get_by_extension(cfg, containing_type) do
name = start_agent_on_first_call(cfg)
Agent.get(name, &Lookup.lookup_extension(containing_type, &1))
Agent.get(name, &State.lookup_extension(containing_type, &1))
end

@spec get_extension_numbers_by_type(cfg_t(), binary()) ::
{:ok, list(integer())} | {:error, binary}
def get_extension_numbers_by_type(cfg, mod) do
name = start_agent_on_first_call(cfg)
Agent.get(name, &Lookup.lookup_extension_numbers(mod, &1))
Agent.get(name, &State.lookup_extension_numbers(mod, &1))
end

@spec put_state(cfg_t(), t()) :: :ok
def put_state(cfg, %__MODULE__{} = state) do
@spec put_state(cfg_t(), State.t()) :: :ok
def put_state(cfg, %State{} = state) do
name = start_agent_on_first_call(cfg)
Agent.update(name, fn _old_state -> state end)
end
Expand Down
Loading

0 comments on commit a86e804

Please sign in to comment.