<commit_before>import { concat, get, pick } from 'lodash';
import { ValidatorError, InvalidAttributeError, RuleNotFound } from './exceptions';
import { findOptionByAnswer } from './utils';

function validateAnswer(bot, rule, answer) {
  const validators = concat([],
    rule.validators || [],
    bot.types[rule.type].validators || [],
  );
  validators.forEach(obj => {
    Object.keys(obj).forEach(k => {
      const validator = bot.validators[k];
      if (!validator || k === 'warning') { return; }
      if (!validator.validate(obj[k], answer, rule)) {
        const warning = obj.warning || validator.warning;
        const message = typeof warning === 'function' ? warning(obj[k]) : warning;
        throw new ValidatorError(message, rule);
      }
    });
  });
}

function getNextFromRule(rule, answer) {
  if (rule.next) { return rule.next; }
  if (rule.options) {
    const { next }  = findOptionByAnswer(rule.options, answer) || {};
    if (next) { return next; }
  }
  // TODO: find in flow
  return null;
}

export default ctrl => ctrl

  .define('configure', bot => {
    ctrl.indexes = {};

    bot.rules.forEach((rule, idx) => {
      ctrl.indexes[rule.name] = idx;
    });

    return ctrl;
  })

  .define('rule', (bot, idx) => {
    if (!bot.rules[idx]) {
      return { exit: true };
    }
    const rule = bot.rules[idx];
    return Object.assign({}, bot.defaults.rule, rule);
  })

  .define('run', async (bot, idx = 0) => {
    bot.setStore('currentIdx', idx);

    const rule = ctrl.rule(bot, idx);

    if (rule.message) {
      await ctrl.send(bot, rule.message, rule.delay);
    }

    if (rule.sleep) {
      await bot.actions.wait(rule.sleep);
    }

    if (rule.exit) {
      return bot.end();
    }

    if (!rule.type) {
      return ctrl.next(bot);
    } else if (!bot.types[rule.type]) {
      throw new InvalidAttributeError('type', rule);
    }

    bot.setStore('waitingForAnswer', true);
    bot._dispatch('hear');

    return ctrl;
  })

  .define('send', async (bot, message, delay) => {
    bot._dispatch('typing');

    if (delay) {
      await bot.actions.wait(delay);
    }

    bot.talk(message);
    bot._dispatch('typed');
  })

  .define('receive', async (bot, message) => {
    const idx = bot.store('currentIdx');
    const rule = ctrl.rule(bot, idx);

    if (!bot.store('waitingForAnswer')) {
      return;
    }

    let answer = message;
    if ('parser' in bot.types[rule.type]) {
      answer = bot.types[rule.type].parser(answer);
    }

    try {
      validateAnswer(bot, rule, message);
    } catch(e) {
      if (e instanceof ValidatorError) {
        await ctrl.send(bot, e.message, rule.delay);
        return;<commit_msg>fix retrying validation flow for async client<commit_after>import { concat, get, pick } from 'lodash';
import { ValidatorError, InvalidAttributeError, RuleNotFound } from './exceptions';
import { findOptionByAnswer } from './utils';

function validateAnswer(bot, rule, answer) {
  const validators = concat([],
    rule.validators || [],
    bot.types[rule.type].validators || [],
  );
  validators.forEach(obj => {
    Object.keys(obj).forEach(k => {
      const validator = bot.validators[k];
      if (!validator || k === 'warning') { return; }
      if (!validator.validate(obj[k], answer, rule)) {
        const warning = obj.warning || validator.warning;
        const message = typeof warning === 'function' ? warning(obj[k]) : warning;
        throw new ValidatorError(message, rule);
      }
    });
  });
}

function getNextFromRule(rule, answer) {
  if (rule.next) { return rule.next; }
  if (rule.options) {
    const { next }  = findOptionByAnswer(rule.options, answer) || {};
    if (next) { return next; }
  }
  // TODO: find in flow
  return null;
}

export default ctrl => ctrl

  .define('configure', bot => {
    ctrl.indexes = {};

    bot.rules.forEach((rule, idx) => {
      ctrl.indexes[rule.name] = idx;
    });

    return ctrl;
  })

  .define('rule', (bot, idx) => {
    if (!bot.rules[idx]) {
      return { exit: true };
    }
    const rule = bot.rules[idx];
    return Object.assign({}, bot.defaults.rule, rule);
  })

  .define('run', async (bot, idx = 0) => {
    bot.setStore('currentIdx', idx);

    const rule = ctrl.rule(bot, idx);

    if (rule.message) {
      await ctrl.send(bot, rule.message, rule.delay);
    }

    if (rule.sleep) {
      await bot.actions.wait(rule.sleep);
    }

    if (rule.exit) {
      return bot.end();
    }

    if (!rule.type) {
      return ctrl.next(bot);
    } else if (!bot.types[rule.type]) {
      throw new InvalidAttributeError('type', rule);
    }

    bot.setStore('waitingForAnswer', true);
    bot._dispatch('hear');

    return ctrl;
  })

  .define('send', async (bot, message, delay) => {
    bot._dispatch('typing');

    if (delay) {
      await bot.actions.wait(delay);
    }

    bot.talk(message);
    bot._dispatch('typed');
  })

  .define('receive', async (bot, message) => {
    const idx = bot.store('currentIdx');
    const rule = ctrl.rule(bot, idx);

    if (!bot.store('waitingForAnswer')) {
      return;
    }

    let answer = message;
    if ('parser' in bot.types[rule.type]) {
      answer = bot.types[rule.type].parser(answer);
    }

    try {
      validateAnswer(bot, rule, message);
    } catch(e) {
      if (e instanceof ValidatorError) {
        await ctrl.send(bot, e.message, rule.delay);
        bot._dispatch('hear');
        return;
{'fraction_non_alphanumeric': 0.169148740577312, 'fraction_numerical': 0.00036771465342893915, 'mean_word_length': 2.703199455411845, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, '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': '6474704', 'n_tokens_mistral': 1799, 'n_tokens_neox': 1753, 'n_words': 396}