Skip to content

Commit 68fa276

Browse files
authored
Merge pull request #1012 from frenchy64/explain-and-gen
README: add dedicated sections for validating vector schemas and `:and` generation
2 parents 56ed9d1 + f88483f commit 68fa276

File tree

2 files changed

+99
-11
lines changed

2 files changed

+99
-11
lines changed

README.md

+89-11
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ Data-driven Schemas for Clojure/Script and [babashka](#babashka).
1919
- [Generating values](#value-generation) from Schemas
2020
- [Inferring Schemas](#inferring-schemas) from sample values and [Destructuring](#destructuring).
2121
- Tools for [Programming with Schemas](#programming-with-schemas)
22-
- [Parsing](#parsing-values), [Unparsing](#unparsing-values) and [Sequence Schemas](#sequence-schemas)
22+
- [Parsing](#parsing-values) and [Unparsing](#unparsing-values) values
23+
- [Sequence](#sequence-schemas), [Vector](#vector-schemas), and [Set](#set-schemas) Schemas
2324
- [Persisting schemas](#persisting-schemas), even [function schemas](#serializable-functions)
2425
- Immutable, Mutable, Dynamic, Lazy and Local [Schema Registries](#schema-registry)
2526
- [Schema Transformations](#schema-Transformation) to [JSON Schema](#json-schema), [Swagger2](#swagger2), and [descriptions in english](#description)
@@ -379,26 +380,19 @@ default branching can be arbitrarily nested:
379380

380381
## Sequence schemas
381382

382-
You can use `:sequential` for any homogeneous Clojure sequence, `:vector` for vectors and `:set` for sets.
383+
You can use `:sequential` to describe homogeneous sequential Clojure collections.
383384

384385
```clojure
385386
(m/validate [:sequential any?] (list "this" 'is :number 42))
386387
;; => true
387388

388-
(m/validate [:vector int?] [1 2 3])
389+
(m/validate [:sequential int?] [42 105])
389390
;; => true
390391

391-
(m/validate [:vector int?] (list 1 2 3))
392+
(m/validate [:sequential int?] #{42 105})
392393
;; => false
393394
```
394395

395-
A `:tuple` describes a fixed length Clojure vector of heterogeneous elements:
396-
397-
```clojure
398-
(m/validate [:tuple keyword? string? number?] [:bing "bang" 42])
399-
;; => true
400-
```
401-
402396
Malli also supports sequence regexes like [Seqexp](https://github.com/cgrand/seqexp) and Spec.
403397
The supported operators are `:cat` & `:catn` for concatenation / sequencing
404398

@@ -500,6 +494,54 @@ it is always better to use less general tools whenever possible:
500494
(cc/quick-bench (valid? (range 10)))) ; Execution time mean : 0.12µs
501495
```
502496

497+
## Vector schemas
498+
499+
You can use `:vector` to describe homogeneous Clojure vectors.
500+
501+
```clojure
502+
(m/validate [:vector int?] [1 2 3])
503+
;; => true
504+
505+
(m/validate [:vector int?] (list 1 2 3))
506+
;; => false
507+
```
508+
509+
A `:tuple` schema describes a fixed length Clojure vector of heterogeneous elements:
510+
511+
```clojure
512+
(m/validate [:tuple keyword? string? number?] [:bing "bang" 42])
513+
;; => true
514+
```
515+
516+
To create a vector schema based on a seqex, use `:and`.
517+
518+
```clojure
519+
;; non-empty vector starting with a keyword
520+
(m/validate [:and [:cat :keyword [:* :any]]
521+
vector?]
522+
[:a 1])
523+
; => true
524+
525+
(m/validate [:and [:cat :keyword [:* :any]]
526+
vector?]
527+
(:a 1))
528+
; => false
529+
```
530+
531+
Note: To generate values from a vector seqex, see [:and generation](#and-generation).
532+
533+
## Set schemas
534+
535+
You can use `:set` to describe homogeneous Clojure sets.
536+
537+
```clojure
538+
(m/validate [:set int?] #{42 105})
539+
;; => true
540+
541+
(m/validate [:set int?] #{:a :b})
542+
;; => false
543+
```
544+
503545
## String schemas
504546

505547
Using a predicate:
@@ -1873,6 +1915,42 @@ Integration with test.check:
18731915
; => (2 1 2 2 2 2 8 1 55 83)
18741916
```
18751917

1918+
### :and generation
1919+
1920+
Generators for `:and` schemas work by generating values from the first child, and then filtering
1921+
out any values that do not pass the overall `:and` schema.
1922+
1923+
For the most reliable results, place the schema that is most likely to generate valid
1924+
values for the entire schema as the first child of an `:and` schema.
1925+
1926+
```clojure
1927+
;; BAD: :string is unlikely to generate values satisfying the schema
1928+
(mg/generate [:and :string [:enum "a" "b" "c"]] {:seed 42})
1929+
; Execution error
1930+
; Couldn't satisfy such-that predicate after 100 tries.
1931+
1932+
;; GOOD: every value generated by the `:enum` is a string
1933+
(mg/generate [:and [:enum "a" "b" "c"] :string] {:seed 42})
1934+
; => "a"
1935+
```
1936+
1937+
You might need to customize the generator for the first `:and` child to improve
1938+
the chances of it generating valid values.
1939+
1940+
For example, a schema for non-empty heterogeneous vectors can validate values
1941+
by combining `:cat` and `vector?`, but since `:cat` generates sequences
1942+
we need to use `:gen/fmap` to make it generate vectors:
1943+
1944+
```clojure
1945+
;; generate a non-empty vector starting with a keyword
1946+
(mg/generate [:and [:cat {:gen/fmap vec}
1947+
:keyword [:* :any]]
1948+
vector?]
1949+
{:size 1
1950+
:seed 2})
1951+
;=> [:.+ [1]]
1952+
```
1953+
18761954
## Inferring schemas
18771955

18781956
Inspired by [F# Type providers](https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/type-providers/):

test/malli/generator_test.cljc

+10
Original file line numberDiff line numberDiff line change
@@ -909,3 +909,13 @@
909909
(is (alphanumeric-string?
910910
(mg/generate [:string {}]
911911
{:seed seed})))))))
912+
913+
(deftest non-empty-vector-generator-test
914+
(is (= [:.+ [1]]
915+
(mg/generate [:and [:cat {:gen/fmap vec} :keyword [:* :any]] vector?]
916+
{:size 1
917+
:seed 2})))
918+
(doseq [v (mg/sample [:and [:cat {:gen/fmap vec} :keyword [:* :any]] vector?]
919+
{:seed 2})]
920+
(is (vector? v))
921+
(is (seq v))))

0 commit comments

Comments
 (0)