Yamato DaiwaE(CMA)S(cript) extensions

RawObjectDataProcessor — Демо

Допустим, значение параметра externalData получено из внешнего источника данных, недосягаемого для проверки типов с помощью TypeScript.

В частности, это могут быть следующие сценарии:

  • Полученные данных с клиентской части при клиент-серверном взаимодействии
  • Наоборот, полученные данных с серверной части при клиент-серверном взаимодействии
  • Получение данных из базы данных
  • Чтение данных из файла (JSON, YAML и подобных)

Мы ожидаем, что полученные данные будут иметь тип SampleType, однако какими будут эти данные на самом деле при выполнении программы — на стадии написания кода мы знать не можем, поэтому параметр и имеет тип unknown, а не SampleType.

Ввиду этого, просто взять и привести тип наподобие externalData as SampleType небезопасно. Хотя в силу природы TypeScript так сделать в итоге придётся (см. «обзор проблематики»), это должно быть чем-то подкреплено, а именно валидацией.

Решая эту задачу с помощью RawObjectDataProcessor, необходимо определить спецификацию валидных данных — по сути это объект, в котором описаны метаданные для конкретных данных, в частности ожидаемые типы каждого свойства, их обязательность и прочие ограничения. Для демонстрации, помимо указания ожидаемых типов свойств добавлены и некоторые другие ограничения, максимальное количество символов для строковых свойств или список возможных значений. На практике такая валидация довольно востребована, потому она не сделает этот пример избыточным.

Когда спецификация валидных данных определена, нужно передать её через параметр статическому методу process класса RawObjectDataProcessor вместе с исходными данными пока ещё типа unknown:

Значение, которое возвращает метод process, имеет тип со следующим определением:

Особенность его том, что мы не можем обратиться к свойству processedData до тех пор, пока не докажем TypeScript-у, что свойство isInvalid имеет значение false:

Но и если isRawDataInvalid имеет значение true, то к свойству processedData обратиться всё ещё нельзя, потому что его просто нет, а вместо него можно обратиться к массиву, в котором содержатся сообщения об ошибках валидации, то есть описания всех несоответствий реальных данных ожидаемым. Как на эти ошибки валидации реагировать — решает разработчик в зависимости от специфики его задачи, но обычно это оповещение пользователя и/или бросание ошибки:

Как видно из примера выше, RawObjectDataProcessor также имеет статический метод для форматирования сообщений об ошибках валидации. Например, при вызове функции onDataRetrieved со следующими входными данными:

Node.js выдаст следующий лог:

InvalidExternalDataError: The data "N External Data" does not match with expected.
─── Error No. 1 ────────────────────────────────────────────────────────────────
Expected and Actual Numbers Set Mismatch [ VALIDATION_ERRORS_MESSAGES-NUMERIC_VALUE_IS_NOT_BELONG_TO_EXPECTED_NUMBERS_SET ]

● Property / Element: foo
Contrary to expectations, this numeric value is not member of "positive integer or zero"
See documentation for details: https://ee.yamato-daiwa.com/CoreLibrary/Functionality/RawObjectDataProcessor/Children/06-ValidationIssues/RawObjectDataProcessor-ValidationIssues.english.html#VALIDATION_ERRORS_MESSAGES-NUMERIC_VALUE_IS_NOT_BELONG_TO_EXPECTED_NUMBERS_SET

● Property / Element Specification:
{
  type: "number",
  isUndefinedForbidden: true,
  isNullForbidden: true,
  numbersSet: "POSITIVE_INTEGER_OR_ZERO",
  isNaN_Forbidden: true
}
● Actual Value: -4

─── Error No. 2 ────────────────────────────────────────────────────────────────
Minimal Characters Count Fall Short [ VALIDATION_ERRORS_MESSAGES-CHARACTERS_COUNT_IS_LESS_THAN_REQUIRED ]

● Property / Element: bar
This string has 3 characters while at least 5 required.
See documentation for details: https://ee.yamato-daiwa.com/CoreLibrary/Functionality/RawObjectDataProcessor/Children/06-ValidationIssues/RawObjectDataProcessor-ValidationIssues.english.html#VALIDATION_ERRORS_MESSAGES-CHARACTERS_COUNT_IS_LESS_THAN_REQUIRED

● Property / Element Specification:
{
  type: "string",
  isUndefinedForbidden: true,
  isNullForbidden: true,
  minimalCharactersCount: 5
}
● Actual Value: "abc"

─── Error No. 3 ────────────────────────────────────────────────────────────────
Forbidden Undefined Value [ VALIDATION_ERRORS_MESSAGES-FORBIDDEN_UNDEFINED_VALUE ]

● Property / Element: baz
This property/element is not defined or have explicit `undefined` value what has been explicitly forbidden.
See documentation for details: https://ee.yamato-daiwa.com/CoreLibrary/Functionality/RawObjectDataProcessor/Children/06-ValidationIssues/RawObjectDataProcessor-ValidationIssues.english.html#VALIDATION_ERRORS_MESSAGES-FORBIDDEN_UNDEFINED_VALUE

● Property / Element Specification:
{
  type: "boolean",
  isUndefinedForbidden: true,
  isNullForbidden: true
}
● Actual Value: undefined

─── Error No. 4 ────────────────────────────────────────────────────────────────
Forbidden Undefined Value [ VALIDATION_ERRORS_MESSAGES-FORBIDDEN_UNDEFINED_VALUE ]

● Property / Element: fuga
This property/element is not defined or have explicit `undefined` value what has been explicitly forbidden.
See documentation for details: https://ee.yamato-daiwa.com/CoreLibrary/Functionality/RawObjectDataProcessor/Children/06-ValidationIssues/RawObjectDataProcessor-ValidationIssues.english.html#VALIDATION_ERRORS_MESSAGES-FORBIDDEN_UNDEFINED_VALUE

● Property / Element Specification:
{
  type: "number",
  isUndefinedForbidden: true,
  isNullForbidden: false,
  numbersSet: "POSITIVE_INTEGER_OR_ZERO",
  isNaN_Forbidden: true
}
● Actual Value: undefined

─── Error No. 5 ────────────────────────────────────────────────────────────────
Minimal Value Fall Short [ VALIDATION_ERRORS_MESSAGES-NUMERIC_VALUE_IS_SMALLER_THAN_REQUIRED_MINIMUM ]

● Property / Element: quux.alpha
This value is smaller than required minimal value 3.
See documentation for details: https://ee.yamato-daiwa.com/CoreLibrary/Functionality/RawObjectDataProcessor/Children/06-ValidationIssues/RawObjectDataProcessor-ValidationIssues.english.html#VALIDATION_ERRORS_MESSAGES-NUMERIC_VALUE_IS_SMALLER_THAN_REQUIRED_MINIMUM

● Property / Element Specification:
{
  type: "number",
  isUndefinedForbidden: true,
  isNullForbidden: true,
  numbersSet: "ANY_INTEGER",
  isNaN_Forbidden: true,
  minimalValue: 3
}
● Actual Value: 2

─── Error No. 6 ────────────────────────────────────────────────────────────────
Disallowed Alternative of Value [ VALIDATION_ERRORS_MESSAGES-VALUE_IS_NOT_AMONG_ALLOWED_ALTERNATIVES ]

● Property / Element: quux.bravo
This value is not among following allowed alternatives:
  ○ PLATINUM
  ○ GOLD
  ○ SILVER
See documentation for details: https://ee.yamato-daiwa.com/CoreLibrary/Functionality/RawObjectDataProcessor/Children/06-ValidationIssues/RawObjectDataProcessor-ValidationIssues.english.html#VALIDATION_ERRORS_MESSAGES-VALUE_IS_NOT_AMONG_ALLOWED_ALTERNATIVES

● Property / Element Specification:
{
  type: "string",
  isUndefinedForbidden: true,
  isNullForbidden: true,
  minimalCharactersCount: 5,
  allowedAlternatives: [
    "PLATINUM",
    "GOLD",
    "SILVER"
  ]
}
● Actual Value: "BRONZE"
    at onDataRetrieved (D:\Demo.ts:87:11)
    at <anonymous> (D:\Demo.ts:99:1)
    at Object.<anonymous> (D:\Demo.ts:106:2)
    at Module._compile (node:internal/modules/cjs/loader:1730:14)
    at Object.transformer (C:\Users\I\AppData\Roaming\npm\node_modules\tsx\dist\register-DpmFHar1.cjs:2:1186)
    at Module.load (node:internal/modules/cjs/loader:1465:32)
    at Function._load (node:internal/modules/cjs/loader:1282:12)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
    at cjsLoader (node:internal/modules/esm/translators:266:5)

Node.js v22.15.0

Если же isInvalid имеет значение false, то можно обращаться к свойству processedData и пользоваться им:

Помимо объектов фиксированной структуры как в примере выше RawObjectDataProcessor может валидировать и другие подвиды объектов:

  • Ассоциативные массивы — отличаются от объектов фиксированной структуры произвольным количеством пар ключ-значение, но при этом все значения должны подчиняться единой закономерности.
  • Индексные массивы
  • Кортежи — отличаются от индексных массивов фиксированным числом элементов и их порядком, при этом для каждого элемента могут быть определены особые ограничения.

Почему же класс называется RawObjectDataProcessor («обработчик сырых объектных данных»), а не RawObjectDataValidator («валидатор сырах объектных данных»)? Потому что он может не только валидировать данные, но и вносить в них изменения, в частности переименовывать ключи и менять значения. Всё это и многое другое описано далее в настоящей документации.