EXTENSIONS.md

# Routex Extensions

Routex Extensions extend the functionality provided by Routex to transform routes or
generate new route based helper functions. Each extension is a module which adopts the
`Routex.Extension` specification. It has to implement one or multiple public functions:
- configure/2
- transform/3
- post_transform/3
- create_helpers/3

Routex will call those public functions at different stages before
the Routex hands-off the result to `Phoenix.Router` for compilation.

## Callbacks and stages
### Stage 1: Configure
This stage enables extensions to pre-process options upfront.

The `configure/2` callback is called with the options provided to `Routex` and
the name of the Routext backend. It is expected to return a new list of
options.

Routex collects all options in this stage for subsequent stages. Therefore,
extensions should add any fallback/default they might use themselves to the
options in this stage.

To aid in code completion, the final configuration is passed as a struct to
subsequent stages.

### Stage 2: Transform
This stage is meant to adapt the properties of `Phoenix.Router.Route`
structs. The routes are grouped by Routext backend and processed
per group, allowing an extension to use accumulating values within
one iteration.

The `transform/3` callback is called with a list of routes belonging to
a Routext backend, the name of the configuration model and the
current environment. It is expected to return a list of Phoenix.Router.Route
structs.

#### Flattening option values
Extensions can make use of `Routex.Attrs` provided by Routex itself, Routex
backends and other extensions.

To make the availability of the attributes as predictable as possible, Routex
uses a flat structure which is stored in a routes' `private.routex` field.
However, using a flat structure might conflict with developer experience;
sometimes a nested structure to provide configuration options might be more
suitable.

One responsibility of the `transform/3` callback is to flatten the structure of
attributes they use for each route they receive, so other extensions can use
attributes set by the current extension without knowledge of the configuration
structure.

**Example**
The Alternatives extension uses nested options and allows inheritance
of properties from parent scopes.

```
alternatives: %{
  "/" =>
    helper: nil,
    locale: "en",
    scopes: %{
      "nl" => %{
          helper: "nl",
          locale: "nl"
        },
      "gb" => %{
        helper: "gb",
        }
    }
}
```
The Alternatives module is therefor responsible for flattening those for
(itself and) other extensions to use. To take the route responsible for the
"gb" scope as an example, the extension should add flattened attributes in the
Route struct. It can do so using the helper function `Routex.Attrs.put/2`.

```
Routex.Attrs.put(route, [locale: "en_GB", helper: "gb"])
```

Now the `Translation` extension can search for the option `:locale` in the
route's opts, unaware of how that locale was initially configured.

### Stage 3: Post Transform
The `post_transform` stage is meant to set `Routex.Attrs` knowing all other
properties of a route are final.

### Stage 4: Create helpers
In this stage helper functions can be generated which will be added to
`MyAppWeb.Router.RoutexHelpers`.

The `create_helpers/3` callback is called with a list of routes belonging to a
Routext backend, the name of the Routext backend and the current environment.
It is expected to return Elixir AST.

As a result the developer only has to `import MyAppWeb.Router.RoutexHelpers`
for all helpers generated by extensions to be included in the app.

## Guidelines
* make functions not defined by the `Routex.Extension` behaviour private.
* provide as many options as possible; other extensions might use the information.
* provide additional options as flat list(s) so other extensions don't have to guess structure.
* as other extensions might use options set by your extension, try to keep their availability stable.

### Documentation

    @moduledoc """
    Summary of feature provided.

    ## Options
    - `name` - description

    ## Example configuration
    ```diff
    # file /lib/example_web/routex_backend.ex
    defmodule ExampleWeb.RoutexBackend do
      use Routex,
      extensions: [
    +   Routex.Extension.Name
    ],
    + name: [name_opt: "value"]
    ```

    ## Pseudo result
        /products/:id/edit  ⇒ /products/:id/edit

    ## `Routex.Attrs`
    **Requires**
    - none

    **Sets**
    - none

    ## Helpers
    function_name(arg1 :: type) :: type
    """