Page MenuHomePhorge

No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None
diff --git a/README.md b/README.md
index cfa2c00..e6fc7eb 100644
--- a/README.md
+++ b/README.md
@@ -1,219 +1,220 @@
# Tesla
[![CircleCI Status](https://circleci.com/gh/teamon/tesla.png?style=shield)](https://circleci.com/gh/monterail/tesla)
[![Hex.pm](https://img.shields.io/hexpm/v/tesla.svg)](http://hex.pm/packages/tesla)
Tesla is an HTTP client losely based on [Faraday](https://github.com/lostisland/faraday).
It embraces the concept of middleware when processing the request/response cycle.
> **WARNING**: Tesla is currently under heavy development, so please don't use it in your production application just yet.
> Nevertheless all comments/issues/suggestions are more than welcome - please submit them using [GitHub issues](https://github.com/monterail/tesla/issues), thanks!
## Basic usage
```ex
# Example get request
response = Tesla.get("http://httpbin.org/ip")
response.status # => 200
response.body # => '{\n "origin": "87.205.72.203"\n}\n'
response.headers # => %{'Content-Type' => 'application/json' ...}
response = Tesla.get("http://httpbin.org/get", %{a: 1, b: "foo"})
response.url # => "http://httpbin.org/get?a=1&b=foo"
# Example post request
response = Tesla.post("http://httpbin.org/post", "data")
```
## Installation
Add `tesla` as dependency in `mix.exs`
```ex
defp deps do
[{:tesla, "~> 0.1.0"},
{:ibrowse, github: "cmullaparthi/ibrowse", tag: "v4.1.1"}, # default adapter
{:exjsx, "~> 3.1.0"}] # for JSON middleware
end
```
When using `ibrowse` adapter add it to list of applications in `mix.exs`
```ex
def application do
[applications: [:ibrowse, ...], ...]
end
```
## Creating API clients
Use `Tesla.Builder` module to create API wrappers.
For example
```ex
defmodule GitHub do
use Tesla.Builder
plug Tesla.Middleware.BaseUrl, "https://api.github.com"
plug Tesla.Middleware.Headers, %{'Authorization' => 'xyz'}
plug Tesla.Middleware.EncodeJson
plug Tesla.Middleware.DecodeJson
adapter Tesla.Adapter.Ibrowse
def user_repos(login) do
get("/user/" <> login <> "/repos")
end
end
```
Then use it like this:
```ex
GitHub.get("/user/teamon/repos")
GitHub.user_repos("teamon")
```
## Adapters
Tesla has support for different adapters that do the actual HTTP request processing.
### ibrowse
Tesla has built-in support for [ibrowse](https://github.com/cmullaparthi/ibrowse) Erlang HTTP client.
To use it simply include `adapter Tesla.Adapter.Ibrowse` line in your API client definition.
NOTE: Remember to include ibrowse in applications list.
ibrowse is also the default adapter when using generic `Tesla.get(...)` etc. methods.
### Test / Mock
When testing it might be useful to use simple function as adapter:
```ex
defmodule MyApi do
use Tesla
adapter fn (env) ->
case env.url do
"/" -> {200, %{}, "home"}
"/about" -> {200, %{}, "about us"}
end
end
end
```
## Middleware
### Basic
- `Tesla.Middleware.BaseUrl` - set base url for all request
- `Tesla.Middleware.Headers` - set request headers
+- `Tesla.Middleware.QueryParams` - set query parameters
### JSON
NOTE: requires [exjsx](https://github.com/talentdeficit/exjsx) as dependency
- `Tesla.Middleware.DecodeJson` - decode response body as JSON
- `Tesla.Middleware.EncodeJson` - endode request body as JSON
If you are using different json library writing middleware should be straightforward. See [`json.ex`](https://github.com/monterail/tesla/blob/master/lib/tesla/middleware/json.ex) for implementation.
## Dynamic middleware
All methods can take a middleware function as the first parameter.
This allow to use convinient syntax for modyfiyng the behaviour in runtime.
Consider the following case: GitHub API can be accessed using OAuth token authorization.
We can't use `plug Tesla.Middleware.Headers, %{'Authorization' => 'token here'}` since this would be compiled only once and there is no way to insert dynamic user token.
Instead, we can use `Tesla.build_client` to create a dynamic middleware function:
```ex
defmodule GitHub do
# same as above
def client(token) do
Tesla.build_client [
{Tesla.Middleware.Headers, %{'Authorization' => "token: " <> token }}
]
end
end
```
and then:
```ex
client = GitHub.client(user_token)
client |> GitHub.user_repos("teamon")
client |> GitHub.get("/me")
```
## Writing your own middleware
A Tesla middleware is a module with `call/3` function:
```ex
defmodule MyMiddleware do
def call(env, run, options) do
# ...
end
end
```
The arguments are:
- `env` - `Tesla.Env` instance
- `run` - continuation function for the rest of middleware/adapter stack
- `options` - arguments passed during middleware configuration (`plug MyMiddleware, options`)
There is no distinction between request and response middleware, it's all about executing `run` function at the correct time.
For example, z request logger middleware could be implemented like this:
```ex
defmodule Tesla.Middleware.RequestLogger do
def call(env, run, _) do
IO.inspect env # print request env
run.(env)
end
end
```
and response logger middleware like this:
```ex
defmodule Tesla.Middleware.ResponseLogger do
def call(env, run, _) do
res = run.(env)
IO.inspect res # print response env
res
end
end
```
See [`core.ex`](https://github.com/monterail/tesla/blob/master/lib/tesla/middleware/core.ex) and [`json.ex`](https://github.com/monterail/tesla/blob/master/lib/tesla/middleware/json.ex) for more examples.
## Asynchronous requests
If adapter supports it, you can make asynchronous requests by passing `respond_to: pid` option:
```ex
Tesla.get("http://example.org", respond_to: self)
receive do
{:tesla_response, res} -> res.status # => 200
end
```
diff --git a/lib/tesla/middleware/core.ex b/lib/tesla/middleware/core.ex
index 414617d..e68b62a 100644
--- a/lib/tesla/middleware/core.ex
+++ b/lib/tesla/middleware/core.ex
@@ -1,53 +1,74 @@
defmodule Tesla.Middleware.BaseUrl do
def call(env, run, base) do
env = if !Regex.match?(~r/^https?:\/\//, env.url) do
%{env | url: base <> env.url}
else
env
end
run.(env)
end
end
defmodule Tesla.Middleware.Headers do
def call(env, run, headers) do
headers = Map.merge(env.headers, headers)
run.(%{env | headers: headers})
end
end
+defmodule Tesla.Middleware.QueryParams do
+ def call(env, run, query) do
+ env = %{env | url: merge_url_and_query(env.url, query)}
+ run.(env)
+ end
+
+ @spec merge_url_and_query(String.t, %{}) :: String.t
+ def merge_url_and_query(url, query) do
+ query = for {key, val} <- query, into: %{}, do: {to_string(key), val}
+ uri = URI.parse(url)
+ q = if uri.query do
+ env_query = URI.decode_query(uri.query)
+ Map.merge(query, env_query)
+ else
+ query
+ end
+
+ uri |> Map.put(:query, URI.encode_query(q)) |> URI.to_string
+ end
+end
+
defmodule Tesla.Middleware.DecodeRels do
def call(env, run, []) do
env = run.(env)
if env.headers['Link'] do
rels = env.headers['Link']
|> to_string
|> String.split(",")
|> Enum.map(&String.strip/1)
|> Enum.map(fn e -> Regex.run(~r/\A<(.+)>; rel="(.+)"\z/, e, capture: :all_but_first) |> List.to_tuple end)
|> Enum.reduce(%{}, fn ({url, key}, a) -> Dict.put(a, key, url) end)
env |> Map.put(:rels, rels)
else
env
end
end
end
defmodule Tesla.Middleware.AdapterOptions do
def call(env, run, opts) do
run.(%{env | opts: env.opts ++ opts})
end
end
defmodule Tesla.Middleware.BaseUrlFromConfig do
def call(env, run, opts) do
run.(%{env | url: config(opts)[:base_url] <> env.url})
end
defp config(opts) do
Application.get_env(Keyword.fetch!(opts, :otp_app), Keyword.fetch!(opts, :module))
end
-end
\ No newline at end of file
+end
diff --git a/test/tesla/middleware/core_test.exs b/test/tesla/middleware/core_test.exs
index 48b4d64..09c9099 100644
--- a/test/tesla/middleware/core_test.exs
+++ b/test/tesla/middleware/core_test.exs
@@ -1,29 +1,41 @@
defmodule CoreTest do
use ExUnit.Case
# TODO: Change order of arguments to (mid, opts, env)
def call(mid, env, opts) do
mid.call(env, fn a -> a end, opts)
end
test "Tesla.Middleware.BaseUrl" do
env = call(Tesla.Middleware.BaseUrl, %{url: "/path"}, "http://example.com")
assert env.url == "http://example.com/path"
end
test "Tesla.Middleware.BaseUrl - skip double append" do
env = call(Tesla.Middleware.BaseUrl, %{url: "http://other.foo"}, "http://example.com")
assert env.url == "http://other.foo"
end
+ test "Tesla.Middlware.QueryParams - joining default query params" do
+ e = %Tesla.Env{url: "http://example.com"}
+ env = call(Tesla.Middleware.QueryParams, e, %{access_token: "secret_token"})
+ assert env.url == "http://example.com?access_token=secret_token"
+ end
+
+ test "Tesla.Middlware.QueryParams - should not override existing key" do
+ e = %Tesla.Env{url: "http://example.com?foo=bar&access_token=params_token"}
+ env = call(Tesla.Middleware.QueryParams, e, %{access_token: "middleware_token"})
+ assert env.url == "http://example.com?access_token=params_token&foo=bar"
+ end
+
test "Tesla.Middleware.Headers" do
env = call(Tesla.Middleware.Headers, %{headers: %{}}, %{'Content-Type' => 'text/plain'})
assert env.headers == %{'Content-Type' => 'text/plain'}
end
test "Tesla.Middleware.BaseUrlFromConfig" do
Application.put_env(:tesla, SomeModule, [base_url: "http://example.com"])
env = call(Tesla.Middleware.BaseUrlFromConfig, %{url: "/path"}, otp_app: :tesla, module: SomeModule)
assert env.url == "http://example.com/path"
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 10, 5:26 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785373
Default Alt Text
(9 KB)

Event Timeline