Skip to content

Commit

Permalink
Serializer registration
Browse files Browse the repository at this point in the history
  • Loading branch information
scrogson committed Oct 6, 2017
1 parent 6d1808c commit 88761bb
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 84 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog

## v0.10.0

### Backward Incompatible Changes

* Serializers are now registered via `OAuth2.register_serializer/2`.
This change allows wrapping applications to provide default serializers
without requiring the user to manually configure a serializer.

## v0.9.1 (2017-03-10)

### Improvements
Expand Down
13 changes: 3 additions & 10 deletions README.md
Expand Up @@ -31,20 +31,13 @@ If you're using [Poison](https://hex.pm/packages/poison) for JSON in your
application, this library is already pre-configured to use it for `"application/json"`
request and response bodies. You will still need to include it as a dependency though.

If you need to handle different MIME types, you can simply configure it like so:
If you need to handle different MIME types, you can simply register it like so:

```elixir
# config/config.exs
config :oauth2,
serializers: %{
"application/vnd.api+json" => Poison,
"application/xml" => MyApp.XmlParser,
}
OAuth2.register_serializer("application/vnd.api+json", Poison)
OAuth2.register_serializer("application/xml", MyApp.XmlParser)
```

The `serializers` option is a map where the keys are MIME types and the values
are modules.

The modules are expected to export `encode!/1` and `decode!/1`.

```elixir
Expand Down
1 change: 0 additions & 1 deletion config/config.exs
Expand Up @@ -6,5 +6,4 @@ config :oauth2,
client_id: "0bee1126b1a1381d9cab60bcd52349484451808a", # first commit sha of this library
client_secret: "f715d64092fe81c396ac383e97f8a7eca40e7c89", #second commit sha
redirect_uri: "http://example.com/auth/callback",
serializers: %{"application/json" => Poison},
request_opts: []
28 changes: 28 additions & 0 deletions lib/oauth2.ex
Expand Up @@ -43,4 +43,32 @@ defmodule OAuth2 do
resource = OAuth2.Client.get!(client, "/api/resource").body
"""

@doc """
Register a serialization module for a given mime type.
## Example
iex> OAuth2.register_serializer("application/json", Poison)
:ok
iex> OAuth2.Serializer.get("application/json")
Poison
"""
@spec register_serializer(binary, atom) :: :ok
def register_serializer(mime, module),
do: OAuth2.Serializer.register(mime, module)

@doc """
Un-register a serialization module for a given mime type.
## Example
iex> OAuth2.unregister_serializer("application/json")
:ok
iex> OAuth2.Serializer.get("application/json")
OAuth2.Serializer.Null
"""
@spec unregister_serializer(binary) :: :ok
def unregister_serializer(mime),
do: OAuth2.Serializer.unregister(mime)
end
15 changes: 15 additions & 0 deletions lib/oauth2/application.ex
@@ -0,0 +1,15 @@
defmodule OAuth2.Application do
@moduledoc false

use Application

def start(_, _) do
import Supervisor.Spec, warn: false

:ets.new(OAuth2.Serializer, [:named_table, :public, read_concurrency: true])

:ok = OAuth2.register_serializer("application/json", Poison)

Supervisor.start_link([], strategy: :one_for_one)
end
end
79 changes: 53 additions & 26 deletions lib/oauth2/serializer.ex
@@ -1,50 +1,77 @@
defmodule OAuth2.Serializer do
@moduledoc false

require Logger
@callback encode!(map) :: binary
@callback decode!(binary) :: map

defmodule NullSerializer do
@moduledoc false
@spec get(binary) :: atom
def get(mime_type) do
case :ets.lookup(__MODULE__, mime_type) do
[] ->
maybe_warn_missing_serializer(mime_type)
OAuth2.Serializer.Null
[{_, module}] ->
module
end
end

@doc """
Register a serialization module for a given mime type.
@doc false
def decode!(content), do: content
## Example
@doc false
def encode!(content), do: content
iex> OAuth2.Serializer.register("application/json", Poison)
:ok
iex> OAuth2.Serializer.get("application/json")
Poison
"""
@spec register(binary, atom) :: :ok
def register(mime_type, module) do
:ets.insert(__MODULE__, {mime_type, module})
:ok
end

def decode!(content, type), do: serializer(type).decode!(content)
@doc """
Un-register a serialization module for a given mime type.
## Example
iex> OAuth2.Serializer.unregister("application/json")
:ok
iex> OAuth2.Serializer.get("application/json")
OAuth2.Serializer.Null
"""
@spec unregister(binary) :: :ok
def unregister(mime_type) do
:ets.delete(__MODULE__, mime_type)
:ok
end

def encode!(content, type), do: serializer(type).encode!(content)
@spec decode!(binary, binary) :: map
def decode!(data, type),
do: get(type).decode!(data)

defp serializer(type) do
serializer = Map.get(configured_serializers(), type, NullSerializer)
warn_missing_serializer = Application.get_env(:oauth2, :warn_missing_serializer, true)
@spec decode!(map, binary) :: binary
def encode!(data, type),
do: get(type).encode!(data)

defp maybe_warn_missing_serializer(type) do
if Application.get_env(:oauth2, :warn_missing_serializer, true) do
require Logger

if serializer == NullSerializer && warn_missing_serializer do
Logger.warn """
A serializer was not configured for content-type '#{type}'.
To remove this warning for this content-type, add the following to your `config.exs` file:
To remove this warning for this content-type, consider registering a serializer:
config :oauth2,
serializers: %{
"#{type}" => MySerializer
}
OAuth2.register_serializer("#{type}", MySerializer)
To remove this warning entirely, add the following to you `config.exs` file:
To remove this warning entirely, add the following to your `config.exs` file:
config :oauth2,
warn_missing_serializer: false
"""
end

serializer
end

defp configured_serializers do
Application.get_env(:oauth2, :serializers) ||
raise("Missing serializers configuration! Make sure oauth2 app is added to mix application list")
end
end
11 changes: 11 additions & 0 deletions lib/oauth2/serializers/null.ex
@@ -0,0 +1,11 @@
defmodule OAuth2.Serializer.Null do
@moduledoc false

@behaviour OAuth2.Serializer

@doc false
def decode!(data), do: data

@doc false
def encode!(data), do: data
end
4 changes: 2 additions & 2 deletions mix.exs
Expand Up @@ -22,8 +22,8 @@ defmodule OAuth2.Mixfile do
end

def application do
[applications: [:logger, :hackney],
env: [serializers: %{"application/json" => Poison}]]
[mod: {OAuth2.Application, []},
applications: [:logger, :hackney]]
end

defp deps do
Expand Down
31 changes: 31 additions & 0 deletions test/oauth2/serializer_test.exs
@@ -0,0 +1,31 @@
defmodule OAuth2.SerializerTest do
use ExUnit.Case, async: false
alias OAuth2.Serializer

doctest OAuth2.Serializer

@json_mime "application/json"

defmodule TestSerializer do
def decode!(_), do: "decode_ok"
def encode!(_), do: "encode_ok"
end

test "has default json serializer" do
OAuth2.register_serializer(@json_mime, Poison)
assert %{"foo" => 1} == Serializer.decode!(~s|{"foo": 1}|, @json_mime)
end

test "accepts serializer override" do
OAuth2.register_serializer(@json_mime, TestSerializer)

assert "decode_ok" == Serializer.decode!(~s|{"foo": 1}|, @json_mime)
assert "encode_ok" == Serializer.encode!(%{"foo" => 1}, @json_mime)

OAuth2.register_serializer(@json_mime, Poison)
end

test "fallsback to Null serializer" do
assert OAuth2.Serializer.Null == Serializer.get("unknown")
end
end
13 changes: 13 additions & 0 deletions test/oauth2/strategies/null_test.exs
@@ -0,0 +1,13 @@
defmodule OAuth2.Serializer.NullTest do
use ExUnit.Case

alias OAuth2.Serializer.Null

test "encode!" do
assert "hello" == Null.encode!("hello")
end

test "decode!" do
assert "hello" == Null.decode!("hello")
end
end
2 changes: 2 additions & 0 deletions test/oauth2_test.exs
Expand Up @@ -2,6 +2,8 @@ defmodule OAuth2Test do
use ExUnit.Case
import OAuth2.TestHelpers

doctest OAuth2

@client build_client(client_id: "abc123",
client_secret: "xyz987",
site: "https://api.github.com",
Expand Down
44 changes: 0 additions & 44 deletions test/serializer_test.exs

This file was deleted.

3 changes: 2 additions & 1 deletion test/test_helper.exs
@@ -1,2 +1,3 @@
Application.ensure_all_started(:bypass)
ExUnit.start
Application.put_env(:oauth2, :warn_missing_serializer, false)
ExUnit.start()

0 comments on commit 88761bb

Please sign in to comment.