

Multiverse – multiverse v1.1.0

try { if(localStorage.getItem('night-mode')) document.body.className += ' night-mode'; } catch (e) { }

Toggle Sidebar

Toggle Theme

Multiverse

==========

[![Deps Status](https://beta.hexfaktor.org/badge/all/github/Nebo15/multiverse.svg)](https://beta.hexfaktor.org/github/Nebo15/multiverse) [![Hex.pm Downloads](https://img.shields.io/hexpm/dw/multiverse.svg?maxAge=3600)](https://hex.pm/packages/multiverse) [![Latest Version](https://img.shields.io/hexpm/v/multiverse.svg?maxAge=3600)](https://hex.pm/packages/multiverse) [![License](https://img.shields.io/hexpm/l/multiverse.svg?maxAge=3600)](https://hex.pm/packages/multiverse) [![Build Status](https://travis-ci.org/Nebo15/multiverse.svg?branch=master)](https://travis-ci.org/Nebo15/multiverse) [![Coverage Status](https://coveralls.io/repos/github/Nebo15/multiverse/badge.svg?branch=master)](https://coveralls.io/github/Nebo15/multiverse?branch=master) [![Ebert](https://ebertapp.io/github/Nebo15/multiverse.svg)](https://ebertapp.io/github/Nebo15/multiverse)

This plug helps to manage multiple API versions based on request and response gateways. This is an awesome practice to hide your backward compatibility. It allows to have your code in a latest possible version, without duplicating controllers or models.

![Compatibility Layers](http://amberonrails.com/images/posts/move-fast-dont-break-your-api/compatibility-layers.png "Compatibility Layers")

Inspired by Stripe API. Read more at [MOVE FAST, DON’T BREAK YOUR API](http://amberonrails.com/move-fast-dont-break-your-api/) or [API versioning](https://stripe.com/blog/api-versioning).

 Goals

--------

* reduce changes required to support multiple API versions;

* provide a way to test and schedule API version releases;

* to have minimum dependencies and low performance hit;

* to be flexible enough for most of projects to adopt it.

 Adapters

-----------

Multiverse allows you to use a custom adapter which can, for eg.:

* store consumer version upon his first request and re-use it as default each time consumer is using your API, eliminating need of passing version headers for them. Change this version when consumer has explicitly set it;

* use *other than ISO date* version types, eg. incremental counters (`v1`, `v2`);

* handle malformed versions by responding with JSON errors.

Default adapter works with ISO-8601 date from `x-api-version` header (configurable). For malformed versions it would log a warning and fallback to the current date.

Also, it allows to use channel name instead of date, where:

* `latest` channel would fallback to the current date;

* `edge` channel would disable all changes altogether.

Channels allow you to plan version releases upfront and test them without affecting users,

just set future date for a change and pass it explicitly or use `edge` channel to test latest

application version.

 Installation

---------------

The package (take look at [hex.pm](https://hex.pm/packages/multiverse)) can be installed as:

1. Add `multiverse` to your list of dependencies in `mix.exs`:

```

  def deps do

    [{:multiverse, "~> 1.1.0"}]

  end

```

2. Make sure that `multiverse` is available at runtime in your production:

```

  def application do

    [applications: [:multiverse]]

  end

```

 How to use

-------------

1. Insert this plug into your API pipeline (in your `router.ex`):

```

  pipeline :api do

    plug :accepts, ["json"]

    plug :put_secure_browser_headers

    plug Multiverse

  end

```

2. Define module that handles change

```

  defmodule AccountTypeChange do

    @behaviour Multiverse.Change

    def handle_request(%Plug.Conn{} = conn) do

      # Mutate your request here

      IO.inspect "AccountTypeChange.handle_request applied to request"

      conn

    end

    def handle_response(%Plug.Conn{} = conn) do

      # Mutate your response here

      IO.inspect "AccountTypeChange.handle_response applied to response"

      conn

    end

  end

```

3. Enable the change:

```

  pipeline :api do

    plug :accepts, ["json"]

    plug :put_secure_browser_headers

    plug Multiverse, gates: %{

      ~D[2016-07-21] => [AccountTypeChange]

    }

  end

```

4. Send your API requests with `X-API-Version` header with version lower or equal to `2016-07-20`.

### 

 Overriding version header

 You can use any version headers by passing option to Multiverse:

```

  pipeline :api do

    plug :accepts, ["json"]

    plug :put_secure_browser_headers

    plug Multiverse,

      version_header: "x-my-version-header",

      gates: %{

        ~D[2016-07-21] => [AccountTypeChange]

      }

  end

```

### 

 Using custom adapters

 You can use your own adapter which implements [`Multiverse.Adapter`](Multiverse.Adapter.html) behaviour:

```

  pipeline :api do

    plug :accepts, ["json"]

    plug :put_secure_browser_headers

    plug Multiverse,

      adapter: MyApp.SmartMultiverseAdapter,

      gates: %{

        ~D[2016-07-21] => [AccountTypeChange]

      }

  end

```

 Structuring your tests

-------------------------

1. Split your tests into versions:

$ ls -l test/acceptance

total 0

drwxr-xr-x 2 andrew staff 68 Aug 1 19:23 AccountTypeChange

drwxr-xr-x 2 andrew staff 68 Aug 1 19:24 OlderChange

2. Avoid touching request or response in old tests. Create API gates and matching folder in acceptance tests.

 Other things you might want to do

------------------------------------

1. Store Multiverse configuration in `config.ex`:

```

  use Mix.Config

  config :multiverse, MyApp.Endpoint,

    gates: %{

      ~D[2016-07-21] => [AccountTypeChange]

    }

```

```

  plug Multiverse, endpoint: __MODULE__

```

2. Generate API documentation from changes `@moduledoc`’s.

3. Other awesome stuff. Open an issue and tell me about it! :).

License

=======

See <LICENSE.md>.



 

Multiverse – multiverse v1.1.0

try { if(localStorage.getItem('night-mode')) document.body.className += ' night-mode'; } catch (e) { }

Toggle Sidebar

Toggle Theme

multiverse v1.1.0

 Multiverse

==============================

This plug helps to manage multiple API versions based on request and response gateways.

This is an awesome practice to hide your backward compatibility.

It allows to have your code in a latest possible version, without duplicating controllers or models.

For more information see [README.md](https://github.com/Nebo15/multiverse/).

[Link to this section](#summary)

 Summary

==========================================

[Types](#types)

----------------

[config()](#t:config/0)

[Functions](#functions)

------------------------

[call(conn, config)](#call/2)

Callback implementation for [`Plug.call/2`](https://hexdocs.pm/plug/1.4.4/Plug.html#c:call/2)

[init(opts)](#init/1)

Initializes Multiverse plug

[Link to this section](#types)

 Types

======================================

[Link to this type](#t:config/0 "Link to this type")

config()

```

config() :: %{

  adapter: [module](https://hexdocs.pm/elixir/typespecs.html#built-in-types)(),

  version_header: [String.t](https://hexdocs.pm/elixir/String.html#t:t/0)(),

  gates: [Multiverse.Adapter.gates](Multiverse.Adapter.html#t:gates/0)()

}

```

[Link to this section](#functions)

 Functions

==============================================

[Link to this function](#call/2 "Link to this function")

call(conn, config)

```

call(conn :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)(), config :: [config](#t:config/0)()) :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()

```

Callback implementation for [`Plug.call/2`](https://hexdocs.pm/plug/1.4.4/Plug.html#c:call/2).

[Link to this function](#init/1 "Link to this function")

init(opts)

```

init(opts :: [Keyword.t](https://hexdocs.pm/elixir/Keyword.html#t:t/0)()) :: [config](#t:config/0)()

```

Initializes Multiverse plug.

Raises at compile time when adapter or change is not loaded.

Available options:

* `:endpoint` - endpoint which is used to fetch configuration from application environment;

* `:adapter` - module which implements [`Multiverse.Adapter`](Multiverse.Adapter.html) behaviour;

* `:version_header` - header which is used to fetch consumer version;

* `:gates` - list of gates (and changes) that are available for consumers.



 

Multiverse.Adapter – multiverse v1.1.0

try { if(localStorage.getItem('night-mode')) document.body.className += ' night-mode'; } catch (e) { }

Toggle Sidebar

Toggle Theme

multiverse v1.1.0

 Multiverse.Adapter behaviour

================================================

This module provides behaviour for Multiverse adapters.

[Link to this section](#summary)

 Summary

==========================================

[Types](#types)

----------------

[changes()](#t:changes/0)

[gates()](#t:gates/0)

[version()](#t:version/0)

[Callbacks](#callbacks)

------------------------

[fetch\_default\_version(conn)](#c:fetch_default_version/1)

Fetch default client version

[init(adapter, opts)](#c:init/2)

Initialize adapter configuration at compile time

[resolve\_version\_or\_channel(conn, channel\_name\_or\_version)](#c:resolve_version_or_channel/2)

Resolve version by string value from request header

[version\_comparator(v1, v2)](#c:version_comparator/2)

Comparator that is used to order and filter versions that

should be applied to a connection

[Link to this section](#types)

 Types

======================================

[Link to this type](#t:changes/0 "Link to this type")

changes()

```

changes() :: [[module](https://hexdocs.pm/elixir/typespecs.html#built-in-types)()]

```

[Link to this type](#t:gates/0 "Link to this type")

gates()

```

gates() :: [%{optional([version](#t:version/0)()) => [changes](#t:changes/0)()}]

```

[Link to this type](#t:version/0 "Link to this type")

version()

```

version() :: [any](https://hexdocs.pm/elixir/typespecs.html#basic-types)()

```

[Link to this section](#callbacks)

 Callbacks

==============================================

[Link to this callback](#c:fetch_default_version/1 "Link to this callback")

fetch\_default\_version(conn)

```

fetch_default_version(conn :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()) :: {:ok, [version](#t:version/0)(), [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()}

```

Fetch default client version.

This callback is used when version header is not set or empty.

Additionally adapters may use it to fallback to default version

in case of errors or when version header value is malformed.

[Link to this callback](#c:init/2 "Link to this callback")

init(adapter, opts)

```

init(adapter :: [module](https://hexdocs.pm/elixir/typespecs.html#built-in-types)(), opts :: [Keyword.t](https://hexdocs.pm/elixir/Keyword.html#t:t/0)()) :: {:ok, [Keyword.t](https://hexdocs.pm/elixir/Keyword.html#t:t/0)()}

```

Initialize adapter configuration at compile time.

This callback can be used to fetch Multiverse configuration

from application environment, file or other places.

[Link to this callback](#c:resolve_version_or_channel/2 "Link to this callback")

resolve\_version\_or\_channel(conn, channel\_name\_or\_version)

```

resolve_version_or_channel(

  conn :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)(),

  channel_name_or_version :: [String.t](https://hexdocs.pm/elixir/String.html#t:t/0)()

) :: {:ok, [version](#t:version/0)(), [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()}

```

Resolve version by string value from request header.

This callback can be used to set named channels for API versions,

for eg. `latest` header value could be resolved to current date and

`edge` to the most recent defined version.

Also, it is responsible for casting string value to adapter-specific

version type and handling possible errors.

You can terminate connection if you want to return error without

further processing of the request.

[Link to this callback](#c:version_comparator/2 "Link to this callback")

version\_comparator(v1, v2)

```

version_comparator(v1 :: [version](#t:version/0)(), v2 :: [version](#t:version/0)()) :: [boolean](https://hexdocs.pm/elixir/typespecs.html#built-in-types)()

```

Comparator that is used to order and filter versions that

should be applied to a connection.

It should compare two arguments, and return true if

the first argument precedes the second one or they are equal.



 

Multiverse.Adapters.ISODate – multiverse v1.1.0

try { if(localStorage.getItem('night-mode')) document.body.className += ' night-mode'; } catch (e) { }

Toggle Sidebar

Toggle Theme

multiverse v1.1.0

 Multiverse.Adapters.ISODate

===============================================

Adapter that fetches ISO-8601 date from request header and `Elixir.Date`

to resolve changes that must be applied to the connection.

Current date is used as fallback when:

* version header is not present in request;

* value of a version header is malformed (and warning is logged);

* `latest` channel is used instead of date.

When `edge` channel is used instead of date, no changes are applied to the connection.

[Link to this section](#summary)

 Summary

==========================================

[Types](#types)

----------------

[version()](#t:version/0)

[Functions](#functions)

------------------------

[fetch\_default\_version(conn)](#fetch_default_version/1)

Fetch default client version

[init(adapter, opts)](#init/2)

Initialize adapter configuration at compile time

[resolve\_version\_or\_channel(conn, version)](#resolve_version_or_channel/2)

Resolve version by string value from request header

[version\_comparator(v1, v2)](#version_comparator/2)

Comparator that is used to order and filter versions that

should be applied to a connection

[Link to this section](#types)

 Types

======================================

[Link to this type](#t:version/0 "Link to this type")

version()

```

version() :: [Date.t](https://hexdocs.pm/elixir/Date.html#t:t/0)()

```

[Link to this section](#functions)

 Functions

==============================================

[Link to this function](#fetch_default_version/1 "Link to this function")

fetch\_default\_version(conn)

```

fetch_default_version(conn :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()) :: {:ok, [version](#t:version/0)(), [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()}

```

Fetch default client version.

This callback is used when version header is not set or empty.

Additionally adapters may use it to fallback to default version

in case of errors or when version header value is malformed.

Callback implementation for [`Multiverse.Adapter.fetch_default_version/1`](Multiverse.Adapter.html#c:fetch_default_version/1).

[Link to this function](#init/2 "Link to this function")

init(adapter, opts)

Initialize adapter configuration at compile time.

This callback can be used to fetch Multiverse configuration

from application environment, file or other places.

Callback implementation for [`Multiverse.Adapter.init/2`](Multiverse.Adapter.html#c:init/2).

[Link to this function](#resolve_version_or_channel/2 "Link to this function")

resolve\_version\_or\_channel(conn, version)

```

resolve_version_or_channel(

  conn :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)(),

  channel_name_or_version :: [String.t](https://hexdocs.pm/elixir/String.html#t:t/0)()

) :: {:ok, [version](#t:version/0)(), [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()}

```

Resolve version by string value from request header.

This callback can be used to set named channels for API versions,

for eg. `latest` header value could be resolved to current date and

`edge` to the most recent defined version.

Also, it is responsible for casting string value to adapter-specific

version type and handling possible errors.

You can terminate connection if you want to return error without

further processing of the request.

Callback implementation for [`Multiverse.Adapter.resolve_version_or_channel/2`](Multiverse.Adapter.html#c:resolve_version_or_channel/2).

[Link to this function](#version_comparator/2 "Link to this function")

version\_comparator(v1, v2)

```

version_comparator(v1 :: [version](#t:version/0)(), v2 :: [version](#t:version/0)()) :: [boolean](https://hexdocs.pm/elixir/typespecs.html#built-in-types)()

```

Comparator that is used to order and filter versions that

should be applied to a connection.

It should compare two arguments, and return true if

the first argument precedes the second one or they are equal.

Callback implementation for [`Multiverse.Adapter.version_comparator/2`](Multiverse.Adapter.html#c:version_comparator/2).



 

Multiverse.Change – multiverse v1.1.0

try { if(localStorage.getItem('night-mode')) document.body.className += ' night-mode'; } catch (e) { }

Toggle Sidebar

Toggle Theme

multiverse v1.1.0

 Multiverse.Change behaviour

===============================================

Provides behaviour for Multiverse API Changes.

 Examples

-----------

```

defmodule ChangeAccountType do

  @behaviour Multiverse.Change

  def handle_request(%Plug.Conn{} = conn) do

    # Mutate your request here

    IO.inspect "GateName.mutate_request applied to request"

    conn

  end

  def handle_response(%Plug.Conn{} = conn) do

    # Mutate your response here

    IO.inspect "GateName.mutate_response applied to response"

    conn

  end

end

```

[Link to this section](#summary)

 Summary

==========================================

[Functions](#functions)

------------------------

[\_\_unsing\_\_(opts)](#__unsing__/1)

Macros that can be used if you want to omit either `handle_request/1`

or `handle_response/1` callback in your change module

[active?(arg1, change)](#active?/2)

Checks if change is active on connection or version schema

[Callbacks](#callbacks)

------------------------

[handle\_request(conn)](#c:handle_request/1)

Defines a request mutator

[handle\_response(conn)](#c:handle_response/1)

Defines a response mutator

[Link to this section](#functions)

 Functions

==============================================

[Link to this macro](#__unsing__/1 "Link to this macro")

\_\_unsing\_\_(opts)

(macro)

Macros that can be used if you want to omit either `handle_request/1`

or `handle_response/1` callback in your change module.

[Link to this function](#active?/2 "Link to this function")

active?(arg1, change)

```

active?(

  conn_or_version_schema :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)() | [Multiverse.VersionSchema.t](Multiverse.VersionSchema.html#t:t/0)(),

  change :: [module](https://hexdocs.pm/elixir/typespecs.html#built-in-types)()

) :: [boolean](https://hexdocs.pm/elixir/typespecs.html#built-in-types)()

```

Checks if change is active on connection or version schema.

[Link to this section](#callbacks)

 Callbacks

==============================================

[Link to this callback](#c:handle_request/1 "Link to this callback")

handle\_request(conn)

```

handle_request(conn :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()) :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()

```

Defines a request mutator.

This function will be called whenever Cowboy receives request.

[Link to this callback](#c:handle_response/1 "Link to this callback")

handle\_response(conn)

```

handle_response(conn :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()) :: [Plug.Conn.t](https://hexdocs.pm/plug/1.4.4/Plug.Conn.html#t:t/0)()

```

Defines a response mutator.

This function will be called before Cowboy is dispatched response to a consumer.



 

Multiverse.VersionSchema – multiverse v1.1.0

try { if(localStorage.getItem('night-mode')) document.body.className += ' night-mode'; } catch (e) { }

Toggle Sidebar

Toggle Theme

multiverse v1.1.0

 Multiverse.VersionSchema

============================================

This module defines schema that is assigned to `conn.private[:multiverse_version_schema]`

and can be used by third-party libraries to store or process API version and changes data.

 Available attributes

-----------------------

* `adapter` - adapter which were used to handle connection;

* `version` - version which is assigned for consumer connection;

* `changes` - ordered list of changes that were applied to the connection.

[Link to this section](#summary)

 Summary

==========================================

[Types](#types)

----------------

[t()](#t:t/0)

[Link to this section](#types)

 Types

======================================

[Link to this type](#t:t/0 "Link to this type")

t()

```

t() :: %Multiverse.VersionSchema{

  adapter: [module](https://hexdocs.pm/elixir/typespecs.html#built-in-types)(),

  changes: [[module](https://hexdocs.pm/elixir/typespecs.html#built-in-types)()],

  version: [any](https://hexdocs.pm/elixir/typespecs.html#basic-types)()

}

```

