-
Notifications
You must be signed in to change notification settings - Fork 2
Complex Specs
While predicates form the foundation of Speculation, higher order specs allow describing complex data structures easily.
Homogeneous collections can be spec'd with S.coll_of:
S.valid? S.coll_of(String), [1, 2, 3] # => false
S.valid? S.coll_of(Integer), [1, 2, 3] # => trueAdditional constraints can also be provided:
spec = S.coll_of(Integer, :min_count => 2, :max_count => 4, :kind => Array, :distinct => true)
S.valid? spec, [1, 2, 3] # => true
S.valid? spec, [1, 2, 3, 4, 5] # => false
S.valid? spec, [1, 2, 3, 3] # => falseS.tuple is for collections with (potentially heterogeneous) positional 'fields'.
S.valid? S.tuple(Integer, Integer), [1, 2] # => true
S.valid? S.tuple(String, Integer, Float), ["foo", 1, 1.2] # => trueS.hash_of describes Hashes with homogeneous key and value predicates:
S.valid?(S.hash_of(String, String), { "foo" => "bar" }) # => true
S.valid?(S.hash_of(String, String), { "foo" => 1 }) # => falseWhen working with heterogeneous hashes where keys have special meaning, S.keys steps in.
S.keys can be given required and optional spec names. When conforming a Hash, presence of required
keys is checked first:
spec = S.keys(:req => [:"foo/bar"])
S.valid?(spec, { :"foo/bar" => "baz" }) # => true
S.valid?(spec, {}) # => falseSecond, each key in the hash is conformed against a registered spec of the same name:
S.def :"foo/bar", Integer
spec = S.keys(:req => [:"foo/bar"])
S.valid?(spec, { :"foo/bar" => "baz" }) # => false
S.explain(spec, { :"foo/bar" => "baz" })
# => In: [:"foo/bar"] val: "baz" fails spec: :"foo/bar" at: [:"foo/bar"] predicate: [Integer, ["baz"]]S.keys will conform a hash's key if a matching one is found in the spec registry even if it isn't
a required key for that spec:
S.def :"foo/bar", Integer
spec = S.keys
S.valid?(spec, { :"foo/bar" => "baz" }) # => false
S.explain(spec, { :"foo/bar" => "baz" })
# => In: [:"foo/bar"] val: "baz" fails spec: :"foo/bar" at: [:"foo/bar"] predicate: [Integer, ["baz"]]This may seem odd at first. However, the reasoning behind this is to separate the specification of required keysets and values. See clojure.spec's rationale for more detail on this feature.
S.keys can conform both namespaced keys and non-namespaced keys in hashes.
To require a non-namespaced symbol, use the :req_un option.
spec = S.keys(:req_un => [:"foo/bar"])
S.valid?(spec, { :bar => "baz" })See the API documentation for additional arguments.
S.every is just like S.coll_of, except that it doesn't validate every single element of
a collection. The number of elements that will be validated can be configured with
S.coll_check_limit, with the default being 101. This upper bound on the
number of items to be validated makes S.every suitable for validating very large collections.
spec = S.every(Integer)
value = [1, 2, 3]
S.valid? spec, value # => true
value = 1.upto(S.coll_check_limit).to_a
value[S.coll_check_limit] = "not-a-number"
S.valid? spec, value # => true
value[S.coll_check_limit - 1] = "not-a-number"
S.valid? spec, value # => falseJust like S.every, except that S.every_kv will take separate key and value
predicates, therefore making it useful for Hashes. Since S.every_kv will only validate up to a
fixed number of items, it is suitable for validating large Hashes.
spec = S.every_kv(String, Integer)
value = { "foo" => 1 }
S.valid? spec, value # => true