Module:Assert

From IdleOn MMO Wiki

The Assert module contains several convenience functions for common assertion use cases.

Using the module

To use the module, it must first be imported at the top of your module.

local Assert = require('Module:Assert')

Once the module is imported, simply call any of its functions to use it.

Custom Message Assertions

Every assertion has an additional *Msg variant which allows the caller to provide its own error message and optionally string format arguments. These functions drop the arguments that are used for formatting the default message and instead take a single message argument. If additional arguments are passed in, the message argument will be treated as a format string and passed to the string.format along with the additional arguments.

The "isType" assertion

The isType assertion checks that a value is of an expected type. If the value is not one of the expected types, then the assertion fails and an error is raised.

The value argument is returned to the caller upon successful completion.

Parameters

Parameter Type Description
value any The value to test the assertion against.
expected string or string[] The expected type or list of types which value must be one of.
name string? The name of the parameter to use in the error message, or nil for a generic error message.

Example Usage

-- Succeeds: "Alice" is a string
Assert.isType('Alice', 'string', 'name')

-- Succeeds: 25 is a number or a string
Assert.isType(25, { 'string', 'number' }, 'age')

-- Fails: true is a boolean, but is expected to be a string
Assert.isType(true, 'string', 'height')

-- Fails with error message: 'This is a custom error, the value is actually a string'
Assert.isTypeMsg('abc', 'number', 'This is a custom error, the value was actually a %s', type('abc'))

The "exists" assertion

The isType assertion checks that a value is not nil. If the value is nil, then the assertion fails and an error is raised. This assertion also returns the value argument upon successful completion.

The value argument is returned to the caller upon successful completion.

Parameters

Parameter Type Description
value any The value to test the existence of.
name string? The name of the parameter to use in the error message, or nil for a generic error message.

Example Usage

--- Maps a quantity suffix to its exponent.
local UNIT_EXPONENTS = {
  'k' = 3,
  'm' = 6,
  'b' = 9,
  't' = 12
}

-- Succeeds: The value exists and is assigned to exponent.
local exponent = Assert.exists(UNIT_EXPONENTS['k'], 'exponent')

-- Fails: The value does not exist.
local exponent = Assert.exists(UNIT_EXPONENTS['p'], 'exponent')

-- Fails with custom error message: "Uh oh, something which should exist doesn't!"
local exponent = Assert.existsMsg(UNIT_EXPONENTS['p'], "Uh oh, something which should exist doesn't!")

The "isTrue/isFalse" assertions

The isTrue assertion checks that an expression evaluates to true. If the expression evaluates to false, then the assertion fails and an error is raised. The isFalse assertion functions the same way, but negates the logic.

These functions do not return the condition to the caller.

Parameters

Parameter Type Description
value any The expression to test the truthiness of.

Example Usage

-- Succeeds: 'Bob' has a length greater than 0.
Assert.isTrue(string.len('Bob') > 0)

-- Fails with message: '3 is not an even number.'
local number = 3
Assert.isTrueMsg(number % 2 == 0, '%d is not an even number.', number)

The "equal" and "notEqual" assertions

The equal assertion checks that two values are equal. If the values are unequal, then the assertion fails and an error is raised. The notEqual assertion functions the same way, but negates the logic.

These functions return both a and b to the caller.

Parameters

Parameter Type Description
a any The first value being compared.
b any The second value being compared.

Example Usage

-- Succeeds: a = 'hi', b = 'hi'
local a, b = Assert.equal('hi', 'hi')

-- Fails: 'Hello' and 'World' are not equal
Assert.equal('Hello', 'World')

-- Fails with custom error: 'These things just aren't the same'
local first, second = Assert.equalMsg(true, false, "These things just aren't the same")

require('strict')

local Assert = {}

--- Formats a message with optional arguments.
--- @param message string The message to format.
--- @param ... any The format arguments.
--- @return string
local function formatMessage(message, ...)
    local args = { ... }
    if #args > 0 then
        return message:format(...)
    else
        return message
    end
end

--- Asserts that `value`'s type matches one of the types in `expected`.
--- @generic T
--- @param value T The value to run the assertion against.
--- @param expected type|type[] The list of expected types.
--- @param name string? The name of the parameter.
--- @return T value
function Assert.isType(value, expected, name)
    local types
    if type(expected) == 'string' then
        types = expected
    elseif type(expected) == 'table' then
    	types = ''
        for i, v in ipairs(expected) do
            if i ~= 0 then
                types = types .. ', '
            end
            types = types .. v
        end
    end

    return Assert.isTypeMsg(value, expected,
            '"%s" has type "%s" but is expected to be one of the following types: %s',
            name or value,
            types,
            type(value))
end

--- Asserts that `value`'s type matches one of the types in `expected` with a custom error message.
--- @generic T
--- @param value T The value to run the assertion against.
--- @param expected type|type[] The list of expected types.
--- @param message string The custom error message or format string.
--- @param ... any The error message format arguments.
--- @return T value
function Assert.isTypeMsg(value, expected, message, ...)
    message = formatMessage(message, ...)

    if type(expected) == 'string' then
        assert(type(value) == expected, message)
    elseif type(expected) == 'table' then
        for _, v in ipairs(expected) do
            if type(value) == v then
                return value
            end
        end
        -- Value type matches none of the expected types.
        error(message)
    else
        -- Guaranteed to generate an error. We're only using this to make use of the prebuilt error string.
        Assert.isType(expected, { 'string', 'table' }, 'expected')
    end

    return value
end

--- Asserts that `value` is not nil.
--- @generic T
--- @param value T The value to run the assertion against.
--- @param name T the name of the parameter.
--- @return T value
function Assert.exists(value, name)
    if name then
        return Assert.existsMsg(value, 'Expected "%s" to not be nil.', name)
    else
        return Assert.existsMsg(value, 'Expected value to not be nil.')
    end
end

--- Asserts that `value` is not nil with a custom error message.
--- @generic T
--- @param value T The value to run the assertion against.
--- @param message string The custom error message or format string.
--- @param ... any The error message format arguments.
--- @return T value
function Assert.existsMsg(value, message, ...)
    message = formatMessage(message, ...)

    assert(value ~= nil, message)
    return value
end

--- Asserts that `value` is true.
---@param value boolean The value to run the assertion on.
---@param name string The name of the parameter.
function Assert.isTrue(value, name)
    if name then
        Assert.isTrueMsg(value, 'Expected "%s" to be true.', name)
    else
        Assert.isTrueMsg(value, 'Expected value to be true.')
    end
end

--- Asserts that `value` is true with a custom error message.
--- @param value boolean The value to run the assertion on.
--- @param message string The custom error message or format string.
--- @param ... any
function Assert.isTrueMsg(value, message, ...)
    message = formatMessage(message, ...)
    assert(value, message)
end

--- Asserts that `value` is false.
--- @param value boolean The value to run the assertion on.
--- @param name string? The name of the value
function Assert.isFalse(value, name)
    if name then
        Assert.isFalseMsg(value, 'Expected "%s" to be false.', name)
    else
        Assert.isFalseMsg(value, 'Expected value to be false.')
    end
end

--- Asserts that `value` is false with a custom error message.
--- @param value boolean The value to run the assertion on.
--- @param message string The custom error message or format string.
--- @param ... any
function Assert.isFalseMsg(value, message, ...)
    message = formatMessage(message, ...)
    assert(not value, message)
end

--- Asserts that `a` and `b` are equal.
--- @generic U
--- @generic V
--- @param a U The first value to run the assertion against.
--- @param b V The second value to run the assertion against.
--- @return U a
--- @return V b
function Assert.equal(a, b)
    return Assert.equalsMsg(a, b,
        'Expected values to be equal "%s" (%s) ~= "%s" (%s)',
        a, type(a),
        b, type(b))
end

--- Asserts that `a` and `b` are equal with a custom error message.
--- @generic U
--- @generic V
--- @param a U The first value to run the assertion against.
--- @param b V The second value to run the assertion against.
--- @param message string The custom error message or format string.
--- @param ... any The error message format arguments.
--- @return U a
--- @return V b
function Assert.equalMsg(a, b, message, ...)
    message = formatMessage(message, ...)
    assert(a == b, message)
    return a, b
end

--- Asserts that `a` and `b` are not equal.
--- @generic U
--- @generic V
--- @param a U The first value to run the assertion against.
--- @param b V The second value to run the assertion against.
--- @return U a
--- @return V b
function Assert.notEqual(a, b)
    return Assert.notEqualMsg(a, b,
        'Expected values to not be equal "%s" (%s) ~= "%s" (%s)',
        a, type(a),
        b, type(b))
end

--- Asserts that `a` and `b` are not equal with a custom error message.
--- @generic U
--- @generic V
--- @param a U The first value to run the assertion against.
--- @param b V The second value to run the assertion against.
--- @param message string The custom error message for format string.
--- @param ... any The error message format arguments.
--- @return U a
--- @return V b
function Assert.notEqualMsg(a, b, message, ...)
    assert(a ~= b, message)
    return a, b
end

return Assert