Packages
tesla
1.11.1
1.20.0
1.18.3
1.18.2
1.18.1
1.18.0
1.17.0
1.16.0
1.15.3
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.3
1.12.2
1.12.1
1.12.0
1.11.2
1.11.1
1.11.0
1.10.3
1.10.2
1.10.1
1.10.0
1.9.0
1.8.1
retired
1.8.0
1.7.0
1.6.1
1.6.0
1.5.1
1.5.0
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.0
1.0.0
1.0.0-beta.1
0.10.0
0.9.0
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.2.2
0.2.1
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
HTTP client library, with support for middleware and multiple adapters.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/tesla/middleware/json.ex
defmodule Tesla.Middleware.JSON do
@moduledoc """
Encode requests and decode responses as JSON.
This middleware requires [Jason](https://hex.pm/packages/jason) (or other engine) as dependency.
Remember to add `{:jason, ">= 1.0"}` to dependencies.
Also, you need to recompile Tesla after adding `:jason` dependency:
```
mix deps.clean tesla
mix deps.compile tesla
```
If you only need to encode the request body or decode the response body,
you can use `Tesla.Middleware.EncodeJson` or `Tesla.Middleware.DecodeJson` directly instead.
## Examples
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.JSON # use jason engine
# or
plug Tesla.Middleware.JSON, engine: JSX, engine_opts: [strict: [:comments]]
# or
plug Tesla.Middleware.JSON, engine: Poison, engine_opts: [keys: :atoms]
# or
plug Tesla.Middleware.JSON, decode: &JSX.decode/1, encode: &JSX.encode/1
end
```
## Options
- `:decode` - decoding function
- `:encode` - encoding function
- `:encode_content_type` - content-type to be used in request header
- `:engine` - encode/decode engine, e.g `Jason`, `Poison` or `JSX` (defaults to Jason)
- `:engine_opts` - optional engine options
- `:decode_content_types` - list of additional decodable content-types
"""
@behaviour Tesla.Middleware
# NOTE: text/javascript added to support Facebook Graph API.
# see https://github.com/teamon/tesla/pull/13
@default_content_types ["application/json", "text/javascript"]
@default_encode_content_type "application/json"
@default_engine Jason
@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)
end
end
@doc """
Encode request body as JSON.
It is used by `Tesla.Middleware.EncodeJson`.
"""
@spec encode(Tesla.Env.t(), keyword()) :: Tesla.Env.result()
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
defp encode_body(%Stream{} = body, opts), do: {:ok, encode_stream(body, opts)}
defp encode_body(body, opts) when is_function(body), do: {:ok, encode_stream(body, 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_stream(body, opts) do
Stream.map(body, fn item ->
{:ok, body} = encode_body(item, opts)
body <> "\n"
end)
end
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 JSON.
It is used by `Tesla.Middleware.DecodeJson`.
"""
@spec decode(Tesla.Env.t(), keyword()) :: Tesla.Env.result()
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
defp decode_body(body, opts) when is_struct(body, Stream) or is_function(body),
do: {:ok, decode_stream(body, 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_body?(env) do
(is_binary(env.body) && env.body != "") ||
(is_list(env.body) && env.body != []) ||
is_function(env.body) ||
is_struct(env.body, Stream)
end
defp decodable_content_type?(env, opts) do
case Tesla.get_header(env, "content-type") do
nil ->
false
content_type ->
content_type = String.downcase(content_type)
opts
|> content_types()
|> Enum.any?(&String.starts_with?(content_type, &1))
end
end
defp decode_stream(body, opts) do
Stream.map(body, fn chunk ->
case decode_body(chunk, opts) do
{:ok, item} -> item
_ -> chunk
end
end)
end
defp content_types(opts),
do: @default_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}}
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
engine = Keyword.get(opts, :engine, @default_engine)
opts = Keyword.get(opts, :engine_opts, [])
apply(engine, op, [data, opts])
end
end
end
defmodule Tesla.Middleware.DecodeJson do
@moduledoc """
Decodes response body as JSON.
Only decodes the body if the `Content-Type` header suggests
that the body is JSON.
"""
@moduledoc since: "1.8.0"
@behaviour Tesla.Middleware
@impl Tesla.Middleware
def call(env, next, opts) do
opts = opts || []
with {:ok, env} <- Tesla.run(env, next) do
Tesla.Middleware.JSON.decode(env, opts)
end
end
end
defmodule Tesla.Middleware.EncodeJson do
@moduledoc """
Encodes request body as JSON.
"""
@moduledoc since: "1.8.0"
@behaviour Tesla.Middleware
@impl Tesla.Middleware
def call(env, next, opts) do
opts = opts || []
with {:ok, env} <- Tesla.Middleware.JSON.encode(env, opts) do
Tesla.run(env, next)
end
end
end