Page MenuHomePhorge

No OneTemporary

Size
28 KB
Referenced Files
None
Subscribers
None
diff --git a/README.md b/README.md
index 27bd58a..c0163c1 100644
--- a/README.md
+++ b/README.md
@@ -1,485 +1,485 @@
# Tesla
[![Build Status](https://travis-ci.org/teamon/tesla.svg?branch=master)](https://travis-ci.org/teamon/tesla)
[![Hex.pm](https://img.shields.io/hexpm/v/tesla.svg)](http://hex.pm/packages/tesla)
[![Hex.pm](https://img.shields.io/hexpm/dt/tesla.svg)](https://hex.pm/packages/tesla)
[![Hex.pm](https://img.shields.io/hexpm/dw/tesla.svg)](https://hex.pm/packages/tesla)
[![codecov](https://codecov.io/gh/teamon/tesla/branch/master/graph/badge.svg)](https://codecov.io/gh/teamon/tesla)
[![Inline docs](http://inch-ci.org/github/teamon/tesla.svg)](http://inch-ci.org/github/teamon/tesla)
Tesla is an HTTP client loosely based on [Faraday](https://github.com/lostisland/faraday).
It embraces the concept of middleware when processing the request/response cycle.
> Note that this README refers to the `master` branch of Tesla, not the latest
released version on Hex. See [the documentation](http://hexdocs.pm/tesla) for
the documentation of the version you're using.
<hr/>
## [`0.x` to `1.0` Migration Guide](https://github.com/teamon/tesla/wiki/0.x-to-1.0-Migration-Guide)
```ex
defp deps do
[{:tesla, github: "teamon/tesla", branch: "1.0"}]
end
```
<hr/>
## HTTP Client example
Define module with `use Tesla` and choose from a variety of middleware.
```elixir
defmodule GitHub do
use Tesla
plug Tesla.Middleware.BaseUrl, "https://api.github.com"
plug Tesla.Middleware.Headers, [{"authorization", "token xyz"}]
plug Tesla.Middleware.JSON
def user_repos(login) do
get("/user/" <> login <> "/repos")
end
end
```
Then use it like this:
```elixir
{:ok, response} = GitHub.user_repos("teamon")
response.status # => 200
response.body # => [%{…}, …]
response.headers # => [{"content-type", "application/json"}, ...]
```
See below for documentation.
## Installation
Add `tesla` as dependency in `mix.exs`
```elixir
defp deps do
[{:tesla, "~> 0.10.0"},
{:poison, ">= 1.0.0"}] # optional, required by JSON middleware
end
```
Also, unless using Elixir `>= 1.4`, add `:tesla` to the `applications` list:
```ex
def application do
[applications: [:tesla, ...], ...]
end
```
## Documentation
- [Middleware](#middleware)
- [Runtime middleware](#runtime-middleware)
- [Adapters](#adapters)
- [Streaming](#streaming)
- [Multipart](#multipart)
- [Testing](#testing)
- [Writing middleware](#writing-middleware)
- [Direct usage](#direct-usage)
- [Cheatsheet](#cheatsheet)
- [Changelog](https://github.com/teamon/tesla/releases)
## Middleware
Tesla is built around the concept of composable middlewares.
This is very similar to how [Plug Router](https://github.com/elixir-plug/plug#the-plug-router) works.
#### Basic
- [`Tesla.Middleware.BaseUrl`](https://hexdocs.pm/tesla/Tesla.Middleware.BaseUrl.html) - set base url
- [`Tesla.Middleware.Headers`](https://hexdocs.pm/tesla/Tesla.Middleware.Headers.html) - set request headers
- [`Tesla.Middleware.Query`](https://hexdocs.pm/tesla/Tesla.Middleware.Query.html) - set query parameters
- [`Tesla.Middleware.Opts`](https://hexdocs.pm/tesla/Tesla.Middleware.Opts.html) - set request options
- [`Tesla.Middleware.FollowRedirects`](https://hexdocs.pm/tesla/Tesla.Middleware.FollowRedirects.html) - follow 3xx redirects
- [`Tesla.Middleware.MethodOverride`](https://hexdocs.pm/tesla/Tesla.Middleware.MethodOverride.html) - set X-Http-Method-Override
- [`Tesla.Middleware.Logger`](https://hexdocs.pm/tesla/Tesla.Middleware.Logger.html) - log requests (method, url, status, time)
- [`Tesla.Middleware.DebugLogger`](https://hexdocs.pm/tesla/Tesla.Middleware.DebugLogger.html) - log full requests & responses
#### Formats
- [`Tesla.Middleware.FormUrlencoded`](https://hexdocs.pm/tesla/Tesla.Middleware.FormUrlencoded.html) - urlencode POST body parameter, useful for POSTing a map/keyword list
- [`Tesla.Middleware.JSON`](https://hexdocs.pm/tesla/Tesla.Middleware.JSON.html) - JSON request/response body
- [`Tesla.Middleware.Compression`](https://hexdocs.pm/tesla/Tesla.Middleware.Compression.html) - gzip & deflate
- [`Tesla.Middleware.DecodeRels`](https://hexdocs.pm/tesla/Tesla.Middleware.DecodeRels.html) - decode `Link` header into `opts[:rels]` field in response
#### Auth
- [`Tesla.Middleware.BasicAuth`](https://hexdocs.pm/tesla/Tesla.Middleware.BasicAuth.html) - HTTP Basic Auth
- [`Tesla.Middleware.DigestAuth`](https://hexdocs.pm/tesla/Tesla.Middleware.DigestAuth.html) - Digest access authentication
#### Error handling
- [`Tesla.Middleware.Timeout`](https://hexdocs.pm/tesla/Tesla.Middleware.Timeout.html) - timeout request after X milliseconds despite of server response
- [`Tesla.Middleware.Retry`](https://hexdocs.pm/tesla/Tesla.Middleware.Retry.html) - retry few times in case of connection refused
- [`Tesla.Middleware.Fuse`](https://hexdocs.pm/tesla/Tesla.Middleware.Fuse.html) - fuse circuit breaker integration
- [`Tesla.Middleware.Tuples`](https://hexdocs.pm/tesla/Tesla.Middleware.Tuples.html) - return `{:ok, env} | {:error, reason}` instead of raising exception
## Runtime middleware
All HTTP functions (`get`, `post`, etc.) can take a dynamic client function as the first parameter.
This allow to use convenient syntax for modifying 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:
```elixir
defmodule GitHub do
# same as above with a slightly change to `user_repos/1`
def user_repos(client, login) do
# pass `client` argument to `get` function
get(client, "/user/" <> login <> "/repos")
end
def issues(client \\ %Tesla.Client{}) do
# default to empty client that will not include runtime token
get(client, "/issues")
end
# build dynamic client based on runtime arguments
def client(token) do
Tesla.build_client [
{Tesla.Middleware.Headers, [{"authorization", "token: " <> token }]}
]
end
end
```
and then:
```elixir
client = GitHub.client(user_token)
client |> GitHub.user_repos("teamon")
client |> GitHub.get("/me")
GitHub.issues()
client |> GitHub.issues()
```
The `Tesla.build_client` function can take two arguments: `pre` and `post` middleware.
The first list (`pre`) will be included before any other middleware. In case there is a need
to inject middleware at the end you can pass a second list (`post`). It will be put just
before adapter. In fact, one can even dynamically override the adapter.
For example, a private (per user) cache could be implemented as:
```elixir
def new(user) do
Tesla.build_client [], [
fn env, next ->
case my_private_cache.fetch(user, env) do
{:ok, env} -> {:ok, env} # return cached response
:error -> Tesla.run(env, next) # make real request
end
end
end
end
```
## Adapters
Tesla supports multiple HTTP adapter that do the actual HTTP request processing.
- [`Tesla.Adapter.Httpc`](https://hexdocs.pm/tesla/Tesla.Adapter.Httpc.html) - the default, built-in erlang [httpc](http://erlang.org/doc/man/httpc.html) adapter
- [`Tesla.Adapter.Hackney`](https://hexdocs.pm/tesla/Tesla.Adapter.Hackney.html) - [hackney](https://github.com/benoitc/hackney), "simple HTTP client in Erlang"
- [`Tesla.Adapter.Ibrowse`](https://hexdocs.pm/tesla/Tesla.Adapter.Ibrowse.html) - [ibrowse](https://github.com/cmullaparthi/ibrowse), "Erlang HTTP client"
When using ibrowse or hackney adapters remember to alter applications list in `mix.exs` (for Elixir < 1.4)
```elixir
def application do
[applications: [:tesla, :ibrowse, ...], ...] # or :hackney
end
```
and add it to the dependency list
```elixir
defp deps do
[{:tesla, "~> 0.7.0"},
{:ibrowse, "~> 4.2"}, # or :hackney
{:poison, ">= 1.0.0"}] # for JSON middleware
end
```
## Streaming
If adapter supports it, you can pass a [Stream](http://elixir-lang.org/docs/stable/elixir/Stream.html) as body, e.g.:
```elixir
defmodule ElasticSearch do
use Tesla
plug Tesla.Middleware.BaseUrl, "http://localhost:9200"
plug Tesla.Middleware.JSON
def index(records_stream) do
stream = records_stream |> Stream.map(fn record -> %{index: [some, data]})
post("/_bulk", stream)
end
end
```
Each piece of stream will be encoded as JSON and sent as a new line (conforming to JSON stream format)
## Multipart
You can pass a `Tesla.Multipart` struct as the body.
```elixir
alias Tesla.Multipart
mp =
Multipart.new
|> Multipart.add_content_type_param("charset=utf-8")
|> Multipart.add_field("field1", "foo")
- |> Multipart.add_field("field2", "bar", headers: [{:"Content-Id", 1}, {:"Content-Type", "text/plain"}])
+ |> Multipart.add_field("field2", "bar", headers: [{"content-id", "1"}, {"content-type", "text/plain"}])
|> Multipart.add_file("test/tesla/multipart_test_file.sh")
|> Multipart.add_file("test/tesla/multipart_test_file.sh", name: "foobar")
|> Multipart.add_file_content("sample file content", "sample.txt")
response = MyApiClient.post("http://httpbin.org/post", mp)
```
## Testing
You can set the adapter to `Tesla.Mock` in tests.
```elixir
# config/test.exs
# Use mock adapter for all clients
config :tesla, adapter: Tesla.Mock
# or only for one
config :tesla, MyClient, adapter: Tesla.Mock
```
Then, mock requests before using your client:
```elixir
defmodule MyAppTest do
use ExUnit.Case
setup do
Tesla.Mock.mock fn
%{method: :get, url: "http://example.com/hello"} ->
%Tesla.Env{status: 200, body: "hello"}
%{method: :post, url: "http://example.com/world"} ->
%Tesla.Env{status: 200, body: "hi!"}
end
:ok
end
test "list things" do
assert {:ok, %Tesla.Env{} = env} = MyApp.get("/hello")
assert env.status == 200
assert env.body == "hello"
end
end
```
## Writing middleware
A Tesla middleware is a module with `call/3` function, that at some point calls `Tesla.run(env, next)` to process
the rest of stack.
```elixir
defmodule MyMiddleware do
@behaviour Tesla.Middleware
def call(env, next, options) do
env
|> do_something_with_request
|> Tesla.run(next)
|> do_something_with_response
end
end
```
The arguments are:
- `env` - `Tesla.Env` instance
- `next` - middleware continuation stack; to be executed with `Tesla.run(env, next)`
- `options` - arguments passed during middleware configuration (`plug MyMiddleware, options`)
There is no distinction between request and response middleware, it's all about executing `Tesla.run/2` function at the correct time.
For example, a request logger middleware could be implemented like this:
```elixir
defmodule Tesla.Middleware.RequestLogger do
@behaviour Tesla.Middleware
def call(env, next, _) do
IO.inspect env # print request env
Tesla.run(env, next)
end
end
```
and response logger middleware like this:
```elixir
defmodule Tesla.Middleware.ResponseLogger do
@behaviour Tesla.Middleware
def call(env, next, _) do
res = Tesla.run(env, next)
IO.inspect res # print response env
res
end
end
```
See [built-in middlewares](https://github.com/teamon/tesla/tree/master/lib/tesla/middleware) for more examples.
Middleware should have documentation following this template:
````elixir
defmodule Tesla.Middleware.SomeMiddleware do
@behaviour Tesla.Middleware
@moduledoc """
Short description what it does
Longer description, including e.g. additional dependencies.
### Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.SomeMiddleware, most: :common, options: "here"
end
```
### Options
- `:list` - all possible options
- `:with` - their default values
"""
end
````
## Direct usage
You can also use Tesla directly, without creating a client module.
This however won’t include any middleware.
```elixir
# Example get request
{:ok, 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" ...}]
{:ok, response} = Tesla.get("http://httpbin.org/get", query: [a: 1, b: "foo"])
response.url # => "http://httpbin.org/get?a=1&b=foo"
# Example post request
{:ok, response} = Tesla.post("http://httpbin.org/post", "data", headers: [{"Content-Type", "application/json"}])
```
## Cheatsheet
#### Making requests 101
```elixir
# GET /path
get("/path")
# GET /path?a=hi&b[]=1&b[]=2&b[]=3
get("/path", query: [a: "hi", b: [1,2,3]])
# GET with dynamic client
get(client, "/path")
get(client, "/path", query: [page: 3])
# arguments are the same for GET, HEAD, OPTIONS & TRACE
head("/path")
options("/path")
trace("/path")
# POST, PUT, PATCH
post("/path", "some-body-i-used-to-know")
put("/path", "some-body-i-used-to-know", query: [a: "0"])
patch("/path", multipart)
```
#### Configuring HTTP functions visibility
```elixir
# generate only get and post function
use Tesla, only: ~w(get post)a
# generate only delete fuction
use Tesla, only: [:delete]
# generate all functions except delete and options
use Tesla, except: [:delete, :options]
```
#### Disable docs for HTTP functions
```elixir
use Tesla, docs: false
```
#### Decode only JSON response (do not encode request)
```elixir
plug Tesla.Middleware.DecodeJson
```
#### Use other JSON library
```elixir
# use JSX
plug Tesla.Middleware.JSON, engine: JSX, engine_opts: [strict: [:comments]]
# use custom functions
plug Tesla.Middleware.JSON, decode: &JSX.decode/1, encode: &JSX.encode/1
```
#### Custom middleware
```elixir
defmodule Tesla.Middleware.MyCustomMiddleware do
def call(env, next, options) do
env
|> do_something_with_request
|> Tesla.run(next)
|> do_something_with_response
end
end
```
## Contributing
1. Fork it (https://github.com/teamon/tesla/fork)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
Copyright (c) 2015-2017 [Tymon Tobolski](http://teamon.eu/about/)
diff --git a/lib/tesla/multipart.ex b/lib/tesla/multipart.ex
index 16fefea..7e8c042 100644
--- a/lib/tesla/multipart.ex
+++ b/lib/tesla/multipart.ex
@@ -1,180 +1,180 @@
defmodule Tesla.Multipart do
@moduledoc """
Multipart functionality.
### Example
```
mp =
Multipart.new
|> Multipart.add_content_type_param("charset=utf-8")
|> Multipart.add_field("field1", "foo")
- |> Multipart.add_field("field2", "bar", headers: [{:"Content-Id", 1}, {:"Content-Type", "text/plain"}])
+ |> Multipart.add_field("field2", "bar", headers: [{"content-id", "1"}, {"content-type", "text/plain"}])
|> Multipart.add_file("test/tesla/multipart_test_file.sh")
|> Multipart.add_file("test/tesla/multipart_test_file.sh", name: "foobar")
|> Multipart.add_file_content("sample file content", "sample.txt")
response = client.post(url, mp)
```
"""
+ defmodule Part do
+ @moduledoc false
+
+ defstruct body: nil,
+ dispositions: [],
+ headers: []
+
+ @type t :: %__MODULE__{
+ body: String.t(),
+ headers: Tesla.Env.headers(),
+ dispositions: Keyword.t()
+ }
+ end
+
@boundary_chars "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|> String.split("")
- @type part_stream :: IO.Stream.t() | File.Stream.t()
+ @type part_stream :: IO.Stream.t() | File.Stream.t() | Enumerable.t()
@type part_value :: String.t() | part_stream
defstruct parts: [],
boundary: nil,
content_type_params: []
@type t :: %__MODULE__{
parts: list(Part.t()),
boundary: String.t(),
content_type_params: [String.t()]
}
- defmodule Part do
- @moduledoc false
-
- defstruct body: nil,
- dispositions: [],
- headers: []
-
- @type t :: %__MODULE__{
- body: String.t(),
- headers: Keyword.t(),
- dispositions: Keyword.t()
- }
- end
-
@doc """
Create a new Multipart struct to be used for a request body.
"""
@spec new() :: t
def new do
%__MODULE__{boundary: unique_string(32)}
end
@doc """
Add a parameter to the multipart content-type.
"""
@spec add_content_type_param(t, String.t()) :: t
def add_content_type_param(%__MODULE__{} = mp, param) do
%{mp | content_type_params: mp.content_type_params ++ [param]}
end
@doc """
Add a field part.
"""
@spec add_field(t, String.t(), part_value, Keyword.t()) :: t
def add_field(%__MODULE__{} = mp, name, value, opts \\ []) do
{headers, opts} = Keyword.pop_first(opts, :headers, [])
part = %Part{
body: value,
headers: headers,
dispositions: [{:name, name}] ++ opts
}
%{mp | parts: mp.parts ++ [part]}
end
@doc """
Add a file part. The file will be streamed.
Options:
- `:name` - name of form param
- `:filename` - filename (defaults to path basename)
- `:headers` - additional headers
- `:detect_content_type` - auto-detect file content-type (defaults to false)
"""
@spec add_file(t, String.t(), Keyword.t()) :: t
def add_file(%__MODULE__{} = mp, path, opts \\ []) do
{filename, opts} = Keyword.pop_first(opts, :filename, Path.basename(path))
{headers, opts} = Keyword.pop_first(opts, :headers, [])
{detect_content_type, opts} = Keyword.pop_first(opts, :detect_content_type, false)
# add in detected content-type if necessary
headers =
case detect_content_type do
- true -> Keyword.put(headers, :"Content-Type", MIME.from_path(path))
+ true -> List.keystore(headers, "content-type", 0, {"content-type", MIME.from_path(path)})
false -> headers
end
data = File.stream!(path, [:read], 2048)
add_file_content(mp, data, filename, opts ++ [headers: headers])
end
@doc """
Add a file part. Same of `add_file/3` but the file content is read from `data` input parameter.
Options:
- `:name` - name of form param
- `:headers` - additional headers
"""
@spec add_file_content(t, part_value, String.t(), Keyword.t()) :: t
def add_file_content(%__MODULE__{} = mp, data, filename, opts \\ []) do
{name, opts} = Keyword.pop_first(opts, :name, "file")
add_field(mp, name, data, opts ++ [filename: filename])
end
@doc false
- @spec headers(t) :: Keyword.t()
+ @spec headers(t) :: Tesla.Env.headers()
def headers(%__MODULE__{boundary: boundary, content_type_params: params}) do
ct_params = (["boundary=#{boundary}"] ++ params) |> Enum.join("; ")
- [{:"Content-Type", "multipart/form-data; #{ct_params}"}]
+ [{"content-type", "multipart/form-data; #{ct_params}"}]
end
@doc false
@spec body(t) :: part_stream
def body(%__MODULE__{boundary: boundary, parts: parts}) do
part_streams = Enum.map(parts, &part_as_stream(&1, boundary))
Stream.concat(part_streams ++ [["--#{boundary}--\r\n"]])
end
@doc false
- @spec part_as_stream(t, String.t()) :: part_stream
+ @spec part_as_stream(Part.t(), String.t()) :: part_stream
def part_as_stream(
%Part{body: body, dispositions: dispositions, headers: part_headers},
boundary
) do
part_headers = Enum.map(part_headers, fn {k, v} -> "#{k}: #{v}\r\n" end)
part_headers = part_headers ++ [part_headers_for_disposition(dispositions)]
enum_body =
case body do
b when is_binary(b) -> [b]
b -> b
end
Stream.concat([
["--#{boundary}\r\n"],
part_headers,
["\r\n"],
enum_body,
["\r\n"]
])
end
@doc false
@spec part_headers_for_disposition(Keyword.t()) :: [String.t()]
def part_headers_for_disposition([]), do: []
def part_headers_for_disposition(kvs) do
ds =
kvs
|> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
|> Enum.join("; ")
- ["Content-Disposition: form-data; #{ds}\r\n"]
+ ["content-disposition: form-data; #{ds}\r\n"]
end
@doc false
@spec unique_string(pos_integer) :: String.t()
defp unique_string(length) do
Enum.reduce(1..length, [], fn _i, acc ->
[Enum.random(@boundary_chars) | acc]
end)
|> Enum.join("")
end
end
diff --git a/test/tesla/multipart_test.exs b/test/tesla/multipart_test.exs
index 3b4aa0e..1503514 100644
--- a/test/tesla/multipart_test.exs
+++ b/test/tesla/multipart_test.exs
@@ -1,223 +1,223 @@
defmodule Tesla.MultipartTest do
use ExUnit.Case
alias Tesla.Multipart
test "headers" do
mp = Multipart.new()
headers = Multipart.headers(mp)
- assert headers == ["Content-Type": "multipart/form-data; boundary=#{mp.boundary}"]
+ assert headers == [{"content-type", "multipart/form-data; boundary=#{mp.boundary}"}]
end
test "add content-type param" do
mp =
Multipart.new()
|> Multipart.add_content_type_param("charset=utf-8")
headers = Multipart.headers(mp)
assert headers == [
- "Content-Type": "multipart/form-data; boundary=#{mp.boundary}; charset=utf-8"
+ {"content-type", "multipart/form-data; boundary=#{mp.boundary}; charset=utf-8"}
]
end
test "add content-type params" do
mp =
Multipart.new()
|> Multipart.add_content_type_param("charset=utf-8")
|> Multipart.add_content_type_param("foo=bar")
headers = Multipart.headers(mp)
assert headers == [
- "Content-Type":
- "multipart/form-data; boundary=#{mp.boundary}; charset=utf-8; foo=bar"
+ {"content-type",
+ "multipart/form-data; boundary=#{mp.boundary}; charset=utf-8; foo=bar"}
]
end
test "add_field" do
mp =
Multipart.new()
|> Multipart.add_field("foo", "bar")
body = Multipart.body(mp) |> Enum.join()
assert body == """
--#{mp.boundary}\r
- Content-Disposition: form-data; name="foo"\r
+ content-disposition: form-data; name="foo"\r
\r
bar\r
--#{mp.boundary}--\r
"""
end
test "add_field with extra headers" do
mp =
Multipart.new()
|> Multipart.add_field(
"foo",
"bar",
- headers: [{:"Content-Id", 1}, {:"Content-Type", "text/plain"}]
+ headers: [{"content-id", "1"}, {"content-type", "text/plain"}]
)
body = Multipart.body(mp) |> Enum.join()
assert body == """
--#{mp.boundary}\r
- Content-Id: 1\r
- Content-Type: text/plain\r
- Content-Disposition: form-data; name="foo"\r
+ content-id: 1\r
+ content-type: text/plain\r
+ content-disposition: form-data; name="foo"\r
\r
bar\r
--#{mp.boundary}--\r
"""
end
test "add_file (filename only)" do
mp =
Multipart.new()
|> Multipart.add_file("test/tesla/multipart_test_file.sh")
body = Multipart.body(mp) |> Enum.join()
assert body == """
--#{mp.boundary}\r
- Content-Disposition: form-data; name="file"; filename="multipart_test_file.sh"\r
+ content-disposition: form-data; name="file"; filename="multipart_test_file.sh"\r
\r
#!/usr/bin/env bash
echo "test multipart file"
\r
--#{mp.boundary}--\r
"""
end
test "add_file (filename with name)" do
mp =
Multipart.new()
|> Multipart.add_file("test/tesla/multipart_test_file.sh", name: "foobar")
body = Multipart.body(mp) |> Enum.join()
assert body == """
--#{mp.boundary}\r
- Content-Disposition: form-data; name="foobar"; filename="multipart_test_file.sh"\r
+ content-disposition: form-data; name="foobar"; filename="multipart_test_file.sh"\r
\r
#!/usr/bin/env bash
echo "test multipart file"
\r
--#{mp.boundary}--\r
"""
end
test "add_file (custom filename)" do
mp =
Multipart.new()
|> Multipart.add_file("test/tesla/multipart_test_file.sh", filename: "custom.png")
body = Multipart.body(mp) |> Enum.join()
assert body == """
--#{mp.boundary}\r
- Content-Disposition: form-data; name="file"; filename="custom.png"\r
+ content-disposition: form-data; name="file"; filename="custom.png"\r
\r
#!/usr/bin/env bash
echo "test multipart file"
\r
--#{mp.boundary}--\r
"""
end
test "add_file (filename with name, extra headers)" do
mp =
Multipart.new()
|> Multipart.add_file(
"test/tesla/multipart_test_file.sh",
name: "foobar",
- headers: [{:"Content-Id", 1}, {:"Content-Type", "text/plain"}]
+ headers: [{"content-id", "1"}, {"content-type", "text/plain"}]
)
body = Multipart.body(mp) |> Enum.join()
assert body == """
--#{mp.boundary}\r
- Content-Id: 1\r
- Content-Type: text/plain\r
- Content-Disposition: form-data; name="foobar"; filename="multipart_test_file.sh"\r
+ content-id: 1\r
+ content-type: text/plain\r
+ content-disposition: form-data; name="foobar"; filename="multipart_test_file.sh"\r
\r
#!/usr/bin/env bash
echo "test multipart file"
\r
--#{mp.boundary}--\r
"""
end
test "add_file (detect content type)" do
mp =
Multipart.new()
|> Multipart.add_file("test/tesla/multipart_test_file.sh", detect_content_type: true)
body = Multipart.body(mp) |> Enum.join()
assert body == """
--#{mp.boundary}\r
- Content-Type: application/x-sh\r
- Content-Disposition: form-data; name="file"; filename="multipart_test_file.sh"\r
+ content-type: application/x-sh\r
+ content-disposition: form-data; name="file"; filename="multipart_test_file.sh"\r
\r
#!/usr/bin/env bash
echo "test multipart file"
\r
--#{mp.boundary}--\r
"""
end
test "add_file (detect content type overrides given header)" do
mp =
Multipart.new()
|> Multipart.add_file(
"test/tesla/multipart_test_file.sh",
detect_content_type: true,
- headers: [{:"Content-Type", "foo/bar"}]
+ headers: [{"content-type", "foo/bar"}]
)
body = Multipart.body(mp) |> Enum.join()
assert body == """
--#{mp.boundary}\r
- Content-Type: application/x-sh\r
- Content-Disposition: form-data; name="file"; filename="multipart_test_file.sh"\r
+ content-type: application/x-sh\r
+ content-disposition: form-data; name="file"; filename="multipart_test_file.sh"\r
\r
#!/usr/bin/env bash
echo "test multipart file"
\r
--#{mp.boundary}--\r
"""
end
test "add_file_content" do
mp =
Multipart.new()
|> Multipart.add_file_content("file-data", "data.gif")
body = Multipart.body(mp) |> Enum.join()
assert body == """
--#{mp.boundary}\r
- Content-Disposition: form-data; name="file"; filename="data.gif"\r
+ content-disposition: form-data; name="file"; filename="data.gif"\r
\r
file-data\r
--#{mp.boundary}--\r
"""
end
test "add non-existing file" do
mp =
Multipart.new()
|> Multipart.add_file("i-do-not-exists.txt")
assert_raise File.Error, fn ->
mp |> Multipart.body() |> Enum.to_list()
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 9:24 AM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39750
Default Alt Text
(28 KB)

Event Timeline