⚠️ EXPERIMENTAL / UNDER ACTIVE DEVELOPMENT ⚠️

AshToonEx

Ash resource extension for implementing ToonEx.Encoder protocol.

Built on top of ToonEx — a high-performance TOON (Token-Oriented Object Notation) encoder/decoder for Elixir.

Inspired by ash_jason.

Installation

Add to the deps:

def deps do
  [
    {:ash_toon_ex, "~> 0.1.0"},
  ]
end

Usage

Add AshToonEx.Resource to extensions list within use Ash.Resource options:

defmodule Example.Resource do
  use Ash.Resource,
    extensions: [AshToonEx.Resource]
end

Configuration

Producing a TOON object can have multiple steps:

By default only the picking step happens and it takes all non-private non-sensitive fields (attributes, relationships, aggregates, calculations) with loaded values from a record.

For adding and configuring those steps there is an optional toon dsl section:

defmodule Example.Resource do
  use Ash.Resource,
    extensions: [AshToonEx.Resource]

  toon do
    # options
  end
end

All optional steps can be specified multiple times and are applied in the order they were defined in.

A result object on which those steps operate is a key-value list — not map, not keyword list.

pick

Keys to pick from a record and include in the result. Accepts a fixed explicit list of keys or a map with a configuration of default behaviour.

Values of nil / Ash.NotLoaded / Ash.ForbiddenField are omitted.

Map can have such options as:

toon do
  # Pick only those listed keys
  pick [:only_some_field]

  # Pick non-sensitive fields
  pick %{private?: true}

  # Pick non-private fields
  pick %{sensitive?: true}

  # Pick all fields
  pick %{private?: true, sensitive?: true}

  # Pick usual but include and exclude some specific keys
  pick %{include: [:ok_private_field], exclude: [:irrelevant_public_field]}
end

compact

A step to remove unneeded values from a result. Accepts a boolean, a tagged only/except tuple or a config map with values/fields keys.

toon do
  # Remove all fields with nil value
  compact true

  # Remove fields with nil value except for specified exceptions
  compact {:except, [:keep_nil]}

  # Remove fields with specific unwanted values
  compact %{values: [nil, false, ""]}
end

merge

A step to merge values into a result. Accepts a map or a tuples list.

Map has no guarantees about keys order so if you care about that prefer the list form.

toon do
  # Merge with map
  merge %{key: "value"}

  # Merge with list
  merge key: "value"
end

rename

A step to rename keys in a result. Accepts a map, a tuples list or a function for mapping.

toon do
  # Rename with map
  rename %{from_key: "to_key"}

  # Rename with list
  rename from_key: "to_key"

  # Rename with a function
  rename fn name -> String.capitalize(to_string(name)) end
end

order

A step to reorder keys in a result. Accepts a boolean, a sort function or a list of keys in a desired order.

If it is a list then it also acts as a filter and removes keys not present in that list.

toon do
  # Order with standard `Enum.sort`
  order true

  # Order with a custom sort function
  order fn keys -> Enum.sort(keys, :desc) end

  # Order in accordance with a list
  order [:only, :these, :keys, :in, :that, "order"]
end

customize

A step to arbitrary customize a result. Accepts a function that will get a result and a resource record as arguments and return a modified result.

As mentioned above a result has a form of a list with two elements, key and value, tuples. To work with it you might want to use List methods like List.keytake or List.keystore.

toon do
  customize fn result, _record ->
    result |> List.keystore(:custom_key, 0, {:custom_key, "custom_value"})
  end
end

Typed structs

To use with Ash.TypedStruct add AshToonEx.TypedStruct extension.

All toon options and steps are the same except there are no private? or sensitive? filters in pick map form (since typed struct fields do not have those options).

defmodule Example.TypedStruct do
  use Ash.TypedStruct,
    extensions: [AshToonEx.TypedStruct]
end

Protocol

Each resource or typed struct with AshToonEx extension also implements AshToonEx.Protocol.

It provides a single method get_fields that retrieves a list of key/value field tuples following the same AshToonEx's logic.

AshToonEx.Protocol.get_fields(%MyResource{id: 1, name: "Alice"})
# => [id: 1, name: "Alice"]

Encoding

With the extension applied, resources can be encoded directly to TOON format:

user = %MyResource{id: 1, name: "Alice", email: "alice@example.com"}
ToonEx.encode!(user)
# => "email: alice@example.com\nid: 1\nname: Alice"

Links

License

MIT License