Вложенные проверки с народной сказкой

Я использовал Folktale's Validation для нового проекта, и я нашел его действительно полезным, но я столкнулся с необходимостью последовательных проверок. У меня есть объект конфигурации, и мне нужно выполнить следующие проверки:

  • это объект?
  • действительны ли ключи объекта (они отображаются в белом списке)?
  • допустимы ли значения ключей?

Каждая проверка зависит от предыдущей проверки - если элемент не является объектом, проверка его ключей бессмысленна (и приведет к ошибке), если объект не имеет ключей, проверка их значений не имеет смысла. По сути, я хочу короткого замыкания проверки, если проверка не удается.

Первоначально я думал использовать Result вместо Validatio., but mixing the two types feels confusing, and I already havevalidateIsObject` определен и используется в другом месте.

Мое текущее (рабочее, но уродливое) решение здесь:

import { validation } from 'folktale';
import { validateIsObject } from 'folktale-validations';
import validateConfigKeys from './validateConfigKeys';
import validateConfigValues from './validateConfigValues';

const { Success, Failure } = validation;

export default config => {
  const wasObject = validateIsObject(config);
  let errorMessages;
  if (Success.hasInstance(wasObject)) {
    const hadValidKeys = validateConfigKeys(config);
    if (Success.hasInstance(hadValidKeys)) {
      const hasValidValues = validateConfigValues(config);
      if (Success.hasInstance(hasValidValues)) {
        return Success(config);
      }
      errorMessages = hasValidValues.value;
    } else {
      errorMessages = hadValidKeys.value;
    }
  } else {
    errorMessages = wasObject.value;
  }
  return Failure(errorMessages);
};

Я изначально принял подход использования вложенных matchWithс, но это было еще сложнее читать.

Как я могу улучшить это решение?

1 ответ

Решение

Вы можете написать помощника, который применяет правила проверки до Failure возвращается Быстрый пример:

const validateUntilFailure = (rules) => (x) => rules.reduce(
  (result, rule) => Success.hasInstance(result) 
    ? result.concat(rule(x)) 
    : result,
  Success()
);

Мы используем concat объединить два результата. Мы используем Success.hasInstance чтобы проверить, нужно ли применять следующее правило. Ваш модуль теперь будет длиной в одну строку:

export default config => validateUntilFailure([ 
  validateIsObject, validateConfigKeys, validateConfigValues
]);

Обратите внимание, что эта реализация не возвращается рано, как только она видит Failure, Рекурсивная реализация может быть более функциональным подходом, но не всем понравится:

const validateUntilFailure = ([rule, ...rules], x, result = Success()) => 
  Failure.hasInstance(result) || !rule
    ? result
    : validateUntilFailure(rules, x, result.concat(rule(x)))

Посмотрите пример ниже для запуска кода. Там есть закомментированный раздел, который показывает, как запустить все правила, даже если есть сбои.

const { Success, Failure } = folktale.validation;

const validateIsObject = (x) =>
  x !== null && x.constructor === Object
   ? Success(x)
   : Failure(['Input is not an object']);

const validateHasRightKeys = (x) =>
  ["a", "b"].every(k => k in x) 
   ?  Success(x)
  :  Failure(['Item does not have a & b.']);

const validateHasRightValues = (x) =>
  x.a < x.b
   ? Success(x)
  : Failure(['b is larger or equal to a']);


// This doesn't work because it calls all validations on
// every item
/*
const validateItem = (x) =>
  Success().concat(validateIsObject(x))
           .concat(validateHasRightKeys(x))
           .concat(validateHasRightValues(x))
           .map(_ => x);
*/

// General validate until failure function:
const validateUntilFailure = (rules) => (x) => rules.reduce(
  (result, rule) => Success.hasInstance(result) 
    ? result.concat(rule(x)) 
    : result,
  Success()
);

// Let's try it out!
const testCases = [
  null,
  { a: 1 },
  { b: 2 },
  { a: 1, b: 2 },
  { a: 2, b: 1 }
];

const fullValidation = validateUntilFailure([
 validateIsObject, 
  validateHasRightKeys,
  validateHasRightValues
]);



console.log(
  testCases
    .map(x => [x, fullValidation(x)])
    .map(stringifyResult)
    .join("\n")
);

function stringifyResult([input, output]) {
  return `input: ${JSON.stringify(input)}, ${Success.hasInstance(output) ? "success:" : "error:"} ${JSON.stringify(output.value)}`;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/folktale/2.0.1/folktale.min.js"></script>

Другие вопросы по тегам