Projects should not be accessing Mix.env() at runtime though, as Mix.env() is not even available inside releases.
Also note that adding Keyword.validate/2 to existing APIs can be a backwards incompatible change, because people may be passing a bunch of unrelated options. We probably need to add some guidelines to the function documentation, but I think that focusing mostly on "DSLs" is a good starting point. On Tue, Dec 14, 2021 at 10:25 PM Stefan Chrobot <ste...@chrobot.io> wrote: > 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 > <https://groups.google.com/d/msgid/elixir-lang-core/CACzMe7aTSK%3Dpni6gM2xDhXoUKFjJTTZ9cY4TqXniDUtddohA3Q%40mail.gmail.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/CAGnRm4KJq%3DxpXZt713AJCxh25UF%2B2M2Z-%2BhH0Tr1mH0mMwUcVQ%40mail.gmail.com.