-
Notifications
You must be signed in to change notification settings - Fork 349
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
121 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,160 +1,162 @@ | ||
defmodule Tesla.Middleware.MessagePack do | ||
@moduledoc """ | ||
Encode requests and decode responses as MessagePack. | ||
if Code.ensure_loaded?(Msgpax) do | ||
defmodule Tesla.Middleware.MessagePack do | ||
@moduledoc """ | ||
Encode requests and decode responses as MessagePack. | ||
This middleware requires [Msgpax](https://hex.pm/packages/msgpax) as dependency. | ||
This middleware requires [Msgpax](https://hex.pm/packages/msgpax) as dependency. | ||
Remember to add `{:msgpax, ">= 2.3.0"}` to dependencies. | ||
Also, you need to recompile Tesla after adding `:msgpax` dependency: | ||
Remember to add `{:msgpax, ">= 2.3.0"}` to dependencies. | ||
Also, you need to recompile Tesla after adding `:msgpax` dependency: | ||
``` | ||
mix deps.clean tesla | ||
mix deps.compile tesla | ||
``` | ||
``` | ||
mix deps.clean tesla | ||
mix deps.compile tesla | ||
``` | ||
## Examples | ||
## Examples | ||
``` | ||
defmodule MyClient do | ||
use Tesla | ||
``` | ||
defmodule MyClient do | ||
use Tesla | ||
plug Tesla.Middleware.MessagePack | ||
# or | ||
plug Tesla.Middleware.MessagePack, engine_opts: [binary: true] | ||
# or | ||
plug Tesla.Middleware.MessagePack, decode: &Custom.decode/1, encode: &Custom.encode/1 | ||
end | ||
``` | ||
plug Tesla.Middleware.MessagePack | ||
# or | ||
plug Tesla.Middleware.MessagePack, engine_opts: [binary: true] | ||
# or | ||
plug Tesla.Middleware.MessagePack, decode: &Custom.decode/1, encode: &Custom.encode/1 | ||
end | ||
``` | ||
## Options | ||
## Options | ||
- `:decode` - decoding function | ||
- `:encode` - encoding function | ||
- `:encode_content_type` - content-type to be used in request header | ||
- `:decode_content_types` - list of additional decodable content-types | ||
- `:engine_opts` - optional engine options | ||
""" | ||
- `:decode` - decoding function | ||
- `:encode` - encoding function | ||
- `:encode_content_type` - content-type to be used in request header | ||
- `:decode_content_types` - list of additional decodable content-types | ||
- `:engine_opts` - optional engine options | ||
""" | ||
|
||
@behaviour Tesla.Middleware | ||
@behaviour Tesla.Middleware | ||
|
||
@default_decode_content_types ["application/msgpack", "application/x-msgpack"] | ||
@default_encode_content_type "application/msgpack" | ||
@default_decode_content_types ["application/msgpack", "application/x-msgpack"] | ||
@default_encode_content_type "application/msgpack" | ||
|
||
@impl Tesla.Middleware | ||
def call(env, next, opts) do | ||
opts = opts || [] | ||
@impl Tesla.Middleware | ||
def call(env, next, opts) do | ||
opts = opts || [] | ||
|
||
with {:ok, env} <- encode(env, opts), | ||
{:ok, env} <- Tesla.run(env, next) do | ||
decode(env, opts) | ||
with {:ok, env} <- encode(env, opts), | ||
{:ok, env} <- Tesla.run(env, next) do | ||
decode(env, opts) | ||
end | ||
end | ||
end | ||
|
||
@doc """ | ||
Encode request body as MessagePack. | ||
It is used by `Tesla.Middleware.EncodeMessagePack`. | ||
""" | ||
def encode(env, opts) do | ||
with true <- encodable?(env), | ||
{:ok, body} <- encode_body(env.body, opts) do | ||
{:ok, | ||
env | ||
|> Tesla.put_body(body) | ||
|> Tesla.put_headers([{"content-type", encode_content_type(opts)}])} | ||
else | ||
false -> {:ok, env} | ||
error -> error | ||
@doc """ | ||
Encode request body as MessagePack. | ||
It is used by `Tesla.Middleware.EncodeMessagePack`. | ||
""" | ||
def encode(env, opts) do | ||
with true <- encodable?(env), | ||
{:ok, body} <- encode_body(env.body, opts) do | ||
{:ok, | ||
env | ||
|> Tesla.put_body(body) | ||
|> Tesla.put_headers([{"content-type", encode_content_type(opts)}])} | ||
else | ||
false -> {:ok, env} | ||
error -> error | ||
end | ||
end | ||
end | ||
|
||
defp encode_body(body, opts), do: process(body, :encode, opts) | ||
defp encode_body(body, opts), do: process(body, :encode, opts) | ||
|
||
defp encode_content_type(opts), | ||
do: Keyword.get(opts, :encode_content_type, @default_encode_content_type) | ||
defp encode_content_type(opts), | ||
do: Keyword.get(opts, :encode_content_type, @default_encode_content_type) | ||
|
||
defp encodable?(%{body: nil}), do: false | ||
defp encodable?(%{body: body}) when is_binary(body), do: false | ||
defp encodable?(%{body: %Tesla.Multipart{}}), do: false | ||
defp encodable?(_), do: true | ||
defp encodable?(%{body: nil}), do: false | ||
defp encodable?(%{body: body}) when is_binary(body), do: false | ||
defp encodable?(%{body: %Tesla.Multipart{}}), do: false | ||
defp encodable?(_), do: true | ||
|
||
@doc """ | ||
Decode response body as MessagePack. | ||
@doc """ | ||
Decode response body as MessagePack. | ||
It is used by `Tesla.Middleware.DecodeMessagePack`. | ||
""" | ||
def decode(env, opts) do | ||
with true <- decodable?(env, opts), | ||
{:ok, body} <- decode_body(env.body, opts) do | ||
{:ok, %{env | body: body}} | ||
else | ||
false -> {:ok, env} | ||
error -> error | ||
It is used by `Tesla.Middleware.DecodeMessagePack`. | ||
""" | ||
def decode(env, opts) do | ||
with true <- decodable?(env, opts), | ||
{:ok, body} <- decode_body(env.body, opts) do | ||
{:ok, %{env | body: body}} | ||
else | ||
false -> {:ok, env} | ||
error -> error | ||
end | ||
end | ||
end | ||
|
||
defp decode_body(body, opts), do: process(body, :decode, opts) | ||
defp decode_body(body, opts), do: process(body, :decode, opts) | ||
|
||
defp decodable?(env, opts), do: decodable_body?(env) && decodable_content_type?(env, opts) | ||
defp decodable?(env, opts), do: decodable_body?(env) && decodable_content_type?(env, opts) | ||
|
||
defp decodable_body?(env) do | ||
(is_binary(env.body) && env.body != "") || (is_list(env.body) && env.body != []) | ||
end | ||
defp decodable_body?(env) do | ||
(is_binary(env.body) && env.body != "") || (is_list(env.body) && env.body != []) | ||
end | ||
|
||
defp decodable_content_type?(env, opts) do | ||
case Tesla.get_header(env, "content-type") do | ||
nil -> false | ||
content_type -> Enum.any?(content_types(opts), &String.starts_with?(content_type, &1)) | ||
defp decodable_content_type?(env, opts) do | ||
case Tesla.get_header(env, "content-type") do | ||
nil -> false | ||
content_type -> Enum.any?(content_types(opts), &String.starts_with?(content_type, &1)) | ||
end | ||
end | ||
end | ||
|
||
defp content_types(opts), | ||
do: @default_decode_content_types ++ Keyword.get(opts, :decode_content_types, []) | ||
defp content_types(opts), | ||
do: @default_decode_content_types ++ Keyword.get(opts, :decode_content_types, []) | ||
|
||
defp process(data, op, opts) do | ||
case do_process(data, op, opts) do | ||
{:ok, data} -> {:ok, data} | ||
{:error, reason} -> {:error, {__MODULE__, op, reason}} | ||
{:error, reason, _pos} -> {:error, {__MODULE__, op, reason}} | ||
defp process(data, op, opts) do | ||
case do_process(data, op, opts) do | ||
{:ok, data} -> {:ok, data} | ||
{:error, reason} -> {:error, {__MODULE__, op, reason}} | ||
{:error, reason, _pos} -> {:error, {__MODULE__, op, reason}} | ||
end | ||
rescue | ||
ex in Protocol.UndefinedError -> | ||
{:error, {__MODULE__, op, ex}} | ||
end | ||
rescue | ||
ex in Protocol.UndefinedError -> | ||
{:error, {__MODULE__, op, ex}} | ||
end | ||
|
||
defp do_process(data, op, opts) do | ||
# :encode/:decode | ||
if fun = opts[op] do | ||
fun.(data) | ||
else | ||
opts = Keyword.get(opts, :engine_opts, []) | ||
|
||
case op do | ||
:encode -> Msgpax.pack(data, opts) | ||
:decode -> Msgpax.unpack(data, opts) | ||
defp do_process(data, op, opts) do | ||
# :encode/:decode | ||
if fun = opts[op] do | ||
fun.(data) | ||
else | ||
opts = Keyword.get(opts, :engine_opts, []) | ||
|
||
case op do | ||
:encode -> Msgpax.pack(data, opts) | ||
:decode -> Msgpax.unpack(data, opts) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
defmodule Tesla.Middleware.DecodeMessagePack do | ||
@moduledoc false | ||
def call(env, next, opts) do | ||
opts = opts || [] | ||
defmodule Tesla.Middleware.DecodeMessagePack do | ||
@moduledoc false | ||
def call(env, next, opts) do | ||
opts = opts || [] | ||
|
||
with {:ok, env} <- Tesla.run(env, next) do | ||
Tesla.Middleware.MessagePack.decode(env, opts) | ||
with {:ok, env} <- Tesla.run(env, next) do | ||
Tesla.Middleware.MessagePack.decode(env, opts) | ||
end | ||
end | ||
end | ||
end | ||
|
||
defmodule Tesla.Middleware.EncodeMessagePack do | ||
@moduledoc false | ||
def call(env, next, opts) do | ||
opts = opts || [] | ||
defmodule Tesla.Middleware.EncodeMessagePack do | ||
@moduledoc false | ||
def call(env, next, opts) do | ||
opts = opts || [] | ||
|
||
with {:ok, env} <- Tesla.Middleware.MessagePack.encode(env, opts) do | ||
Tesla.run(env, next) | ||
with {:ok, env} <- Tesla.Middleware.MessagePack.encode(env, opts) do | ||
Tesla.run(env, next) | ||
end | ||
end | ||
end | ||
end | ||
end |