std.contract

apply

Applies a contract to a label and a value.

Type: Contract -> Label -> Dyn -> Dyn (for technical reasons, this function isn't actually statically typed)

Nickel supports user-defined contracts defined as functions, but also as records. Moreover, the interpreter performs additional bookkeeping for error reporting when applying a contract in an expression value | Contract. You should not use standard function application to apply a contract, but this function instead.

Examples

let Nullable = fun param_contract label value =>
  if value == null then null
  else std.contract.apply param_contract label value
in
let Contract = Nullable {foo | Number} in
({foo = 1} | Contract)

Diagnostics stack

Using apply will stack the current custom error reporting data, and create a fresh working diagnostic. apply thus acts as a split point between a contract and its subcontracts, providing the subcontracts with a fresh diagnostic to use, while remembering the previous diagnostics set by parent contracts.

Illustration
let ChildContract = fun label value =>
  label
  |> std.contract.label.with_message "child's message"
  |> std.contract.label.append_note "child's note"
  |> std.contract.blame
in

let ParentContract = fun label value =>
  let label =
    label
    |> std.contract.label.with_message "parent's message"
    |> std.contract.label.append_note "parent's note"
  in
  std.contract.apply ChildContract label value
in

null | ParentContract

This example will print two diagnostics: the main one, using the message and note of the child contract, and a secondary diagnostic, using the parent contract message and note.


blame

Raises blame for a given label.

Type: forall a. Label -> a (for technical reasons, this function isn't actually statically typed)

Blame is the mechanism to signal contract violation in Nickel. It ends the program execution and prints a detailed report thanks to the information tracked inside the label.

Examples

IsZero = fun label value =>
  if value == 0 then
    value
  else
    std.contract.blame label

blame_with_message

Raises blame with respect to a given label with a custom error message.

Type: forall a. String -> Label -> a (for technical reasons, this function isn't actually statically typed)

Same as blame, but takes an additional error message that will be displayed as part of the blame error. blame_with_message message label is equivalent to blame (label.with_message message label)

Examples

let IsZero = fun label value =>
  if value == 0 then
    value
  else
    std.contract.blame_with_message "Not zero" label
in

0 | IsZero

Equal

Equal is a contract enforcing equality to a given constant.

Equal is lazy over arrays and records. When checking such values, Equal doesn't test for equality of elements right away (it just tests that the size is the same for arrays and that fields are the same for records), but returns a new value where equality subcontracts have been pushed inside each element.

Example

1 + 4 | std.contract.Equal 5
 => 5
4 | std.contract.Equal 5
 => error: contract broken by a value

from_predicate

Generates a contract from a boolean predicate.

Type: (Dyn -> Bool) -> (Label -> Dyn -> Dyn) (for technical reasons, this function isn't actually statically typed)

Examples

let IsZero = std.contract.from_predicate (fun x => x == 0) in
0 | IsZero

label

The label submodule provides functions that manipulate the label associated with a contract application.

A label is a special opaque value automatically passed by the Nickel interpreter to contracts when performing a contract check.

A label stores a stack of custom error diagnostics, that can be manipulated by the functions in this module. Labels thus offer a way to customize the error message that will be shown if a contract is broken.

The setters (with_XXX functions) always operate on the current error diagnostic, which is the last diagnotic on the stack. If the stack is empty, a fresh diagnostic is silently created when using a setter.

The previous diagnostics on the stack are considered archived, and can't be modified anymore. All diagnostics will be shown during error reporting, with the most recent being used as the main one.

std.contract.apply is the operation that pushes a new fresh diagnostic on the stack, saving the previously current error diagnostic. The function std.contract.apply is typically used to apply subcontracts inside a parent contract. Stacking the current diagnostic potentially customized by the parent contract saves the information inside, and provides a fresh diagnostic for the child contract to use.


label.append_note

Appends a note to the error notes of the current diagnostic of a label.

Type: String -> Label -> Label (for technical reasons, this function isn't actually statically typed)

Examples

let AlwaysFailWithNotes = fun label _value =>
  label
  |> std.contract.label.append_note "This is note 1"
  |> std.contract.label.append_note "This is note 2"
  |> std.contract.blame
in
null | AlwaysFailWithNotes

label.with_message

Attaches a custom error message to the current error diagnostic of a label.

Type: String -> Label -> Label (for technical reasons, this function isn't actually statically typed)

If a custom error message was previously set, there are two possibilities:

  • the label has gone through a std.contract.apply call in-between. In this case, the previous diagnostic has been stacked, and using with_message won't erase anything but rather modify the fresh current diagnostic.
  • no std.contract.apply has taken place since the last message was set. In this case, the current diagnostic's error message is replaced.

Examples

let ContractNum = std.contract.from_predicate (fun x => x > 0 && x < 50) in

let Contract = fun label value =>
  if std.is_number value then
    std.contract.apply
      ContractNum
      (std.contract.label.with_message
        "num subcontract failed! (out of bound)"
        label
      )
      value
  else
    value
in

5 | Contract

label.with_notes

Attaches custom error notes to the current error diagnostic of a label.

Type: Array String -> Label -> Label (for technical reasons, this function isn't actually statically typed)

If custom error notes were previously set, there are two possibilities:

  • the label has gone through a std.contract.apply call in-between. In this case, the previous diagnostic has been stacked, and using with_notes won't erase anything but rather modify the fresh current diagnostic.
  • no std.contract.apply has taken place since the last message was set. In this case, the current diagnostic's error notes will be replaced.

Examples

let ContractNum = std.contract.from_predicate (fun x => x > 0 && x < 50) in

let Contract = fun label value =>
  if std.is_number value then
    std.contract.apply
      ContractNum
      (label
       |> std.contract.label.with_message "num subcontract failed! (out of bound)"
       |> std.contract.label.with_notes [
            "The value was a number, but this number is outside the expected bounds",
            "The value must be a number between 0 and 50, both excluded",
          ]
      )
      value
  else
    value
in

5 | Contract

Sequence

Apply multiple contracts from left to right.

Type: Array Contract -> Contract (for technical reasons, this function isn't actually statically typed)

Examples

x | std.contract.Sequence [ Foo, Bar ]

is equivalent to

(x | Foo) | Bar

This is useful in positions where one cannot apply multiple contracts, such as an argument to another contract:

x | Array (std.contract.Sequence [ Foo, Bar ])

Or stored in a variable:

let C = std.contract.Sequence [ Foo, Bar ] in
x | C

unstable

The unstable module gathers contracts that are used right now in the standard library to check additional pre-condition, but aren't yet stabilized.

Unstable contracts are useful as a temporary solution for argument validation, but they shouldn't be used outside of the standard library. They aren't part of the backward compatibility policy, and are subject to change at any point in time.


unstable.ArraySliceFun

Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.

A function contract which checks that the three first arguments are, in order, two numbers and an array such that the two numbers are a valid slice of that array, that is they are natural numbers such that: 0 <= n1 <= n2 <= length array

This contract blames only if the arguments are of the right type but don't satisfy the tested condition. The type of arguments is assumed to be checked by an associated static type annotation. For example, if the first argument is a string, this contract silently passes.


unstable.DependentFun

Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.

A dependent function contract, where the argument is also passed to the contract of the returned value.

That is, some_function | DependentFun Foo Bar performs the same checks as

fun arg =>
  let arg_checked = (arg | Foo) in
  (some_function arg_checked | (Bar arg_checked))

unstable.HasField

Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.

A function contract for 2-ary functions which checks that second argument is a record that contains the first argument as a field. This contract is parametrized by the return type of the function.

This contract blames only if the arguments are of the right type but don't satisfy the tested condition (the first is a string and the second is a record). The type of arguments is assumed to be checked by an associated static type annotation. For example, if the first argument is a number, this contract silently passes.

Example
let f | HasField Dyn = fun field record => record."%{field}" in
(f "foo" { foo = 1, bar = 2 })
+ (f "baz" { foo = 1, bar = 2 })

In this example, the first call to f won't blame, but the second will, as baz isn't a field of the record argument.


unstable.IndexedArrayFun | [| 'Index, 'Split |] -> Dyn -> Dyn

Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.

A function contract which checks that the two first argument are, in order, a number and an array such that the number is a valid index for that array, that is a natural number such that 0 <= index <= length array.

This contract blames only if the arguments are of the right type but don't satisfy the tested condition. The type of arguments is assumed to be checked by an associated static type annotation. For example, if the first argument is a string, this contract silently passes.


unstable.PosNumber

Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.

A contract for a positive number.

This contract blames only if the value is a number but is not positive: the fact that the value is a number is assumed to be checked by an associated static type annotation.


unstable.RangeFun

Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.

A function contract which checks that the two first argument are a valid range, that is two numbers start and end such that start <= end.

This contract blames only if the arguments are of the right type but don't satisfy the tested condition. The type of arguments is assumed to be checked by an associated static type annotation. For example, if the first argument is a string, this contract silently passes.


unstable.RangeStep

Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.

A contract for a range step, which must be a positive number. Mostly a wrapper around std.contract.unstable.PosNumber with a custom error message.

This contract blames only if the value is a number but is not positive: the fact that the value is a number is assumed to be checked by an associated static type annotation.