defmodule MjolnirJsonDatasource do @moduledoc """ Provides data from json files. """ use Mjolnir.Datasource alias Mjolnir.Entity @impl true def find(spec) when is_binary(spec) do spec |> parse_pathspec() |> find(spec) end def find(pathspec) when is_struct(pathspec, Entity.Pathspec) do find(pathspec, pathspec |> to_path()) end def find(pathspec, spec) do case all(pathspec) do {:ok, entities} -> entity = Enum.filter(entities, fn entity -> entity.id == spec end) |> List.first() if is_nil(entity) do {:error, :notfound} else {:ok, entity} end {:error, error} -> {:error, error} end end @impl true def all(spec) when is_binary(spec) do spec |> parse_pathspec |> all() end def all(pathspec) when is_struct(pathspec, Entity.Pathspec) do marshall_from_pathspec(pathspec) end @impl true def create(entity) do pathspec = entity.id |> parse_pathspec() case find(pathspec) do {:ok, entity} -> {:error, "an entity with id of' '#{entity.id}' already exists"} {:error, :notfound} -> unmarshall_to_pathspec(entity, pathspec) end end @impl true def update(entity) do pathspec = entity.id |> parse_pathspec() case find(pathspec) do {:ok, _entity} -> unmarshall_and_replace_in_pathspec(entity, pathspec) {:error, :notfound} -> {:error, :notfound} end end @impl true def destroy(entity) do pathspec = entity.id |> parse_pathspec() case find(pathspec) do {:ok, _entity} -> unmarshall_and_remove_from_pathspec(pathspec) {:error, :notfound} -> {:error, :notfound} end end defp unmarshall_and_replace_in_pathspec(entity, pathspec) do case all(pathspec) do {:ok, entities} -> entities = Enum.filter(entities, fn entity -> entity.pathspec.id != pathspec.id end) unmarshall_and_encode(entities ++ [entity], pathspec) {:error, message} -> {:error, message} end end defp unmarshall_and_remove_from_pathspec(pathspec) do case all(pathspec) do {:ok, entities} -> entities = Enum.filter(entities, fn entity -> entity.pathspec.id != pathspec.id end) unmarshall_and_encode(entities, pathspec) {:error, message} -> {:error, message} end end defp unmarshall_to_pathspec(entity, pathspec) do case all(pathspec) do {:ok, entities} -> unmarshall_and_encode(entities ++ [entity], pathspec) {:error, error} -> {:error, error} end end defp unmarshall_and_encode(entities, pathspec) do entities = Enum.reduce(entities, [], fn entity, acc -> acc ++ [Entity.unmarshall(entity)] end) case Jason.encode(entities) do {:ok, encoded} -> File.write("#{pathspec.path}.json", encoded) {:error, message} -> {:error, message} end end defp marshall_from_pathspec(pathspec) do case File.read("#{pathspec.path}.json") do {:ok, content} -> decode_and_marshall(content, pathspec) {:error, _message} -> {:error, "The file at '#{pathspec.path}' could not be read"} end end defp decode_and_marshall(content, pathspec) do case Jason.decode(content) do {:ok, maps} -> {:ok, Enum.reduce(maps, [], fn map, acc -> acc ++ [Entity.marshall(map, pathspec)] end)} {:error, message} -> {:error, message} end end defp parse_pathspec(spec) do Entity.parse_pathspec(spec) end defp to_path(pathspec) do Entity.Pathspec.to_path(pathspec) end end