RawObjectDataProcessorのデモ
引数externalDataの値が、 TypeScriptに依る型チェックが及ばない 外部データソースから取得されたものだとする。
function onDataRetrieved(externalData: unknown): void {
// ...
}
例えば、次の様なシナリオがよくある。
- クライアント・—サーバーモデルに於けるクライアント側からのデータ取得
- 逆に、クライアント—・サーバーモデルに於けるサーバー側からのデータ取得
- データベースからのデート取得
- ファイル(JSON、YAML等)からの読み取り
受け取るデータはSampleType型であると期待されているが、実行時に 実際どの様なデータに成るかはコード記述段階では不明。 従って此の引数の型はSampleTypeではなく、 unknownに成っている。
type SampleType = {
foo: number;
bar: string;
baz: boolean;
hoge?: number;
fuga: string | null;
quux: {
alpha: number;
bravo: "PLATINUM" | "GOLD" | "SILVER";
};
};
此の為、externalData as SampleTypeの様に型を強制するのは 危険。 とはいえTypeScriptの自然上、最終的にはそうせざるを得ない ( 問題の概観を参照)が、型を強制する上で何れかの安全性対策が必用、即ち妥当性確認。
此の課題をRawObjectDataProcessorで解決するには、妥当データ仕様を定義する事が必要。 此れは本質的上特定のデータに該当しているメタデータと成り、とりわけ想定される各プロパティの型、 それらの必須性や其の他の制約を記述したオブジェクト。 デモの為、期待されるプロパティ毎型の指定に加え、文字列プロパティの最大文字数 や許可される値の候補といった制約も幾つか加えてある。 上記の様な検証は実戦に於いて需要が高い為、此の例にとって余計な分に成らない。
const validDataSpecification: RawObjectDataProcessor.ObjectDataSpecification = {
nameForLogging: "Example",
subtype: RawObjectDataProcessor.ObjectSubtypes.fixedSchema,
properties: {
foo: {
type: Number,
isUndefinedForbidden: true,
isNullForbidden: true,
numbersSet: RawObjectDataProcessor.NumbersSets.positiveIntegerOrZero,
isNaN_Forbidden: true
},
bar: {
type: String,
isUndefinedForbidden: true,
isNullForbidden: true,
minimalCharactersCount: 5
},
baz: {
type: Boolean,
isUndefinedForbidden: true,
isNullForbidden: true
},
hoge: {
type: Number,
isUndefinedForbidden: false,
isNullForbidden: true,
isNaN_Forbidden: true,
numbersSet: RawObjectDataProcessor.NumbersSets.positiveIntegerOrZero
},
fuga: {
type: Number,
isUndefinedForbidden: true,
isNullForbidden: false,
numbersSet: RawObjectDataProcessor.NumbersSets.positiveIntegerOrZero,
isNaN_Forbidden: true
},
quux: {
type: Object,
isUndefinedForbidden: true,
isNullForbidden: true,
properties: {
alpha: {
type: Number,
isUndefinedForbidden: true,
isNullForbidden: true,
numbersSet: RawObjectDataProcessor.NumbersSets.anyInteger,
isNaN_Forbidden: true,
minimalValue: 3
},
bravo: {
type: String,
isUndefinedForbidden: true,
isNullForbidden: true,
minimalCharactersCount: 5,
allowedAlternatives: [ "PLATINUM", "GOLD", "SILVER" ]
}
}
}
}
};
妥当データ仕様を定義したら、其の仕様と、依然としてunknown型のままの 生データを併せて、RawObjectDataProcessorのstaticなメソッド processにパラメータとして渡す事。
function onDataRetrieved(externalData: unknown): void {
const externalDataProcessingResult: RawObjectDataProcessor.ProcessingResult<SampleType> = RawObjectDataProcessor.
process(externalData, validDataSpecification);
}
processメソッドの戻り値は、次の様に定義された型に成る。
export type ProcessingResult<ProcessedData> =
Readonly<
{
isRawDataInvalid: false;
processedData: ProcessedData;
} |
{
isRawDataInvalid: true;
validationErrorsMessages: ReadonlyArray<string>;
}
>;
此処での大事な事は、先ずisRawDataInvalidプロパティが false値を持っているとTypeScriptに証明しないからには、 processedDataプロパティにアクセス不可能という事だ。
const externalDataProcessingResult: RawObjectDataProcessor.ProcessingResult<SampleType> = RawObjectDataProcessor.
process(externalData, validDataSpecification);
console.log(externalDataProcessingResult.processedData);
externalDataProcessingResult.isRawDataInvalidを確認せずに externalDataProcessingResult.processedDataにアクセスする事は TypeScriptの仕組み不可能。 しようとしたら、「TS2339: Property processedData does not exist on type ProcessingResult<SampleType>」エラーが発生する。 実は、此のエラーメッセージは少し不確定があり、即ちProcessingResult<SampleType>全体に 其のプロパティが無いのではなく、部分型の何方か一方、具体的には isRawDataInvalid: trueを持つ方には其のプロパティが存在しない方は厳密。 従ってprocessedDataにアクセスする前に、 其のプロパティを持たない部分型を除外する必要だ。 此れを通常の条件分岐で実現出来る。if (externalDataProcessingResult.isRawDataInvalid) {
console.log(externalDataProcessingResult.validationErrorsMessages);
}
isRawDataInvalidプロパティがtrueになっている 場合にもprocessedDataにはアクセス不可能だ。 そもそも其の値が存在しない事が当然な理由で、代わりに実データと期待値の不一致を全て記述した検証エラーメッセージの配列にアクセス出来る。 これらの検証エラーをどうすれば良いか、要件次第になっているが、通常はユーザーへの通知やエラー投げ。
if (externalDataProcessingResult.isRawDataInvalid) {
throw new InvalidExternalDataError({
mentionToExpectedData: "N External Data",
messageSpecificPart: RawObjectDataProcessor.
formatValidationErrorsList(externalDataProcessingResult.validationErrorsMessages)
});
}
上の例の通り、RawObjectDataProcessorは検証エラーメッセージを整形する為の静的メソッド も提供している。 例えば次の入力でonDataRetrievedを呼び出すと、
onDataRetrieved({
foo: -4,
bar: "abc",
quux: {
alpha: 2,
bravo: "BRONZE"
}
});
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
isRawDataInvalidがfalseの場合、 processedDataプロパティにアクセスして利用可能。
if (externalDataProcessingResult.isRawDataInvalid) {
throw new InvalidExternalDataError({
mentionToExpectedData: "N External Data",
messageSpecificPart: RawObjectDataProcessor.
formatValidationErrorsList(externalDataProcessingResult.validationErrorsMessages)
});
}
console.log(externalDataProcessingResult.processedData)processedDataプロパティにアクセス出来る。 直前のif ブロック内でエラーを投げる事に依り、 { isRawDataInvalid: true; validationErrorsMessages: ReadonlyArray<string> } という部分型が除外されている為。 もし上記のコードを含む関数・メソッドが妥当なデータを返す必要がないなら、エラー投擲の代わりに returnキーワードを使っても良い場合が有る。上記の例の様な固定スキーマのオブジェクト以外にも、RawObjectDataProcessorは他の種類の オブジェクトも検証出来る。
- 連想配列。固定スキーマのオブジェクトと異なり、キー・値組数が 規制は無いが、全値は共通の規則に従わなければいけない。
- 指数配列
- タプル。指数配列と異なり、要素数と順序が固定で、 更に各要素毎に個別の制約を設定可能。
何故此のクラス名がRawObjectDataValidator(「生オブジェクト型データのバリデータ」)ではなく、 RawObjectDataProcessor(「生オブジェクト型データのプロセッサ」)になっているだろうか。 理由は、此のクラスが妥当性確認を行っているだけでなく、必用に応じて変換(例えばキーの名前の変更や値の変更)も可能という事。 今後詳細を当説明書に解説。