Skip to content

Commit dff3d5c

Browse files
authored
Improve configuration for environments (#10)
Fixes #3 Also removes compilation warnings when electric not installed
1 parent 3c359f0 commit dff3d5c

File tree

3 files changed

+170
-121
lines changed

3 files changed

+170
-121
lines changed

lib/phoenix/sync/electric.ex

Lines changed: 161 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ defmodule Phoenix.Sync.Electric do
140140
@doc false
141141
@impl Phoenix.Sync.Adapter
142142
def children(env, opts) do
143-
{mode, electric_opts} = electric_opts(opts)
143+
{mode, electric_opts} =
144+
opts
145+
|> set_environment_defaults(env)
146+
|> electric_opts(env)
144147

145148
case mode do
146149
:disabled ->
@@ -158,7 +161,11 @@ defmodule Phoenix.Sync.Electric do
158161
@doc false
159162
@impl Phoenix.Sync.Adapter
160163
def plug_opts(env, opts) do
161-
{mode, electric_opts} = electric_opts(opts)
164+
{mode, electric_opts} =
165+
opts
166+
|> set_environment_defaults(env)
167+
|> electric_opts(env)
168+
162169
# don't need to validate the mode here -- it will have already been
163170
# validated by children/0 which is run at phoenix_sync startup before the
164171
# plug opts call even comes through
@@ -179,7 +186,10 @@ defmodule Phoenix.Sync.Electric do
179186
@doc false
180187
@impl Phoenix.Sync.Adapter
181188
def client(env, opts) do
182-
{mode, electric_opts} = electric_opts(opts)
189+
{mode, electric_opts} =
190+
opts
191+
|> set_environment_defaults(env)
192+
|> electric_opts(env)
183193

184194
case mode do
185195
mode when mode in @client_valid_modes ->
@@ -192,40 +202,74 @@ defmodule Phoenix.Sync.Electric do
192202
end
193203
end
194204

205+
# if we want to set up per-run configuration, and avoid weird state errors in
206+
# dev and test, then we have to write them to the application config, because
207+
# `children/2`, `client/2` and `plug_opts/2` need to have consistent
208+
# configuration values.
209+
defp set_environment_defaults(opts, :test) do
210+
opts
211+
|> set_persistent_config(:stack_id, fn ->
212+
"electric-stack#{System.monotonic_time()}"
213+
end)
214+
|> set_persistent_config(:replication_stream_id, fn ->
215+
String.replace("phoenix_sync#{System.monotonic_time()}", "-", "_")
216+
end)
217+
|> set_persistent_config(:replication_slot_temporary?, true)
218+
end
219+
220+
defp set_environment_defaults(opts, :dev) do
221+
opts
222+
|> set_environment_defaults(:prod)
223+
|> set_persistent_config(:storage_dir, fn ->
224+
Path.join([System.tmp_dir!(), "phoenix-sync#{System.monotonic_time()}"])
225+
end)
226+
end
227+
228+
defp set_environment_defaults(opts, _env) do
229+
opts
230+
|> set_persistent_config(:stack_id, "electric-embedded")
231+
end
232+
233+
defp set_persistent_config(opts, key, value_fun) when is_function(value_fun) do
234+
Keyword.put_new_lazy(opts, key, fn ->
235+
value = value_fun.()
236+
Application.put_env(:phoenix_sync, key, value)
237+
value
238+
end)
239+
end
240+
241+
defp set_persistent_config(opts, key, value) do
242+
set_persistent_config(opts, key, fn -> value end)
243+
end
244+
195245
@doc false
196246
def electric_available? do
197247
@electric_available?
198248
end
199249

200-
defp electric_opts(opts) do
250+
defp electric_opts(opts, env) do
201251
Keyword.pop_lazy(opts, :mode, fn ->
202-
if electric_available?() do
203-
Logger.warning([
204-
"missing mode configuration for :phoenix_sync. Electric is installed so assuming `embedded` mode"
205-
])
206-
207-
:embedded
208-
else
209-
Logger.warning("No `:mode` configuration for :phoenix_sync, assuming `:disabled`")
210-
211-
:disabled
212-
end
252+
default_mode(env)
213253
end)
214254
end
215255

216-
defp electric_api_server(opts) do
217-
config = electric_http_config(opts)
256+
defp default_mode(:test) do
257+
:disabled
258+
end
218259

219-
cond do
220-
Code.ensure_loaded?(Bandit) ->
221-
Electric.Application.api_server(Bandit, config)
260+
if @electric_available? do
261+
defp default_mode(_env) do
262+
Logger.warning([
263+
"missing mode configuration for :phoenix_sync. Electric is installed so assuming `embedded` mode"
264+
])
222265

223-
Code.ensure_loaded?(Plug.Cowboy) ->
224-
Electric.Application.api_server(Plug.Cowboy, config)
266+
:embedded
267+
end
268+
else
269+
defp default_mode(_env) do
270+
Logger.warning("No `:mode` configuration for :phoenix_sync, assuming `:disabled`")
225271

226-
true ->
227-
raise RuntimeError,
228-
message: "No HTTP server found. Please install either Bandit or Plug.Cowboy"
272+
:disabled
229273
end
230274
end
231275

@@ -236,15 +280,18 @@ defmodule Phoenix.Sync.Electric do
236280
end
237281
end
238282

239-
defp plug_opts(env, :embedded, electric_opts) do
240-
if electric_available?() do
283+
if @electric_available? do
284+
defp plug_opts(env, :embedded, electric_opts) do
241285
env
242286
|> core_configuration(electric_opts)
243287
|> Electric.Application.api_plug_opts()
244288
|> Keyword.fetch!(:api)
245-
else
246-
raise RuntimeError,
247-
message: "Configured for embedded mode but `:electric` dependency not installed"
289+
end
290+
else
291+
defp plug_opts(_env, :embedded, _electric_opts) do
292+
raise ArgumentError,
293+
message:
294+
"phoenix_sync configured in `mode: :embedded` but electric not installed. Please add `:electric` to your dependencies or use `:http` mode."
248295
end
249296
end
250297

@@ -259,36 +306,71 @@ defmodule Phoenix.Sync.Electric do
259306
defp electric_children(env, mode, opts) do
260307
case validate_database_config(env, mode, opts) do
261308
{:start, db_config_fun, message} ->
262-
if electric_available?() do
263-
db_config =
264-
db_config_fun.()
265-
|> Keyword.update!(:connection_opts, &Electric.Utils.obfuscate_password/1)
309+
start_embedded(env, mode, db_config_fun, message)
266310

267-
electric_config = core_configuration(env, db_config)
311+
:ignore ->
312+
{:ok, []}
268313

269-
Logger.info(message)
314+
{:error, _} = error ->
315+
error
316+
end
317+
end
270318

271-
http_server =
272-
case mode do
273-
:http -> electric_api_server(electric_config)
274-
:embedded -> []
275-
end
319+
if @electric_available? do
320+
defp start_embedded(env, mode, db_config_fun, message) do
321+
db_config =
322+
db_config_fun.()
323+
|> Keyword.update!(:connection_opts, &Electric.Utils.obfuscate_password/1)
276324

277-
{:ok,
278-
[
279-
{Electric.StackSupervisor, Electric.Application.configuration(electric_config)}
280-
| http_server
281-
]}
282-
else
283-
{:error,
284-
"Electric configured to start in embedded mode but :electric dependency not available"}
325+
electric_config = core_configuration(env, db_config)
326+
327+
Logger.info(message)
328+
329+
http_server =
330+
case mode do
331+
:http -> electric_api_server(electric_config)
332+
:embedded -> []
285333
end
286334

287-
:ignore ->
288-
{:ok, []}
335+
{:ok,
336+
[
337+
{Electric.StackSupervisor, Electric.Application.configuration(electric_config)}
338+
| http_server
339+
]}
340+
end
289341

290-
{:error, _} = error ->
291-
error
342+
defp electric_api_server(opts) do
343+
config = electric_http_config(opts)
344+
345+
cond do
346+
Code.ensure_loaded?(Bandit) ->
347+
Electric.Application.api_server(Bandit, config)
348+
349+
Code.ensure_loaded?(Plug.Cowboy) ->
350+
Electric.Application.api_server(Plug.Cowboy, config)
351+
352+
true ->
353+
raise RuntimeError,
354+
message: "No HTTP server found. Please install either Bandit or Plug.Cowboy"
355+
end
356+
end
357+
358+
defp electric_http_config(opts) do
359+
case Keyword.fetch(opts, :http) do
360+
{:ok, http_opts} ->
361+
opts
362+
|> then(fn o ->
363+
if(port = http_opts[:port], do: Keyword.put(o, :service_port, port), else: o)
364+
end)
365+
366+
:error ->
367+
opts
368+
end
369+
end
370+
else
371+
defp start_embedded(_env, _mode, _db_config_fun, _message) do
372+
{:error,
373+
"Electric configured to start in embedded mode but :electric dependency not available"}
292374
end
293375
end
294376

@@ -299,8 +381,8 @@ defmodule Phoenix.Sync.Electric do
299381
defp core_configuration(env, opts) do
300382
opts
301383
|> env_defaults(env)
302-
|> overrides()
303384
|> stack_id()
385+
|> overrides()
304386
end
305387

306388
defp env_defaults(opts, :dev) do
@@ -310,24 +392,11 @@ defmodule Phoenix.Sync.Electric do
310392
# if we want to use emphemeral dir for dev storage then we have to persist
311393
# the storage_dir into the application config.
312394
opts
313-
# |> Keyword.put_new(
314-
# :storage_dir,
315-
# Path.join(System.tmp_dir!(), "electric/shape-data#{System.monotonic_time()}")
316-
# )
317-
|> Keyword.put_new(
318-
:storage,
319-
{Electric.ShapeCache.InMemoryStorage,
320-
table_base_name: :"electric-storage#{opts[:stack_id]}", stack_id: opts[:stack_id]}
321-
)
322-
|> Keyword.put_new(
323-
:persistent_kv,
324-
{Electric.PersistentKV.Memory, :new!, []}
325-
)
326395
|> Keyword.put_new(:send_cache_headers?, false)
327396
end
328397

329398
defp env_defaults(opts, :test) do
330-
stack_id = "electric-stack#{System.monotonic_time()}"
399+
stack_id = "electric-stack"
331400

332401
opts = Keyword.put_new(opts, :stack_id, stack_id)
333402

@@ -389,26 +458,32 @@ defmodule Phoenix.Sync.Electric do
389458
end
390459
end
391460

392-
defp convert_repo_config(repo_config) do
393-
expected_keys = Electric.connection_opts_schema() |> Keyword.keys()
394-
395-
ssl_opts =
396-
case Keyword.get(repo_config, :ssl, nil) do
397-
off when off in [nil, false] -> [sslmode: :disable]
398-
true -> [sslmode: :require]
399-
_opts -> []
400-
end
461+
if @electric_available? do
462+
defp convert_repo_config(repo_config) do
463+
expected_keys = Electric.connection_opts_schema() |> Keyword.keys()
464+
465+
ssl_opts =
466+
case Keyword.get(repo_config, :ssl, nil) do
467+
off when off in [nil, false] -> [sslmode: :disable]
468+
true -> [sslmode: :require]
469+
_opts -> []
470+
end
401471

402-
tcp_opts =
403-
if :inet6 in Keyword.get(repo_config, :socket_options, []),
404-
do: [ipv6: true],
405-
else: []
472+
tcp_opts =
473+
if :inet6 in Keyword.get(repo_config, :socket_options, []),
474+
do: [ipv6: true],
475+
else: []
406476

407-
repo_config
408-
|> Keyword.take(expected_keys)
409-
|> Keyword.merge(ssl_opts)
410-
|> Keyword.merge(tcp_opts)
411-
|> Keyword.put_new(:port, 5432)
477+
repo_config
478+
|> Keyword.take(expected_keys)
479+
|> Keyword.merge(ssl_opts)
480+
|> Keyword.merge(tcp_opts)
481+
|> Keyword.put_new(:port, 5432)
482+
end
483+
else
484+
defp convert_repo_config(_repo_config) do
485+
[]
486+
end
412487
end
413488

414489
defp http_mode_plug_opts(electric_config) do
@@ -426,19 +501,6 @@ defmodule Phoenix.Sync.Electric do
426501
end
427502
end
428503

429-
defp electric_http_config(opts) do
430-
case Keyword.fetch(opts, :http) do
431-
{:ok, http_opts} ->
432-
opts
433-
|> then(fn o ->
434-
if(port = http_opts[:port], do: Keyword.put(o, :service_port, port), else: o)
435-
end)
436-
437-
:error ->
438-
opts
439-
end
440-
end
441-
442504
if @electric_available? do
443505
defp configure_client(opts, :embedded) do
444506
Electric.Client.embedded(opts)

lib/phoenix/sync/plug.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ defmodule Phoenix.Sync.Plug do
395395
end
396396
397397
"""
398-
@spec send_configuration(Plug.Conn.t(), Phoenix.Sync.shape_definition(), Client.t()) ::
398+
@spec send_configuration(Plug.Conn.t(), Phoenix.Sync.shape_definition(), Electric.Client.t()) ::
399399
Plug.Conn.t()
400400
def send_configuration(conn, shape_or_queryable, client \\ Phoenix.Sync.client!()) do
401401
shape = normalise_shape(shape_or_queryable)

0 commit comments

Comments
 (0)