lib/generator.ex

defmodule AshThrift.Generator do
  alias Ash.Resource.Attribute
  alias AshThrift.Conversion
  alias AshThrift.Field
  alias Spark.Dsl.Extension

  def resource(resource) do
    Extension.get_persisted(resource, :thrift)
    |> Enum.map(&thrift_struct/1)
  end

  def namespace({language, namespace}),
    do: """
    namespace #{language} #{namespace}
    """

  def thrift_struct({name, fields}) do
    fields = Enum.map(fields, &field/1)

    """
    struct #{name} {
    #{fields}\
    }
    """
  end

  def field(
        {%Field{id: id, optional: optional},
         %Attribute{
           name: name,
           type: type,
           allow_nil?: allow_nil?
         }}
      ) do
    optional =
      case optional do
        nil -> allow_nil?
        bool -> bool
      end

    optional_or_required =
      if optional do
        "optional"
      else
        "required"
      end

    thrift_type = Conversion.type(type)

    thrift_name = name(name)

    """
    #{id}: #{optional_or_required} #{thrift_type} #{thrift_name};
    """
  end

  # relationships
  def field(
        {%Field{id: id, optional: optional, variant: variant},
         %{
           name: name,
           destination: destination,
           cardinality: cardinality
         }}
      ) do
    optional =
      case optional do
        nil -> true
        bool -> bool
      end

    optional_or_required =
      if optional do
        "optional"
      else
        "required"
      end

    variant =
      case variant do
        nil ->
          Spark.Dsl.Extension.get_persisted(destination, :thrift, %{})
          |> Map.keys()
          |> List.first()

        v ->
          v
      end

    thrift_type =
      case cardinality do
        :one -> {:relationship, variant}
        _ -> {:array, {:relationship, variant}}
      end
      |> Conversion.type()

    thrift_name = name(name)

    """
    #{id}: #{optional_or_required} #{thrift_type} #{thrift_name};
    """
  end

  def name(name) do
    Macro.camelize(Atom.to_string(name))
    |> then(fn <<first::utf8, rest::binary>> ->
      String.downcase(<<first::utf8>>) <> rest
    end)
  end
end