diff --git a/config/prod.exs b/config/prod.exs index 1524332..78136e5 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -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 diff --git a/config/test.exs b/config/test.exs index be4968a..57a2acd 100644 --- a/config/test.exs +++ b/config/test.exs @@ -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 diff --git a/lib/resource_kit/deref.ex b/lib/resource_kit/deref.ex index d0531f1..66d21e6 100644 --- a/lib/resource_kit/deref.ex +++ b/lib/resource_kit/deref.ex @@ -4,7 +4,7 @@ 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 @@ -12,15 +12,15 @@ defmodule ResourceKit.Deref do 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 @@ -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 diff --git a/lib/resource_kit/deref/context.ex b/lib/resource_kit/deref/context.ex index fe2b5b0..173f77d 100644 --- a/lib/resource_kit/deref/context.ex +++ b/lib/resource_kit/deref/context.ex @@ -7,6 +7,5 @@ defmodule ResourceKit.Deref.Context do typedstruct do field :current, Ref.t(), enforce: true - field :opts, keyword(), default: [] end end diff --git a/lib/resource_kit/deref/local.ex b/lib/resource_kit/deref/local.ex index 9b27b7a..aab4aae 100644 --- a/lib/resource_kit/deref/local.ex +++ b/lib/resource_kit/deref/local.ex @@ -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), diff --git a/lib/resource_kit/pipeline/compile/deref.ex b/lib/resource_kit/pipeline/compile/deref.ex index 2e841c3..2b2e955 100644 --- a/lib/resource_kit/pipeline/compile/deref.ex +++ b/lib/resource_kit/pipeline/compile/deref.ex @@ -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 @@ -25,9 +24,9 @@ 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} -> @@ -35,23 +34,23 @@ defmodule ResourceKit.Pipeline.Compile.Deref do 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) diff --git a/lib/resource_kit/pipeline/compile/preload_reference.ex b/lib/resource_kit/pipeline/compile/preload_reference.ex index 71f01d5..6378b0e 100644 --- a/lib/resource_kit/pipeline/compile/preload_reference.ex +++ b/lib/resource_kit/pipeline/compile/preload_reference.ex @@ -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 @@ -58,21 +57,21 @@ 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} @@ -80,9 +79,9 @@ defmodule ResourceKit.Pipeline.Compile.PreloadReference do 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 diff --git a/lib/resource_kit/pipeline/execute/build_params.ex b/lib/resource_kit/pipeline/execute/build_params.ex index bad396c..54b0fa0 100644 --- a/lib/resource_kit/pipeline/execute/build_params.ex +++ b/lib/resource_kit/pipeline/execute/build_params.ex @@ -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 @@ -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), @@ -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), @@ -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 @@ -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}} diff --git a/lib/resource_kit/utils.ex b/lib/resource_kit/utils.ex index 1753ea2..d26865b 100644 --- a/lib/resource_kit/utils.ex +++ b/lib/resource_kit/utils.ex @@ -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 @@ -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" diff --git a/lib/resource_kit_cli/plug.ex b/lib/resource_kit_cli/plug.ex index 3b62c6b..9abd87e 100644 --- a/lib/resource_kit_cli/plug.ex +++ b/lib/resource_kit_cli/plug.ex @@ -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 diff --git a/lib/resource_kit_plug/controller.ex b/lib/resource_kit_plug/controller.ex index 1e31fe8..ba4206d 100644 --- a/lib/resource_kit_plug/controller.ex +++ b/lib/resource_kit_plug/controller.ex @@ -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 @@ -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} @@ -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} -> diff --git a/lib/resource_kit_plug/router.ex b/lib/resource_kit_plug/router.ex index 06c3c34..871350d 100644 --- a/lib/resource_kit_plug/router.ex +++ b/lib/resource_kit_plug/router.ex @@ -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