⚠️ 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"},
]
endUsage
Add AshToonEx.Resource to extensions list within use Ash.Resource options:
defmodule Example.Resource do
use Ash.Resource,
extensions: [AshToonEx.Resource]
endConfiguration
Producing a TOON object can have multiple steps:
- Picking keys from a record.
- Removing fields with unnecessary values (like nil).
- Merging some values.
- Renaming keys.
- Ordering keys.
- Customizing a result with a function.
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
endAll 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.
- Unlike map the order is stable and guaranteed.
- Unlike keyword list it can have string keys.
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:
private?- Whether to pick private fields.sensitive?- Whether to pick sensitive fields.include- Keys to pick. In addition to fields.exclude- Keys not to pick.
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]}
endcompact
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, ""]}
endmerge
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"
endrename
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
endorder
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"]
endcustomize
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
endTyped 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]
endProtocol
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
- ToonEx — TOON encoder/decoder for Elixir
- ash_jason — Ash extension for Jason protocol (inspiration for this project)
- Ash — Resource framework for Elixir
License
MIT License