View Source CozyKV (cozy_kv v0.2.0)

Validates data containing key-value pairs.

Features

  • Supports any type of key, not limited to atoms.
  • Provides errors with detailed metadata, which is useful to build custom error messages.

Terminologies

  • spec - a list which describes the structure of key-value pairs.
  • data - the key-value pairs to be validated using a spec.

Usage

# 1. create a raw spec
spec = [
  name: [type: :string, required: true]
]

# 2. validate the spec
spec = CozyKV.validate_spec!(spec)

# 3. validate a piece of data using above spec

# 3.1 validate key-value pairs in list form.
kv_list = [name: "zeke"]
{:ok, [name: "zeke"]} = CozyKV.validate(spec, kv_list)

# 3.2 validate key-value pairs in map form.
kv_map = %{name: "zeke"}
{:ok, %{name: "zeke"}} = CozyKV.validate(spec, kv_map)

# 3.3 when validation fails, an error is returned
kv_bad = []
{:error, %CozyKV.ValidationError{path: _, type: _}} = CozyKV.validate(spec, kv_bad)

Specs

A valid spec is a list of two-element tuples.

In each tuple, the first element is the key, the second element is the spec options of the value correspoding to the key.

atom key

[
  name: [
    type: :string,
    required: true
  ],
  # ...
]

string key

[
  {"name", [
    type: :string,
    required: true
  ]},
  # ...
]

arbitrary key

# allows to use arbitrary key in any type.
[
  {%{}, [
    type: :string,
    required: true
  ]},
  # ...
]

Spec options

These options are supported:

  • :type
  • :required
  • :default
  • :deprecated
  • :doc

Types and Values

The left side of - is the type name, and the right side is the description of the value corresponding to the type.

  • :any - Any value.

  • nil - nil itself.

  • :atom - An atom.

  • :boolean - A boolean.

  • :string - A string.

  • :integer - An integer.

  • :pos_integer - A positive integer.

  • :non_neg_integer - A non-negative integer.

  • :float - A float.

  • :tuple - A tuple.

  • {:tuple, subtypes} - A tuple as described by subtypes. The length of the expected tuple must match the length of subtypes. The type of each element in tuple must match the type at the same position of subtypes. For example, a valid value of {:tuple, [:atom, :string, {:list, :integer}]} can be {:name, "zeke", [3, 2, 2]}.

  • :list - A list.

  • {:list, subtype} - A list with elements of type subtype.

  • :keyword_list - A keyword list.

  • {:keyword_list, spec} - A keyword list with key-value pairs structured by spec.

  • :non_empty_keyword_list - A non-empty keyword list.

  • {:non_empty_keyword_list, spec} - A non-empty keyword list with key-value pairs structured by spec.

  • :map - A map with atom keys. It is a shortcut of {:map, :atom, :any}.

  • {:map, key_type, value_type} - A map with key_type keys and value_type values.

  • {:map, spec} - A map with key-value pairs structured by spec.

  • {:struct, struct_name} - A struct.

  • :timeout - A non-negative integer or the atom :infinity.

  • :pid - A PID.

  • :reference - A reference (see reference/0).

  • :mfa - A tuple in the format {mod, fun, args}.

  • {:fun, arity} - A function with a specific arity.

  • :mod_args - A tuple in the format {mod, args}. It is usually used for process initialization using start_link and similar.

  • {:value_in, values} - A value which is one of the values in values. values can be any enumerable value, such as a list or a %Range{}.

  • {:type_in, subtypes} - A value which matches one of the types in subtypes. subtypes is a list of types.

  • {:custom, mod, fun} - A custom type. The related value will be validated by apply(mod, fun, [type, value, metadata]). type is the {:custom, mod, fun} itself. mod.fun(type, value, metadata) must return {:ok, value} or {:error, exception}.

Data

A valid piece of data is a list of two-element tuples or a map.

list (atom key)

[
  name: "zeke",
  # ...
]

list (string key)

[
  {"name", "zeke"},
  # ...
]

list (arbitrary key)

# In general, you don't do this.
# I just want to demonstrate what this package can do.
[
  {%{}, "zeke"},
  # ...
]

map (atom key)

%{
  name: "zeke",
  # ...
}

map (string key)

%{
  "name" => "zeke",
  # ...
}

map (arbitrary key)

# In general, you don't do this.
# I just want to demonstrate what this package can do.
%{
  %{} => "zeke",
  # ...
}

Use cases

  • validate options (like what nimble_options does).
  • validate maps parsed from a JSON or YAML files.

Limitations

Todo

  • doc generation

Summary

Functions

Validates a key-value data.

Validates a spec.

Types

@type data() :: [{term(), term()}] | map()
@type spec() :: [{term(), term()}]

Functions

@spec validate(spec(), data()) :: {:ok, data()} | {:error, CozyKV.ValidationError.t()}

Validates a key-value data.

@spec validate!(spec(), data()) :: data()

Bang version of validate/2.

@spec validate_spec!(spec()) :: spec()

Validates a spec.