Yamato DaiwaE(CMA)S(cript) extensions

RawObjectDataProcessor — Демо

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

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

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

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

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

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

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

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

Если же 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 in 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"
}
● 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 value 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 Of Property/Element [ 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 Of Property/Element [ 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"
}
● 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",
  "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:85:11)
    at <anonymous> (D:\Demo.ts:97:1)
    at Object.<anonymous> (D:\Demo.ts:104: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? Потому что он может не только валидировать данные, но и вносить в них изменения, в частности переименовывать ключи и менять значения. Всё это и многое другое описано далее в настоящей документации.