Skip to content

Commit

Permalink
Merge pull request #5 from danielberkompas/doc/1/inline
Browse files Browse the repository at this point in the history
Improve documentation and typespecs for all modules
  • Loading branch information
danielberkompas committed Mar 31, 2015
2 parents 0b406dd + ff9703a commit b6aa420
Show file tree
Hide file tree
Showing 28 changed files with 325 additions and 46 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
erl_crash.dump
*.ez
.env
doc/
13 changes: 13 additions & 0 deletions lib/ex_twilio.ex
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
defmodule ExTwilio do
@moduledoc """
ExTwilio is a relatively full-featured API client for the Twilio API.
If you're a user, take a look at the various Resource modules, such as
`ExTwilio.Account`, `ExTwilio.Call`.
If you want to learn more about how ExTwilio works internally, take a gander
at:
- `ExTwilio.Api`
- `ExTwilio.Resource`
- `ExTwilio.Parser`
"""
end
87 changes: 77 additions & 10 deletions lib/ex_twilio/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@ defmodule ExTwilio.Api do

alias ExTwilio.Config
alias ExTwilio.Parser
alias __MODULE__ # Necessary for testing/mocking
alias __MODULE__ # Necessary for mocks in tests

@moduledoc """
Provides a basic HTTP interface to allow easy communication with the Twilio
API, by wrapping `HTTPotion`.
## Examples
Requests are made to the Twilio API by passing in a resource module into one
of this `Api` module's many functions. The correct URL to the resource is
inferred from the module name.
ExTwilio.Api.all(Resource)
[%Resource{ ... }, %Resource{ ... }]
Items are returned as instances of the given module's struct. For more
details, see the documentation for each function.
"""

@doc """
Expand All @@ -29,6 +41,12 @@ defmodule ExTwilio.Api do
# pages have been fetched from Twilio.
Enum.into stream, []
[%Call{ ... }, %Call{ ... }, ...]
# Progressively build filters with the Pipe operator.
ExTwilio.Api.stream(ExTwilio.Call)
|> Stream.filter fn(call) -> call.duration > 120 end
|> Stream.map fn(call) -> call.sid end
|> Enum.into [] # Only here is the stream actually executed
"""
@spec stream(module, list) :: Stream.t
def stream(module, options \\ []) do
Expand All @@ -51,9 +69,9 @@ defmodule ExTwilio.Api do
end
end

next_item = fn state = {[], nil, _} -> {:halt, state}
state = {[], next, opts} -> fetch_next_page.(module, state)
state -> pop_item.(state)
next_item = fn state = {[], nil, _} -> {:halt, state}
state = {[], _next, _opts} -> fetch_next_page.(module, state)
state -> pop_item.(state)
end

stop = fn (_) -> end
Expand All @@ -72,16 +90,21 @@ defmodule ExTwilio.Api do
ExTwilio.Api.all(ExTwilio.Call)
[%Call{ ... }, %Call{ ... }, ...]
If you want the function to take less time, you can increase the size of the
pages returned by Twilio. This will reduce the number of requests.
ExTwilio.Api.all(ExTwilio.Call, page_size: 100)
"""
@spec all(atom) :: list
@spec all(atom, list) :: [map]
def all(module, options \\ []) do
Enum.into stream(module, options), []
end

@doc """
Get the first page of results for a given resource. Page size is configurable
with the `page_size` option. For paging through multiple pages, see one of
these methods:
these functions:
- `fetch_page/2`
- `all/0`
Expand All @@ -91,11 +114,13 @@ defmodule ExTwilio.Api do
{:ok, list, meta} = ExTwilio.Api.list(ExTwilio.Call, page_size: 1)
"""
@spec list(atom, list) :: Parser.success_list | Parser.error
def list(module, options \\ []) do
url = resource_url_with_options(module, options)
do_list(module, url)
end

@spec do_list(module, String.t) :: Parser.success_list | Parser.error
defp do_list(module, url) do
Parser.parse_list(module, Api.get(url), module.resource_collection_name)
end
Expand All @@ -108,13 +133,14 @@ defmodule ExTwilio.Api do
iex> ExTwilio.Api.resource_collection_name(Resource)
"resources"
"""
@spec resource_collection_name(atom) :: String.t
def resource_collection_name(module) do
module |> resource_name |> Mix.Utils.underscore
end

@doc """
Fetch a particular page of results from the API, using a page URL provided by
Twilio in its page metadata.
Twilio in its pagination metadata.
## Example
Expand Down Expand Up @@ -257,6 +283,7 @@ defmodule ExTwilio.Api do
iex> ExTwilio.Api.url_segment(:address, %{sid: "sid"})
"Addresses/sid/"
"""
@spec url_segment(atom | nil, String.t | map) :: String.t
def url_segment(nil, _), do: ""
def url_segment(_key, nil), do: ""
def url_segment(key, %{sid: value}), do: url_segment(key, value)
Expand All @@ -277,6 +304,7 @@ defmodule ExTwilio.Api do
iex> ExTwilio.Api.resource_name(:"ExTwilio.Resources.Call")
"Calls"
"""
@spec resource_name(atom | String.t) :: String.t
def resource_name(module) do
name = to_string(module)
[[name]] = Regex.scan(~r/[a-z]+$/i, name)
Expand All @@ -291,6 +319,7 @@ defmodule ExTwilio.Api do
iex> ExTwilio.Api.resource_url_with_options(:"Elixir.ExTwilio.Call", [page: 1])
"Calls.json?Page=1"
"""
@spec resource_url_with_options(atom, list) :: String.t
def resource_url_with_options(module, options) when length(options) > 0 do
resource_url(module, options) <> ".json?" <> to_querystring(options)
end
Expand All @@ -304,10 +333,12 @@ defmodule ExTwilio.Api do
iex> ExTwilio.Api.to_querystring([page: 1, page_size: 2])
"Page=1&PageSize=2"
"""
@spec to_querystring(list) :: String.t
def to_querystring(list) do
list |> reject_protected |> camelize_keys |> URI.encode_query
end

@spec camelize_keys(list) :: map
defp camelize_keys(list) do
list = Enum.map list, fn({key, val}) ->
key = key |> to_string |> camelize |> String.to_atom
Expand All @@ -317,12 +348,14 @@ defmodule ExTwilio.Api do
Enum.into list, %{}
end

@spec reject_protected(list) :: list
defp reject_protected(list) do
list
|> List.delete(:account)
|> List.delete(:account_sid)
end

@spec camelize(String.t) :: String.t
defp camelize(string) do
String.capitalize(string) |> Inflex.camelize
end
Expand All @@ -332,9 +365,18 @@ defmodule ExTwilio.Api do
###

@doc """
Prepends whatever URL is passed into one of the http methods with the
Prepends whatever URL is passed into one of the http functions with the
`Config.base_url`.
# Examples
iex> ExTwilio.Api.process_url("Accounts/sid")
"#{Config.base_url}Accounts/sid.json"
iex> ExTwilio.Api.process_url("Calls/sid")
"#{Config.base_url}Accounts/#{Config.account_sid}/Calls/sid.json"
"""
@spec process_url(String.t) :: String.t
def process_url(url) do
base = case url =~ ~r/Accounts/ do
true -> Config.base_url <> url
Expand All @@ -350,23 +392,48 @@ defmodule ExTwilio.Api do

@doc """
Adds the Account SID and Auth Token to every request through HTTP basic auth.
## Example
iex> ExTwilio.Api.process_options([])
[basic_auth: { #{inspect Config.account_sid}, #{inspect Config.auth_token} }]
"""
@spec process_url(list) :: list
def process_options(options) do
Dict.put(options, :basic_auth, { Config.account_sid, Config.auth_token })
end

@doc """
Automatically add the Content-Type application/x-www-form-urlencoded.
Automatically add the Content-Type application/x-www-form-urlencoded. This
allows POST request data to be processed properly. It seems to have no
negative effect on GET calls, so it is added to all requests.
## Example
iex> ExTwilio.Api.process_request_headers([])
[{:"Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"}]
"""
@spec process_request_headers(list) :: list
def process_request_headers(headers) do
Dict.put(headers, :"Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
end

@doc """
Correctly format the request body.
If the request body is a list, then convert the list to a query string.
Otherwise, pass it through unmodified.
## Examples
iex> ExTwilio.Api.process_request_body([hello: "world"])
"Hello=world"
iex> ExTwilio.Api.process_request_body("Hello, world!")
"Hello, world!"
"""
@spec process_request_body(list) :: String.t
def process_request_body(body) when is_list(body) do
to_querystring(body)
end
@spec process_request_body(any) :: any
def process_request_body(body), do: body
end
16 changes: 5 additions & 11 deletions lib/ex_twilio/config.ex
Original file line number Diff line number Diff line change
@@ -1,43 +1,37 @@
defmodule ExTwilio.Config do
@account_sid Application.get_env(:ex_twilio, :account_sid)
@auth_token Application.get_env(:ex_twilio, :auth_token)
@api_domain Application.get_env(:ex_twilio, :base_url) || "api.twilio.com"
@api_version Application.get_env(:ex_twilio, :api_version) || "2010-04-01"

@moduledoc """
Provides easy access into ExTwilio's configuration for all the modules that
need to know.
Stores configuration variables used to communicate with Twilio's API.
"""

@doc """
Returns the Twilio Account SID. Set it in `mix.exs`:
config :ex_twilio, account_sid: "YOUR_ACCOUNT_SID"
"""
def account_sid, do: @account_sid
def account_sid, do: Application.get_env(:ex_twilio, :account_sid)

@doc """
Returns the Twilio Auth Token for your account. Set it in `mix.exs`:
config :ex_twilio, auth_token: "YOUR_AUTH_TOKEN"
"""
def auth_token, do: @auth_token
def auth_token, do: Application.get_env(:ex_twilio, :auth_token)

@doc """
Returns the domain of the Twilio API. This will default to "api.twilio.com",
but can be overridden using the following setting in `mix.exs`:
config :ex_twilio, api_domain: "other.twilio.com"
"""
def api_domain, do: @api_domain
def api_domain, do: Application.get_env(:ex_twilio, :api_domain) || "api.twilio.com"

@doc """
Returns the version of the API that ExTwilio is going to talk to. Set it in
`mix.exs`:
config :ex_twilio, api_version: "2015-05-06"
"""
def api_version, do: @api_version
def api_version, do: Application.get_env(:ex_twilio, :api_version) || "2010-04-01"

@doc """
Return the combined base URL of the Twilio API, using the configuration
Expand Down
5 changes: 4 additions & 1 deletion lib/ex_twilio/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ defmodule ExTwilio.Parser do
@type success_delete :: :ok
@type error :: {:error, String.t, http_status_code}

@type parsed_response :: success | error
@type parsed_list_response :: success_list | error

@doc """
Parse a response expected to contain a single resource. If you pass in a
module as the first argument, the JSON will be parsed into that module's
Expand All @@ -28,7 +31,7 @@ defmodule ExTwilio.Parser do
You can parse JSON into that module's struct like so:
...> response = %{body: "{ \\"sid\\": \\"AD34123\\" }", status_code: 200}
iex> response = %{body: "{ \\"sid\\": \\"AD34123\\" }", status_code: 200}
...> ExTwilio.Parser.parse(Resource, response)
{:ok, %Resource{sid: "AD34123"}}
Expand Down
Loading

0 comments on commit b6aa420

Please sign in to comment.