> I think the behaviour I'm proposing is in fact to raise, rather then to
ignore the field. The code I showed produces this result when the field
doesn't pass the check
Yes, definitely. I was commenting in regards to JSON values where you may
want to validate the input and report that accordingly instead of just
crashing.
> Wrapping the struct creation in a function is a good way (because it
helps abstracting the struct implementation), but the problem is that this
doesn't prevent using %Foo{key: value} syntax directly which can lead to
bugs. And while guards are in fact limited, this is supposed to be a
minimal (and optional) check for the data format, not a full blown
validation.
We can't guarantee that anyway. You can create the struct and later use
Map.put/3 or use %{struct | key: "invalid"}. If you don't force your users
to go through a regular API, be it your own function or be it a special
%Foo{} syntax.
> And when it comes to limits, the proposed solution can be quite
universal, actually. Because I use Kernel.apply/3, we could allow to pass
arbitrary arguments inside @guards, for example:
Then it is not a guard. :)
*José Valimwww.plataformatec.com.br
<http://www.plataformatec.com.br/>Founder and Director of R&D*
On Wed, Oct 4, 2017 at 12:34 PM, Maciej Kaszubowski <
[email protected]> wrote:
> I think the behaviour I'm proposing is in fact to raise, rather then to
> ignore the field. The code I showed produces this result when the field
> doesn't pass the check:
>
> iex(2)> %MyStruct{name: 5}
> ** (ArgumentError) The following fields didn't match the guards: struct
> MyStruct: [{:name, :is_binary, 5}]
> expanding struct: MyStruct.__struct__/1
>
>
> Wrapping the struct creation in a function is a good way (because it helps
> abstracting the struct implementation), but the problem is that this
> doesn't prevent using %Foo{key: value} syntax directly which can lead to
> bugs. And while guards are in fact limited, this is supposed to be a
> minimal (and optional) check for the data format, not a full blown
> validation.
>
> And when it comes to limits, the proposed solution can be quite universal,
> actually. Because I use Kernel.apply/3, we could allow to pass arbitrary
> arguments inside @guards, for example:
>
> @guards [active: {:in, [true, false]}]
> @guards [age: {:>, [18]}]
>
> Then,
>
> case @guards[key] do
> {fun, args} ->
> if apply(Kernel, fun, [value | args]) do
> # ok
> else
> # add error
> end
> # ...
> end
>
>
> While the syntax can probably be improved, this is quite flexible solution
> to validate the types.
>
>
> If this doesn't convince you, I guess I trust that you're correct and this
> is the right decision :) Thanks!
>
>
> W dniu środa, 4 października 2017 11:08:17 UTC+2 użytkownik Maciej
> Kaszubowski napisał:
>>
>> Hello,
>>
>> *Proposed feature*
>>
>> I'd like to propose another improvement on structs. Inspired by
>> @enforce_keys,
>> I'd like to propose adding @guards which can help to validate the types
>> of the fields in the struct.
>>
>> Example usage:
>>
>> defmodule MyStruct do
>> @guards [name: :is_binary]
>> defstruct [:name]
>> end
>>
>>
>> which will fail if the given condition is not satisfied:
>>
>> iex(2)> %MyStruct{name: 5}
>> ** (ArgumentError) The following fields didn't match the guards: struct
>> MyStruct: [{:name, :is_binary, 5}]
>> expanding struct: MyStruct.__struct__/1
>>
>>
>> *Notes*
>>
>> - As the example shows, the behaviour will be similar to
>> @enforce_keys - it will be checked only when creating the struct, not when
>> updating
>> - Using module attribute allows to keep this optional and allows to
>> keep backwards compatibility
>>
>> *Possible implementation*
>>
>> With https://hexdocs.pm/elixir/master/guards.html and Kernel.apply/3, we
>> can modify existing def __struct__(kv) from Kernel:
>>
>>
>> def __struct__(kv) do
>> {map, errors} =
>> Enum.reduce(kv, {@struct, {[], @enforce_keys}}, fn {key, val}, {map,
>> {type_errors, key_errors}} ->
>>
>> guard = @guards[key]
>> if guard && apply(Kernel, guard, [val]) do
>> {Map.replace!(map, key, val), {type_errors,
>> List.delete(key_errors, key)}}
>> else
>> {
>> Map.replace!(map, key, val),
>> {[{key, guard, val} | type_errors], List.delete(key_errors,
>> key)}
>> }
>> end
>>
>> end)
>> case errors do
>> {[], []} -> map
>> {types, []} ->
>> raise ArgumentError, "The following fields didn't match the guards:
>> " <>
>> "struct #{inspect __MODULE__}: #{inspect types}"
>> end
>> end
>>
>>
>>
>> This, of course, needs style improvements (and validation of required
>> fields which is currently removed for the sake of clarity), but this is
>> only a proof of concept to verify that the implementation is possible and
>> quite easy.
>>
>> *Why not use @type?*
>>
>> While it would be cool to be able to verify the types based on typespecs,
>> it would be harder because I think not all types can be easily validated.
>> The suggested approach with guards will be feel more familiar because we
>> can already do this for functions. Adding guard validation for struct
>> fields feels like reasonable step.
>>
>> *What do you think?*
>>
>> I'd be happy to start working on this feature, but I wanted to know what
>> do you all think about this.
>>
>>
>> Cheers,
>> Maciej
>>
>> --
> 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 [email protected].
> To view this discussion on the web visit https://groups.google.com/d/
> msgid/elixir-lang-core/21c292a5-1f09-473c-a489-
> a275ff5f892e%40googlegroups.com
> <https://groups.google.com/d/msgid/elixir-lang-core/21c292a5-1f09-473c-a489-a275ff5f892e%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
> For more options, visit https://groups.google.com/d/optout.
>
--
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 [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4L0n7wmc6x8hQBAsmSLNcm86U8W9Qprd0511g-BJbtA_Q%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.