defprotocol Altstd.Compact do @moduledoc ~S""" ### Deriving defmodule Valuable do @derive [{Altstd.Compact, keys: [:price, :tax]}] defstruct [ id: nil, price: 0, tax: 0, ] end ### Custom implementation defmodule Valuable do defstruct [ id: nil, price: 0, tax: 0, ] end defimpl Altstd.Compact, for: Valuable do def resolve(object, _opts \\ []) do case object.price + object.tax do 0 -> {:reject, object} _any -> {:resolve, object} end end def cast(object, opts \\ []) do resolution = object |> Altstd.Compact.resolve(opts) case resolution do {:resolve, resolved_value} -> resolved_value |> List.wrap() {:reject, _any} -> [] :reject -> [] resolved_value -> resolved_value |> List.wrap() end end end """ @moduledoc since: "0.1.0" @doc since: "0.1.0" @type t :: list(any()) @doc since: "0.1.0" @type resolution_t :: {:resolve, any()} | any() | {:reject, any()} | :reject @fallback_to_any true @doc ~S""" Should reject `object` if it's a falsey value. Otherwise, it should resolve the `object`. The values are falsey: - `nil` - `false` - `:error` - `:reject` - `0` - `''` - `""` - `[]` - `{}` - `%{}` - `PID`s that are not alive - `Port`s that are closed - `Tuple`s whose first elements are falsey values ## Examples iex> Altstd.Compact.resolve(0) {:reject, 0} iex> Altstd.Compact.resolve(nil) {:reject, nil} iex> Altstd.Compact.resolve(true) {:resolve, true} """ @doc since: "0.1.0" @spec resolve(any(), keyword) :: Altstd.Compact.resolution_t() def resolve(object, opts \\ []) @doc ~S""" Should remove all falsey values in the given `object` recursively. If `object` is not an instance of list, it must be simply wrapped by an empty list after resolution succeed. If `object` gets rejected while resolving, the function should return an empty list. ## Examples iex> Altstd.Compact.cast([0, 1, false, 2, '', 3]) [1, 2, 3] iex> Altstd.Compact.cast(true) [true] iex> Altstd.Compact.cast(nil) [] """ @doc since: "0.1.0" @spec cast(any(), keyword) :: Altstd.Compact.t() def cast(object, opts \\ []) end