Skip to content

Commit 90a162a

Browse files
committed
add disconnect_on_error_codes option
1 parent 5a84f52 commit 90a162a

File tree

5 files changed

+119
-140
lines changed

5 files changed

+119
-140
lines changed

Diff for: lib/arangox.ex

+6-3
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,16 @@ defmodule Arangox do
7070
isn't already prepended. If a value is not given, nothing is prepended (_ArangoDB_ will
7171
assume the __system_ database).
7272
* `:headers` - A map of headers to merge with every request.
73-
* `:auth?` - Configure whether or not to resolve authorization with the `:username` and
74-
`:password` options. Defaults to `true`.
73+
* `:disconnect_on_error_codes` - A list of status codes that will trigger a forced disconnect.
74+
Only integers within the range `400..599` are affected. Defaults to
75+
`[401, 405, 503, 505]`.
76+
* `:auth?` - Configure whether or not to resolve authorization (with the `:username` and
77+
`:password` options). Defaults to `true`.
7578
* `:username` - Defaults to `"root"`.
7679
* `:password` - Defaults to `""`.
7780
* `:read_only?` - Read-only pools will only connect to _followers_ in an active failover
7881
setup and add an _x-arango-allow-dirty-read_ header to every request. Defaults to `false`.
79-
* `:connect_timeout` - Sets the timeout for establishing a connection with a database.
82+
* `:connect_timeout` - Sets the timeout for establishing connections with a database.
8083
* `:tcp_opts` - Transport options for the tcp socket interface (`:gen_tcp` in the case
8184
of gun or mint).
8285
* `:ssl_opts` - Transport options for the ssl socket interface (`:ssl` in the case of

Diff for: lib/arangox/connection.ex

+94-110
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
defimpl DBConnection.Query, for: BitString do
2+
def parse(query, _opts), do: query
3+
4+
def describe(query, _opts), do: query
5+
6+
def encode(_query, params, _opts), do: Enum.into(params, %{})
7+
8+
def decode(_query, params, _opts), do: params
9+
end
10+
111
defmodule Arangox.Connection do
212
@moduledoc false
313

@@ -22,6 +32,7 @@ defmodule Arangox.Connection do
2232
username: binary,
2333
password: binary,
2434
headers: Arangox.headers(),
35+
disconnect_on_error_codes: [integer],
2536
read_only?: boolean,
2637
cursors: map
2738
}
@@ -38,6 +49,7 @@ defmodule Arangox.Connection do
3849
username: "root",
3950
password: "",
4051
headers: %{},
52+
disconnect_on_error_codes: [401, 405, 503, 505],
4153
read_only?: false,
4254
cursors: %{}
4355
]
@@ -238,117 +250,111 @@ defmodule Arangox.Connection do
238250
|> Keyword.get(:properties, [])
239251
|> Enum.into(%{collections: collections})
240252

241-
with(
242-
{:ok, %Response{status: 201} = response, state} <-
243-
%Request{
244-
method: :post,
245-
path: Path.join(@path_trx, "begin"),
246-
body: body
247-
}
248-
|> merge_headers(state.headers)
249-
|> maybe_prepend_database(state)
250-
|> maybe_encode_body(state)
251-
|> Client.request(state),
252-
%Response{body: %{"result" => %{"id" => id}}} = response <-
253-
maybe_decode_body(response, state)
254-
) do
255-
{:ok, response, put_header(state, {@header_trx_id, id})}
256-
else
257-
{:ok, %Response{}, state} ->
253+
request = %Request{
254+
method: :post,
255+
path: Path.join(@path_trx, "begin"),
256+
body: body
257+
}
258+
259+
case handle_execute(nil, request, opts, state) do
260+
{:ok, _request, %Response{status: 201, body: %{"result" => %{"id" => id}}} = response,
261+
state} ->
262+
{:ok, response, put_header(state, {@header_trx_id, id})}
263+
264+
{:ok, _request, %Response{}, state} ->
258265
{:error, state}
259266

260-
{:error, :noproc, state} ->
261-
{:disconnect, exception(state, "connection lost"), state}
267+
{:error, _exception, state} ->
268+
{:error, state}
262269

263-
{:error, _reason, state} ->
270+
{:disconnect, _exception, state} ->
264271
{:error, state}
265272
end
266273
end
267274

268275
@impl true
269-
def handle_status(_opts, %__MODULE__{} = state) do
276+
def handle_status(opts, %__MODULE__{} = state) do
270277
with(
271-
{id, headers} when is_binary(id) <- Map.pop(state.headers, @header_trx_id),
272-
{:ok, %Response{status: 200}, state} <-
273-
%Request{
274-
method: :get,
275-
path: Path.join(@path_trx, id)
276-
}
277-
|> merge_headers(headers)
278-
|> maybe_prepend_database(state)
279-
|> Client.request(state)
278+
{id, _headers} when is_binary(id) <-
279+
Map.pop(state.headers, @header_trx_id),
280+
{:ok, _request, %Response{status: 200}, state} <-
281+
handle_execute(
282+
nil,
283+
%Request{method: :get, path: Path.join(@path_trx, id)},
284+
opts,
285+
state
286+
)
280287
) do
281288
{:transaction, state}
282289
else
283290
{nil, _headers} ->
284291
{:idle, state}
285292

286-
{:ok, %Response{status: _status}, state} ->
293+
{:ok, _request, %Response{}, state} ->
287294
{:error, state}
288295

289-
{:error, :noproc, state} ->
290-
{:disconnect, exception(state, "connection lost"), state}
291-
292-
{:error, _reason, state} ->
296+
{:error, _exception, state} ->
293297
{:error, state}
298+
299+
{:disconnect, exception, state} ->
300+
{:disconnect, exception, state}
294301
end
295302
end
296303

297304
@impl true
298-
def handle_commit(_opts, %__MODULE__{} = state) do
305+
def handle_commit(opts, %__MODULE__{} = state) do
299306
with(
300-
{id, headers} when is_binary(id) <- Map.pop(state.headers, @header_trx_id),
301-
{:ok, %Response{status: 200} = response, state} <-
302-
%Request{
303-
method: :put,
304-
path: Path.join(@path_trx, id)
305-
}
306-
|> merge_headers(headers)
307-
|> maybe_prepend_database(state)
308-
|> Client.request(state)
307+
{id, headers} when is_binary(id) <-
308+
Map.pop(state.headers, @header_trx_id),
309+
{:ok, _request, %Response{status: 200} = response, state} <-
310+
handle_execute(
311+
nil,
312+
%Request{method: :put, path: Path.join(@path_trx, id)},
313+
opts,
314+
%{state | headers: headers}
315+
)
309316
) do
310-
{:ok, maybe_decode_body(response, state), %{state | headers: headers}}
317+
{:ok, response, state}
311318
else
312319
{nil, _headers} ->
313320
{:idle, state}
314321

315-
{:ok, %Response{status: _status}, state} ->
322+
{:ok, _request, %Response{}, state} ->
316323
{:error, state}
317324

318-
{:error, :noproc, state} ->
319-
{:disconnect, exception(state, "connection lost"), state}
320-
321-
{:error, _reason, state} ->
325+
{:error, _exception, state} ->
322326
{:error, state}
327+
328+
{:disconnect, exception, state} ->
329+
{:disconnect, exception, state}
323330
end
324331
end
325332

326333
@impl true
327-
def handle_rollback(_opts, %__MODULE__{} = state) do
334+
def handle_rollback(opts, %__MODULE__{} = state) do
328335
with(
329336
{id, headers} when is_binary(id) <- Map.pop(state.headers, @header_trx_id),
330-
{:ok, %Response{status: 200} = response, state} <-
331-
%Request{
332-
method: :delete,
333-
path: Path.join(@path_trx, id)
334-
}
335-
|> merge_headers(headers)
336-
|> maybe_prepend_database(state)
337-
|> Client.request(state)
337+
{:ok, _request, %Response{status: 200} = response, state} <-
338+
handle_execute(
339+
nil,
340+
%Request{method: :put, path: Path.join(@path_trx, id)},
341+
opts,
342+
%{state | headers: headers}
343+
)
338344
) do
339-
{:ok, maybe_decode_body(response, state), %{state | headers: headers}}
345+
{:ok, response, state}
340346
else
341347
{nil, _headers} ->
342348
{:idle, state}
343349

344-
{:ok, %Response{status: _status}, state} ->
350+
{:ok, _request, %Response{}, state} ->
345351
{:error, state}
346352

347-
{:error, :noproc, state} ->
348-
{:disconnect, exception(state, "connection lost"), state}
349-
350-
{:error, _reason, state} ->
353+
{:error, _exception, state} ->
351354
{:error, state}
355+
356+
{:disconnect, exception, state} ->
357+
{:disconnect, exception, state}
352358
end
353359
end
354360

@@ -429,41 +435,14 @@ defmodule Arangox.Connection do
429435
end
430436
end
431437

432-
# Execution callbacks
433-
434-
defmacrop exec_case(condition, do: body) do
435-
{:case, [], [condition, [do: exec_cases_before() ++ body ++ exec_cases_after()]]}
436-
end
437-
438-
def exec_cases_before do
439-
quote do
440-
{:ok, %Response{status: 505} = response, state} ->
441-
{:disconnect, exception(state, response), state}
442-
443-
{:ok, %Response{status: 503} = response, state} ->
444-
{:disconnect, exception(state, response), state}
445-
446-
{:ok, %Response{status: 405} = response, state} ->
447-
{:disconnect, exception(state, response), state}
448-
449-
{:ok, %Response{status: 401} = response, state} ->
450-
{:disconnect, exception(state, response), state}
451-
452-
{:ok, %Response{status: status} = response, state} when status in 400..599 ->
453-
{:error, exception(state, response), state}
454-
end
455-
end
456-
457-
def exec_cases_after do
458-
quote do
459-
{:error, :noproc, state} ->
460-
{:disconnect, exception(state, "connection lost"), state}
461-
462-
{:error, %_{} = reason, state} ->
463-
{:error, reason, state}
438+
@impl true
439+
def ping(%__MODULE__{} = state) do
440+
case handle_execute(nil, @request_ping, [], state) do
441+
{:ok, _request, %Response{}, state} ->
442+
{:ok, state}
464443

465-
{:error, reason, state} ->
466-
{:error, exception(state, reason), state}
444+
{call, exception, state} when call in [:error, :disconnect] ->
445+
{:disconnect, exception, state}
467446
end
468447
end
469448

@@ -475,21 +454,26 @@ defmodule Arangox.Connection do
475454
|> maybe_prepend_database(state)
476455
|> maybe_encode_body(state)
477456

478-
exec_case Client.request(request, state) do
457+
case Client.request(request, state) do
458+
{:ok, %Response{status: status} = response, state} when status in 400..599 ->
459+
{disc_or_err(status, state.disconnect_on_error_codes), exception(state, response), state}
460+
479461
{:ok, response, state} ->
480462
{:ok, sanitize_headers(request), maybe_decode_body(response, state), state}
463+
464+
{:error, :noproc, state} ->
465+
{:disconnect, exception(state, "connection lost"), state}
466+
467+
{:error, %_{} = reason, state} ->
468+
{:error, reason, state}
469+
470+
{:error, reason, state} ->
471+
{:error, exception(state, reason), state}
481472
end
482473
end
483474

484-
@impl true
485-
def ping(%__MODULE__{} = state) do
486-
@request_ping
487-
|> merge_headers(state.headers)
488-
|> Client.request(state)
489-
|> exec_case do
490-
{:ok, %Response{}, state} ->
491-
{:ok, state}
492-
end
475+
defp disc_or_err(status, codes) do
476+
if status in codes, do: :disconnect, else: :error
493477
end
494478

495479
# Unsupported callbacks

Diff for: lib/arangox/request.ex

-10
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,4 @@ defmodule Arangox.Request do
3232

3333
def decode(_query, %Response{} = response, _opts), do: response
3434
end
35-
36-
defimpl DBConnection.Query, for: BitString do
37-
def parse(query, _opts), do: query
38-
39-
def describe(query, _opts), do: query
40-
41-
def encode(_query, params, _opts), do: Enum.into(params, %{})
42-
43-
def decode(_query, params, _opts), do: params
44-
end
4535
end

Diff for: mix.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Arangox.MixProject do
22
use Mix.Project
33

4-
@version "0.4.0"
4+
@version "0.4.1"
55
@description """
66
ArangoDB 3.3.9+ driver for Elixir with connection pooling, VelocyStream, \
77
support for Active Failover, transactions and cursors.

Diff for: test/arangox_test.exs

+18-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule ArangoxTest do
22
use ExUnit.Case, async: true
3+
import ExUnit.CaptureLog
34
import TestHelper, only: [opts: 1, opts: 0]
45

56
alias Arangox.{
@@ -39,6 +40,23 @@ defmodule ArangoxTest do
3940
end
4041
end
4142

43+
@tag capture_log: false
44+
test "disconnect_on_error_codes option" do
45+
{:ok, conn_empty} = Arangox.start_link(opts(disconnect_on_error_codes: [], auth?: false))
46+
47+
refute capture_log(fn ->
48+
Arangox.get(conn_empty, "/_admin/server/mode")
49+
:timer.sleep(500)
50+
end) =~ "disconnected"
51+
52+
{:ok, conn_401} = Arangox.start_link(opts(disconnect_on_error_codes: [401], auth?: false))
53+
54+
assert capture_log(fn ->
55+
Arangox.get(conn_401, "/_admin/server/mode")
56+
:timer.sleep(500)
57+
end) =~ "disconnected"
58+
end
59+
4260
test "connecting with default options" do
4361
{:ok, conn} = Arangox.start_link(opts())
4462
Arangox.get!(conn, "/_admin/time")
@@ -231,22 +249,6 @@ defmodule ArangoxTest do
231249
assert %Response{} = Arangox.get!(conn, "/")
232250
end
233251

234-
test "transaction/2" do
235-
{:ok, conn1} = Arangox.start_link(opts())
236-
237-
assert {:ok, %Response{}} =
238-
Arangox.transaction(conn1, fn c ->
239-
Arangox.get!(c, "/_admin/time")
240-
end)
241-
242-
{:ok, conn2} = Arangox.start_link(opts(auth?: false))
243-
244-
assert {:error, :rollback} =
245-
Arangox.transaction(conn2, fn c ->
246-
Arangox.get(c, "/_admin/server/status")
247-
end)
248-
end
249-
250252
test "transaction/3" do
251253
{:ok, conn1} = Arangox.start_link(opts())
252254

0 commit comments

Comments
 (0)