Skip to content

Commit

Permalink
Write loads of documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
tomtaylor committed Jan 2, 2021
1 parent 11976f5 commit 90d4db4
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 12 deletions.
61 changes: 59 additions & 2 deletions lib/snap.ex
Original file line number Diff line number Diff line change
@@ -1,23 +1,80 @@
defmodule Snap do
@moduledoc """
The Snap module handles basic interaction with Elasticsearch: making simple
requests and parsing responses.
Snap is split into 3 main components:
* `Snap.Cluster` - clusters are wrappers around the Elasticsearch HTTP API.
We can use this to perform low-level HTTP requests.
* `Snap.Bulk` - a convenience wrapper around bulk operations, using `Stream`
to stream actions (such as `Snap.Bulk.Action.Create`) to be performed
against the `Snap.Cluster`.
* `Snap.Indexes` - a convenience wrapped around Elasticsearch indexes,
allowing the creation, deleting and aliasing of indexes, along with hotswap
functionality to bulk load documents into an aliased index, switching to it
atomically.
Additionally, there are other supporting modules:
* `Snap.Auth` - defines how an HTTP request is modified to include
authentication headers. `Snap.Auth.Plain` implements HTTP Basic Auth.
## Clusters
`Snap.Cluster` is a wrapped around an Elasticsearch cluster. We can define
it like so:
```
defmodule MyApp.Cluster do
use Snap.Cluster, otp_app: :my_app
end
```
The configuration for the cluster is defined in your config:
```
config :my_app, MyApp.Cluster,
url: "http://localhost:9200",
username: "username",
password: "password"
```
Each cluster defines `start_link/1` which must be invoked before using the
cluster and optionally accepts an explicit config. It creates the
supervision tree, including the connection pool.
Include it in your application:
```
def start(_type, _args) do
children = [
{MyApp.Cluster, []}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
```
"""

alias Snap.Request

@doc false
def get(cluster, path, params \\ [], headers \\ [], opts \\ []) do
Request.request(cluster, "GET", path, nil, params, headers, opts)
end

@doc false
def post(cluster, path, body \\ nil, params \\ [], headers \\ [], opts \\ []) do
Request.request(cluster, "POST", path, body, params, headers, opts)
end

@doc false
def put(cluster, path, body \\ nil, params \\ [], headers \\ [], opts \\ []) do
Request.request(cluster, "PUT", path, body, params, headers, opts)
end

@doc false
def delete(cluster, path, params \\ [], headers \\ [], opts \\ []) do
Request.request(cluster, "DELETE", path, nil, params, headers, opts)
end
Expand Down
13 changes: 8 additions & 5 deletions lib/snap/auth/auth.ex
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
defmodule Snap.Auth do
@moduledoc """
The `Snap.Auth` behaviour can be implemented by modules to define how `snap`
transforms HTTP requests to add authentication.
Defines how HTTP request is transformed to add authentication.
"""

@type method :: String.t()
@type path :: String.t()
@type url :: String.t()
@type headers :: Mint.Types.headers()
@type body :: iodata()
@type opts :: Keyword.t()
@type config :: map()

@type response :: {:ok, {method, path, headers, body}} | {:error, term()}
@type response :: {:ok, {method, url, headers, body}} | {:error, term()}

@callback sign(map(), method, path, headers, body) :: response()
@doc """
Modifies an HTTP request to include authentication details
"""
@callback sign(config, method, url, headers, body) :: response()
end
16 changes: 16 additions & 0 deletions lib/snap/auth/plain.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
defmodule Snap.Auth.Plain do
@moduledoc """
Implements HTTP Basic Auth, if necessary.
If the cluster config defines a username and password for the Elasticsearch
cluster it add Basic auth.
```
config :my_app, MyApp.Cluster,
url: "http://localhost:9200",
username: "foo",
password: "bar
```
If no username or password is defined, no auth header is added, making it
suitable for local development.
"""
@behaviour Snap.Auth

def sign(config, method, path, headers, body) do
Expand Down
39 changes: 39 additions & 0 deletions lib/snap/bulk/action.ex
Original file line number Diff line number Diff line change
@@ -1,19 +1,58 @@
defmodule Snap.Bulk.Action.Create do
@moduledoc """
Represents a create step in a `Snap.Bulk` operation
"""
@enforce_keys [:doc]
defstruct [:_index, :_id, :require_alias, :doc]

@type t :: %__MODULE__{
_index: String.t() | nil,
_id: String.t() | nil,
require_alias: boolean() | nil,
doc: map()
}
end

defmodule Snap.Bulk.Action.Delete do
@moduledoc """
Represents a delete step in a `Snap.Bulk` operation
"""
@enforce_keys [:_id]
defstruct [:_index, :_id, :require_alias]

@type t :: %__MODULE__{
_index: String.t() | nil,
_id: String.t(),
require_alias: boolean() | nil
}
end

defmodule Snap.Bulk.Action.Index do
@moduledoc """
Represents an index step in a `Snap.Bulk` operation
"""
@enforce_keys [:doc]
defstruct [:_index, :_id, :require_alias, :doc]

@type t :: %__MODULE__{
_index: String.t() | nil,
_id: String.t() | nil,
require_alias: boolean() | nil,
doc: map()
}
end

defmodule Snap.Bulk.Action.Update do
@moduledoc """
Represents an update step in a `Snap.Bulk` operation
"""
@enforce_keys [:doc]
defstruct [:_index, :_id, :require_alias, :doc]

@type t :: %__MODULE__{
_index: String.t() | nil,
_id: String.t() | nil,
require_alias: boolean() | nil,
doc: map()
}
end
2 changes: 2 additions & 0 deletions lib/snap/bulk/actions.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule Snap.Bulk.Actions do
@moduledoc false

alias Snap.Bulk.Action.{Create, Index, Update, Delete}

@doc """
Expand Down
82 changes: 80 additions & 2 deletions lib/snap/cluster.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ defmodule Snap.Cluster do
A cluster maps to an Elasticsearch endpoint.
When used, the cluster expects :otp_app as an option. The :otp_app should
point to an OTP application that has the cluster configuration. For
When used, the cluster expects `:otp_app` as an option. The `:otp_app`
should point to an OTP application that has the cluster configuration. For
example, this cluster:
```
Expand Down Expand Up @@ -42,6 +42,9 @@ defmodule Snap.Cluster do
Supervisor.config(__MODULE__)
end

@doc """
Returns the otp_app that the Cluster was defined with.
"""
def otp_app() do
unquote(opts[:otp_app])
end
Expand Down Expand Up @@ -80,4 +83,79 @@ defmodule Snap.Cluster do
end
end
end

@typedoc "The path of the HTTP endpoint"
@type path :: String.t()

@typedoc "The query params, which will be appended to the path"
@type params :: Keyword.t()

@typedoc "The body of the HTTP request"
@type body :: String.t() | nil | binary() | map()

@typedoc "Any additional HTTP headers sent with the request"
@type headers :: Mint.Types.headers()

@typedoc "Options passed through to the request"
@type opts :: Keyword.t()

@typedoc "The result from an HTTP operation"
@type result ::
{:ok, map()} | {:error, %Snap.Exception{} | Mint.Types.error() | Jason.DecodeError.t()}

@doc """
Sends a GET request.
Returns either:
* `{:ok, response}` - where response is a map representing the parsed JSON response.
* `{:error, error}` - where the error can be a struct of either:
* `Snap.Exception`
* `Mint.TransportError`
* `Mint.HTTPError`
* `Jason.DecodeError`
"""
@callback get(path, params, headers, opts) :: result()

@doc """
Sends a POST request.
Returns either:
* `{:ok, response}` - where response is a map representing the parsed JSON response.
* `{:error, error}` - where the error can be a struct of either:
* `Snap.Exception`
* `Mint.TransportError`
* `Mint.HTTPError`
* `Jason.DecodeError`
"""
@callback post(path, body, params, headers, opts) :: result()

@doc """
Sends a PUT request.
Returns either:
* `{:ok, response}` - where response is a map representing the parsed JSON response.
* `{:error, error}` - where the error can be a struct of either:
* `Snap.Exception`
* `Mint.TransportError`
* `Mint.HTTPError`
* `Jason.DecodeError`
"""
@callback put(path, body, params, headers, opts) :: result()

@doc """
Sends a DELETE request.
Returns either:
* `{:ok, response}` - where response is a map representing the parsed JSON response.
* `{:error, error}` - where the error can be a struct of either:
* `Snap.Exception`
* `Mint.TransportError`
* `Mint.HTTPError`
* `Jason.DecodeError`
"""
@callback delete(path, params, headers, opts) :: result()
end
1 change: 1 addition & 0 deletions lib/snap/config.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
defmodule Snap.Config do
@moduledoc false
use GenServer

def start_link({name, config}) do
Expand Down
3 changes: 2 additions & 1 deletion lib/snap/request.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
defmodule Snap.Request do
@moduledoc false
@default_headers [{"content-type", "application/json"}, {"accept", "application/json"}]

def request(cluster, method, path, body \\ nil, params \\ [], headers \\ [], opts \\ []) do
Expand All @@ -18,7 +19,7 @@ defmodule Snap.Request do

start_time = System.os_time()

with {:ok, {method, path, headers, body}} <- auth.sign(config, method, path, headers, body) do
with {:ok, {method, url, headers, body}} <- auth.sign(config, method, url, headers, body) do
queue_time = System.os_time() - start_time

response =
Expand Down
2 changes: 2 additions & 0 deletions lib/snap/supervisor.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule Snap.Cluster.Supervisor do
@moduledoc false

use Supervisor
@default_pool_size 5

Expand Down
22 changes: 20 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,24 @@ defmodule Snap.MixProject do
elixir: "~> 1.11",
start_permanent: Mix.env() == :prod,
elixirc_paths: elixirc_paths(Mix.env()),
deps: deps()
deps: deps(),
name: "Snap",
source_url: "https://github.com/tomtaylor/snap",
docs: [
main: "Snap",
groups_for_modules: [
Authentication: [
Snap.Auth,
Snap.Auth.Plain
],
"Bulk operations": [
Snap.Bulk.Action.Create,
Snap.Bulk.Action.Index,
Snap.Bulk.Action.Update,
Snap.Bulk.Action.Delete
]
]
]
]
end

Expand All @@ -28,7 +45,8 @@ defmodule Snap.MixProject do
{:finch, "~> 0.6"},
{:castore, "~> 0.1.0"},
{:jason, "~> 1.0"},
{:telemetry, "~> 0.4"}
{:telemetry, "~> 0.4"},
{:ex_doc, "~> 0.23", only: :dev, runtime: false}
]
end
end
5 changes: 5 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
%{
"castore": {:hex, :castore, "0.1.8", "1b61eaba71bb755b756ac42d4741f4122f8beddb92456a84126d6177ec0af1fc", [:mix], [], "hexpm", "23ab8305baadb057bc689adc0088309f808cb2247dc9a48b87849bb1d242bb6c"},
"earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"},
"ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"},
"finch": {:hex, :finch, "0.6.0", "b2d5dc82603ee88bcd05ad06ee57ad2b3a07e0f9fdcd2a28087d8fb61e09aaf4", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.2", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "668692f629943711e935acc5c426d7ff5bbc391b162b27775dabbf0fbbb31dd6"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"},
"mint": {:hex, :mint, "1.2.0", "65e9d75c60c456a5fb1b800febb88f061f56157d103d755b99fcaeaeb3e956f3", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "19cbb3a5be91b7df4a35377ba94b26199481a541add055cf5d1d4299b55125ab"},
"nimble_options": {:hex, :nimble_options, "0.3.5", "a4f6820cdcb4ee444afd78635f323e58e8a5ddf2fbbe9b9d283a99f972034bae", [:mix], [], "hexpm", "f5507cc90033a8d12769522009c80aa9164af6bab245dbd4ad421d008455f1e1"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"nimble_pool": {:hex, :nimble_pool, "0.2.3", "4b84df87cf8b40c7363782a99faad6aa2bb0811bcd3d275b5402ae4bab1f1251", [:mix], [], "hexpm", "a6bf677d3499ef1639c42bf16b8a72bf490f5fed70206d5851d43dd750c7eaca"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
Expand Down
1 change: 1 addition & 0 deletions test/support/cluster.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
defmodule Snap.Test.Cluster do
@moduledoc false
use Snap.Cluster, otp_app: :snap

def init(config) do
Expand Down
1 change: 1 addition & 0 deletions test/support/integration_case.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
defmodule Snap.IntegrationCase do
@moduledoc false
use ExUnit.CaseTemplate

alias Snap.Test.Cluster
Expand Down

0 comments on commit 90d4db4

Please sign in to comment.