# `Trogon.UnionObjectId`
[🔗](https://github.com/straw-hat-team/beam-monorepo/blob/trogon_object_id@v0.1.1/apps/trogon_object_id/lib/trogon/union_object_id.ex#L1)

Discriminated union type that can hold any of multiple `Trogon.ObjectId` types.

# `__using__`
*macro* 

Defines a union type that can hold any of the specified ObjectId types.

Creates a discriminated union that combines multiple ObjectId types into a single field.
The prefix of each ObjectId determines which type it is when parsing from storage.

## Options

* `:types` (list of `t:atom/0`) - Required. List of ObjectId modules that can be held by this union.

## Usage

First, define individual ObjectId types:

    defmodule MyApp.TenantId do
      use Trogon.ObjectId, object_type: "tenant"
    end

    defmodule MyApp.SystemId do
      use Trogon.ObjectId, object_type: "system"
    end

Then, create a union that combines them:

    defmodule MyApp.PrincipalId do
      use Trogon.UnionObjectId,
        types: [MyApp.TenantId, MyApp.SystemId]
    end

Use the union:

    iex> tenant_id = MyApp.TenantId.new("abc-123")
    iex> principal = MyApp.PrincipalId.new(tenant_id)
    %MyApp.PrincipalId{id: %MyApp.TenantId{id: "abc-123"}}

    iex> MyApp.PrincipalId.parse("tenant_abc-123")
    {:ok, %MyApp.PrincipalId{id: %MyApp.TenantId{id: "abc-123"}}}

    iex> to_string(principal)
    "tenant_abc-123"

## Type Safety

The union preserves the type of the inner ObjectId, so you can pattern match to determine
which variant you have:

    iex> case principal.id do
    ...>   %MyApp.TenantId{} -> "Got a tenant!"
    ...>   %MyApp.SystemId{} -> "Got a system!"
    ...> end
    "Got a tenant!"

## Storage Format

The union stores the complete prefixed string (e.g., `"tenant_abc-123"`). The prefix is
essential for type identification when parsing. Without it, the union cannot determine
which type to deserialize to.

    iex> MyApp.PrincipalId.to_storage(principal)
    "tenant_abc-123"

## Ecto Integration

The union implements `Ecto.Type`, so you can use it directly in Ecto schemas:

    defmodule MyApp.Event do
      use Ecto.Schema

      schema "events" do
        field :actor_id, MyApp.PrincipalId
      end
    end

## Compile-Time Validation

The macro validates your union definition at compile time:

- **Non-empty types list**: At least one ObjectId type must be provided
- **No exact prefix duplicates**: No two types can have the same prefix

> #### Warning {: .warning}
>
> **Prefix Overlaps Are Not Caught at Compile Time**
>
> Compile-time validation only catches **exact prefix duplicates**, not partial overlaps.
> If one prefix is a substring of another, the shorter prefix will match first during parsing,
> causing silent type mismatches.
>
> **Example of the Problem:**
>
> ```elixir
> defmodule AcmeId do
>   use Trogon.ObjectId, object_type: "acme"  # prefix: "acme_"
> end
>
> defmodule AcmeAdminId do
>   use Trogon.ObjectId, object_type: "acme_admin"  # prefix: "acme_admin_"
> end
>
> defmodule PrincipalId do
>   use Trogon.UnionObjectId, types: [AcmeId, AcmeAdminId]
> end
>
> # This compiles but gives the wrong result:
> PrincipalId.parse("acme_admin_xyz")
> # => {:ok, %PrincipalId{id: %AcmeId{id: "admin_xyz"}}}  # WRONG!
> # Should be: %PrincipalId{id: %AcmeAdminId{id: "xyz"}}
> ```
>
> **How to Avoid:**
>
> Design ObjectId prefixes to be semantically distinct and non-overlapping:
> - Good: `"tenant"`, `"system"`, `"service"`
> - Bad: `"acme"`, `"acme_admin"` (one is substring of other)
> - Bad: `"app"`, `"apple"` (one is substring of other)

---

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