Skip to content

Commit

Permalink
feat: validate options with NimbleOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
c4710n committed Dec 30, 2023
1 parent a94df77 commit 0514840
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 120 deletions.
12 changes: 7 additions & 5 deletions lib/plug_locale/header.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ defmodule PlugLocale.Header do
## Options
* `:default_locale` - the default locale.
* `:locales` - all the supported locales. Default to `[]`.
* `:locales` - all the supported locales.
Default to `[]`.
* `:cast_locale_by` - specify the function for casting extracted or
detected locales. Default to `nil`.
detected locales.
Default to `nil`.
* `:header_name` - the header for getting locale.
Default to `"x-client-locale"`.
* `:assign_key` - the key for putting value into `assigns` storage.
Expand Down Expand Up @@ -103,7 +105,7 @@ defmodule PlugLocale.Header do
plug `#{inspect(__MODULE__)}`,
default_locale: "en",
locales: ["en", "zh"],
sanitize_locale_by: &DemoWeb.I18n.cast_locale/1,
cast_locale_by: &DemoWeb.I18n.cast_locale/1,
# ...
"""
Expand Down Expand Up @@ -131,12 +133,12 @@ defmodule PlugLocale.Header do
end
end

defp cast_locale(config, locale, opts \\ []) do
defp cast_locale(config, locale, opts) do
default = Keyword.get(opts, :default, nil)

if locale do
casted_locale =
if is_function(config.cast_locale_by, 1),
if config.cast_locale_by,
do: config.cast_locale_by.(locale),
else: locale

Expand Down
82 changes: 37 additions & 45 deletions lib/plug_locale/header/config.ex
Original file line number Diff line number Diff line change
@@ -1,64 +1,56 @@
defmodule PlugLocale.Header.Config do
@moduledoc false

defstruct [
:default_locale,
:locales,
:cast_locale_by,
:header_name,
:assign_key
]

@doc false
def new!(opts) when is_list(opts) do
default_locale = Keyword.get(opts, :default_locale)
locales = Keyword.get(opts, :locales, [])
cast_locale_by = Keyword.get(opts, :cast_locale_by, nil)
header_name = Keyword.get(opts, :header_name, "x-client-locale")
assign_key = Keyword.get(opts, :assign_key, :locale)

[
default_locale: default_locale,
locales: locales,
cast_locale_by: cast_locale_by,
header_name: header_name,
assign_key: assign_key
@opts_schema [
default_locale: [
type: :string,
required: true
],
locales: [
type: {:list, :string},
default: []
],
cast_locale_by: [
type: {:or, [nil, {:fun, 1}]},
default: nil
],
header_name: [
type: :string,
default: "x-client-locale"
],
assign_key: [
type: :atom,
default: :locale
]
|> as_map!()
|> as_struct!()
end

defp validate!(opts) do
keys = Keyword.keys(opts)
unset_keys = Enum.filter(keys, fn key -> Keyword.get(opts, key) == nil end)
]

if unset_keys != [] do
raise RuntimeError, "following keys #{inspect(unset_keys)} should be set"
end
defstruct Keyword.keys(@opts_schema)

@doc false
def new!(opts) do
opts
|> NimbleOptions.validate!(@opts_schema)
|> finalize()
|> as_struct!()
end

defp as_map!(opts) do
defp finalize(opts) do
default_locale = Keyword.fetch!(opts, :default_locale)
locales = Keyword.fetch!(opts, :locales)
cast_locale_by = Keyword.fetch!(opts, :cast_locale_by)
header_name = Keyword.fetch!(opts, :header_name)
assign_key = Keyword.fetch!(opts, :assign_key)
locales = Enum.uniq([default_locale | locales])

%{
default_locale: default_locale,
locales: Enum.uniq([default_locale | locales]),
cast_locale_by: cast_locale_by,
header_name: header_name,
assign_key: assign_key
}
Keyword.merge(opts, locales: locales)
end

defp as_struct!(config) do
defp as_struct!(opts) do
default_struct = __MODULE__.__struct__()
valid_keys = Map.keys(default_struct)
config = Map.take(config, valid_keys)

config =
opts
|> Enum.into(%{})
|> Map.take(valid_keys)

Map.merge(default_struct, config)
end
end
14 changes: 8 additions & 6 deletions lib/plug_locale/web_browser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -85,21 +85,23 @@ defmodule PlugLocale.WebBrowser do
## Options
* `:default_locale` - the default locale.
* `:locales` - all the supported locales. Default to `[]`.
* `:locales` - all the supported locales.
Default to `[]`.
* `:detect_locale_from` - specify *the sources* and *the order of sources*
for detecting locale.
Available sources are `:query`, `:cookie`, `:referrer`, `:accept_language`.
Default to `[:cookie, :referrer, :accept_language]`.
* `:cast_locale_by` - specify the function for casting extracted or
detected locales. Default to `nil`.
detected locales.
Default to `nil`.
* `:route_identifier` - the part for identifying locale in route.
Default to `:locale`.
* `:assign_key` - the key for putting value into `assigns` storage.
Default to the value of `:route_identifier` option.
Default to `:locale`.
* `:query_key` - the key for getting locale from querystring.
Default to the **stringified** value of `:route_identifier` option.
Default to `"locale"`.
* `:cookie_key` - the key for getting locale from cookie.
Default to `"preferred_locale"`.
Default to `"locale"`.
### about `:cast_locale_by` option
Expand Down Expand Up @@ -463,7 +465,7 @@ defmodule PlugLocale.WebBrowser do

if locale do
casted_locale =
if is_function(config.cast_locale_by, 1),
if config.cast_locale_by,
do: config.cast_locale_by.(locale),
else: locale

Expand Down
116 changes: 53 additions & 63 deletions lib/plug_locale/web_browser/config.ex
Original file line number Diff line number Diff line change
@@ -1,84 +1,74 @@
defmodule PlugLocale.WebBrowser.Config do
@moduledoc false

defstruct [
:default_locale,
:locales,
:detect_locale_from,
:cast_locale_by,
:route_identifier,
:path_param_key,
:assign_key,
:query_key,
:cookie_key
]

@doc false
def new!(opts) when is_list(opts) do
default_locale = Keyword.get(opts, :default_locale)
locales = Keyword.get(opts, :locales, [])

detect_locale_from =
Keyword.get(opts, :detect_locale_from, [:cookie, :referrer, :accept_language])

cast_locale_by = Keyword.get(opts, :cast_locale_by, nil)
route_identifier = Keyword.get(opts, :route_identifier, :locale)
assign_key = Keyword.get(opts, :assign_key, route_identifier)
query_key = Keyword.get(opts, :query_key, to_string(route_identifier))
cookie_key = Keyword.get(opts, :cookie_key, "preferred_locale")

[
default_locale: default_locale,
locales: locales,
detect_locale_from: detect_locale_from,
cast_locale_by: cast_locale_by,
route_identifier: route_identifier,
assign_key: assign_key,
query_key: query_key,
cookie_key: cookie_key
@opts_schema [
default_locale: [
type: :string,
required: true
],
locales: [
type: {:list, :string},
default: []
],
detect_locale_from: [
type: {:list, :atom},
default: [:cookie, :referrer, :accept_language]
],
cast_locale_by: [
type: {:or, [nil, {:fun, 1}]},
default: nil
],
route_identifier: [
type: :atom,
default: :locale
],
assign_key: [
type: :atom,
default: :locale
],
query_key: [
type: :string,
default: "locale"
],
cookie_key: [
type: :string,
default: "locale"
]
|> as_map!()
|> as_struct!()
end
]

defp validate!(opts) do
keys = Keyword.keys(opts)
unset_keys = Enum.filter(keys, fn key -> Keyword.get(opts, key) == nil end)
@direct_fields Keyword.keys(@opts_schema)
@derived_fields [:path_param_key]

if unset_keys != [] do
raise RuntimeError, "following keys #{inspect(unset_keys)} should be set"
end
defstruct @direct_fields ++ @derived_fields

@doc false
def new!(opts) do
opts
|> NimbleOptions.validate!(@opts_schema)
|> finalize()
|> as_struct!()
end

defp as_map!(opts) do
defp finalize(opts) do
default_locale = Keyword.fetch!(opts, :default_locale)
locales = Keyword.fetch!(opts, :locales)
detect_locale_from = Keyword.fetch!(opts, :detect_locale_from)
cast_locale_by = Keyword.fetch!(opts, :cast_locale_by)
locales = Enum.uniq([default_locale | locales])

route_identifier = Keyword.fetch!(opts, :route_identifier)
assign_key = Keyword.fetch!(opts, :assign_key)
query_key = Keyword.fetch!(opts, :query_key)
cookie_key = Keyword.fetch!(opts, :cookie_key)
path_param_key = to_string(route_identifier)

%{
default_locale: default_locale,
locales: Enum.uniq([default_locale | locales]),
detect_locale_from: detect_locale_from,
cast_locale_by: cast_locale_by,
route_identifier: route_identifier,
path_param_key: to_string(route_identifier),
assign_key: assign_key,
query_key: query_key,
cookie_key: cookie_key
}
Keyword.merge(opts, locales: locales, path_param_key: path_param_key)
end

defp as_struct!(config) do
defp as_struct!(opts) do
default_struct = __MODULE__.__struct__()
valid_keys = Map.keys(default_struct)
config = Map.take(config, valid_keys)

config =
opts
|> Enum.into(%{})
|> Map.take(valid_keys)

Map.merge(default_struct, config)
end
end
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ defmodule PlugLocale.MixProject do
defp deps do
[
{:plug, "~> 1.14"},
{:nimble_options, "~> 1.0"},
{:ex_check, "~> 0.15.0", only: [:dev], runtime: false},
{:credo, ">= 0.0.0", only: [:dev], runtime: false},
{:dialyxir, ">= 0.0.0", only: [:dev], runtime: false},
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mix_audit": {:hex, :mix_audit, "2.1.1", "653aa6d8f291fc4b017aa82bdb79a4017903902ebba57960ef199cbbc8c008a1", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "541990c3ab3a7bb8c4aaa2ce2732a4ae160ad6237e5dcd5ad1564f4f85354db1"},
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"},
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
Expand Down
2 changes: 1 addition & 1 deletion test/plug_locale/web_browser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ defmodule PlugLocale.WebBrowserTest do
test "is redirected to a path detected from cookie" do
conn =
conn(:get, "/unknown-locale/posts/7")
|> put_resp_cookie("preferred_locale", "zh-Hans")
|> put_resp_cookie("locale", "zh-Hans")
|> fetch_cookies()

conn = DemoRouter.call(conn, @opts)
Expand Down

0 comments on commit 0514840

Please sign in to comment.