diff --git a/.formatter.exs b/.formatter.exs index d2cda26..1dc2590 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,11 @@ -# Used by "mix format" +# THIS FILE IS AUTOGENERATED USING `mix ash.formatter` +# DONT MODIFY IT BY HAND +locals_without_parens = [endpoint_base: 1, entity_path: 1, field: 1, field: 2, finch: 1, path: 1] + [ - inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], + locals_without_parens: locals_without_parens, + export: [ + locals_without_parens: locals_without_parens + ] ] diff --git a/.gitignore b/.gitignore index cde78ce..7dae78e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ erl_crash.dump *.ez # Ignore package tarball (built via "mix hex.build"). -ash_api_wrapper-*.tar +ash_json_api_wrapper-*.tar # Temporary files, for example, from tests. /tmp/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..90e1393 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "workbench.colorCustomizations": { + "activityBar.background": "#041593", + "titleBar.activeBackground": "#061ECE", + "titleBar.activeForeground": "#F9F9FF" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 3316934..6e09a59 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ -# AshApiWrapper +# AshJsonApiWrapper **TODO: Add description** ## Installation If [available in Hex](https://hex.pm/docs/publish), the package can be installed -by adding `ash_api_wrapper` to your list of dependencies in `mix.exs`: +by adding `ash_json_api_wrapper` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:ash_api_wrapper, "~> 0.1.0"} + {:ash_json_api_wrapper, "~> 0.1.0"} ] end ``` Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) and published on [HexDocs](https://hexdocs.pm). Once published, the docs can -be found at [https://hexdocs.pm/ash_api_wrapper](https://hexdocs.pm/ash_api_wrapper). +be found at [https://hexdocs.pm/ash_json_api_wrapper](https://hexdocs.pm/ash_json_api_wrapper). diff --git a/lib/ash_api_wrapper.ex b/lib/ash_api_wrapper.ex deleted file mode 100644 index 7f48fba..0000000 --- a/lib/ash_api_wrapper.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule AshApiWrapper do - @moduledoc """ - Documentation for `AshApiWrapper`. - """ - - @doc """ - Hello world. - - ## Examples - - iex> AshApiWrapper.hello() - :world - - """ - def hello do - :world - end -end diff --git a/lib/ash_json_api_wrapper.ex b/lib/ash_json_api_wrapper.ex new file mode 100644 index 0000000..5684eb3 --- /dev/null +++ b/lib/ash_json_api_wrapper.ex @@ -0,0 +1,95 @@ +defmodule AshJsonApiWrapper do + @moduledoc """ + """ + + alias Ash.Dsl.Extension + + @spec endpoint_base(Ash.Resource.t()) :: String.t() | nil + def endpoint_base(resource) do + Extension.get_opt(resource, [:json_api_wrapper, :endpoints], :base, nil, false) + end + + @spec finch(Ash.Resource.t()) :: module | nil + def finch(resource) do + Extension.get_opt(resource, [:json_api_wrapper], :finch, nil, false) + end + + @spec before_request(Ash.Resource.t()) :: module | nil + def before_request(resource) do + Extension.get_opt(resource, [:json_api_wrapper], :before_request, nil, false) + end + + @spec field(Ash.Resource.t(), atom) :: AshJsonApiWrapper.Field.t() | nil + def field(resource, name) do + resource + |> fields() + |> Enum.find(&(&1.name == name)) + end + + @spec fields(Ash.Resource.t()) :: list(AshJsonApiWrapper.Field.t()) + def fields(resource) do + Extension.get_entities(resource, [:json_api_wrapper, :fields]) + end + + @spec endpoint(Ash.Resource.t(), atom) :: AshJsonApiWrapper.Endpoint.t() | nil + def endpoint(resource, action) do + default_endpoint = AshJsonApiWrapper.Endpoint.default(endpoint_base(resource)) + + resource + |> Extension.get_entities([:json_api_wrapper, :endpoints]) + |> Enum.find(&(&1.action == action)) + |> case do + nil -> + default_endpoint + endpoint -> + if default_endpoint.path && endpoint.path do + %{endpoint | path: default_endpoint.path <> endpoint.path} + else + %{endpoint | path: endpoint.path || default_endpoint.path} + end + end + end + + @spec endpoints(Ash.Resource.t()) :: list(AshJsonApiWrapper.Endpoint.t()) + def endpoints(resource) do + Extension.get_entities(resource, [:json_api_wrapper, :endpoints]) + end + + @spec set_body_param(query_or_changeset, String.t(), any) :: query_or_changeset + when query_or_changeset: Ash.Query.t() | Ash.Changeset.t() + def set_body_param(query, key, value) do + new_context = + query.context + |> Map.put_new(:data_layer, %{}) + |> Map.update!(:data_layer, fn data_layer -> + data_layer + |> Map.put_new(:body, %{}) + |> Map.update!(:body, &Map.put(&1, key, value)) + end) + + %{query | context: new_context} + end + + @spec merge_query_params(query_or_changeset, map) :: query_or_changeset + when query_or_changeset: Ash.Query.t() | Ash.Changeset.t() + def merge_query_params(%Ash.Query{} = query, params) do + Ash.Query.set_context(query, %{data_layer: %{query_params: params}}) + end + + def merge_query_params(%Ash.Changeset{} = changeset, params) do + Ash.Changeset.set_context(changeset, %{data_layer: %{query_params: params}}) + end + + @spec set_query_params(query_or_changeset, map) :: query_or_changeset + when query_or_changeset: Ash.Query.t() | Ash.Changeset.t() + def set_query_params(query, params) do + new_context = + query.context + |> Map.put_new(:data_layer, %{}) + |> Map.update!(:data_layer, fn data_layer -> + Map.put(data_layer, :query_params, params) + end) + + %{query | context: new_context} + end +end diff --git a/lib/data_layer.ex b/lib/data_layer.ex new file mode 100644 index 0000000..4f546a0 --- /dev/null +++ b/lib/data_layer.ex @@ -0,0 +1,324 @@ +defmodule AshJsonApiWrapper.DataLayer do + @field %Ash.Dsl.Entity{ + name: :field, + target: AshJsonApiWrapper.Field, + schema: AshJsonApiWrapper.Field.schema(), + docs: """ + Configure an individual field's behavior, for example its path in the response. + """, + args: [:name] + } + + @fields %Ash.Dsl.Section{ + name: :fields, + describe: "Contains configuration for individual fields in the response", + entities: [ + @field + ] + } + + @endpoint %Ash.Dsl.Entity{ + name: :endpoint, + target: AshJsonApiWrapper.Endpoint, + schema: AshJsonApiWrapper.Endpoint.schema(), + docs: """ + Configure the endpoint that a given action will use. + + Accepts overrides for fields as well. + """, + entities: [ + fields: @field + ], + args: [:action] + } + + @endpoints %Ash.Dsl.Section{ + name: :endpoints, + describe: "Contains the configuration for the endpoints used in each action", + schema: [ + base: [ + type: :string, + doc: "The base endpoint to which all relative urls provided will be appended." + ] + ], + entities: [ + @endpoint + ] + } + + @json_api_wrapper %Ash.Dsl.Section{ + name: :json_api_wrapper, + describe: "Contains the configuration for the json_api_wrapper data layer", + sections: [ + @fields, + @endpoints + ], + schema: [ + before_request: [ + type: :any, + doc: """ + A function that takes the finch request and returns the finch request. + Will be called just before the request is made for all requests, but before JSON encoding the body and query encoding the query parameters. + """ + ], + finch: [ + type: :atom, + required: true, + doc: """ + The name used when setting up your finch supervisor in your Application. + + e.g in this example from finch's readme: + + ```elixir + {Finch, name: MyConfiguredFinch <- this value} + ``` + """ + ] + ] + } + + use Ash.Dsl.Extension, sections: [@json_api_wrapper] + + defmodule Query do + defstruct [:request, :action] + end + + @behaviour Ash.DataLayer + + @impl true + def can?(_, :create), do: true + def can?(_, _), do: false + + @impl true + def resource_to_query(resource) do + %Query{request: Finch.build(:get, AshJsonApiWrapper.endpoint_base(resource))} + end + + @impl true + def set_context(_resource, query, context) do + params = context[:data_layer][:query_params] + + if params do + {:ok, %{query | request: %{query.request | query: params}, action: context[:action]}} + else + {:ok, %{query | action: context[:action]}} + end + end + + @impl true + def create(resource, changeset) do + endpoint = AshJsonApiWrapper.endpoint(resource, changeset.action.name) + + base = + case endpoint && endpoint.fields_in do + :body -> + changeset.context[:data_layer][:body] || %{} + + :params -> + changeset.context[:data_layer][:query_params] || %{} + end + + {:ok, with_attrs} = + changeset.attributes + |> Kernel.||(%{}) + |> Enum.reduce_while({:ok, base}, fn {key, value}, {:ok, acc} -> + attribute = Ash.Resource.Info.attribute(resource, key) + field = AshJsonApiWrapper.field(resource, attribute.name) + + case Ash.Type.dump_to_embedded( + attribute.type, + value, + attribute.constraints + ) do + {:ok, dumped} -> + path = + if field && field.write_path do + field.write_path + else + [to_string(attribute.name)] + end + + path = + if endpoint.write_entity_path do + endpoint.write_entity_path ++ path + else + path + end + + {:cont, {:ok, put_in!(acc, path, dumped)}} + + :error -> + {:halt, + {:error, + Ash.Error.Changes.InvalidAttribute.exception( + field: attribute.name, + message: "Could not be dumped to embedded" + )}} + end + end) + + {body, params} = + case endpoint.fields_in do + :params -> + {changeset.context[:data_layer][:body] || %{}, with_attrs} + + :body -> + {with_attrs, changeset.context[:data_layer][:query_params] || %{}} + end + + :post + |> Finch.build( + endpoint.path || AshJsonApiWrapper.endpoint_base(resource), + [{"Content-Type", "application/json"}, {"Accept", "application/json"}], + body + ) + |> Map.put(:query, params) + |> request(resource) + + {:ok, struct(resource, [])} + end + + defp put_in!(body, [key], value) do + Map.put(body, key, value) + end + + defp put_in!(body, [first | rest], value) do + body + |> Map.put_new(first, %{}) + |> Map.update!(first, &put_in!(&1, rest, value)) + end + + @impl true + def run_query(query, resource) do + endpoint = AshJsonApiWrapper.endpoint(resource, query.action.name) + + with {:ok, %{status: status} = response} when status >= 200 and status < 300 <- + request(query.request, resource), + {:ok, body} <- Jason.decode(response.body), + {:ok, entities} <- get_entities(body, endpoint) do + process_entities(entities, resource) + else + {:ok, %{status: status} = response} -> + {:error, + "Received status code #{status} in request #{inspect(query.request)}. Response: #{inspect(response)}"} + + other -> + other + end + end + + defp request(request, resource) do + case AshJsonApiWrapper.before_request(resource) do + nil -> + request + |> encode_query() + |> encode_body() + |> IO.inspect() + |> Finch.request(AshJsonApiWrapper.finch(resource)) + + hook -> + request + |> hook.() + |> encode_query() + |> encode_body() + |> IO.inspect() + |> Finch.request(AshJsonApiWrapper.finch(resource)) + end + |> IO.inspect() + end + + defp encode_query(%{query: query} = request) when is_map(query) do + %{request | query: URI.encode_query(query)} + end + + defp encode_query(request), do: request + + defp encode_body(%{body: body} = request) when is_map(body) do + %{request | body: Jason.encode!(body)} + end + + defp encode_body(request), do: request + + defp process_entities(entities, resource) do + Enum.reduce_while(entities, {:ok, []}, fn entity, {:ok, entities} -> + case process_entity(entity, resource) do + {:ok, entity} -> {:cont, {:ok, [entity | entities]}} + {:error, error} -> {:halt, {:error, error}} + end + end) + |> case do + {:ok, entities} -> {:ok, Enum.reverse(entities)} + {:error, error} -> {:error, error} + end + end + + defp process_entity(entity, resource) do + resource + |> Ash.Resource.Info.attributes() + |> Enum.reduce_while( + {:ok, + struct(resource, + __meta__: %Ecto.Schema.Metadata{ + state: :loaded + } + )}, + fn attr, {:ok, record} -> + case get_field(entity, attr, resource) do + {:ok, value} -> + {:cont, {:ok, Map.put(record, attr.name, value)}} + + {:error, error} -> + {:halt, {:error, error}} + end + end + ) + end + + defp get_field(entity, attr, resource) do + raw_value = get_raw_value(entity, attr, resource) + + case Ash.Type.cast_stored(attr.type, raw_value, attr.constraints) do + {:ok, value} -> + {:ok, value} + + _ -> + {:error, + AshJsonApiWrapper.Errors.InvalidData.exception(field: attr.name, value: raw_value)} + end + end + + defp get_raw_value(entity, attr, resource) do + case Enum.find(AshJsonApiWrapper.fields(resource), &(&1.name == attr.name)) do + %{path: path} when not is_nil(path) -> + case ExJSONPath.eval(entity, path) do + {:ok, [value | _]} -> + value + + _ -> + nil + end + + _ -> + Map.get(entity, to_string(attr.name)) + end + end + + defp get_entities(body, endpoint) do + case endpoint.entity_path do + nil -> + {:ok, List.wrap(body)} + + path -> + case ExJSONPath.eval(body, path) do + {:ok, [entities | _]} -> + {:ok, List.wrap(entities)} + + {:ok, _} -> + {:ok, []} + + {:error, error} -> + {:error, error} + end + end + end +end diff --git a/lib/endpoint.ex b/lib/endpoint.ex new file mode 100644 index 0000000..a2ab310 --- /dev/null +++ b/lib/endpoint.ex @@ -0,0 +1,41 @@ +defmodule AshJsonApiWrapper.Endpoint do + defstruct [:action, :path, :entity_path, :fields, :fields_in, :write_entity_path] + + @type t :: %__MODULE__{} + + def schema do + [ + action: [ + type: :atom, + required: true, + doc: "The action this path is for" + ], + path: [ + type: :string, + default: "/", + doc: "The path of the endpoint relative to the base, or an absolute path" + ], + fields_in: [ + type: {:in, [:body, :params]}, + default: :body, + doc: "Where to place the fields when writing them." + ], + write_entity_path: [ + type: {:list, :string}, + doc: + "The list path at which the entity should be placed in the body when creating/updating." + ], + entity_path: [ + type: :string, + doc: "A json path at which the entities can be read back from the response" + ] + ] + end + + def default(path) do + %__MODULE__{ + path: path, + fields_in: :body + } + end +end diff --git a/lib/errors/invalid_data.ex b/lib/errors/invalid_data.ex new file mode 100644 index 0000000..13372c1 --- /dev/null +++ b/lib/errors/invalid_data.ex @@ -0,0 +1,19 @@ +defmodule AshJsonApiWrapper.Errors.InvalidData do + @moduledoc "Used when an invalid value is present in the response for a given attribute" + use Ash.Error.Exception + + def_ash_error([:field, :value], class: :invalid) + + defimpl Ash.ErrorKind do + def id(_), do: Ash.UUID.generate() + + def code(_), do: "invalid_data" + + def message(error) do + "Invalid value provided#{for_field(error)}: #{inspect(error.value)}" + end + + defp for_field(%{field: field}) when not is_nil(field), do: " for #{field}" + defp for_field(_), do: "" + end +end diff --git a/lib/field.ex b/lib/field.ex new file mode 100644 index 0000000..6db1564 --- /dev/null +++ b/lib/field.ex @@ -0,0 +1,23 @@ +defmodule AshJsonApiWrapper.Field do + defstruct [:name, :path, :write_path] + + @type t :: %__MODULE__{} + + def schema do + [ + name: [ + type: :atom, + required: true, + doc: "The attribute this field is configuring" + ], + path: [ + type: :string, + doc: "The path of the value for this field, relative to the entity's path" + ], + write_path: [ + type: {:list, :string}, + doc: "The list path of the value for this field when writing." + ] + ] + end +end diff --git a/mix.exs b/mix.exs index 06c247b..f5ca2b9 100644 --- a/mix.exs +++ b/mix.exs @@ -1,11 +1,12 @@ -defmodule AshApiWrapper.MixProject do +defmodule AshJsonApiWrapper.MixProject do use Mix.Project def project do [ - app: :ash_api_wrapper, + app: :ash_json_api_wrapper, version: "0.1.0", elixir: "~> 1.12", + aliases: aliases(), start_permanent: Mix.env() == :prod, deps: deps() ] @@ -18,11 +19,28 @@ defmodule AshApiWrapper.MixProject do ] end + defp aliases do + [ + "ash.formatter": "ash.formatter --extensions AshJsonApiWrapper.DataLayer" + ] + end + # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:dep_from_hexpm, "~> 0.3.0"}, - # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + {:ash, "~> 1.48.0-rc.23"}, + {:finch, "~> 0.9.0"}, + {:exjsonpath, "~> 0.1"}, + # Dev/Test dependencies + {:ex_doc, "~> 0.22", only: :dev, runtime: false}, + {:ex_check, "~> 0.12.0", only: :dev}, + {:credo, ">= 0.0.0", only: :dev, runtime: false}, + {:dialyxir, ">= 0.0.0", only: :dev, runtime: false}, + {:sobelow, ">= 0.0.0", only: :dev, runtime: false}, + {:git_ops, "~> 2.4.4", only: :dev}, + {:excoveralls, "~> 0.13.0", only: [:dev, :test]}, + {:mix_test_watch, "~> 1.0", only: :dev, runtime: false}, + {:parse_trans, "3.3.0", only: [:dev, :test], override: true} ] end end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..fa463fa --- /dev/null +++ b/mix.lock @@ -0,0 +1,47 @@ +%{ + "ash": {:hex, :ash, "1.48.0-rc.23", "0a41e4ad62dff764bb8ea9b4645f2f9539a596587910d1b1919f3d25a30f35e9", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "9cce85c4a2bcc91aaaee3b1e5d39f1218bc608974792caefe27cf56d6532cfd1"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "castore": {:hex, :castore, "0.1.12", "b5755d7668668a74c0e3c4c68df91da927e063a5cade17d693eff04e6ab64805", [:mix], [], "hexpm", "981c79528f88ec4ffd627214ad4cdd25052dc56c002996c603011ae37ec1b4b0"}, + "certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, + "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, + "credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.16", "607709303e1d4e3e02f1444df0c821529af1c03b8578dfc81bb9cf64553d02b9", [:mix], [], "hexpm", "69fcf696168f5a274dd012e3e305027010658b2d1630cef68421d6baaeaccead"}, + "ecto": {:hex, :ecto, "3.7.1", "a20598862351b29f80f285b21ec5297da1181c0442687f9b8329f0445d228892", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d36e5b39fc479e654cffd4dbe1865d9716e4a9b6311faff799b6f90ab81b8638"}, + "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"}, + "ex_check": {:hex, :ex_check, "0.12.0", "c0e2919ecc06afeaf62c52d64f3d91bd4bc7dd8deaac5f84becb6278888c967a", [:mix], [], "hexpm", "cfafa8ef97c2596d45a1f19b5794cb5c7f700f25d164d3c9f8d7ec17ee67cf42"}, + "ex_doc": {:hex, :ex_doc, "0.25.5", "ac3c5425a80b4b7c4dfecdf51fa9c23a44877124dd8ca34ee45ff608b1c6deb9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "688cfa538cdc146bc4291607764a7f1fcfa4cce8009ecd62de03b27197528350"}, + "excoveralls": {:hex, :excoveralls, "0.13.4", "7b0baee01fe150ef81153e6ffc0fc68214737f54570dc257b3ca4da8e419b812", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "faae00b3eee35cdf0342c10b669a7c91f942728217d2a7c7f644b24d391e6190"}, + "exjsonpath": {:hex, :exjsonpath, "0.9.0", "87e593eb0deb53aa0688ca9f9edc9fb3456aca83c82245f83201ea04d696feba", [:mix], [], "hexpm", "8d7a8e9ba784e1f7a67c6f1074a3ac91a3a79a45969514ee5d95cea5bf749627"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "finch": {:hex, :finch, "0.9.0", "8b772324aebafcaba763f1dffaa3e7f52f8c4e52485f50f48bbb2f42219a2e87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a93bfcad9ca50fa3cb2d459f27667d9a87cfbb7fecf9b29b2e78a50bc2ab445d"}, + "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"}, + "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, + "git_ops": {:hex, :git_ops, "2.4.5", "185a724dfde3745edd22f7571d59c47a835cf54ded67e9ccbc951920b7eec4c2", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e323a5b01ad53bc8c19c3a444be3e61ed7803ecd2e95530446ae9327d0143ecc"}, + "hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mint": {:hex, :mint, "1.4.0", "cd7d2451b201fc8e4a8fd86257fb3878d9e3752899eb67b0c5b25b180bde1212", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "10a99e144b815cbf8522dccbc8199d15802440fc7a64d67b6853adb6fa170217"}, + "mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"}, + "nimble_options": {:hex, :nimble_options, "0.3.7", "1e52dd7673d36138b1a5dede183b5d86dff175dc46d104a8e98e396b85b04670", [:mix], [], "hexpm", "2086907e6665c6b6579be54ef5001928df5231f355f71ed258f80a55e9f63633"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "nimble_pool": {:hex, :nimble_pool, "0.2.4", "1db8e9f8a53d967d595e0b32a17030cdb6c0dc4a451b8ac787bf601d3f7704c3", [:mix], [], "hexpm", "367e8071e137b787764e6a9992ccb57b276dc2282535f767a07d881951ebeac6"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, + "picosat_elixir": {:hex, :picosat_elixir, "0.2.1", "407dcb90755167fd9e3311b60565ff32ed0d234010363406c07cdb4175b95bc5", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "68f4bdb2ac3b594209e54625d3d58c9e2e98b90f2ec8e03235f66e88c9eda5fe"}, + "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, + "timex": {:hex, :timex, "3.7.6", "502d2347ec550e77fdf419bc12d15bdccd31266bb7d925b30bf478268098282f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a296327f79cb1ec795b896698c56e662ed7210cc9eb31f0ab365eb3a62e2c589"}, + "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, + "tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, +} diff --git a/test/ash_api_wrapper_test.exs b/test/ash_api_wrapper_test.exs index 3113842..9e6364e 100644 --- a/test/ash_api_wrapper_test.exs +++ b/test/ash_api_wrapper_test.exs @@ -1,8 +1,8 @@ -defmodule AshApiWrapperTest do +defmodule AshJsonApiWrapperTest do use ExUnit.Case - doctest AshApiWrapper + doctest AshJsonApiWrapper test "greets the world" do - assert AshApiWrapper.hello() == :world + assert AshJsonApiWrapper.hello() == :world end end