README.md

# TypedStructLens

[![Build Status](https://travis-ci.com/ejpcmac/typed_struct_lens.svg?branch=develop)](https://travis-ci.com/ejpcmac/typed_struct_lens)
[![hex.pm version](http://img.shields.io/hexpm/v/typed_struct_lens.svg?style=flat)](https://hex.pm/packages/typed_struct_lens)

TypedStructLens is a [TypedStruct](https://github.com/ejpcmac/typed_struct)
plugin for defining a [Lens](https://github.com/obrok/lens) on each field
without writing boilerplate code.

## Rationale

If you define your structs with TypedStruct and use Lens alongside, you may end
up defining lenses for your fields:

```elixir
defmodule Person do
  use TypedStruct

  import Lens.Macros

  typedstruct do
    field :name, String.t(), enforce: true
    field :age, non_neg_integer()
    field :happy?, boolean(), default: true
    field :phone, String.t()
  end

  deflens name, do: Lens.key(:name)
  deflens age, do: Lens.key(:age)
  deflens happy?, do: Lens.key(:happy?)
  deflens phone, do: Lens.key(:phone)
end
```

But if you are using TypedStruct, it is also highly probable that you do not
like to write boilerplate code. TypedStructLens is here to write the `deflens`
for you:

```elixir
defmodule Person do
  use TypedStruct

  typedstruct do
    plugin TypedStructLens

    field :name, String.t(), enforce: true
    field :age, non_neg_integer()
    field :happy?, boolean(), default: true
    field :phone, String.t()
  end
end
```

## Usage

### Setup

To use this plugin in your project, add this to your Mix dependencies:

```elixir
{:typed_struct_lens, "~> 0.1.0"}
```

If you do not plan to compile modules using this TypedStruct plugin at
runtime, you can add `runtime: false` to the dependency tuple as it is only
used during compilation.

### General usage

To use this plugin in a typed struct, simply register it in the `typedstruct`
block:

```elixir
defmodule MyStruct do
  use TypedStruct

  typedstruct do
    # Just add this line to your struct.
    plugin TypedStructLens

    field :a_field, String.t()
    field :other_field, atom()
  end

  @spec change(t()) :: t()
  def change(data) do
    # a_field/0 is generated by TypedStructLens.
    lens = a_field()
    put_in(data, [lens], "Changed")
  end
end
```

### Options

You can generate private lenses:

```elixir
defmodule MyStruct do
  use TypedStruct

  typedstruct do
    # Define private lenses instead.
    plugin TypedStructLens, lens: :private

    field :a_field, String.t()
    # You can still make it public for a given field.
    field :other_field, atom(), lens: :public
  end
end
```

Conversely, you can make only a given lens private:

```elixir
defmodule MyStruct do
  use TypedStruct

  typedstruct do
    # By default lenses are public.
    plugin TypedStructLens

    field :a_field, String.t()
    # You can still make it private for a given field.
    field :other_field, atom(), lens: :private
  end
end
```

To avoid naming clashes, you can also prefix or postfix the generated function
names:

```elixir
defmodule MyStruct do
  use TypedStruct

  typedstruct do
    # Configure a prefix and postfix.
    plugin TypedStructLens, prefix: :demo_, postfix: :_lens

    field :a_field, String.t()
    field :other_field, atom()
  end

  @spec change(t()) :: t()
  def change(data) do
    # demo_a_field_lens/0 is generated by TypedStructLens instead of
    # a_field/0.
    lens = demo_a_field_lens()
    put_in(data, [lens], "Changed")
  end
end
```

## [Contributing](CONTRIBUTING.md)

Before contributing to this project, please read the
[CONTRIBUTING.md](CONTRIBUTING.md).

## License

Copyright © 2020 Jean-Philippe Cugnet

This project is licensed under the [MIT license](LICENSE).