std.contract

all_of : Array Dyn -> Dyn

Builds a contract that checks if the value satisfies all of the given contracts. all_of is just an alias for std.contract.Sequence, to maintain consistency with std.contract.any_of and other JSON Schema-style combinators.

As opposed to other boolean combinators such as not or any_of, all_of work as you expect most of the time, including on contracts with a delayed part. The only contracts for which all_of is overstrict are function contracts. In general, you shouldn't use function contracts with contract combinators.


any_of | Array Dyn -> Dyn

Builds a contract that checks if the value satisfies at least one of the given contracts. More precisely, any_of applies each contract in order and returns the result of the first one that doesn't fail immediately (that is, the first one which returns the value 'Ok _).

Important any_of is only an approximation of what you could expect from an or operation on contracts. Because any_of must preserve the lazyness of contracts, it means that it has to pick the right branch by relying only on the immediate part of each contract, and not on the delayed part.

Fortunately, any_of is an overstrict approximation, in that it might reject more values than what you expect, but it'll never let invalid values slip through.

Typically, std.contract.any_of [{ foo | String }, { foo | Number }] will behave exactly as { foo | Number }, because the immediate part of the built-in record contracts can't discriminate between the two possibilities. In particular, perhaps surprisingly, the value {foo = 1+1} is rejected. Please refer to the boolean combinators section of the contract chapter of the manual for a more detailed explanation of the limitations of any_of and other boolean combinators, as well as how to work around them.

Examples

let Date = std.contract.any_of [
  String,
  {
    day | Number,
    month | Number,
    year | Number
  }
]
in

{ day = 1, month = 1, year = 1970 } | Date

apply

Applies a contract to a label and a value. Either aborts the execution with a broken contract error if the contract fails, or proceeds with the evaluation of the value.

A contract annotation such as value | Contract is eventually translated (automatically) to a call to std.contract.apply Contract label value where label is provided by the Nickel interpreter.

Note that depending on the situation, you might need the variant std.contract.check instead. Please refer to the documentation of std.contract.check for more information.

Type

For technical reasons, apply isn't statically typed nor protected by a contract. The hypothetical type of apply is Contract -> Label -> Dyn -> Dyn (note Contract and Label currently don't exist).

Arguments

apply takes three arguments. The arguments are in order:

  • a contract which is currently either a custom contract (produced by the stdlib constructors std.contract.custom, std.contract.from_validator or std.contract.from_predicate), a record, a static type or a naked function (see legacy contracts below).
  • a label (see std.contract.label)
  • the value to be checked

Return value

apply returns the original value upon success, with potential delayed checks inserted inside. Some contracts even return a modified value, such as record contract which can set default values.

Legacy contracts

For backward-compatibility reasons, the argument can also be a naked function Label -> Dyn -> Dyn, but this is discouraged and will be deprecated in the future. For such a contract, please wrap it using std.contract.custom instead (the return type must be adapted).

Examples

This example is a custom re-implementation of the builtin contract [| 'Foo Number |]. This makes for a short illustration of apply, but you should of course just use the builtin contract in real-world applications.

let FooOfNumber =
  std.contract.custom
    (fun label =>
      match {
        'Foo arg => 'Ok ('Foo (std.contract.apply Number label arg)),
        _ => 'Error {},
      }
    )
in
'Foo 3 | FooOfNumber

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 = std.contract.from_validator (fun _ =>
  'Error {
    message = "child's message",
    notes = ["child's note"]
  }
)
in

let ParentContract = std.contract.from_validator (fun _ =>
  'Error {
    message = "parent's message",
    notes = ["parent's note"]
  }
)
in

null | ParentContract
# => error: contract broken by a value

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.

This behavior is also observed when manipulating the label directly and using std.contract.blame instead of returning an error value.


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

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

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

check : Dyn -> Dyn -> Dyn -> [| 'Ok Dyn, 'Error { message | String | optional, notes | Array String | optional, } |]

Variant of std.contract.apply which returns 'Error {..} upon immediate failure of the contract instead of aborting the execution with a blame error, or wraps the return value as 'Ok value otherwise. Delayed errors may still cause uncatchable blame errors.

check is intended to be used when calling another contract from a custom contract, since the return type of check is precisely the same as the return type of a custom contract, and because it preserves immediate errors.

Another use-case is to check if the immediate part of a contract is failing or not without aborting the execution with an uncatchable error.

check has the same behavior as std.contract.apply with respect to the diagnostic stack. See std.contract.apply for more details.

Looking at the action of check on a custom contract might help: if Contract is a custom contract defined as std.contract.custom contract_impl where contract_impl is a function fun label value => ..., then std.contract.check Contract label value is just the normal function application contract_impl label value (this isn't entirely true because of the diagnostic stack and how checked values are tracked for error reporting, but it's mostly true). Of course, check works on other types of contracts, such as static types or record contracts.

Examples

let Nullable = fun Contract =>
  std.contract.custom
    (fun label value =>
      if value == null then
        'Ok value
      else
        std.contract.check Contract label value
    )
in

{ foo = 1 } | Nullable {foo | Number}

custom : (Dyn -> Dyn -> [| 'Ok Dyn, 'Error { message | String | optional, notes | Array String | optional, } |]) -> Dyn

Build a generic custom contract. Whenever possible, more restricted constructors such as std.contract.from_predicate or std.contract.from_validator should be used. However, the most general std.contract.custom might be required to write delayed contracts or contracts taking other contracts as parameters.

Custom contracts

The argument of std.contract.custom is a function that takes a label and the value to be checked, and returns 'Ok value to return the original value with potential delayed checks inside, or 'Error {..} to signal an immediate contract violation.

The label should only be used for delayed checks. The preferred method to indicate a contract violation is to return 'Error {..}.

For more details on custom contracts and delayed contracts, please refer to the corresponding section of the user manual.

Typing

Because Nickel doesn't currently have proper types for labels and contracts, they are replaced with Dyn in the type signature. You can think of the actual type of the delayed part as (Label -> Dyn -> [| ... |]), and the return type of custom as Contract.

Examples

let Nullable = fun Contract =>
  std.contract.custom (fun label value =>
    if value == null then
      'Ok value
    else
      std.contract.check Contract label value
  )
in

null | Nullable Number
# => null

Equal

Equal is a delayed 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 | (Dyn -> Bool) -> Dyn

Build a contract from a boolean predicate.

Contract application

Before Nickel 1.8, from_predicate used to wrap the predicate as a naked function of a label and value. You could thus theoretically apply it as a normal function, as in (std.contract.from_predicate my_pred) label value. While possible, this wasn't recommended nor really officially supported: all contracts should be applied using a contract annotation or std.contract.apply and its variants, as explained in the documentation of std.contract.apply.

From Nickel 1.8 and onwards, from_predicate generates a bespoke contract value that can only be used in an annotation, be passed to std.contract.apply or other operations on contracts, but can't be used as normal function anymore.

Typing

Because Nickel doesn't currently have proper types for contracts, the return type of from_predicate is simply Dyn in the type signature. You should think of from_predicate as some returning a value of some hypothetical type Contract.

Examples

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

from_validator | (Dyn -> [| 'Ok, 'Error { message | String | optional, notes | Array String | optional, } |]) -> Dyn

Build a contract from a validator.

Validator

A validator is a function returning either 'Ok or 'Error {message, notes} with an optional error message and notes to display. Both validators and predicates are immediate contracts: they must decide right away if the value passes or fails (as opposed to delayed contracts). But as opposed to predicates, validators have the additional ability to customize the error reporting.

Typing

Because Nickel doesn't currently have proper types for contracts, the return type of from_validator is simply Dyn in the type signature. You should think of from_validator as returning a value of some hypothetical type Contract.

Examples

let IsZero = std.contract.from_validator (match {
  0 => 'Ok,
  n if std.is_number n =>
    'Error {
      message = "expected 0, got %{std.to_string n}",
      notes = ["The value is a number, but it isn't 0"],
    },
  v =>
    let vtype = v |> std.typeof |> std.to_string in
    'Error { message = "expected a number, got a %{vtype}" }
})
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.

Note that the label should only be used for delayed checks. Immediate error reporting is better done in custom contracts by returning an error value.

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 and std.contract.check are the operations that push a new fresh diagnostic on the stack, saving the previously current error diagnostic. apply and friends are 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

This example is illustrative. In practice, returning 'Error {..} should always be preferred over manipulating the label directly whenever possible.

let AlwaysFailWithNotes = std.contract.custom (fun label _ =>
  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
# => error

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

This example implements a contract which ensures that the value is a record with a field foo that is an even number.

let FooIsEven = std.contract.custom (fun label =>
  match {
    record @ { foo, .. } =>
      'Ok (
        std.record.map (fun key value =>
          if key == "foo"
          && !(std.is_number value && value % 2 == 0) then
            label
            |> std.contract.label.with_message "field foo must be an even number"
            |> std.contract.blame
          else
            value
        )
        record
      ),
    _ => 'Error {},
  }
)
in

{ foo = 4, hello = "world" } | FooIsEven
# => { foo = 4, hello = "world" }

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

This example is illustrative. In practice, returning 'Error {..} should always be preferred over manipulating the label directly, at least whenever possible.

let AlwaysFailWithNotes = std.contract.custom (fun label _ =>
  label
  |> std.contract.label.with_notes [
    "This is note 1",
    "This is note 2",
  ]
  |> std.contract.blame
)
in

null | AlwaysFailWithNotes

# => error

not | Dyn -> Dyn

Builds a contract that checks if the value doesn't satisfy the given contract. More specifically, not Contract will accept value if the application of Contract to value fails immediately, returning 'Error {..}.

Important: as for other boolean combinators, not is only an overstrict approximation of what you could expect. For example, std.contract.not (Array Number) will reject ["a"], because the check that elements are numbers is delayed. See the boolean combinators section of the contract chapter of the manual for a more detailed explanation of the limitations of not and other boolean combinators, as well as how to work around them.

Examples

let NotNumber = std.contract.not Number in
"a" | NotNumber
# => "a"

Sequence | Array Dyn -> Dyn

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 })
# => error: missing field

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

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.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.

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.