public function check(array $input)
    {
        $missing = [];
        $invalid = [];

        $check = function ($constraint, $key, $value, $input) use (
            &$missing,
            &$invalid
        ) {
            if ($constraint instanceof AbstractConstraint) {
                if (!$constraint->check($value, $input)) {
                    $invalid[$key][] = $constraint;
                }
            } elseif ($constraint instanceof CheckableInterface) {
                $result = $constraint->check($value);

                $missing = Std::concat(
                    $missing,
                    array_map(
                        function ($subKey) use ($key) {
                            return vsprintf('%s.%s', [$key, $subKey]);
                        },
                        $result->getMissing()
                    )
                );

                foreach ($result->getFailed() as $failedField => $constraints) {
                    $fullPath = vsprintf('%s.%s', [$key, $failedField]);

                    if (array_key_exists($fullPath, $invalid)) {
                        $invalid[$fullPath] = array_merge(
                            $invalid[$fullPath],
                            $constraints
                        );
                    } else {
                        $invalid[$fullPath] = $constraints;
                    }
                }
            } else {
                throw new CoreException(
                    vsprintf(
                        'Unexpected constraint type: %s.',
                        [
                            TypeHound::fetch($constraint),
                        ]
                    )
                );
            }
        };

        $inputMap = ArrayMap::of($input);

        $this->annotations->each(
            function ($value, $key) use (
                $check,
                $input,
                $inputMap,
                &$missing
            ) {
                // If a field is required but not present, we should report it.
                if (Maybe::fromMaybe(
                        false,
                        $value->lookup(static::ANNOTATION_REQUIRED)
                    )
                    && $inputMap->member($key) === false
                ) {
                    $missing[] = $key;

                    // There's no point on checking constraints on the field
                    // since it is missing.
                    return;
                } elseif ($inputMap->member($key) === false) {
                    // There's no point on checking constraints on the field
                    // since it is missing.
                    return;
                }

                $fieldValue = Maybe::fromJust($inputMap->lookup($key));

                $this
                    ->getInternalFieldConstraints($key)
                    ->each(
                        function ($constraint) use (
                            $check,
                            $key,
                            $fieldValue,
                            $input
                        ) {
                            $check($constraint, $key, $fieldValue, $input);
                        }
                    );
            }
        );

        if (count($missing) === 0 && count($invalid) === 0) {
            return new SpecResult($missing, $invalid, SpecResult::STATUS_PASS);
        }

        return new SpecResult($missing, $invalid, SpecResult::STATUS_FAIL);
    }