zoedsoupe / peri

Elixir library for declarative data description and validation
MIT License
84 stars 2 forks source link

[BUG] A crash while validating lists #7

Closed dvv closed 3 months ago

dvv commented 3 months ago

Describe the bug A crash while validating lists

To Reproduce

Peri.validate({:list, %{x: :string}}, [%{x: "X"}])
** (KeyError) key :root_data not found in: [%{x: "X"}]

If you are using the dot syntax, such as map.field, make sure the left-hand side of the dot is a map
    (peri 0.2.7) lib/peri.ex:672: Peri.validate_field/3
    (peri 0.2.7) lib/peri.ex:652: anonymous fn/4 in Peri.validate_field/3
    (elixir 1.17.2) lib/enum.ex:4858: Enumerable.List.reduce/3
    (elixir 1.17.2) lib/enum.ex:2585: Enum.reduce_while/3
    (peri 0.2.7) lib/peri.ex:651: Peri.validate_field/3
    (peri 0.2.7) lib/peri.ex:264: Peri.validate/2

Expected behavior

Peri.validate({:list, %{x: :string}}, [%{x: "X"}])
{:ok, [%{x: "X"}]}
dvv commented 3 months ago

Hi! What could be the cause of:

[%Peri.Error{path: [:locks], key: :locks, content: %{type: {:list, %{object_id: {:required, :string}}}}, message: \"expected a nested schema but received schema: {:list, %{object_id: {:required, :string}}}\", errors: nil}]
zoedsoupe commented 3 months ago

what is the schema and data you're defining?

dvv commented 3 months ago
schema = %{approved: {:boolean, {:default, false}}, locks: {:list,
%{object_id: {:required, :string}}}, id: :uuid}

defmodule Foo.Entry do
use Ecto.Schema

schema "entry" do
field :approved, :boolean, default: false
has_many :locks, Undo.ObjectLock, foreign_key: :object_id
end
end

Peri.validate(result_schema, %Foo.Entry{approved: false, locks: []})
{:error,
 [
   %Peri.Error{
     path: [:locks],
     key: :locks,
     content: %{type: {:list, %{object_id: {:required, :string}}}},
     message: "expected a nested schema but received schema: {:list,
%{object_id: {:required, :string}}}",
     errors: nil
   }
 ]}

Peri.validate(result_schema, %{approved: false, locks: []})
{:ok, %{approved: false, locks: []}}

On Fri, Aug 2, 2024 at 8:48 AM Zoey de Souza Pessanha < @.***> wrote:

what is the schema and data you're defining?

— Reply to this email directly, view it on GitHub https://github.com/zoedsoupe/peri/issues/7#issuecomment-2264607662, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABTTB24GSGA2LRUU2AJAYLZPMMQ7AVCNFSM6AAAAABLY2UVSSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENRUGYYDONRWGI . You are receiving this because you authored the thread.Message ID: @.***>

dvv commented 3 months ago

The strangest thing is when I remove the default:

result_schema = %{approved: :boolean, locks: {:list, %{object_id:
{:required, :string}}}, id: get_schema(:uuid)}
Peri.validate(result_schema, %Foo.Entry{approved: false, locks: []})

just works

On Fri, Aug 2, 2024 at 9:05 AM Vladimir Dronnikov @.***> wrote:

schema = %{approved: {:boolean, {:default, false}}, locks: {:list,
%{object_id: {:required, :string}}}, id: :uuid}

defmodule Foo.Entry do
use Ecto.Schema

schema "entry" do
field :approved, :boolean, default: false
has_many :locks, Undo.ObjectLock, foreign_key: :object_id
end
end

Peri.validate(result_schema, %Foo.Entry{approved: false, locks: []})
{:error,
 [
   %Peri.Error{
     path: [:locks],
     key: :locks,
     content: %{type: {:list, %{object_id: {:required, :string}}}},
     message: "expected a nested schema but received schema: {:list,
%{object_id: {:required, :string}}}",
     errors: nil
   }
 ]}

Peri.validate(result_schema, %{approved: false, locks: []})
{:ok, %{approved: false, locks: []}}

On Fri, Aug 2, 2024 at 8:48 AM Zoey de Souza Pessanha < @.***> wrote:

what is the schema and data you're defining?

— Reply to this email directly, view it on GitHub https://github.com/zoedsoupe/peri/issues/7#issuecomment-2264607662, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABTTB24GSGA2LRUU2AJAYLZPMMQ7AVCNFSM6AAAAABLY2UVSSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENRUGYYDONRWGI . You are receiving this because you authored the thread.Message ID: @.***>

dvv commented 3 months ago

The above example SILENTLY spoils the data in fact:

Peri.validate(result_schema, %Foo.Entry{approved: true, locks:
[%{object_id: "1"}]})
{:ok,
 %Foo.Entry{
   __meta__: #Ecto.Schema.Metadata<:built, "app", "entry">,
   id: nil,
   approved: false,
   locks: [%{object_id: "1"}]
 }}

Notice it resets approved to false. I believe the problem is in how the context is transferred when diving to nested schemas.

On Fri, Aug 2, 2024 at 9:09 AM Vladimir Dronnikov @.***> wrote:

The strangest thing is when I remove the default:

result_schema = %{approved: :boolean, locks: {:list, %{object_id:
{:required, :string}}}, id: get_schema(:uuid)}
Peri.validate(result_schema, %Foo.Entry{approved: false, locks: []})

just works

On Fri, Aug 2, 2024 at 9:05 AM Vladimir Dronnikov @.***> wrote:

schema = %{approved: {:boolean, {:default, false}}, locks: {:list,
%{object_id: {:required, :string}}}, id: :uuid}

defmodule Foo.Entry do
use Ecto.Schema

schema "entry" do
field :approved, :boolean, default: false
has_many :locks, Undo.ObjectLock, foreign_key: :object_id
end
end

Peri.validate(result_schema, %Foo.Entry{approved: false, locks: []})
{:error,
 [
   %Peri.Error{
     path: [:locks],
     key: :locks,
     content: %{type: {:list, %{object_id: {:required, :string}}}},
     message: "expected a nested schema but received schema: {:list,
%{object_id: {:required, :string}}}",
     errors: nil
   }
 ]}

Peri.validate(result_schema, %{approved: false, locks: []})
{:ok, %{approved: false, locks: []}}

On Fri, Aug 2, 2024 at 8:48 AM Zoey de Souza Pessanha < @.***> wrote:

what is the schema and data you're defining?

— Reply to this email directly, view it on GitHub https://github.com/zoedsoupe/peri/issues/7#issuecomment-2264607662, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABTTB24GSGA2LRUU2AJAYLZPMMQ7AVCNFSM6AAAAABLY2UVSSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENRUGYYDONRWGI . You are receiving this because you authored the thread.Message ID: @.***>