diff --git a/lib/mix/tasks/gen/resource.ex b/lib/mix/tasks/gen/resource.ex new file mode 100644 index 0000000..24562f4 --- /dev/null +++ b/lib/mix/tasks/gen/resource.ex @@ -0,0 +1,108 @@ +defmodule Mix.Tasks.AshAtproto.Gen.Resource do + use Igniter.Mix.Task + + @shortdoc "Generates an Ash Resource from an ATProto Lexicon JSON file." + + @impl Igniter.Mix.Task + def info(_argv, _composing_task) do + %Igniter.Mix.Task.Info{ + group: :ash_atproto, + positional: [:lexicon_path, :base_namespace] + } + end + + @impl Igniter.Mix.Task + def igniter(igniter, argv) do + [lexicon_path, base_namespace] = argv + + lexicon = + lexicon_path + |> File.read!() + |> JSON.decode!() + + lexicon_id = lexicon["id"] + + module_suffix = + lexicon_id + |> String.split(".") + |> Enum.map(&Macro.camelize/1) + |> Enum.join(".") + + resource_module = Module.concat([base_namespace, module_suffix]) + + main_def = lexicon["defs"]["main"] || %{} + record_def = main_def["record"] || %{} + properties = record_def["properties"] || %{} + required_fields = record_def["required"] || [] + + igniter = + Igniter.Project.Module.create_module( + igniter, + resource_module, + """ + use Ash.Resource, data_layer: AshAtproto.DataLayer + """ + ) + + igniter = + Spark.Igniter.set_option( + igniter, + resource_module, + [:atproto_repo, :record_type], + lexicon_id + ) + + igniter = + igniter + |> Ash.Resource.Igniter.add_new_attribute( + resource_module, + :repo_did, + "attribute :repo_did, :string, primary_key?: true, allow_nil?: false" + ) + |> Ash.Resource.Igniter.add_new_attribute( + resource_module, + :rkey, + "attribute :rkey, :string, primary_key?: true, allow_nil?: false" + ) + |> Ash.Resource.Igniter.add_new_attribute(resource_module, :cid, "attribute :cid, :string") + + Enum.reduce(properties, igniter, fn {field_name, field_def}, ig -> + ash_type = map_lexicon_type_to_ash(field_def) + is_required? = field_name in required_fields + + attribute_code = """ + attribute :#{field_name}, #{ash_type} do + allow_nil? #{not is_required?} + public? true + end + """ + + Ash.Resource.Igniter.add_new_attribute( + ig, + resource_module, + String.to_atom(field_name), + attribute_code + ) + end) + end + + # TODO: add strongref relationship + defp map_lexicon_type_to_ash(%{"type" => "string", "format" => "datetime"}), do: ":utc_datetime" + defp map_lexicon_type_to_ash(%{"type" => "string"}), do: ":string" + defp map_lexicon_type_to_ash(%{"type" => "boolean"}), do: ":boolean" + defp map_lexicon_type_to_ash(%{"type" => "integer"}), do: ":integer" + defp map_lexicon_type_to_ash(%{"type" => "cid-link"}), do: ":string" + defp map_lexicon_type_to_ash(%{"type" => "bytes"}), do: ":binary" + + defp map_lexicon_type_to_ash(%{"type" => "array", "items" => items_def}) do + inner_type = map_lexicon_type_to_ash(items_def) + "{:array, #{inner_type}}" + end + + defp map_lexicon_type_to_ash(%{"type" => "blob"}), do: ":map" + defp map_lexicon_type_to_ash(%{"type" => "object"}), do: ":map" + defp map_lexicon_type_to_ash(%{"type" => "union"}), do: ":map" + defp map_lexicon_type_to_ash(%{"type" => "ref"}), do: ":map" + + defp map_lexicon_type_to_ash(_), do: ":term" +end diff --git a/lib/strong_ref.ex b/lib/strong_ref.ex index cb8dd9e..067cca5 100644 --- a/lib/strong_ref.ex +++ b/lib/strong_ref.ex @@ -1,15 +1,15 @@ defmodule AshAtproto.StrongRef do @moduledoc """ - Embedded resource to help modeling strong references. + Typed struct to help modeling strong references. ## Example ```elixir defmodule MyApp.ReplyRef do - use Ash.Resource, data_layer: :embedded + use Ash.TypedStruct - attributes do - attribute :root, AshAtproto.StrongRef, allow_nil?: false - attribute :parent, AshAtproto.StrongRef, allow_nil?: false + typed_struct do + field :root, AshAtproto.StrongRef, allow_nil?: false + field :parent, AshAtproto.StrongRef, allow_nil?: false end end @@ -40,10 +40,10 @@ defmodule AshAtproto.StrongRef do ``` """ - use Ash.Resource, data_layer: :embedded + use Ash.TypedStruct - attributes do - attribute(:uri, :string, allow_nil?: false) - attribute(:cid, :string, allow_nil?: false) + typed_struct do + field :uri, :string, allow_nil?: false + field :cid, :string, allow_nil?: false end end