Add support for JSON Schema If-Then-Else via :if#1288
Conversation
Adds support for JSON Schema's conditional If-Then-Else structure by implementing a new schema type :if to enable detection and handling of the logic when transforming given data to JSON Schema. This new :if schema type also has equivalence using Malli's existing core schemas (:and/:or/:not) which is internally used for transform and generator support.
| (then-v value) | ||
| (if else-v (else-v value) true))))) | ||
| (-explainer [this path] | ||
| (let [then-explainer (-explainer then' (conj path 'then)) |
There was a problem hiding this comment.
The -explainer seems to do extra work. It's enough to simply extend the path here instead of then associng :branch later. And I suggest using keywords as the path elements and implementing LensSchema.
| (-options [this] options) | ||
| (-children [this] children) | ||
| (-parent [this] parent) | ||
| (-form [this] form)))))) |
There was a problem hiding this comment.
Here's the correct implementation here for ParserInfo.
ParserInfo
(-parser-info [_ opts]
{:simple-parser (and (:simple-parser (-parser-info then' opts))
(if else' (:simple-parser (-parser-info else' opts)) true))})
There are some other useful methods missed that are implemented in the surrounding schemas.
EDIT: actually this depends on which implementation of -parser we use so let's defer this discussion until that is resolved.
| (let [then-u (-unparser then') | ||
| else-u (when else' (-unparser else'))] | ||
| (fn [x] | ||
| (if (condition-v x) |
There was a problem hiding this comment.
I don't think this is right. x here is potentially parsed so it's meaningless to test the condition validator. For example, consider [:if :int [:orn [:int :int]]]. It's now a tag at this point, so will always fail the :int validator.
I think there's a few options to choose here:
- we return a tagged result from
-parsersimilar to:orn - we ensure
:ifnever transforms the parsed value by using-simple-parser - we try each unparser in turn similar to
:or
| (-transformer [this transformer method options] | ||
| (-transformer equivalent-schema' transformer method options)) | ||
| (-walk [this walker path options] | ||
| (-walk-leaf this walker path options)) |
| [:map [:y string?]]]] ; ELSE | ||
| ``` | ||
|
|
||
| The `ELSE` branch can be omitted for `when`-like behavior." |
There was a problem hiding this comment.
It's more like logical implication as written. This is the opposite of what I expected. For example [:or [:if S T] [:if S' T']] would never hit the second schema.
Additionally, the equivalent schema to [:if S T] is actually [:or [:and S T] :any], so there's some internal inconsistency here.
This might need more time to bake, I propose we support 2 args later and start with 3. I understand JSON Schema maps missing branches to true but this doesn't map nicely to Clojure's semantics.
opqdonut
left a comment
There was a problem hiding this comment.
Ambrose reviewed the impl so I just had a look at the tests & docs.
| :says "meow"} | ||
| decoded {:animal :cat | ||
| :says :meow}] | ||
| (testing "roundtip works" |
| decoded {:animal :cat | ||
| :says :meow}] | ||
| (testing "roundtip works" | ||
| (is (= encoded (m/encode schema decoded (mt/string-transformer))))))) No newline at end of file |
There was a problem hiding this comment.
I was expecting this to also test that (= decoded (m/decode ...))
| ["card-number"]} | ||
| :else {:properties {"iban" {:type "string"}} | ||
| :required ["iban"]}} | ||
| (json-schema/transform if-then-else-schema))))) |
There was a problem hiding this comment.
tests should use transform-and-check, which uses AJV to validate the schema
| (is (m/coerce ConsCell [1 [2 [3 [4 [1 [2 [3 [4 nil]]]]]]]])) | ||
| (is (= @count-into-schemas 3)))) ;; was 10 | ||
|
|
||
| (deftest if-conditional-schema-test |
There was a problem hiding this comment.
good test, though it doesn't exercise the 2-arity form at all!
| result (json-schema/transform type-emitting-schema)] | ||
| (is (= "object" (get-in result [:if :type]))) | ||
| (is (= "object" (get-in result [:then :type]))) | ||
| (is (= "object" (get-in result [:else :type])))))) No newline at end of file |
There was a problem hiding this comment.
I'd like this test to show what happens when something other than :map is used inside :if
Adds support for JSON Schema's conditional If-Then-Else structure by implementing a new schema type :if to enable detection and handling of the logic when transforming given data to JSON Schema.
This new :if schema type also has equivalence using Malli's existing core schemas (:and/:or/:not) which is internally used for transform and generator support.
Fixes #1287