# `AshDynamo.EmbeddedType`
[🔗](https://github.com/rauann/ash_dynamo/blob/v0.6.1/lib/ash_dynamo/embedded_type.ex#L1)

Generates a custom `Ash.Type` for an embedded Ash resource stored in DynamoDB.

DynamoDB stores embedded resources as plain maps with string keys. When Ash reads
them back, it tries to "load through" embedded attributes expecting Ash structs with
`__metadata__`. This causes a `KeyError` on `:__metadata__`.

This macro generates a type that handles the DynamoDB map ↔ struct conversion and
sets `cast_in_query?: false` to prevent Ash from loading through.

## Usage

First, define your embedded resource:

    defmodule MyApp.Tag do
      use Ash.Resource, data_layer: :embedded

      attributes do
        attribute :name, :string, allow_nil?: false, public?: true
        attribute :color, :string, allow_nil?: false, public?: true
      end
    end

Then create a type module for it:

    defmodule MyApp.Types.Tag do
      use AshDynamo.EmbeddedType, resource: MyApp.Tag
    end

Finally, use the type in your DynamoDB-backed resource:

    defmodule MyApp.Post do
      use Ash.Resource,
        data_layer: AshDynamo.DataLayer,
        domain: MyApp.Domain

      attributes do
        attribute :tags, {:array, MyApp.Types.Tag}, allow_nil?: false
      end
    end

## Why is this needed?

When using `{:array, MyApp.Tag}` directly (the embedded resource), Ash attempts to
"load through" the embedded attributes after reading from the data layer. This works
with data layers like AshPostgres that return properly cast embedded structs, but
DynamoDB returns plain maps with string keys, causing:

    ** (KeyError) key :__metadata__ not found in: %{"name" => "elixir", "color" => "purple"}

The custom type solves this by:

1. Casting string-keyed maps from DynamoDB into the embedded resource struct (`cast_stored/2`)
2. Casting input maps (atom or string keys) into the struct (`cast_input/2`)
3. Dumping the struct back to a plain map for DynamoDB storage (`dump_to_native/2`)
4. Returning `cast_in_query?: false` to prevent Ash from loading through the type

## Type Validation

Both `cast_input/2` and `cast_stored/2` validate each field through their respective
`Ash.Type` casting functions. If any field fails validation, the entire cast returns
`:error`. This ensures invalid data is caught on both writes and reads — a corrupted
DynamoDB record will raise an error immediately rather than propagating bad data.

## ExAws.Dynamo.Encodable

The macro automatically implements the `ExAws.Dynamo.Encodable` protocol for the
embedded resource, so ExAws can encode it when writing to DynamoDB. The implementation
converts the struct to a plain map containing only the resource's attribute fields.

If you prefer to control the encoding yourself (e.g. to exclude specific fields), you
can add `@derive {ExAws.Dynamo.Encodable, only: [:name, :color]}` to your embedded
resource module before `use AshDynamo.EmbeddedType` is called. The macro will skip
the automatic implementation if one already exists.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
