Skip to content

Commit

Permalink
chore: Replace AnyHttp by Req (#41)
Browse files Browse the repository at this point in the history
* Updated dependencies

New:
  castore 1.0.6
  finch 0.18.0
  hpax 0.1.2
  mint 1.5.2
  nimble_options 1.1.0
  nimble_ownership 0.3.1
  nimble_pool 1.1.0
  req 0.4.14
Upgraded:
  cowboy 2.10.0 => 2.12.0
  cowlib 2.12.1 => 2.13.0
  plug_cowboy 2.7.0 => 2.7.1

* Refactored the module Client to use Req

* Converted the Client to use Req directly

* Updated CHANGELOG.md

* Changed the version to 1.3.0

* Fixed an issue on create_pit
  • Loading branch information
GRoguelon committed Apr 4, 2024
1 parent c50d6ae commit aa5177a
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 308 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# Changelog


## v1.4.0 (2024-04-04)

* **Breaking Changes:**

Replace the key `http_opts` by `req_opts`

* **Changes:**
* Replaced `:any_http` by `:req`, `Req` offers the ability to provide custom `:adapter`
* Removed the dependency `bypass`

* **Bug fixes:**
* Fixed a bug on `ElasticsearchEx.Api.Search.create_pid/2` with empty body

## v1.3.0 (2024-04-04)

* **New features:**
Expand Down
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,13 @@ Documentation can be found at https://hexdocs.pm/elasticsearch_ex.
### Configure your cluster

```elixir
# Define the `any_http` client adapter
config :any_http, client_adapter: AnyHttp.Adapters.Httpc

# Configure ElasticsearchEx
config :elasticsearch_ex,
clusters: %{
default: %{
endpoint: "https://elastic:elastic@localhost:9200",
# For development only, if not specified, SSL is configured for you.
http_opts: [ssl: [verify: :verify_none]]
req_opts: [ssl: [verify: :verify_none]]
}
}
```
Expand Down
5 changes: 1 addition & 4 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import Config
# Clear the terminal when running mix test.watch
config :mix_test_watch, clear: true

# Define default any_http client adapter
config :any_http, client_adapter: AnyHttp.Adapters.Httpc

# Configure ElasticsearchEx
config :elasticsearch_ex,
clusters: %{
default: %{
endpoint: "https://elastic:elastic@localhost:9200",
http_opts: [ssl: [verify: :verify_none]]
req_opts: [connect_options: [transport_opts: [verify: :verify_none]]]
}
}
5 changes: 1 addition & 4 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import Config

# Define default any_http client adapter
config :any_http, client_adapter: AnyHttp.Adapters.Httpc

# Configure ElasticsearchEx
config :elasticsearch_ex,
clusters: %{
default: %{
endpoint: "https://elastic:elastic@localhost:9200",
http_opts: [ssl: [verify: :verify_none]]
req_opts: [connect_options: [transport_opts: [verify: :verify_none]]]
}
}
2 changes: 1 addition & 1 deletion lib/elasticsearch_ex/api/search.ex
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ defmodule ElasticsearchEx.Api.Search do
def create_pit(index, opts \\ []) when is_index(index) do
index
|> format_path(:_pit)
|> Client.post(nil, "", opts)
|> Client.post(nil, nil, opts)
end

@doc """
Expand Down
93 changes: 41 additions & 52 deletions lib/elasticsearch_ex/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ defmodule ElasticsearchEx.Client do

## Module attributes

@redact_auth Mix.env() == :prod

@content_type_key "content-type"

@application_json "application/json"
Expand All @@ -17,18 +19,19 @@ defmodule ElasticsearchEx.Client do

def request(method, path, headers, body, opts \\ []) when is_list(opts) do
{cluster, opts} = get_cluster_configuration(opts)
{http_opts, query} = prepare_options(cluster, opts)
uri = prepare_uri(cluster, path, query)
{headers, uri} = prepare_headers(cluster, uri, headers || @default_headers)
body = prepare_body!(headers, body)

# URI.to_string(uri) |> IO.inspect(label: "URL")

AnyHttp.request(method, uri, headers, body, http_opts)
|> maybe_decode_json_body!()
Req.new(method: method, redact_auth: @redact_auth, compressed: true)
|> set_uri_and_userinfo(cluster, path)
|> set_headers(cluster, headers)
|> set_body(body)
|> set_query_params(cluster, opts)
|> Req.Request.append_response_steps(nilify_empty_body: &nilify_empty_body/1)
# |> Req.Request.append_request_steps(inspect: &IO.inspect/1)
|> Req.request()
|> parse_result()
end

@spec head(binary(), nil | map(), keyword()) :: :ok | :error
def head(path, headers \\ nil, opts \\ []) do
case request(:head, path, headers, nil, opts) do
{:ok, nil} ->
Expand All @@ -43,7 +46,7 @@ defmodule ElasticsearchEx.Client do
request(:get, path, headers, body, opts)
end

def post(path, headers \\ nil, body \\ "", opts \\ []) do
def post(path, headers \\ nil, body \\ nil, opts \\ []) do
request(:post, path, headers, body, opts)
end

Expand All @@ -63,30 +66,18 @@ defmodule ElasticsearchEx.Client do

## Private functions

defp parse_result({:ok, %{status: status, body: body}}) when status in 200..299 do
defp parse_result({:ok, %Req.Response{status: status, body: body}}) when status in 200..299 do
{:ok, body}
end

defp parse_result({:ok, %{status: status} = response}) when status in 300..599 do
defp parse_result({:ok, %Req.Response{status: status} = response}) when status in 300..599 do
{:error, ElasticsearchEx.Error.exception(response)}
end

defp parse_result({:error, error}) do
raise "Unknown error: #{inspect(error)}"
end

defp maybe_decode_json_body!({:ok, %{headers: headers, body: body} = response} = result) do
content_type = Map.get(headers, @content_type_key)

if content_type == [@application_json] and is_binary(body) and body != "" do
{:ok, %{response | body: Jason.decode!(body)}}
else
result
end
end

defp maybe_decode_json_body!(any), do: any

defp get_cluster_configuration(opts) do
{cluster, opts} = Keyword.pop(opts, :cluster, :default)

Expand All @@ -100,50 +91,48 @@ defmodule ElasticsearchEx.Client do
end
end

defp prepare_uri(cluster, path, []) do
cluster |> Map.fetch!(:endpoint) |> URI.new!() |> URI.merge(path)
end

defp prepare_uri(cluster, path, query) do
uri_query = URI.encode_query(query)
uri = prepare_uri(cluster, path, [])
defp set_uri_and_userinfo(%Req.Request{} = req, cluster, path) do
uri = cluster |> Map.fetch!(:endpoint) |> URI.new!() |> URI.merge(path)
auth = uri.userinfo && {:basic, uri.userinfo}
uri = %{uri | userinfo: nil}

%{uri | query: uri_query}
Req.merge(req, url: uri, auth: auth)
end

defp prepare_options(cluster, opts) do
global_http_opts = Map.get(cluster, :http_opts, [])
{http_opts, query} = Keyword.pop(opts, :http_opts, [])
http_opts = Keyword.merge(global_http_opts, http_opts)
defp set_query_params(%Req.Request{} = req, cluster, opts) do
global_req_opts = Map.get(cluster, :req_opts, [])
{req_opts, query} = Keyword.pop(opts, :req_opts, [])
req_opts = Keyword.merge(global_req_opts, req_opts)

{http_opts, query}
req |> Req.merge(params: query) |> Req.merge(req_opts)
end

defp prepare_headers(cluster, uri, headers) when is_map(headers) do
defp set_headers(%Req.Request{} = req, cluster, headers) do
headers =
(Map.get(cluster, :headers) || %{})
|> Map.merge(headers)
|> Map.merge(headers || @default_headers)
|> Map.reject(fn {_key, value} -> is_nil(value) end)

if is_binary(uri.userinfo) do
headers = Map.put(headers, "authorization", "Basic #{Base.encode64(uri.userinfo)}")
uri = %{uri | userinfo: nil}
Req.merge(req, headers: headers)
end

{headers, uri}
else
{headers, uri}
end
defp set_body(%Req.Request{} = req, nil), do: Req.merge(req, body: "")

defp set_body(%Req.Request{headers: %{@content_type_key => [@application_ndjson]}} = req, body) do
Req.merge(req, body: ElasticsearchEx.Ndjson.encode!(body), compress_body: true)
end

defp prepare_body!(%{@content_type_key => @application_json}, body)
when not is_nil(body) and body != "" do
Jason.encode!(body)
defp set_body(%Req.Request{headers: %{@content_type_key => [@application_json]}} = req, body) do
Req.merge(req, json: body, compress_body: true)
end

defp prepare_body!(%{@content_type_key => @application_ndjson}, body)
when not is_nil(body) and body != "" do
ElasticsearchEx.Ndjson.encode!(body)
defp set_body(%Req.Request{} = req, body), do: Req.merge(req, body: body, compress_body: true)

defp nilify_empty_body({request, %Req.Response{body: ""} = response}) do
{request, %{response | body: nil}}
end

defp prepare_body!(_headers, body), do: body
defp nilify_empty_body({request, response}) do
{request, response}
end
end
8 changes: 4 additions & 4 deletions lib/elasticsearch_ex/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ defmodule ElasticsearchEx.Error do
## Public functions

@impl true
@spec exception(AnyHttp.Response.t()) :: t()
def exception(%AnyHttp.Response{status: status, body: nil}) do
@spec exception(Req.Response.t()) :: t()
def exception(%Req.Response{status: status, body: nil}) do
%__MODULE__{status: status, reason: "Response returned #{status} status code"}
end

@impl true
def exception(%AnyHttp.Response{status: status, body: %{"error" => error}}) do
def exception(%Req.Response{status: status, body: %{"error" => error}}) do
%__MODULE__{
status: status,
reason: error["reason"],
Expand All @@ -36,7 +36,7 @@ defmodule ElasticsearchEx.Error do
end

@impl true
def exception(%AnyHttp.Response{
def exception(%Req.Response{
status: 404,
body: %{"_id" => doc_id, "result" => "not_found"} = body
}) do
Expand Down
6 changes: 3 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule ElasticsearchEx.MixProject do
use Mix.Project

@source_url "https://github.com/CoreCareinc/elasticsearch_ex"
@version "1.3.0"
@version "1.4.0"

def project do
[
Expand Down Expand Up @@ -77,15 +77,15 @@ defmodule ElasticsearchEx.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:any_http, "~> 0.6"},
{:req, "~> 0.4"},
{:jason, "~> 1.4"},

## Dev dependencies
{:benchee, "~> 1.0", only: :dev},
{:ex_doc, "~> 0.30", only: :dev, runtime: false},

## Test dependencies
{:bypass, "~> 2.1", only: :test},
{:plug, "~> 1.15", only: :test},

## Dev & Test dependencies
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
Expand Down
Loading

0 comments on commit aa5177a

Please sign in to comment.