# SQUID Forms API Proposal: Fn-based Validation API v2:
**Please read original [PoA/Design Doc](https://docs.google.com/document/d/10WCeiclxUiLCbGrWW-k3cS3hap0yBTBvt5wEUYjH-zk)**
## High Level:
Reminder of priority order of api improvement features from original GDoc:
1. Nested field
2. Cross field
- *-- the line --*
4. Group-level
- *-- bonus --*
6. Multiple error messages
7. Field Coercion
# End State:
```clojure
{:default-values {:group-1 {:group-1-1 {:number-1-1-1 33
:number-1-1-2 33
:number-1-1-3 34}
:text-1-1 "hello"}
:group-2 {:text-2-1 "hello"}
:number-1 2}
:validator (make-validator
{:& (fn [values _form-values]
"At the top-level :&, `values` == `form-values`"
(if (invalid? values) "It's all invalid!"))
:group-1 {:group-1-1 {:& (fn [values _form-values]
"Now `values` is the sub-map at [:group-1 :group-1-1]"
(when (not= 100 (apply + (vals values)))
"The sum of all the numbers must be 100."))}}
:group-2 {:text-2-1 (coerce
str/trim
(fn [value form-values]
(when (= value (get-in form-values [:group-1 :text-1-1]))
"Cannot be same as :text-1-1.")))}
:number-1 (fn [value]
(if (nil? value)
"Required."
[(when (even? value) "Must be odd")
(when-not (< 1 value) "Must be greater than 2.")]))}})
```
## ***Now let's zoom in***:
# 1. `:validator (make-validator {...})`:
## A. The `:validator` prop will now accept a simple flexible function with this signature:
> (and thus no longer accept a `map`)
```clojure
(fn [form-values] ;; a map in :default-values's shape
{:values {...}
:errors {...}})
```
**Args**: `[form-values]`
- `form-values` will be the current form values.
- It will already be `clj->js`-ified.
**Return**: `{:values {...} :errors {...}}`
- `:values`,
- if no errors, will be used as the `values` passed to `handle-submit`/`on-submit`
- Used to coerce the values of form fields
- eg `string` -> `number` for field `[:foo :bar]`
- eg apply `string/trim` to field `[:foo]`
- `:errors`
- validation errors
The dev will be responsible for matching `values` and `errors` shapes to the `:default-values` map. (or a subset, for `:errors`)
We then `clj->js`-ify the return value and pass that to `react-hook-form`
## B. Introducing **`(make-validator)`** :tada:: how people will *likely* actually use `:validator`
This helper will construct the right function for `:validator`, with a familiar API.
`make-validator` is a function with this signature:
```clojure
(fn make-validator
[validators-map]
(fn [form-values]
{:values {...}
:errors {...}})
```
**Args**: `[validators-map]`
- `validators-map`
- An arbitrarily nested map of field names -> validator/coercion fns
- Resembles `:default-values`, but instead of values at each node, they're functions that validate and/or coerce those fields' values.
**Return**: `(fn [form-values] {:values {...} :errors {...}})`
- A fn of the exact signature that `:validator` will want (see above)
All of this is already the exact same thing we *currently* do internally for `:validator`'s map-based API
**Example usage:**
```clojure
{:default-values {:foo {:bar "boo"}}
:validator (make-validator {:foo {:bar (fn [val]
(when (= val "boo")
"Invalid value"))}})
```
- The rest of this document will articulate the vision for `make-validator`'s new features that it adds to the existing `:validators` map conveniences.
# 2. Nested field validation:
Currently, `:validators`'s map can only be flat/1-level deep. So `make-validator` will fix that:
```clojure
{...
:validator (make-validator
{:group-2 {:text-2-1 (fn [value]
(when (invalid? value)
"[:group-2 :text-2-1] is Invalid"))}})}
```
# 3. Cross field validation:
For each field's validation fn, add a 2nd argument (eg `form-values`) that simply contains the whole form should a field need it:
```clojure
{...
:validator (make-validator
{:text-2-1 (fn [value form-values]
(when (= value (get-in form-values [:group-1 :text-1-1]))
":text-2-1 cannot be same as :text-1-1"))})}
```
- From `(fn [value] ...)`
- To `(fn [value form-values] ...)`
**OPEN QUESTION:** should `form-values` be pre- or post-coercion?
> Maybe offer a 3rd arg that is the uncoerced whole map?
# 4. Group/whole-form-level:
`make-validator` will accept an optional self-referential `:&` key at any level of the map.
It's value is a validator/coercion function just like above, but applied to a sub-map instead of a single value:
- first arg is the **sub-map of values *at that depth/path***
- second arg is the whole form
If `:&` used at the highest depth, the first and second arg will be the same.
**Example: Whole-form validation:**
```clojure
{...
:validator (make-validator
{:& (fn [values _form-values]
(when (whole-form-invalid? values) "It's all invalid!"))})}
```
**Example: Group-level validation:**
```clojure
{...
:validator (make-validator
{:group-1 {:group-1-1 {:& (fn [values _form-values]
(when (not= 100 (apply + (vals values)))
"The sum of these number inputs' values must be 100."))}}})}
```
# 4. Multiple error msgs per field:
Accept a `coll` of error msg `string`s as return value of validation fns:
- (in addition to existing single-`string` return value)
```clojure
{...
:validator (make-validator
{:number-1 (fn [value]
(if-not value
"Required."
[(when (even? value) "Must be odd")
(when-not (< 1 value) "Must be greater than 1.")]))})}
```
# 5. Field Coercion
Introducing... `forms/coerce` 🎉:
It's a function that allows devs to define a coercion fn for a field, either instead of, or in addition to, a validation fn.
It already existed in `react-hook-forms`, but our abstractions neither supported it nor allowed it.
Coercion is the transformation or parsing of field data at 2 phases:
1.
**Note about coercion in react-hook-form**:
> (and the `:values` map)