spark.spec-tacular

A database-agnostic DSL for data specifications

check-component!

(check-component! spec k v)

Checks the field name k and its value v against the given spec, errors if v is not of the correct type. Returns true otherwise.

coerce

(coerce spec kw val)

Coerces val into something that could be put in the kw field of spec. Returns nil only if the value is coerced to nil; i.e. if val does not have a coercion the val is returned unharmed.

database-coercion

multimethod

Returns the coercion function that maps an object of a known database type (currently datomic.query.EntityMap) to a map that can be used in a spec constructor. Defaults to nil if the object does not come from a known database type.

defenum

macro

added in 0.6.0

(defenum & stx)

Defines an enumeration of values under a parent name.

(defenum Name docstring? symbol ...)

The resulting enumeration recognizes :Name/<symbol> keywords, which answer true to name?. Enumerations do not define constructors as keywords are already Clojure values.

defspec

macro

(defspec & stx)

Defines a spec-tacular spec type.

(defspec Name
  docstring?
  [field-name arity type option ...]
  ...)

creates the spec :Name; where arity is either :is-a or :is-many and type is either another spec name or a primitive type keyword. The docstring is optional; if given, it will become the :doc metadata on the spec var.

spec-tacular supports base types :keyword, :string, :boolean, :long, :bigint, :float, :double, :bigdec, :instant, :calendarday, :uuid, :uri, and :bytes.

Fields are allowed to have the following options:

  • :unique and :identity, meaning only one entity can have a given value for this attribute in the database
  • :link, meaning the instance is always passed by-reference
  • :component, mutually exclusive with :link, means the instance only exists when tied to it’s parent
  • :required, a required field

A core.typed alias Name is created, as well as a constructor name and a predicate name?.

Spec instances that are created from a database may also contain the :db-ref field which, in the case of Datomic, contains a map {:eid database-id} containing the entity’s :db/id.

defunion

macro

(defunion & stx)

Defines a spec-tacular union.

(defunion Name docstring? :SpecName ...)

where each SpecName is another spec to be added to the union.

diff

(diff sp1 sp2)

Takes two spec instances and returns a vector of three maps created by calling clojure.data/diff on each item of the spec.

Only well defined when sp1 and sp2 share the same spec.

For :is-many fields, expect to see sets of similarities or differences in the result, as order should not matter.

get-spec

multimethod

Acts like the identity function if sent an actual spec object.

Otherwise, if there is a spec (defined with defspec, defunion, or defenum) such that:

  • the spec has the :name denoted by the argument, when given a keyword;
  • the spec instance is an instance of that spec, when given an actual spec instance;

then returns that spec, otherwise returns nil.

get-type

multimethod

Returns a SpecType record containing the name of the type, the class type, a symbol type-symbol that would eval to the type (useful for macros), and possibly a coercion function.

has-spec?

(has-spec? o)

Returns whether or not the given object has a spec.

identical-keys

(identical-keys si)

Takes any number of spec instances and returns the keys for fields with values they all share in common. Useful for error messages.

Item

The type of a field in a spec

namespace->specs

(namespace->specs namespace)

Returns a sequence containing every spec in the given namespace.

primitive?

(primitive? spec-name)

Returns true if the given spec name is primitive (i.e. is part of the base type environment and not defined as a spec), false otherwise. See defspec for a list of primitive type keywords.

Note that all specs defined by defenum are considered primitive, since their values are all keywords.

recursiveness

(recursiveness {[_ t] :type})

Returns :non-rec if the given item is primitive?, :rec otherwise.

refless

added in 0.5.0

(refless si)

Returns a version of the given spec instance with no :db-refs on any sub-instance.

refless=

added in 0.5.0

(refless= x y)

Given any walkable collection, returns true if the two collections would be = if no spec instances had :db-refs.

Contains a fast path if both x and y have specs, otherwise expect bad asymptotics as each collection must be rebuilt without :db-refs.

spec-meta

added in 0.6.0

(spec-meta ns spec spec-meta)

Parses the meta-data that can be placed on a namespace containing spec definitions, or on spec definitions themselves. Not intended to be called directly.

Currently, it is possible to override the constructor, predicate, and type aliases name for a single spec or all specs in a namespace.

(ns my-ns
 {....
  :spec-tacular
  {:ctor-name-fn  (fn [s] ....)
   :huh-name-fn   (fn [s] ....)
   :alias-name-fn (fn [s] ....)}
  ....}
  (:require ....))

Each function should expect a string and return a string. The function is currently evaluated in the spark.spec-tacular namespace, so plan accordingly.

These names can be overriden on a per-spec basis in a similar fashion.

(defspec
  ^{:ctor-name  "mk-my-spec"
    :huh-name   false ;; fall back to default
    :alias-name "my-spec-type"}
  my-spec
  ....)

The spec-level meta-data has higher priority than the namespace-level meta-data.

SpecInstance

The broadest type for a spec instance, but it is preferable to use an alias defined via defspec.

SpecName

The type of the :name field of a spec

SpecT

The type of a spec