README.md

# Starchoice

[![Build Status](https://circleci.com/gh/nicklayb/starchoice.svg?style=svg)](https://circleci.com/gh/nicklayb/starchoice.svg)
[![Coverage Status](https://coveralls.io/repos/github/nicklayb/starchoice/badge.svg?branch=master)](https://coveralls.io/github/nicklayb/starchoice?branch=master)
[![Module Version](https://img.shields.io/hexpm/v/starchoice.svg)](https://hex.pm/packages/starchoice)
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/starchoice/)
[![Total Download](https://img.shields.io/hexpm/dt/starchoice.svg)](https://hex.pm/packages/starchoice)
[![License](https://img.shields.io/hexpm/l/starchoice.svg)](https://github.com/nicklayb/starchoice/blob/master/LICENSE)
[![Last Updated](https://img.shields.io/github/last-commit/nicklayb/starchoice.svg)](https://github.com/nicklayb/starchoice/commits/master)

<!-- MDOC !-->

Starchoice takes his name from the satellite TV company (now called [Shaw Direct](https://en.wikipedia.org/wiki/Shaw_Direct)) because they are selling TV decoders. Since this lib is used to declare map decoders, I thought it felt appropriate to be named that way. Maybe not. Anyway.

The goal of the library is to provide a streamline process for converting String keyed maps to well defined structures. It is highly inspired by [Elm](https://elm-lang.org/)'s JSON decoders where you create different JSON decoders for the same data type.

For more information about creating decoder, visit the `Starchoice.Decoder` module documentation.

<!-- MDOC !-->

## Installation

```elixir
def deps do
  [
    {:starchoice, "~> 0.3.0"}
  ]
end
```

## Basic usage

Examples:
- [Snowhite](https://github.com/nicklayb/snowhite/tree/master/lib/open_weather): Snowhite uses Starchoice to decode HTTP responses from APIs.

### Define decoders

You can define decoders in your struct's module by doing the following (this is the macro approach).

```elixir
defmodule User do
  defstruct email: nil, password: nil, profile: nil, permissions: []
  use Starchoice.Decoder

  defdecoder do
    field(:email, with: &String.downcase/1)
    field(:password)
    field(:profile, with: Profile)
    field(:permissions, with: {Permission, :simple})
  end

  defdecoder :simple do
    field(:email)
  end
end

defmodule Profile do
  defstruct address: nil

  defdecoder do
    field(:address)
  end
end

defmodule Permission do
  defstruct name: nil, access: nil

  defdecoder :simple do
    field(:name)
    field(:access, with: &Permission.decode_access/1)
  end

  def decode_access("r"), do: :read
  def decode_access("w"), do: :write
  def decode_access("rw"), do: :full
end
```

We can now easily decode map payloads:

```elixir
input = %{
  "email" => "NICOLAS@nboisvert.com",
  "password" => "noneofyourbusiness",
  "profile" => %{
    "address" => "Somewhere str."
  },
  "permissions" => [
    %{"name" => "Articles", "access" => "rw"}
    %{"name" => "Settings", "access" => "r"}
  ]
}
{:ok, decoded} = Starchoice.decode(input, User)
%User{
  email: "nicolas@nboisvert.com",
  password: "noneofyourbusiness",
  profile: %Profile{
    address: "Somewhere str."
  },
  permissions: [
    %Permission{name: "Articles", access: :full},
    %Permission{name: "Settings", access: :read},
  ]
}
```

The basic of this can easily be achieved by using Ecto. However, for building a HTTP client or packaging lib, it might be a bit overkill to import a whole library like Ecto. This lightweight package can be pretty handy and is quite extensible.

## Polymorphic decoding

This is something that might become helpful. Have for an instance, an API that returns every results under a `results` key like `{"results": [{}, {}, ...]}`. It would be pretty useful to have a polymorphic decoder. It is supported out of the box by doing the following:

```elixir
defmodule Results do
  defstruct results: []

  def decoder(sub_type) do
    __MODULE__
    |> Starchoice.Decoder.new()
    |> Starchoice.Decoder.put_field(:results, sub_type)
  end
end
```

Then you can use it like that:

```elixir
input = %{"results" => [%{"email" => "email@email.com"}, %{"email" => "another_email@email.com"}]}
Starchoice.decode(input, Results.decoder({User, :simple})) # this uses the :simple decoder defined for User before.
%Results{
  results: %{
    %User{email: "email@email.com"},
    %User{email: "another_email@email.com"},
  }
}
```


## License

This source code is licensed under the [MIT license](https://github.com/nicklayb/starchoice/blob/master/LICENSE). Copyright (c) 2020-present Nicolas Boisvert.