Skip to content

Commit

Permalink
Supervised sqids: raise when missing options are detected
Browse files Browse the repository at this point in the history
To force those affected into action:
* #32

Otherwise, if not using child_spec() is detected (mea culpa), a warning
is emmitted.

Finally, update README to @decius7bc's suggestion of a workaround.
  • Loading branch information
g-andrade committed Oct 12, 2024
1 parent 31a20a0 commit 0910060
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ iex> defmodule MyApp.Application do
iex> # ...
iex> def start(_type, _args) do
iex> children = [
iex> MyApp.SupervisedSqids,
iex> MyApp.SupervisedSqids.child_spec(),
iex> # ...
iex> ]
iex>
Expand Down
46 changes: 46 additions & 0 deletions lib/sqids.ex
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,44 @@ defmodule Sqids do
end
end

@doc false
@spec different_opts(opts, opts) :: opts
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
def different_opts(opts1, opts2) do
alphabet_str1 = opts1[:alphabet] || @default_alphabet
min_length1 = opts1[:min_length] || @default_min_length
blocklist_words1 = opts1[:blocklist] || :default

alphabet_str2 = opts2[:alphabet] || @default_alphabet
min_length2 = opts2[:min_length] || @default_min_length
blocklist_words2 = opts2[:blocklist] || :default

different_opts = []

different_opts =
if alphabet_str2 === alphabet_str1 do
different_opts
else
different_opts ++ [alphabet: alphabet_str2]
end

different_opts =
if min_length2 === min_length1 do
different_opts
else
different_opts ++ [min_length: min_length2]
end

if blocklist_words2 === :default !== (blocklist_words1 === :default) or
(blocklist_words2 !== :default and
blocklist_words1 !== :default and
Enum.sort(Enum.uniq(blocklist_words2)) !== Enum.sort(Enum.uniq(blocklist_words1))) do
different_opts ++ [blocklist: blocklist_words2]
else
different_opts
end
end

## Internal Functions: Encoding

@spec read_default_blocklist_words! :: [String.t()]
Expand Down Expand Up @@ -523,6 +561,14 @@ defmodule Sqids do
Starts `Sqids.Agent` for #{__MODULE__}.
"""
def start_link(opts) do
case __MODULE__.child_spec() do
%{start: {__MODULE__, :start_link, [desired_opts]}} when desired_opts !== opts ->
Sqids.Hacks.raise_exception_if_missed_desired_options(opts, desired_opts, __MODULE__)

_child_spec ->
:ok
end

shared_state_init = {&Sqids.new/1, [opts]}
Sqids.Agent.start_link(__MODULE__, shared_state_init)
end
Expand Down
66 changes: 66 additions & 0 deletions lib/sqids/hacks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,77 @@ defmodule Sqids.Hacks do
@moduledoc "Workarounds"
@moduledoc since: "0.1.2"

require Logger

@doc """
Function to work around Dialyzer warnings on violating
type opacity when Sqids context is placed in a module attribute, since it
becomes "hardcoded" from Dialyzer's point of view.
"""
@spec dialyzed_ctx(term) :: Sqids.t()
def dialyzed_ctx(sqids), do: Sqids.dialyzed_ctx(sqids)

@doc false
@spec raise_exception_if_missed_desired_options(Sqids.opts(), Sqids.opts(), module) :: :ok
def raise_exception_if_missed_desired_options(opts, desired_opts, using_mod) do
missed_opts = Sqids.different_opts(opts, desired_opts)

if missed_opts === [] do
Logger.warning("""
Direct call of #{inspect(using_mod)}.child_spec/1 may lead to unintended results in the future.
Update #{inspect(using_mod)}'s entry under your supervisor,
from:
[
#{inspect(using_mod)}
]
To:
[
#{inspect(using_mod)}.child_spec()
]
Apologies for the disruption. Context for the issue:
* https://github.com/sqids/sqids-elixir/issues/32
""")
else
raise """
The following Sqids options were declared but are not being used:
* #{inspect(missed_opts, pretty: true)}
IF YOU START USING THEM NOW IT WILL BREAK COMPATIBILITY WITH PREVIOUSLY ENCODED IDS.
How can I fix this?
First step is optional: if you don't want to breaking existing IDs,
update your #{inspect(using_mod)} options to match the ones in use:
```
defmodule #{using_mod} do
def child_spec do
# Was: child_spec(#{inspect(desired_opts)})
child_spec(
#{inspect(opts, pretty: true)}
)
end
end
```
Second step: update #{inspect(using_mod)}'s entry under your supervisor, from:
[
#{inspect(using_mod)}
]
To:
[
#{inspect(using_mod)}.child_spec()
]
Apologies for the disruption. Context for the issue:
* https://github.com/sqids/sqids-elixir/issues/32
"""
end
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ defmodule Sqids.MixProject do
threshold: 93
]
],
dialyzer: [plt_add_apps: [:ex_unit]],
dialyzer: [plt_add_apps: [:ex_unit, :logger]],
package: package()
]
end
Expand Down
2 changes: 1 addition & 1 deletion test/sqids_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule SqidsTest do
use Sqids

@impl true
def child_spec, do: child_spec([])
def child_spec, do: child_spec(unquote(Macro.escape(opts)))
end

Module.create(module_name, module_content, Macro.Env.location(__ENV__))
Expand Down

0 comments on commit 0910060

Please sign in to comment.