<commit_before><?php
/**
 * Created by PhpStorm.
 * User: bianca.vadean
 * Date: 1/19/2016
 * Time: 12:22 PM
 */

namespace Zitec\ApiZitecExtension\Util;

use DateTime;

class TypeChecker
{
    /**
     * @var array
     */
    protected $dataTypes = array(
        'string' => 'checkString',
        'integer' => 'checkInteger',
        'float' => 'checkFloat',
        'array' => 'checkArray',
        'boolean' => 'checkBoolean',
        'date' => 'checkDate',
        'email' => 'checkEmail',
        'url' => 'checkUrl',
        'regex' => 'checkRegex',
        'list' => 'checkValueInList',
        'numeric' => 'checkNumeric',
    );

    /**
     * Parse the expected result and matches the right function to be called.
     * Returns an array of error messages for each key in the expected response.
     *
     * @param array $current
     * @param array $expected
     * @return array|string
     */
    public function checkType (array $current, array $expected)
    {
        $noMatch = array();
        foreach ($expected as $key => &$value) {
            // Manage the flow in case of collections.
            if (($key == "__collection") || substr($key, 0, strpos($key, '(')) == "__collection") {
                $collectionErr = $this->manageCollection($expected, $key, $current);
                if (!empty($collectionErr)) {
                    $noMatch = array_merge($noMatch, $collectionErr);
                }
                continue;
            }

            if (!array_key_exists($key, $current)) {
                $noMatch[$key] = 'Expected key not found in response.';
                continue;
            }

            if (is_array($value)) {
                $message = $this->checkType($current[$key], $value);
                if (!empty($message)) {
                    $noMatch[$key] = $message;
                }
                unset($current[$key]);
                continue;
            }
            // Get the data type arguments, if there are any.
            if ($pos = strpos($value, '(')) {
                $type = substr($value, 0, $pos);
                $arguments = trim(substr($value, $pos + 1, strpos($value, ')') - $pos - 1), ')');
                $arguments = $type == 'regex' ? array($arguments) : explode(',', $arguments);
                $arguments = array_map('trim', $arguments);
            } else {
                $type = $value;
                $arguments = array();
            }
            // Check for null option.
            if ($pos = strpos($value, '|')) {
                $type = strpos($type, '|') ? trim(substr($value, 0, $pos)) : $type;
                if (is_null($current[$key])) {
                    unset($current[$key]);
                    continue;
                }
            }

            array_unshift($arguments, $current[$key]);
            if (array_key_exists($type, $this->dataTypes)) {
                $result = call_user_func_array(array($this, $this->dataTypes[$type]), $arguments);
                if (is_string($result)) {
                    $noMatch[$key] = $result;
                }
            } else {
                $noMatch[$key] = "Undefined data type.";
            }
            unset($current[$key]);
        }
        // If all response elements were unset in foreach, it means that all response elements were expected.
        if (!empty($current)) {
            $noMatch['unexpected data'] = $current;
        }
        if (!empty($noMatch)) {
            return $noMatch;
        }
    }

    /**
     * Parse the collection info in both situations
     * (when the collection is root in dataset and when the collection is a branch in dataset)
     *
     * @param $expected
     * @param $key
     * @param $current
     * @return array|string
     */
    public function manageCollection(&$expected, $key, &$current)
    {
        $value = $expected[$key];
        $noMatch = array();
        $collectionInfo = [];
        /**
         * Handle the situation when the collection is root and it's saved as __collection
         * and the info about collection is stored in __collection_info.
         */
        if ($key == '__collection') {
            if (empty($expected['__collection_info'])) {
                $noMatch[$key] = 'There is no info about the collection.';
                unset($expected[$key]);
                return $noMatch;
            }
            $collectionInfo = $expected['__collection_info'];
            unset($expected['__collection_info']);
        /**
         * Handle collections when they are saved as __collection(key, min, max)
         */
        } elseif (substr($key, 0, strpos($key, '(')) == "__collection") {
            preg_match_all("/\((.*?)\)/u", $key, $collectionArgs);
            $collectionArgs = explode(',', $collectionArgs[1][0]);
            $collectionInfo = [
                'name' => $collectionArgs[0],
                'min' => isset($collectionArgs[1]) ? isset($collectionArgs[1]) : null,
                'max' => isset($collectionArgs[2]) ? isset($collectionArgs[2]) : null,
            ];
        }

        $response = $this->checkCollection($collectionInfo, $value, $current);
        if ($collectionInfo['name'] == '-') {
            $current = array();
            empty($response) ? $noMatch = array() : $noMatch[] = $response;
        } else {
            unset($current[$collectionInfo['name']]);
            if (!empty($response)) {
                $noMatch[$collectionInfo['name']] = $response;
            }
        }
        return $noMatch;
    }


    /**
     * A collection can be defined as: __collection(key, min, max).
     * Min and max represent the number of elements expected in collection and they are optional.
     * To represent an open range can be used "*" instead of min/max.
     * If the collection has no key use "-" .
     *
     * Checks each element in a collection to match the expected element structure.
     * It also checks the number if elements in the collection if the expected number of elements is set.
     *
     * @param $collectionInfo
     * @param array $expectedValue
     * @param array $current
     * @return array|string
     */
    public function checkCollection ($collectionInfo, array $expectedValue, array $current)
    {
        $expectedKey = $collectionInfo['name'];

        if ($expectedKey == '-') {
            $currentValue = $current;
            if (!is_array($current)) {
                return ("The response is not a collection.");
            }
        } else {
            $currentValue = $current[$expectedKey];
            if (!array_key_exists($expectedKey, $current) || !is_array($currentValue)) {
                return sprintf('There is no collection with the key: %s', $expectedKey);
            }
        }

        if(isset($collectionInfo['min']) && isset($collectionInfo['max'])) {
            $min = $collectionInfo['min'];
            $max = $collectionInfo['max'];
            $elements = count($currentValue);
            if (!$this->valueInInterval($elements, $min, $max)) {
                $message = sprintf('The number of elements in collection ' . $expectedKey . ' is '
                    . $elements . ' but should be between ' . $min . ' and ' . $max);
                return $message;
            }
        }

        foreach ($currentValue as $key => $value) {<commit_msg>Fix bug on empty collections.<commit_after><?php
/**
 * Created by PhpStorm.
 * User: bianca.vadean
 * Date: 1/19/2016
 * Time: 12:22 PM
 */

namespace Zitec\ApiZitecExtension\Util;

use DateTime;

class TypeChecker
{
    /**
     * @var array
     */
    protected $dataTypes = array(
        'string' => 'checkString',
        'integer' => 'checkInteger',
        'float' => 'checkFloat',
        'array' => 'checkArray',
        'boolean' => 'checkBoolean',
        'date' => 'checkDate',
        'email' => 'checkEmail',
        'url' => 'checkUrl',
        'regex' => 'checkRegex',
        'list' => 'checkValueInList',
        'numeric' => 'checkNumeric',
    );

    /**
     * Parse the expected result and matches the right function to be called.
     * Returns an array of error messages for each key in the expected response.
     *
     * @param array $current
     * @param array $expected
     * @return array|string
     */
    public function checkType (array $current, array $expected)
    {
        $noMatch = array();
        foreach ($expected as $key => &$value) {
            // Manage the flow in case of collections.
            if (($key == "__collection") || substr($key, 0, strpos($key, '(')) == "__collection") {
                $collectionErr = $this->manageCollection($expected, $key, $current);
                if (!empty($collectionErr)) {
                    $noMatch = array_merge($noMatch, $collectionErr);
                }
                continue;
            }

            if (!array_key_exists($key, $current)) {
                $noMatch[$key] = 'Expected key not found in response.';
                continue;
            }

            if (is_array($value)) {
                $message = $this->checkType($current[$key], $value);
                if (!empty($message)) {
                    $noMatch[$key] = $message;
                }
                unset($current[$key]);
                continue;
            }
            // Get the data type arguments, if there are any.
            if ($pos = strpos($value, '(')) {
                $type = substr($value, 0, $pos);
                $arguments = trim(substr($value, $pos + 1, strpos($value, ')') - $pos - 1), ')');
                $arguments = $type == 'regex' ? array($arguments) : explode(',', $arguments);
                $arguments = array_map('trim', $arguments);
            } else {
                $type = $value;
                $arguments = array();
            }
            // Check for null option.
            if ($pos = strpos($value, '|')) {
                $type = strpos($type, '|') ? trim(substr($value, 0, $pos)) : $type;
                if (is_null($current[$key])) {
                    unset($current[$key]);
                    continue;
                }
            }

            array_unshift($arguments, $current[$key]);
            if (array_key_exists($type, $this->dataTypes)) {
                $result = call_user_func_array(array($this, $this->dataTypes[$type]), $arguments);
                if (is_string($result)) {
                    $noMatch[$key] = $result;
                }
            } else {
                $noMatch[$key] = "Undefined data type.";
            }
            unset($current[$key]);
        }
        // If all response elements were unset in foreach, it means that all response elements were expected.
        if (!empty($current)) {
            $noMatch['unexpected data'] = $current;
        }
        if (!empty($noMatch)) {
            return $noMatch;
        }
    }

    /**
     * Parse the collection info in both situations
     * (when the collection is root in dataset and when the collection is a branch in dataset)
     *
     * @param $expected
     * @param $key
     * @param $current
     * @return array|string
     */
    public function manageCollection(&$expected, $key, &$current)
    {
        $value = $expected[$key];
        $noMatch = array();
        $collectionInfo = [];
        /**
         * Handle the situation when the collection is root and it's saved as __collection
         * and the info about collection is stored in __collection_info.
         */
        if ($key == '__collection') {
            if (empty($expected['__collection_info'])) {
                $noMatch[$key] = 'There is no info about the collection.';
                unset($expected[$key]);
                return $noMatch;
            }
            $collectionInfo = $expected['__collection_info'];
            unset($expected['__collection_info']);
        /**
         * Handle collections when they are saved as __collection(key, min, max)
         */
        } elseif (substr($key, 0, strpos($key, '(')) == "__collection") {
            preg_match_all("/\((.*?)\)/u", $key, $collectionArgs);
            $collectionArgs = explode(',', $collectionArgs[1][0]);
            $collectionInfo = [
                'name' => $collectionArgs[0],
                'min' => isset($collectionArgs[1]) ? isset($collectionArgs[1]) : null,
                'max' => isset($collectionArgs[2]) ? isset($collectionArgs[2]) : null,
            ];
        }

        if (!is_array($value)) {
            $noMatch[$collectionInfo['name']] = "The collection must be array.";
            return $noMatch;
        }

        $response = $this->checkCollection($collectionInfo, $value, $current);
        if ($collectionInfo['name'] == '-') {
            $current = array();
            empty($response) ? $noMatch = array() : $noMatch[] = $response;
        } else {
            unset($current[$collectionInfo['name']]);
            if (!empty($response)) {
                $noMatch[$collectionInfo['name']] = $response;
            }
        }
        return $noMatch;
    }


    /**
     * A collection can be defined as: __collection(key, min, max).
     * Min and max represent the number of elements expected in collection and they are optional.
     * To represent an open range can be used "*" instead of min/max.
     * If the collection has no key use "-" .
     *
     * Checks each element in a collection to match the expected element structure.
     * It also checks the number if elements in the collection if the expected number of elements is set.
     *
     * @param $collectionInfo
     * @param array $expectedValue
     * @param array $current
     * @return array|string
     */
    public function checkCollection ($collectionInfo, array $expectedValue, array $current)
    {
        $expectedKey = $collectionInfo['name'];

        if ($expectedKey == '-') {
            $currentValue = $current;
            if (!is_array($current)) {
                return ("The response is not a collection.");
            }
        } else {
            $currentValue = $current[$expectedKey];
            if (!array_key_exists($expectedKey, $current) || !is_array($currentValue)) {
                return sprintf('There is no collection with the key: %s', $expectedKey);
            }
        }

        if(isset($collectionInfo['min']) && isset($collectionInfo['max'])) {
            $min = $collectionInfo['min'];
            $max = $collectionInfo['max'];
            $elements = count($currentValue);
            if (!$this->valueInInterval($elements, $min, $max)) {
                $message = sprintf('The number of elements in collection ' . $expectedKey . ' is '
                    . $elements . ' but should be between ' . $min . ' and ' . $max);
                return $message;
            }
        }

        if (empty($currentValue) && !empty($expectedValue)) {
            $noMatch = [];
            foreach ($expectedValue as $key => $value) {
                if (!array_key_exists($key, $currentValue)) {
                    $noMatch[$key] = "Expected key not found in response.";
                }
            }
            return $noMatch;
        }

        foreach ($currentValue as $key => $value) {
{'fraction_non_alphanumeric': 0.14206932773109243, 'fraction_numerical': 0.0031512605042016808, 'mean_word_length': 1.79658527629888, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 48, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2883605', 'n_tokens_mistral': 4083, 'n_tokens_neox': 3983, 'n_words': 1091}