When would it make sense to add validation for options using
Keyword.validate (or the backported variant) in Elixir's standard lib, Ecto
or Phoenix?

There's a runtime penalty for doing the validation. Would it make sense to
add it "everywhere" but actually do it only for certain values of Mix.env()?

wt., 14 gru 2021 o 13:26 Wojtek Mach <woj...@wojtekmach.pl> napisał(a):

> I don't think most features should be backported as it increases
> maintenance burden (on features themselves but also more minor stuff like
> @doc since). I definitely agree about the usefulness though. I tend to
> vendor-in the new functionality and at compile-time pick between the
> vendored and upstream implementation. For Keyword.validate/2 it would be:
>
>     # TODO: use Keyword.validate when we require Elixir 1.13
>     if Version.compare(System.version(), ">= 1.13.0") do
>       defp keyword_validate(keyword, values) do
>         Keyword.validate(keyword, values)
>       end
>     else
>       defp keyword_validate(keyword, values) when is_list(keyword) and 
> is_list(values) do
>         validate(keyword, values, [], [], [])
>       end
>
>       defp validate([{key, _} = pair | keyword], values1, values2, acc, 
> bad_keys)
>            when is_atom(key) do
>         case find_key!(key, values1, values2) do
>           {values1, values2} ->
>             validate(keyword, values1, values2, [pair | acc], bad_keys)
>
>           :error ->
>             case find_key!(key, values2, values1) do
>               {values1, values2} ->
>                 validate(keyword, values1, values2, [pair | acc], bad_keys)
>
>               :error ->
>                 validate(keyword, values1, values2, acc, [key | bad_keys])
>             end
>         end
>       end
>
>       defp validate([], values1, values2, acc, []) do
>         {:ok, move_pairs!(values1, move_pairs!(values2, acc))}
>       end
>
>       defp validate([], _values1, _values2, _acc, bad_keys) do
>         {:error, bad_keys}
>       end
>
>       defp validate([pair | _], _values1, _values2, _acc, []) do
>         raise ArgumentError,
>               "expected a keyword list as first argument, got invalid entry: 
> #{inspect(pair)}"
>       end
>
>       defp find_key!(key, [key | rest], acc), do: {rest, acc}
>       defp find_key!(key, [{key, _} | rest], acc), do: {rest, acc}
>       defp find_key!(key, [head | tail], acc), do: find_key!(key, tail, [head 
> | acc])
>       defp find_key!(_key, [], _acc), do: :error
>
>       defp move_pairs!([key | rest], acc) when is_atom(key),
>         do: move_pairs!(rest, acc)
>
>       defp move_pairs!([{key, _} = pair | rest], acc) when is_atom(key),
>         do: move_pairs!(rest, [pair | acc])
>
>       defp move_pairs!([], acc),
>         do: acc
>
>       defp move_pairs!([other | _], _) do
>         raise ArgumentError,
>               "expected the second argument to be a list of atoms or tuples, 
> got: #{inspect(other)}"
>       end
>
>       defp keyword_validate!(keyword, values) do
>         case keyword_validate(keyword, values) do
>           {:ok, kw} ->
>             kw
>
>           {:error, invalid_keys} ->
>             keys =
>               for value <- values,
>                   do: if(is_atom(value), do: value, else: elem(value, 0))
>
>             raise ArgumentError,
>                   "unknown keys #{inspect(invalid_keys)} in 
> #{inspect(keyword)}, the allowed keys are: #{inspect(keys)}"
>         end
>       end
>     end
>
>
>
>
> On December 14, 2021, "chrobot.io" <ste...@chrobot.io> wrote:
>
> Hi, I'd like to follow up on this since Keyword.validate has landed in
> 1.13. Is there a will to use the validation in common libraries?
>
> Just recently I've been hit by misspelling "redact: true" with "redacted:
> true" when defining a field in Ecto. Sounds like a perfect use case for
> Keyword.validate. Ideally this would explode, but that's a breaking change,
> so emitting a warning would probably make more sense.
>
> The other thing is that most libraries tend to stay on the oldest Elixir
> version possible, so they can't just call Keyword.validate (unless I'm
> missing something). In our case we're running on Elixir 1.10, but we
> backport small bits because of their usefulness (so for example we have
> Future.Keyword.validate and Future.tap). Is something like that a viable
> way to do this in libraries? I guess the other option is conditional
> compilation, but it sounds like a code that's going to stay there for a
> very long time (what would be the incentive to bump the minimum version as
> high as 1.13?).
>
> Looking forward to your thoughts on this.
>
> Best,
> Stefan
>
> wtorek, 27 lipca 2021 o 10:02:38 UTC+2 polva...@gmail.com napisał(a):
>>
>> The PR has been sent! Link:
>> https://github.com/elixir-lang/elixir/pull/11149
>>
>> Em segunda-feira, 26 de julho de 2021 às 15:07:18 UTC-3, José Valim
>> escreveu:
>>
>>> In this case, I would call Keyword.validate! And then call Map.new. I
>>> don’t see a benefit in the generic API as it won’t be faster and not
>>> necessarily clearer either. So I would stick with my last proposal. :)
>>>
>>> On Mon, Jul 26, 2021 at 20:01 Paulo Valente <polva...@gmail.com> wrote:
>>>
>>>> I was considering making it only accept keywords, and return keywords
>>>> as well.
>>>>
>>>> However, I can see benefits in accepting Enumerables. What do you think
>>>> about using another verb such as cast or coerce, instead of validate, for
>>>> this typing
>>>> (e.g. @spec Keyword.cast!(Enumerable.t(), keyword) :: keyword |
>>>> no_return and @spec Map.cast!(Enumerable.t(), keyword) :: map | no_return)?
>>>>
>>>> Should I also implement it for Map as well, or maybe add it in a second
>>>> PR?
>>>>
>>>> Thanks for the idea on Enumerables!
>>>> Em segunda-feira, 26 de julho de 2021 às 12:47:31 UTC-3, Wojtek Mach
>>>> escreveu:
>>>>
>>>>> Keyword.validate will accept just a keyword, right? Did you consider
>>>>> making it accept any enumerable of pairs, just like Keyword.new does? Same
>>>>> for Map.
>>>>>
>>>>> I think one particular scenario is something like this:
>>>>>
>>>>>     def foo(opts) when is_list(opts) do
>>>>>       config = Map.validate!(opts, …)
>>>>>       config.name # the assertive map.key is really convenient here
>>>>>
>>>>> what do you think? The only concern, besides feature creep :), is
>>>>> perhaps validate is not the best name given it accepts broader set of
>>>>> inputs. But then again, Keyword.keyword and in particular Map.map are 
>>>>> maybe
>>>>> a bit awkward.
>>>>>
>>>>> On July 26, 2021, josevalim <jose....@gmail.com> wrote:
>>>>>
>>>>> I debated this with the Elixir team and we agreed on the following API:
>>>>>     Keyword.validate! :: keyword | no_return    Keyword.validate ::
>>>>> {:ok, keyword} | {:error, unknown_keys, missing_keys}
>>>>> The same functionality would have to be defined for Map.
>>>>> The API for Keyword.validate! and Map.validate! would be the exact
>>>>> same as the one found in Nx.Defn.Kernel.keyword!.
>>>>> Could you please send a PR? Or open up an issue if you can't submit it
>>>>> at the moment.
>>>>>
>>>>> Thanks!On Monday, July 12, 2021 at 12:46:38 PM UTC+2
>>>>> polva...@gmail.com wrote:
>>>>>
>>>>> > I would propose to add this to the Kernel module, to mirror the
>>>>>> struct! helper. But adding to Keyword also works. Thoughts?
>>>>>>
>>>>>> Makes sense! I suggested Keyword mostly because there's the module
>>>>>> already, but since we have struct! in Kernel, it might be better to have
>>>>>> keyword! there too. Usage would also be nicer this way.
>>>>>>
>>>>>> > Should the bang version really raise for redundant options?
>>>>>>
>>>>>> As José said, it would help clean out stray options being passed. I
>>>>>> can think of a few cases in which I thought one option was being used, 
>>>>>> but
>>>>>> had either a typo or wrong name altogether (think "end" vs "end_datetime"
>>>>>> sort of situation).
>>>>>> However, I can see a less strict version being useful as well.
>>>>>> Perhaps there could be a keyword!/3 which accepted options like:
>>>>>> keyword!(opts, [extra_keys: true], key1: :default1, key2: :default2)
>>>>>>
>>>>>>  --
>>>>>  You received this message because you are subscribed to the Google
>>>>> Groups "elixir-lang-core" group.
>>>>>  To unsubscribe from this group and stop receiving emails from it,
>>>>> send an email to elixir-lang-co...@googlegroups.com.
>>>>>
>>>>> To view this discussion on the web visit
>>>>> https://groups.google.com/d/msgid/elixir-lang-core/9b83a803-c2c1-49e7-a531-56791607bcaan%40googlegroups.com
>>>>> <https://groups.google.com/d/msgid/elixir-lang-core/9b83a803-c2c1-49e7-a531-56791607bcaan%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>>> .
>>>>>
>>>>> --
>>>> You received this message because you are subscribed to the Google
>>>> Groups "elixir-lang-core" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>> an email to elixir-lang-co...@googlegroups.com.
>>>>
>>> To view this discussion on the web visit
>>>> https://groups.google.com/d/msgid/elixir-lang-core/a96fb19a-8148-41dc-8b45-0e2b9fe755b8n%40googlegroups.com
>>>> <https://groups.google.com/d/msgid/elixir-lang-core/a96fb19a-8148-41dc-8b45-0e2b9fe755b8n%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>> .
>>>>
>>>
> --
> You received this message because you are subscribed to the Google Groups
> "elixir-lang-core" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to elixir-lang-core+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/elixir-lang-core/43f206e4-6b22-4622-b8d9-1ade01556039n%40googlegroups.com
> <https://groups.google.com/d/msgid/elixir-lang-core/43f206e4-6b22-4622-b8d9-1ade01556039n%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
> --
> You received this message because you are subscribed to the Google Groups
> "elixir-lang-core" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to elixir-lang-core+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/elixir-lang-core/b8beea46f50e401bf00961fff49a76f68c360bb3%40hey.com
> <https://groups.google.com/d/msgid/elixir-lang-core/b8beea46f50e401bf00961fff49a76f68c360bb3%40hey.com?utm_medium=email&utm_source=footer>
> .
>

-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to elixir-lang-core+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/CACzMe7aTSK%3Dpni6gM2xDhXoUKFjJTTZ9cY4TqXniDUtddohA3Q%40mail.gmail.com.

Reply via email to