Skip to content

Request with missing nested map param throws validation error when "type: :map" specified for param #23

@theirishpenguin

Description

@theirishpenguin

Thanks for you work on the project @ozziexsh !

I have found a issue when I am working with nested parameters. Here is my example...

The Problem

  • Given I have the following validation rules defined - especially with type: :map specified for the "address" param
   def rules(conn) do                           
     dbg() # <---- Callout: conn.params is %{} here                                                                                                                                                                                                                                                                           
     %{                                                                                                                                                                                                                                                                                                                    
       "address" => [                                                                                                                                                                                                                                                                                                         
         required: false,                                                                                                                                                                                                                                                                                                  
         # Callout: The next line seems to cause the problem                                                                                                                                                                                              
         type: :map,                                                                                                                                                                                                                                                                                                     
         nullable: true,                                                                                                                                                                                                                                                                                                   
         map: %{                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
           "house_number" => [required: false, nullable: true, cast: :integer, between: {1, 50}]                                                                                                                                                                                                                          
         }                                                                                                                                                                                                                                                                                                                 
       ]                                                                                                                                                                                                                                                                                                                   
     }                                                                                                                                                                                                                                                                                                                     
   end       
  • When I make a simple GET request in which I don't supply any "address" param
  • Then I get the following validation errors...
[       
  %Validate.Validator.Error{
    path: ["address"],
    message: "expected map received nil",
    rule: :type
  }
]

Workaround
If I comment up the type: :map specified for the "address" param and repeat the experiment I get no validation errors (as expected). ie.

   def rules(conn) do                           
     dbg() # <---- Callout: conn.params is %{} here                                                                                                                                                                                                                                                                           
     %{                                                                                                                                                                                                                                                                                                                    
       "address" => [                                                                                                                                                                                                                                                                                                         
         required: false,                                                                                                                                                                                                                                                                                                  
         # NB: Important: to *not* set type here or else map will blow up when no "page" params supplied at all                                                                                                                                                                                                            
         # type: :map,                                                                                                                                                                                                                                                                                                     
         nullable: true,                                                                                                                                                                                                                                                                                                   
         map: %{                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
           "house_number" => [required: false, nullable: true, cast: :integer, between: {1, 50}]                                                                                                                                                                                                                          
         }                                                                                                                                                                                                                                                                                                                 
       ]                                                                                                                                                                                                                                                                                                                   
     }                                                                                                                                                                                                                                                                                                                     
   end       

Here is the example in a unit test...

   test "it should allow a nested map param to *not* be supplied when type: :map and nullable: true and specified" do                                                                                                                                                                                                                                                               
     rules =%{                                                                                                                                                                                                                                                                                                             
       "address" => [                                                                                                                                                                                                                                                                                                      
         required: false,                                                                                                                                                                                                                                                                                                  
         # Callout: The next line seems to cause the problem                                                                                                                                                                                                                                                               
         type: :map,                                                                                                                                                                                                                                                                                                       
         nullable: true,                                                                                                                                                                                                                                                                                                   
         map: %{                                                                                                                                                                                                                                                                                                           
           "house_number" => [required: false, nullable: true, cast: :integer, between: {1, 50}]                                                                                                                                                                                                                           
         }                                                                                                                                                                                                                                                                                                                 
       ]                                                                                                                                                                                                                                                                                                                   
     }                                                                                                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                                                                           
     # No address key supplied                                                                                                                                                                                                                                                                         
     input = %{}                                                                                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                                                                           
     assert {:ok, _} = Validate.validate(input, rules)                                                                                                                                                                                                                                                                     
   end      

This test fails. If you comment out the type: map line it passes.

My Expectation
This issue occurred when I was used the plug / form request approach. In that scenario, when I stop submitting an "address" param with the HTTP request, the request param obviously does not get sent at all to the server (ie. I won't get a %{"address" => nil}, rather I will get %{} and that is problematic. I am not sure if the "nullable" option is behaving as you would expect in the above scenario - maybe it is. If it is behaving as expected then maybe a allow_missing: true option could be added? This would allow a param to be absent as well as present but nil - though that I what I would expect the required: false to allow me to do.

P.S. I can workaround the issue by not supplying type: :map of course - but that seems like a hack - really what I should be able to specify here is a map that can be missing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions