Skip to content

Commit

Permalink
[JET-2098] refactor: fetch dynamic repo by behavior (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
vanppo authored Oct 25, 2024
1 parent ab9ff17 commit c468b39
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 104 deletions.
4 changes: 3 additions & 1 deletion config/prod.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Config

config :resource_kit, ResourceKit.Deref, adapter: {ResourceKit.Deref.Local, directory: "/data"}
config :resource_kit, ResourceKit.Deref, adapter: ResourceKit.Deref.Local

config :resource_kit, ResourceKit.Deref.Local, directory: "/data"

config :resource_kit, ResourceKit.Repo, adapter: ResourceKitCLI.Repo

Expand Down
5 changes: 3 additions & 2 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Config

config :resource_kit, ResourceKit.Deref,
adapter: {ResourceKit.Deref.Local, directory: "test/fixtures"}
config :resource_kit, ResourceKit.Deref, adapter: ResourceKit.Deref.Local

config :resource_kit, ResourceKit.Deref.Local, directory: "test/fixtures"

config :resource_kit, ResourceKit.Repo, adapter: ResourceKitCLI.Repo

Expand Down
31 changes: 13 additions & 18 deletions lib/resource_kit/deref.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ defmodule ResourceKit.Deref do
## Options
* `adapter` - A module that implemented the deref behaviour. Or `{adapter, opts}` if the adapter has options.
* `adapter` - A module that implemented the deref behaviour.
"""

alias ResourceKit.Types

alias ResourceKit.Deref.Context
alias ResourceKit.Schema.Ref

@conf Application.compile_env!(:resource_kit, [__MODULE__, :adapter])
@adapter if is_tuple(@conf), do: elem(@conf, 0), else: @conf
@opts if is_tuple(@conf), do: elem(@conf, 1), else: []
@adapter Application.compile_env!(:resource_kit, [__MODULE__, :adapter])

@callback dynamic_repo(ref :: Ref.t()) ::
{:ok, ResourceKit.Repo.dynamic_repo()} | {:error, Types.error()}

@callback resolve(ref :: Ref.t(), ctx :: Context.t()) ::
{:ok, Ref.t()} | {:error, Types.error()}

@callback fetch(ref :: Ref.t(), ctx :: Context.t()) ::
{:ok, Types.json_value()} | {:error, Types.error()}
@callback fetch(ref :: Ref.t()) :: {:ok, Types.json_value()} | {:error, Types.error()}

defmacro __using__(_args) do
quote location: :keep do
Expand Down Expand Up @@ -53,18 +53,13 @@ defmodule ResourceKit.Deref do
@spec adapter() :: module()
def adapter, do: @adapter

@spec resolve(ref :: Ref.t(), ctx :: Context.t()) :: {:ok, Ref.t()} | {:error, Types.error()}
def resolve(ref, ctx) do
adapter().resolve(ref, put_options(ctx))
end
@spec dynamic_repo(ref :: Ref.t()) ::
{:ok, ResourceKit.Repo.dynamic_repo()} | {:error, Types.error()}
def dynamic_repo(ref), do: adapter().dynamic_repo(ref)

@spec fetch(ref :: Ref.t(), ctx :: Context.t()) ::
{:ok, Types.json_value()} | {:error, Types.error()}
def fetch(ref, ctx) do
adapter().fetch(ref, put_options(ctx))
end
@spec fetch(ref :: Ref.t()) :: {:ok, Types.json_value()} | {:error, Types.error()}
def fetch(ref), do: adapter().fetch(ref)

defp put_options(ctx) do
%{ctx | opts: @opts}
end
@spec resolve(ref :: Ref.t(), ctx :: Context.t()) :: {:ok, Ref.t()} | {:error, Types.error()}
def resolve(ref, ctx), do: adapter().resolve(ref, ctx)
end
1 change: 0 additions & 1 deletion lib/resource_kit/deref/context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,5 @@ defmodule ResourceKit.Deref.Context do

typedstruct do
field :current, Ref.t(), enforce: true
field :opts, keyword(), default: []
end
end
12 changes: 9 additions & 3 deletions lib/resource_kit/deref/local.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ defmodule ResourceKit.Deref.Local do

use ResourceKit.Deref

alias ResourceKit.Deref.Context
alias ResourceKit.Schema.Ref

@opts Application.compile_env(:resource_kit, __MODULE__, [])

@impl ResourceKit.Deref
def dynamic_repo(%Ref{}) do
{:ok, ResourceKit.Repo.adapter()}
end

@impl ResourceKit.Deref
def fetch(%Ref{uri: %URI{} = uri}, %Context{opts: opts}) do
directory = Keyword.get_lazy(opts, :directory, &File.cwd!/0)
def fetch(%Ref{uri: %URI{} = uri}) do
directory = Keyword.get_lazy(@opts, :directory, &File.cwd!/0)
file = uri.path |> Path.relative() |> Path.expand(directory)

with {:ok, content} <- fetch_file(file),
Expand Down
19 changes: 9 additions & 10 deletions lib/resource_kit/pipeline/compile/deref.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ defmodule ResourceKit.Pipeline.Compile.Deref do

import ResourceKit.Guards

alias ResourceKit.Deref.Context, as: DerefContext
alias ResourceKit.Pipeline.Compile.Token
alias ResourceKit.Schema.Ref

Expand All @@ -25,33 +24,33 @@ defmodule ResourceKit.Pipeline.Compile.Deref do
@impl Pluggable
def call(%Token{halted: true} = token, _opts), do: token

def call(%Token{action: action, context: ctx} = token, _opts) do
with {:ok, action} <- deref_schema(action, ctx),
{:ok, action} <- deref_returning_schema(action, ctx) do
def call(%Token{action: action} = token, _opts) do
with {:ok, action} <- deref_schema(action),
{:ok, action} <- deref_returning_schema(action) do
Token.put_assign(token, :action, action)
else
{:error, reason} ->
Token.put_error(token, reason)
end
end

defp deref_schema(%{"schema" => ref} = action, ctx) when is_ref(ref) do
defp deref_schema(%{"schema" => ref} = action) when is_ref(ref) do
with {:ok, ref} <- cast_ref(ref),
{:ok, schema} <- ResourceKit.Deref.fetch(ref, %DerefContext{current: ctx.current}) do
{:ok, schema} <- ResourceKit.Deref.fetch(ref) do
{:ok, Map.put(action, "schema", schema)}
end
end

defp deref_schema(action, _ctx), do: {:ok, action}
defp deref_schema(action), do: {:ok, action}

defp deref_returning_schema(%{"returning_schema" => ref} = action, ctx) when is_ref(ref) do
defp deref_returning_schema(%{"returning_schema" => ref} = action) when is_ref(ref) do
with {:ok, ref} <- cast_ref(ref),
{:ok, returning} <- ResourceKit.Deref.fetch(ref, %DerefContext{current: ctx.current}) do
{:ok, returning} <- ResourceKit.Deref.fetch(ref) do
{:ok, Map.put(action, "returning_schema", returning)}
end
end

defp deref_returning_schema(action, _ctx), do: {:ok, action}
defp deref_returning_schema(action), do: {:ok, action}

defp cast_ref(ref) do
ref |> Ref.changeset() |> Ecto.Changeset.apply_action(:insert)
Expand Down
13 changes: 6 additions & 7 deletions lib/resource_kit/pipeline/compile/preload_reference.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ defmodule ResourceKit.Pipeline.Compile.PreloadReference do

@behaviour Pluggable

alias ResourceKit.Deref.Context, as: DerefContext
alias ResourceKit.Pipeline.Compile.Token
alias ResourceKit.Schema.Column
alias ResourceKit.Schema.Ref
Expand Down Expand Up @@ -58,31 +57,31 @@ defmodule ResourceKit.Pipeline.Compile.PreloadReference do
defp preload_column(column, context, references)
when is_struct(column, Column.Belongs) or is_struct(column, Column.Has) do
with {:ok, schema, references} <-
resolve_association_schema(column.association_schema, context, references),
resolve_association_schema(column.association_schema, references),
{:ok, _schema, references} <-
preload_schema(schema, update_context(column.association_schema, context), references) do
{:ok, column, references}
end
end

defp resolve_association_schema(%Ref{} = ref, context, references) do
case resolve(ref, context, references) do
defp resolve_association_schema(%Ref{} = ref, references) do
case resolve(ref, references) do
{:ok, schema} -> {:ok, schema, Map.put_new(references, ref, schema)}
{:error, reason} -> {:error, reason}
end
end

defp resolve_association_schema(schema, _context, references), do: {:ok, schema, references}
defp resolve_association_schema(schema, references), do: {:ok, schema, references}

defp update_context(%Ref{uri: uri}, context) do
%{context | current: uri}
end

defp update_context(_schema, context), do: context

defp resolve(ref, context, references) do
defp resolve(ref, references) do
with :error <- Map.fetch(references, ref) do
ResourceKit.Utils.resolve_association_schema(ref, %DerefContext{current: context.current})
ResourceKit.Utils.resolve_association_schema(ref)
end
end
end
14 changes: 5 additions & 9 deletions lib/resource_kit/pipeline/execute/build_params.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ defmodule ResourceKit.Pipeline.Execute.BuildParams do

@behaviour Pluggable

alias ResourceKit.Deref.Context, as: DerefContext
alias ResourceKit.JSONPointer.Absolute
alias ResourceKit.JSONPointer.Relative
alias ResourceKit.Pipeline.Execute.Token
Expand Down Expand Up @@ -86,7 +85,7 @@ defmodule ResourceKit.Pipeline.Execute.BuildParams do
%HasColumn{association_schema: schema} = definition,
scope
) do
with {:ok, definition} <- fetch_association_definition(definition, scope.context, assoc.name),
with {:ok, definition} <- fetch_association_definition(definition, assoc.name),
{:ok, _value, location} <- __MODULE__.Scope.resolve(pointer, scope),
scope = %{scope | current_value: scope.root_value, location: location},
scope = update_context(schema, scope),
Expand All @@ -103,7 +102,7 @@ defmodule ResourceKit.Pipeline.Execute.BuildParams do
%HasColumn{association_schema: schema} = definition,
scope
) do
with {:ok, definition} <- fetch_association_definition(definition, scope.context, assoc.name),
with {:ok, definition} <- fetch_association_definition(definition, assoc.name),
{:ok, _value, location} <- __MODULE__.Scope.resolve(pointer, scope),
scope = %{scope | location: location},
scope = update_context(schema, scope),
Expand All @@ -124,7 +123,7 @@ defmodule ResourceKit.Pipeline.Execute.BuildParams do
%HasColumn{association_schema: schema} = definition,
scope
) do
with {:ok, definition} <- fetch_association_definition(definition, scope.context, assoc.name),
with {:ok, definition} <- fetch_association_definition(definition, assoc.name),
scope = %{scope | current_value: value, location: []},
scope = update_context(schema, scope),
{:ok, value} <- handle_changeset(assoc.changeset, definition, scope) do
Expand All @@ -141,11 +140,8 @@ defmodule ResourceKit.Pipeline.Execute.BuildParams do
end
end

defp fetch_association_definition(%HasColumn{association_schema: schema}, context, name) do
with {:ok, schema} <-
ResourceKit.Utils.resolve_association_schema(schema, %DerefContext{
current: context.current
}) do
defp fetch_association_definition(%HasColumn{association_schema: schema}, name) do
with {:ok, schema} <- ResourceKit.Utils.resolve_association_schema(schema) do
case Enum.find(schema.columns, &(&1.name === name)) do
%HasColumn{} = definition -> {:ok, definition}
%LiteralColumn{} -> {:error, {"is not an association definition", validation: :custom}}
Expand Down
9 changes: 4 additions & 5 deletions lib/resource_kit/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ defmodule ResourceKit.Utils do

alias ResourceKit.Types

alias ResourceKit.Deref.Context, as: DerefContext
alias ResourceKit.Schema.Column
alias ResourceKit.Schema.Ref
alias ResourceKit.Schema.Schema
Expand Down Expand Up @@ -102,17 +101,17 @@ defmodule ResourceKit.Utils do
end)
end

@spec resolve_association_schema(ref_or_schema :: Ref.t() | Schema.t(), ctx :: DerefContext.t()) ::
@spec resolve_association_schema(ref_or_schema :: Ref.t() | Schema.t()) ::
{:ok, Schema.t()} | {:error, Ecto.Changeset.t() | Types.error()}
def resolve_association_schema(%Ref{} = ref, ctx) do
with {:ok, params} <- ResourceKit.Deref.fetch(ref, ctx) do
def resolve_association_schema(%Ref{} = ref) do
with {:ok, params} <- ResourceKit.Deref.fetch(ref) do
params
|> Schema.changeset()
|> Ecto.Changeset.apply_action(:insert)
end
end

def resolve_association_schema(%Schema{} = schema, _ctx), do: {:ok, schema}
def resolve_association_schema(%Schema{} = schema), do: {:ok, schema}

@spec __root__() :: binary()
def __root__, do: "root"
Expand Down
5 changes: 0 additions & 5 deletions lib/resource_kit_cli/plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@ defmodule ResourceKitCLI.Plug do
plug Plug.Logger
plug Plug.Parsers, parsers: [{:json, json_decoder: Jason}]
plug :match
plug :put_dynamic
plug :dispatch
plug Sentry.PlugContext

post "/rpc/actions", to: ResourceKitPlug.Router

defp put_dynamic(conn, _opts) do
ResourceKitPlug.Router.put_dynamic(conn, ResourceKit.Repo.adapter())
end
end
23 changes: 17 additions & 6 deletions lib/resource_kit_plug/controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ defmodule ResourceKitPlug.Controller do
alias ResourceKit.Schema.Ref
alias ResourceKit.Schema.Request

@dialyzer {:no_match, fetch_dynamic_repo: 1}

for type <- [:insert, :list] do
@spec unquote(type)(request :: map(), ctx :: PhxJsonRpc.Router.Context.t()) :: map()
def unquote(type)(request, %PhxJsonRpc.Router.Context{
meta_data: %{dynamic_repo: dynamic_repo}
}) do
def unquote(type)(request, _ctx) do
with {:ok, request} <- cast_request(request),
{:ok, action} <- fetch_action(request),
{:ok, dynamic_repo} <- fetch_dynamic_repo(request),
{:ok, result} <- run(request, unquote(type), action, dynamic_repo: dynamic_repo) do
result
end
Expand Down Expand Up @@ -42,9 +43,7 @@ defmodule ResourceKitPlug.Controller do
end

defp fetch_action(%Request{uri: uri}) do
alias ResourceKit.Deref.Context

case ResourceKit.Deref.fetch(%Ref{uri: uri}, %Context{current: %Ref{uri: uri}}) do
case ResourceKit.Deref.fetch(%Ref{uri: uri}) do
{:ok, action} ->
{:ok, action}

Expand All @@ -55,6 +54,18 @@ defmodule ResourceKitPlug.Controller do
end
end

defp fetch_dynamic_repo(%Request{uri: uri}) do
case ResourceKit.Deref.dynamic_repo(%Ref{uri: uri}) do
{:ok, dynamic_repo} ->
{:ok, dynamic_repo}

{:error, {message, options}} ->
raise PhxJsonRpc.Error.InvalidParams,
message: "can't fetch dynamic repo due to: '#{message}'",
data: Map.new(options)
end
end

defp run(%Request{uri: uri, params: params}, type, action, opts) do
case apply(ResourceKit, type, [action, params, Keyword.put(opts, :root, uri)]) do
{:ok, result} ->
Expand Down
41 changes: 4 additions & 37 deletions lib/resource_kit_plug/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,13 @@ defmodule ResourceKitPlug.Router do

use Plug.Builder

@dynamic_private :resource_kit_plug_dynamic_repo

@spec put_dynamic(conn :: Plug.Conn.t(), dynamic_repo :: ResourceKit.Repo.dynamic_repo()) ::
Plug.Conn.t()
def put_dynamic(%Plug.Conn{} = conn, dynamic_repo) do
put_private(conn, @dynamic_private, dynamic_repo)
end

@impl Plug
def call(%Plug.Conn{private: private} = conn, _opts) do
def call(%Plug.Conn{} = conn, _opts) do
import PhxJsonRpcWeb.Views.Helpers

case Map.fetch(private, @dynamic_private) do
{:ok, dynamic_repo} ->
conn
|> put_resp_header("content-type", "application/json; charset=utf-8")
|> send_json(
conn.params
|> ResourceKitPlug.Service.handle(%{dynamic_repo: dynamic_repo})
|> render_json()
)

:error ->
raise RuntimeError, """
Could not get dynamic repo from conn, please set dynamic repo before ResourceKitPlug.Router:
```
defmodule MyApp.Plug do
use Plug.Builder
plug :put_dynamic
post "/rpc", to: ResourceKitPlug.Router
defp put_dynamic(conn, _opts) do
ResourceKitPlug.Router.put_dynamic_repo(conn, MyApp.Repo)
end
end
```
"""
end
conn
|> put_resp_header("content-type", "application/json; charset=utf-8")
|> send_json(conn.params |> ResourceKitPlug.Service.handle() |> render_json())
end

defp send_json(%Plug.Conn{} = conn, data) do
Expand Down

0 comments on commit c468b39

Please sign in to comment.