clojureclojurescriptplumatic-schema

Clojure prismatic/schema defrecord no enum runtime validation?


When using prismatic/schema, validation of enum on defrecord doesn't work, as shown here:

(s/defrecord Action [type :- (s/enum :a :b)])
#'user/strict-map->Action
user> (Action. "3")            ; this should fail
#user.Action{:type "3"}
user> (Action. 1)              ; this should fail
#user.Action{:type 1}
user> (Action. "abc")          ; this should fail
#user.Action{:type "abc"}

However, when I change enum to long, it works as expected:

(s/defrecord ThisWorks [type :- long])
#'user/strict-map->ThisWorks
user> (ThisWorks. 3)
#user.ThisWorks{:type 3}
user> (ThisWorks. "abc")
ClassCastException java.lang.String cannot be cast to java.lang.Number  user/eval11888 (form-init4803894880546699153.clj:1)

Does anybody know? Thank you so much.


Solution

  • Because you can switch on and off validation during runtime your Records aren't actually checked until you pass them into a function:

    (s/defrecord Action [type :- (s/enum :a :b)])
    (s/defn process-action [x :- Action])
    (process-action (Action. "3")) ;; => Exception
    

    Regarding long magically working. This is just special clojure behavior due to primitives:

    fields can have type hints, and can be primitive

    • note that currently a type hint of a non-primitive type will not be used to constrain the field type nor the constructor arg, but will be used to optimize its use in the class methods

    • constraining the field type and constructor arg is planned

    (s/defrecord PrimitveRec [foo :- long])
    (s/defrecord NonPrimitveRec [foo :- String])
    
    
    (.? NonPrimitveRec :field #"foo" :type)
    ;=> (java.lang.Object)
    (.? PrimitveRec :field #"foo" :type)
    ;=> (long)
    

    Where .? is from Vinyasa.