From 7421e4ad363e6697a2f8c8f913308a04f44f9bde Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Wed, 17 Jan 2024 10:00:17 -0800 Subject: [PATCH 01/20] - Added support for instantiation of an object in the transformation. --- packages/data-transformer/package-lock.json | 40 ++++++- packages/data-transformer/package.json | 4 +- .../transformers/data-transformer.builder.ts | 1 + .../src/transformers/data.transformer.spec.ts | 30 +++++ .../src/transformers/data.transformer.ts | 103 +++++++++--------- 5 files changed, 125 insertions(+), 53 deletions(-) diff --git a/packages/data-transformer/package-lock.json b/packages/data-transformer/package-lock.json index 6d6ac0810..301677e39 100644 --- a/packages/data-transformer/package-lock.json +++ b/packages/data-transformer/package-lock.json @@ -10,7 +10,9 @@ "license": "ISC", "dependencies": { "@pristine-ts/common": "file:../common", - "@pristine-ts/logging": "file:../logging" + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1" } }, "../common": { @@ -40,6 +42,24 @@ "node_modules/@pristine-ts/logging": { "resolved": "../logging", "link": true + }, + "node_modules/@pristine-ts/metadata": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pristine-ts/metadata/-/metadata-1.0.2.tgz", + "integrity": "sha512-flztBNC8Rf8IqrapZCrz86IUc/wf+L13gRL+R3Z5wCQxldG5GTGd+eJYEgDfzKa7crxoofz8Y1+2kJDj4nScdg==", + "dependencies": { + "reflect-metadata": "^0.2.1" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" } }, "dependencies": { @@ -58,6 +78,24 @@ "@pristine-ts/configuration": "file:../configuration", "date-fns": "^2.30.0" } + }, + "@pristine-ts/metadata": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pristine-ts/metadata/-/metadata-1.0.2.tgz", + "integrity": "sha512-flztBNC8Rf8IqrapZCrz86IUc/wf+L13gRL+R3Z5wCQxldG5GTGd+eJYEgDfzKa7crxoofz8Y1+2kJDj4nScdg==", + "requires": { + "reflect-metadata": "^0.2.1" + } + }, + "class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" } } } diff --git a/packages/data-transformer/package.json b/packages/data-transformer/package.json index 0c23c3707..d3466a1f4 100644 --- a/packages/data-transformer/package.json +++ b/packages/data-transformer/package.json @@ -21,7 +21,9 @@ }, "dependencies": { "@pristine-ts/common": "file:../common", - "@pristine-ts/logging": "file:../logging" + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1" }, "jest": { "transform": { diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.ts b/packages/data-transformer/src/transformers/data-transformer.builder.ts index 49d1b627b..4e6619996 100644 --- a/packages/data-transformer/src/transformers/data-transformer.builder.ts +++ b/packages/data-transformer/src/transformers/data-transformer.builder.ts @@ -21,6 +21,7 @@ export class DataTransformerBuilder { public properties: {[sourceProperty in string]: DataTransformerProperty} = {} + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerBuilder { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); diff --git a/packages/data-transformer/src/transformers/data.transformer.spec.ts b/packages/data-transformer/src/transformers/data.transformer.spec.ts index 6c7280571..6ee221026 100644 --- a/packages/data-transformer/src/transformers/data.transformer.spec.ts +++ b/packages/data-transformer/src/transformers/data.transformer.spec.ts @@ -8,6 +8,7 @@ import {DataTransformerRow} from "../types/data-transformer.row"; import 'jest-extended'; import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; +import {property} from "@pristine-ts/metadata"; describe('Data Transformer', () => { it("should properly transform", async () => { @@ -191,4 +192,33 @@ describe('Data Transformer', () => { await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); }) + + it("should properly type the return object when a destinationType is passed", async () => { + class Source { + @property() + title: string; + } + + class Destination { + @property() + name: string; + } + + const source = new Source(); + source.title = "TITLE"; + + const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + + const dataTransformerBuilder = new DataTransformerBuilder(); + dataTransformerBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .addNormalizer(LowercaseNormalizer.name) + .end() + + const destination = await dataTransformer.transform(dataTransformerBuilder, source, Destination); + + expect(destination.name).toBe("title"); + }) }); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data.transformer.ts b/packages/data-transformer/src/transformers/data.transformer.ts index 2569d97b0..3a9fe8d06 100644 --- a/packages/data-transformer/src/transformers/data.transformer.ts +++ b/packages/data-transformer/src/transformers/data.transformer.ts @@ -10,12 +10,14 @@ import {DataTransformerRow} from "../types/data-transformer.row"; import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; +import {ClassMetadata} from "@pristine-ts/metadata"; +import {ClassConstructor, plainToInstance} from "class-transformer"; @moduleScoped(DataTransformerModuleKeyname) @injectable() export class DataTransformer { - private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface} = {} - private readonly dataTransformerInterceptorsMap: { [key in DataTransformerInterceptorUniqueKeyType]: DataTransformerInterceptorInterface} = {} + private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface } = {} + private readonly dataTransformerInterceptorsMap: { [key in DataTransformerInterceptorUniqueKeyType]: DataTransformerInterceptorInterface } = {} public constructor(@injectAll("DataNormalizerInterface") private readonly dataNormalizers: DataNormalizerInterface[], @injectAll("DataTransformerInterceptor") private readonly dataTransformerInterceptors: DataTransformerInterceptorInterface[],) { @@ -24,80 +26,79 @@ export class DataTransformer { }) dataTransformerInterceptors.forEach(interceptor => { - this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; + this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; }); } - public async transform(builder: DataTransformerBuilder, source: (DataTransformerRow)[]): Promise { - const globalNormalizers = builder.normalizers; + public async transformRows(builder: DataTransformerBuilder, rows: DataTransformerRow[], destinationType?: ClassConstructor): Promise { + return rows.map(row => this.transform(builder, row, destinationType)); + } - const destination: DataTransformerRow[] = []; + public async transform(builder: DataTransformerBuilder, source: DataTransformerRow, destinationType?: ClassConstructor): Promise { + const globalNormalizers = builder.normalizers; let row: DataTransformerRow = {}; - for(const key in source) { - if(source.hasOwnProperty(key) === false) { - continue; - } - let interceptedInputRow = source[key]; + if(destinationType) { + row = plainToInstance(destinationType, {}); + } - // Execute the before row interceptors. - for(const element of builder.beforeRowTransformInterceptors) { - const interceptor = this.dataTransformerInterceptorsMap[element.key]; + let interceptedInputRow = source; - if(interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } + // Execute the before row interceptors. + for (const element of builder.beforeRowTransformInterceptors) { + const interceptor = this.dataTransformerInterceptorsMap[element.key]; - // todo: Pass the options when we start using them. - interceptedInputRow = await interceptor.beforeRowTransform(interceptedInputRow); + if (interceptor === undefined) { + throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); } - // Loop over the properties defined in the builder - for (const key in builder.properties) { - if(builder.properties.hasOwnProperty(key) === false) { - continue; - } + // todo: Pass the options when we start using them. + interceptedInputRow = await interceptor.beforeRowTransform(interceptedInputRow); + } - const property: DataTransformerProperty = builder.properties[key]; - if(interceptedInputRow.hasOwnProperty(property.sourceProperty) === false) { - if(property.isOptional) { - continue; - } + // Loop over the properties defined in the builder + for (const key in builder.properties) { + if (builder.properties.hasOwnProperty(key) === false) { + continue; + } - throw new DataTransformerSourcePropertyNotFoundError("The property '" + key + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", key) + const property: DataTransformerProperty = builder.properties[key]; + if (interceptedInputRow.hasOwnProperty(property.sourceProperty) === false) { + if (property.isOptional) { + continue; } - let value = interceptedInputRow[property.sourceProperty]; + throw new DataTransformerSourcePropertyNotFoundError("The property '" + key + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", key) + } - // Remove the normalizers part of the excludedNormalizers - const normalizers = globalNormalizers.filter(element => property.excludedNormalizers.has(element.key) === false); - normalizers.push(...property.normalizers); + let value = interceptedInputRow[property.sourceProperty]; - normalizers.forEach(element => { - const dataNormalizer = this.dataNormalizersMap[element.key]; - value = dataNormalizer.normalize(value, element.options); - }) + // Remove the normalizers part of the excludedNormalizers + const normalizers = globalNormalizers.filter(element => property.excludedNormalizers.has(element.key) === false); + normalizers.push(...property.normalizers); - // Assign the resulting value in the destination - row[property.destinationProperty] = value; - } + normalizers.forEach(element => { + const dataNormalizer = this.dataNormalizersMap[element.key]; + value = dataNormalizer.normalize(value, element.options); + }) - // Execute the before row interceptors. - for(const element of builder.afterRowTransformInterceptors) { - const interceptor: DataTransformerInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; + // Assign the resulting value in the destination + row[property.destinationProperty] = value; + } - if(interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } + // Execute the before row interceptors. + for (const element of builder.afterRowTransformInterceptors) { + const interceptor: DataTransformerInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; - // todo pass the options when we start using it. - row = await interceptor.afterRowTransform(row); + if (interceptor === undefined) { + throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); } - destination.push(row); + // todo pass the options when we start using it. + row = await interceptor.afterRowTransform(row); } - return destination; + return row; } } \ No newline at end of file From 7f493c6a7287558b4621c58b1b5ebee0b3779d7f Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Wed, 17 Jan 2024 16:05:45 -0800 Subject: [PATCH 02/20] - Very early draft of starting the data-mapping tree structure instead. This will support multiple nesting levels as well as arrays. --- .../data-transformer/src/builders/builders.ts | 0 .../src/builders/data-mapping.builder.ts | 3 + .../src/enums/data-mapping-node-type.enum.ts | 5 ++ packages/data-transformer/src/enums/enums.ts | 0 .../mapping-nodes/array-data-mapping.node.ts | 12 +++ .../src/mapping-nodes/data-mapping.leaf.ts | 83 +++++++++++++++++++ .../src/mapping-nodes/data-mapping.node.ts | 21 +++++ .../src/mapping-nodes/data-mapping.tree.ts | 79 ++++++++++++++++++ .../src/mapping-nodes/mapping-nodes.ts | 3 + .../transformers/data-transformer.builder.ts | 1 - .../data-transformer.property.spec.ts | 2 + .../transformers/data-transformer.property.ts | 4 + .../src/transformers/data.transformer.spec.ts | 30 +++++++ 13 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 packages/data-transformer/src/builders/builders.ts create mode 100644 packages/data-transformer/src/builders/data-mapping.builder.ts create mode 100644 packages/data-transformer/src/enums/data-mapping-node-type.enum.ts create mode 100644 packages/data-transformer/src/enums/enums.ts create mode 100644 packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts create mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts create mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.node.ts create mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts create mode 100644 packages/data-transformer/src/mapping-nodes/mapping-nodes.ts diff --git a/packages/data-transformer/src/builders/builders.ts b/packages/data-transformer/src/builders/builders.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/data-transformer/src/builders/data-mapping.builder.ts b/packages/data-transformer/src/builders/data-mapping.builder.ts new file mode 100644 index 000000000..9274d4b05 --- /dev/null +++ b/packages/data-transformer/src/builders/data-mapping.builder.ts @@ -0,0 +1,3 @@ +export class DataMappingBuilder { + +} \ No newline at end of file diff --git a/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts b/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts new file mode 100644 index 000000000..39cf495d4 --- /dev/null +++ b/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts @@ -0,0 +1,5 @@ +export enum DataMappingNodeTypeEnum { + ArrayDataMappingNode = "ARRAY_DATA_MAPPING_NODE", + Node = "DATA_MAPPING_NODE", + Leaf = "DATA_MAPPING_LEAF", +} \ No newline at end of file diff --git a/packages/data-transformer/src/enums/enums.ts b/packages/data-transformer/src/enums/enums.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts new file mode 100644 index 000000000..7a2a664e0 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts @@ -0,0 +1,12 @@ +import {DataMappingNode} from "./data-mapping.node"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; + +/** + * We need an array node because the behaviour when mapping an array is different. For each element in the source property, + * we will + */ +export class ArrayDataMappingNode extends DataMappingNode { + public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.ArrayDataMappingNode; + + +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts new file mode 100644 index 000000000..927a6fbe7 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts @@ -0,0 +1,83 @@ +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataMappingNode} from "./data-mapping.node"; +import {DataMappingTree} from "./data-mapping.tree"; + +export class DataMappingLeaf { + public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Leaf; + + public sourceProperties: string[] = []; + + public destinationProperty: string; + + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + + public excludedNormalizers: Set = new Set(); + + public isOptional: boolean; + + public constructor( + private readonly parent: DataMappingNode, + private readonly root: DataMappingTree, + ) { + } + + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingLeaf { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) + } + + if(this.root.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the root and cannot be also added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + + return this; + } + + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + public excludeNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): DataMappingLeaf { + if(this.excludedNormalizers.has(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The EXCLUDED data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey) + } + + this.excludedNormalizers.add(normalizerUniqueKey); + + return this; + } + + public import(schema: any) { + this.normalizers = schema.normalizers; + + this.excludedNormalizers = new Set() + if(schema.hasOwnProperty("excludedNormalizers")) { + for(const item in schema.excludedNormalizers) { + this.excludeNormalizer(item); + } + } + + this.isOptional = schema.isOptional; + this.sourceProperties = schema.sourceProperties; + this.destinationProperty = schema.destinationProperty; + } + + public export(): any { + return { + "sourceProperties": this.sourceProperties, + "destinationProperty": this.destinationProperty, + "isOptional": this.isOptional, + "normalizers": this.normalizers, + "excludedNormalizers": this.excludedNormalizers, + } + } +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts new file mode 100644 index 000000000..02f7aff76 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts @@ -0,0 +1,21 @@ +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingLeaf} from "./data-mapping.leaf"; + +export class DataMappingNode { + public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node; + public nodes: DataMappingNode[] = []; + public leaves: DataMappingLeaf[] = []; + + public parent?: DataMappingNode; + + constructor(private readonly root: DataMappingTree) { + } + + /** + * The DataMappingNode can only have one sourceProperty assigned. We need one sourceProperty to understand how to + * navigate through the source object to pass the exact properties to the leaf nodes; + */ + public sourceProperty: string; + + public destinationProperty: string; +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts new file mode 100644 index 000000000..6ea5f6e22 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts @@ -0,0 +1,79 @@ +import {DataMappingNode} from "./data-mapping.node"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import { + DataBeforeRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-before-row-transformer-interceptor-already-added.error"; +import { + DataAfterRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-after-row-transformer-interceptor-already-added.error"; +import {DataTransformerProperty} from "../transformers/data-transformer.property"; +import {DataMappingLeaf} from "./data-mapping.leaf"; + +export class DataMappingTree extends DataMappingNode { + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; + public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; + + + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingTree { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + return this; + } + + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingTree { + if(this.hasBeforeRowTransformInterceptor(key)) { + throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) + } + + this.beforeRowTransformInterceptors.push({ + key, + options, + }); + + return this; + } + + public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { + return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; + } + + public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingTree { + if(this.hasAfterRowTransformInterceptor(key)) { + throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) + } + + this.afterRowTransformInterceptors.push({ + key, + options, + }); + + return this; + } + + public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { + return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; + } + + public add() { + return new DataMappingLeaf(this); + } + + public addArray() {} + + public addNestingLevel() { + return new DataMappingNode(); + } +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts new file mode 100644 index 000000000..454d7ad47 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts @@ -0,0 +1,3 @@ +export * from "./array-data-mapping.node"; +export * from "./data-mapping.node"; +export * from "./data-mapping.tree"; \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.ts b/packages/data-transformer/src/transformers/data-transformer.builder.ts index 4e6619996..49d1b627b 100644 --- a/packages/data-transformer/src/transformers/data-transformer.builder.ts +++ b/packages/data-transformer/src/transformers/data-transformer.builder.ts @@ -21,7 +21,6 @@ export class DataTransformerBuilder { public properties: {[sourceProperty in string]: DataTransformerProperty} = {} - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerBuilder { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); diff --git a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts b/packages/data-transformer/src/transformers/data-transformer.property.spec.ts index 576cf44e4..e28356aa7 100644 --- a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts +++ b/packages/data-transformer/src/transformers/data-transformer.property.spec.ts @@ -64,4 +64,6 @@ describe('Data Transformer Property', () => { it("should properly return the builder when calling end()", () => { expect(dataTransformerProperty.end()).toBe(dataTransformerBuilder); }) + + it("should properly accept a list of sourceProperties", () => {}) }); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.property.ts b/packages/data-transformer/src/transformers/data-transformer.property.ts index 2cd4b51bb..63d85012f 100644 --- a/packages/data-transformer/src/transformers/data-transformer.property.ts +++ b/packages/data-transformer/src/transformers/data-transformer.property.ts @@ -9,6 +9,8 @@ export class DataTransformerProperty { public excludedNormalizers: Set = new Set(); public isOptional: boolean = false; + public children: {[key in (string|number)]: DataTransformerProperty} = {}; + public constructor(private readonly builder: DataTransformerBuilder) { } @@ -21,6 +23,8 @@ export class DataTransformerProperty { return this; } + public setChildProperty(key:) + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerProperty { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) diff --git a/packages/data-transformer/src/transformers/data.transformer.spec.ts b/packages/data-transformer/src/transformers/data.transformer.spec.ts index 6ee221026..691822064 100644 --- a/packages/data-transformer/src/transformers/data.transformer.spec.ts +++ b/packages/data-transformer/src/transformers/data.transformer.spec.ts @@ -221,4 +221,34 @@ describe('Data Transformer', () => { expect(destination.name).toBe("title"); }) + + + it("should properly type the nested objects", async () => { + class Source { + @property() + title: string; + } + + class Destination { + @property() + name: string; + } + + const source = new Source(); + source.title = "TITLE"; + + const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + + const dataTransformerBuilder = new DataTransformerBuilder(); + dataTransformerBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .addNormalizer(LowercaseNormalizer.name) + .end() + + const destination = await dataTransformer.transform(dataTransformerBuilder, source, Destination); + + expect(destination.name).toBe("title"); + }) }); \ No newline at end of file From a9b8cedf937b9a0d6fc570cf3adfc764cbcbab5d Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Wed, 17 Jan 2024 20:43:05 -0800 Subject: [PATCH 03/20] - Continuing progress. --- .../src/builders/data-mapping.builder.spec.ts | 134 ++++++++++++++++++ .../src/builders/data-mapping.builder.ts | 93 +++++++++++- .../src/enums/data-mapping-node-type.enum.ts | 2 +- .../errors/undefined-source-property.error.ts | 21 +++ .../mapping-nodes/array-data-mapping.node.ts | 40 +++++- .../mapping-nodes/base-data-mapping.node.ts | 24 ++++ .../src/mapping-nodes/data-mapping.leaf.ts | 39 +++-- .../src/mapping-nodes/data-mapping.node.ts | 51 +++++-- .../src/mapping-nodes/data-mapping.tree.ts | 79 ----------- .../src/mapping-nodes/mapping-nodes.ts | 2 +- .../transformers/data-transformer.property.ts | 2 - 11 files changed, 381 insertions(+), 106 deletions(-) create mode 100644 packages/data-transformer/src/builders/data-mapping.builder.spec.ts create mode 100644 packages/data-transformer/src/errors/undefined-source-property.error.ts create mode 100644 packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts delete mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts diff --git a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts b/packages/data-transformer/src/builders/data-mapping.builder.spec.ts new file mode 100644 index 000000000..aa3567675 --- /dev/null +++ b/packages/data-transformer/src/builders/data-mapping.builder.spec.ts @@ -0,0 +1,134 @@ +import {DataMappingBuilder} from "./data-mapping.builder"; +import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; +import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; + +describe("Data Mapping Builder", () => { + it("should properly build a simple DataMappingBuilder", () => { + class Source { + title: string; + + rank: number; + + firstName: string; + + lastName: string; + } + + class Destination { + name: string; + + position: number; + + familyName: string; + } + + const dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .add() + .setSourceProperty("rank") + .setDestinationProperty("position") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .add() + .setSourceProperty("lastName") + .setDestinationProperty("familyName") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .end() + + expect(dataMappingBuilder.nodes.title).toBeDefined() + expect(dataMappingBuilder.nodes.title.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.title.destinationProperty).toBe("name") + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.rank).toBeDefined() + expect(dataMappingBuilder.nodes.rank.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.rank.destinationProperty).toBe("position") + expect((dataMappingBuilder.nodes.rank as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.rank as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.lastName).toBeDefined() + expect(dataMappingBuilder.nodes.lastName.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.lastName.destinationProperty).toBe("familyName") + expect((dataMappingBuilder.nodes.lastName as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.lastName as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.firstName).toBeUndefined() + }) + it("should properly build a complex DataMappingBuilder", () => { + class ArraySource { + rank: number; + } + + class NestedSource { + nestedTitle: string; + } + + class Source { + title: string; + + nested: NestedSource; + + array: ArraySource[] = []; + } + + class ArrayDestination { + position: number; + } + + class NestedDestination { + nestedName: string; + } + + class Destination { + name: string; + + child: NestedDestination; + + list: ArrayDestination[]; + } + + const dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .addNestingLevel() + .setSourceProperty("nested") + .setDestinationProperty("child") + .add() + .setSourceProperty("nestedTitle") + .setDestinationProperty("nestedName") + .end() + .end() + .addArray() + .setSourceProperty("array") + .setDestinationProperty("list") + .add() + .setSourceProperty("rank") + .setDestinationProperty("position") + .end() + .end() + .end(); + + expect(dataMappingBuilder.nodes.title).toBeDefined() + expect(dataMappingBuilder.nodes.title.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.title.destinationProperty).toBe("name") + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + + }) +}) \ No newline at end of file diff --git a/packages/data-transformer/src/builders/data-mapping.builder.ts b/packages/data-transformer/src/builders/data-mapping.builder.ts index 9274d4b05..2ebac2023 100644 --- a/packages/data-transformer/src/builders/data-mapping.builder.ts +++ b/packages/data-transformer/src/builders/data-mapping.builder.ts @@ -1,3 +1,94 @@ -export class DataMappingBuilder { +import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import { + DataBeforeRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-before-row-transformer-interceptor-already-added.error"; +import { + DataAfterRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-after-row-transformer-interceptor-already-added.error"; +import {DataTransformerProperty} from "../transformers/data-transformer.property"; +import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; +import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; +import {BaseDataMappingNode} from "../mapping-nodes/base-data-mapping.node"; +import {ArrayDataMappingNode} from "../mapping-nodes/array-data-mapping.node"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; + +export class DataMappingBuilder extends BaseDataMappingNode{ + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; + public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; + + + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingBuilder { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + return this; + } + + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasBeforeRowTransformInterceptor(key)) { + throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) + } + + this.beforeRowTransformInterceptors.push({ + key, + options, + }); + + return this; + } + + public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { + return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; + } + + public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasAfterRowTransformInterceptor(key)) { + throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) + } + + this.afterRowTransformInterceptors.push({ + key, + options, + }); + + return this; + } + + public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { + return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; + } + + public add() { + return new DataMappingLeaf(this, this); + } + + + public addNestingLevel() { + return new DataMappingNode(this, this); + } + + public addArray() { + return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); + } + /** + * This method is called at the end just to make it nice since all the nodes will have one, it's nice + * that the builder has one too. + */ + public end(): DataMappingBuilder { + return this; + } } \ No newline at end of file diff --git a/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts b/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts index 39cf495d4..8a04000b3 100644 --- a/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts +++ b/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts @@ -1,5 +1,5 @@ export enum DataMappingNodeTypeEnum { - ArrayDataMappingNode = "ARRAY_DATA_MAPPING_NODE", + Array = "DATA_MAPPING_NODE_ARRAY", Node = "DATA_MAPPING_NODE", Leaf = "DATA_MAPPING_LEAF", } \ No newline at end of file diff --git a/packages/data-transformer/src/errors/undefined-source-property.error.ts b/packages/data-transformer/src/errors/undefined-source-property.error.ts new file mode 100644 index 000000000..2c1c364b3 --- /dev/null +++ b/packages/data-transformer/src/errors/undefined-source-property.error.ts @@ -0,0 +1,21 @@ +import {LoggableError} from "@pristine-ts/common"; +import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; +import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; +import {ArrayDataMappingNode} from "../mapping-nodes/array-data-mapping.node"; + +/** + * This Error is thrown when you are trying to add a Node which has an undefined sourceProperty value. + */ +export class UndefinedSourcePropertyError extends LoggableError { + + public constructor(node: DataMappingLeaf | DataMappingNode | ArrayDataMappingNode) { + super("The `sourceProperty` property of the Node cannot be undefined to be added as a Node to its parent.", { + node, + }); + + // Set the prototype explicitly. + // As specified in the documentation in TypeScript + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, UndefinedSourcePropertyError.prototype); + } +} diff --git a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts index 7a2a664e0..5ab09d03c 100644 --- a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts +++ b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts @@ -1,12 +1,48 @@ import {DataMappingNode} from "./data-mapping.node"; import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {BaseDataMappingNode} from "./base-data-mapping.node"; /** * We need an array node because the behaviour when mapping an array is different. For each element in the source property, * we will */ -export class ArrayDataMappingNode extends DataMappingNode { - public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.ArrayDataMappingNode; +export class ArrayDataMappingNode extends BaseDataMappingNode{ + public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Array; + public sourceProperty!: string; + public destinationProperty!: string; + constructor(public readonly root: DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder) { + super(); + } + + public setSourceProperty(sourceProperty: string): ArrayDataMappingNode { + this.sourceProperty = sourceProperty; + return this; + } + public setDestinationProperty(destinationProperty: string): ArrayDataMappingNode { + this.destinationProperty = destinationProperty; + return this; + } + + /** + * You don't necessarily have to call the set method. If you have an array of + * simply types: string[] for example, you can simply skip this. The sourceProperty will be directly + * assigned to destinationProperty. + * + * For now, you won't be able to normalize each individual inside of it though. + */ + public set() { + return new DataMappingNode(this.root, this); + } + + + public end(): DataMappingNode | DataMappingBuilder { + this.parent.addNode(this) + + return this.parent; + } } \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts new file mode 100644 index 000000000..16cc88328 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts @@ -0,0 +1,24 @@ +import {DataMappingNode} from "./data-mapping.node"; +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; +import {ArrayDataMappingNode} from "./array-data-mapping.node"; + +export abstract class BaseDataMappingNode { + public nodes: {[sourceProperty in string]: DataMappingNode | DataMappingLeaf} = {}; + + + /** + * This method is called by the node itself to tell its parent that it has been build and is ready to be added. + * We use this mechanism to force the `end()` method on the leaf to be called so we can do some validations before + * adding it to the tree. + * + * @param node + */ + public addNode(node: DataMappingLeaf | DataMappingNode) { + if(node.sourceProperty === undefined) { + throw new UndefinedSourcePropertyError(node); + } + + this.nodes[node.sourceProperty] = node; + } +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts index 927a6fbe7..ebe73fc8a 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts @@ -4,27 +4,36 @@ import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-adde import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {DataMappingNode} from "./data-mapping.node"; -import {DataMappingTree} from "./data-mapping.tree"; export class DataMappingLeaf { public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Leaf; - public sourceProperties: string[] = []; + public sourceProperty!: string; - public destinationProperty: string; + public destinationProperty!: string; public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; public excludedNormalizers: Set = new Set(); - public isOptional: boolean; + public isOptional: boolean = false; public constructor( - private readonly parent: DataMappingNode, - private readonly root: DataMappingTree, + private readonly root: DataMappingBuilder, + private readonly parent: DataMappingNode | DataMappingBuilder, ) { } + public setSourceProperty(sourceProperty: string): DataMappingLeaf { + this.sourceProperty = sourceProperty; + return this; + } + + public setDestinationProperty(destinationProperty: string): DataMappingLeaf { + this.destinationProperty = destinationProperty; + return this; + } + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingLeaf { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) @@ -56,6 +65,19 @@ export class DataMappingLeaf { return this; } + public setIsOptional(isOptional: boolean): DataMappingLeaf { + this.isOptional = isOptional; + + return this; + } + + public end(): DataMappingNode | DataMappingBuilder { + // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. + this.parent.addNode(this) + + return this.parent; + } + public import(schema: any) { this.normalizers = schema.normalizers; @@ -67,13 +89,14 @@ export class DataMappingLeaf { } this.isOptional = schema.isOptional; - this.sourceProperties = schema.sourceProperties; + this.sourceProperty = schema.sourceProperty; this.destinationProperty = schema.destinationProperty; } public export(): any { return { - "sourceProperties": this.sourceProperties, + "_type": this.type, + "sourceProperty": this.sourceProperty, "destinationProperty": this.destinationProperty, "isOptional": this.isOptional, "normalizers": this.normalizers, diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts index 02f7aff76..f06097ad5 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts @@ -1,21 +1,48 @@ import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; import {DataMappingLeaf} from "./data-mapping.leaf"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataTransformerProperty} from "../transformers/data-transformer.property"; +import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; +import {BaseDataMappingNode} from "./base-data-mapping.node"; +import {ArrayDataMappingNode} from "./array-data-mapping.node"; -export class DataMappingNode { - public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node; - public nodes: DataMappingNode[] = []; - public leaves: DataMappingLeaf[] = []; +export class DataMappingNode extends BaseDataMappingNode { + public sourceProperty!: string; + public destinationProperty!: string; - public parent?: DataMappingNode; + constructor(public readonly root: DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder, + public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node, + ) { + super(); + } - constructor(private readonly root: DataMappingTree) { + public setSourceProperty(sourceProperty: string): DataMappingNode { + this.sourceProperty = sourceProperty; + return this; + } + public setDestinationProperty(destinationProperty: string): DataMappingNode { + this.destinationProperty = destinationProperty; + return this; } - /** - * The DataMappingNode can only have one sourceProperty assigned. We need one sourceProperty to understand how to - * navigate through the source object to pass the exact properties to the leaf nodes; - */ - public sourceProperty: string; + public add() { + return new DataMappingLeaf(this.root, this); + } + + public addNestingLevel() { + return new DataMappingNode(this.root, this); + } - public destinationProperty: string; + public addArray(): DataMappingNode { + return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); + } + + public end(): DataMappingNode | DataMappingBuilder { + // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. + this.parent.addNode(this) + + //@ts-ignore + return this.parent; + } } \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts deleted file mode 100644 index 6ea5f6e22..000000000 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {DataMappingNode} from "./data-mapping.node"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import { - DataBeforeRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-before-row-transformer-interceptor-already-added.error"; -import { - DataAfterRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-after-row-transformer-interceptor-already-added.error"; -import {DataTransformerProperty} from "../transformers/data-transformer.property"; -import {DataMappingLeaf} from "./data-mapping.leaf"; - -export class DataMappingTree extends DataMappingNode { - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingTree { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingTree { - if(this.hasBeforeRowTransformInterceptor(key)) { - throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) - } - - this.beforeRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingTree { - if(this.hasAfterRowTransformInterceptor(key)) { - throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) - } - - this.afterRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public add() { - return new DataMappingLeaf(this); - } - - public addArray() {} - - public addNestingLevel() { - return new DataMappingNode(); - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts index 454d7ad47..d9ab55d25 100644 --- a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts +++ b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts @@ -1,3 +1,3 @@ export * from "./array-data-mapping.node"; export * from "./data-mapping.node"; -export * from "./data-mapping.tree"; \ No newline at end of file +export * from "../builders/data-mapping.builder"; \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.property.ts b/packages/data-transformer/src/transformers/data-transformer.property.ts index 63d85012f..ef54b3c4b 100644 --- a/packages/data-transformer/src/transformers/data-transformer.property.ts +++ b/packages/data-transformer/src/transformers/data-transformer.property.ts @@ -23,8 +23,6 @@ export class DataTransformerProperty { return this; } - public setChildProperty(key:) - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerProperty { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) From 3808695557bece8ab213d0a8a3dd0ce3eb936ebb Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 09:02:25 -0800 Subject: [PATCH 04/20] - Added the tests that ensures that the tree is properly built. --- .../data-transformer/src/builders/builders.ts | 1 + .../src/builders/data-mapping.builder.spec.ts | 77 ++++++++++++++++++- .../src/builders/data-mapping.builder.ts | 10 ++- packages/data-transformer/src/enums/enums.ts | 1 + .../data-transformer/src/errors/errors.ts | 4 +- .../errors/undefined-source-property.error.ts | 3 +- .../mapping-nodes/array-data-mapping.node.ts | 48 ------------ .../mapping-nodes/base-data-mapping.node.ts | 2 - .../src/mapping-nodes/data-mapping.leaf.ts | 5 +- .../src/mapping-nodes/data-mapping.node.ts | 9 ++- .../src/mapping-nodes/mapping-nodes.ts | 4 +- .../src/transformers/transformers.ts | 3 +- 12 files changed, 98 insertions(+), 69 deletions(-) delete mode 100644 packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts diff --git a/packages/data-transformer/src/builders/builders.ts b/packages/data-transformer/src/builders/builders.ts index e69de29bb..c9cfed586 100644 --- a/packages/data-transformer/src/builders/builders.ts +++ b/packages/data-transformer/src/builders/builders.ts @@ -0,0 +1 @@ +export * from "./data-mapping.builder"; \ No newline at end of file diff --git a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts b/packages/data-transformer/src/builders/data-mapping.builder.spec.ts index aa3567675..5682f7fd7 100644 --- a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts +++ b/packages/data-transformer/src/builders/data-mapping.builder.spec.ts @@ -79,6 +79,8 @@ describe("Data Mapping Builder", () => { nested: NestedSource; array: ArraySource[] = []; + + children: string[] = []; } class ArrayDestination { @@ -95,6 +97,8 @@ describe("Data Mapping Builder", () => { child: NestedDestination; list: ArrayDestination[]; + + infants: string[] = [] } const dataMappingBuilder = new DataMappingBuilder(); @@ -113,7 +117,7 @@ describe("Data Mapping Builder", () => { .setDestinationProperty("nestedName") .end() .end() - .addArray() + .addArrayOfObjects() .setSourceProperty("array") .setDestinationProperty("list") .add() @@ -121,6 +125,10 @@ describe("Data Mapping Builder", () => { .setDestinationProperty("position") .end() .end() + .addArrayOfScalar() + .setSourceProperty("children") + .setDestinationProperty("infants") + .end() .end(); expect(dataMappingBuilder.nodes.title).toBeDefined() @@ -129,6 +137,73 @@ describe("Data Mapping Builder", () => { expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.size).toBe(1) expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + expect(dataMappingBuilder.nodes.nested).toBeDefined() + expect(dataMappingBuilder.nodes.nested.type).toBe(DataMappingNodeTypeEnum.Node) + expect(dataMappingBuilder.nodes.nested.destinationProperty).toBe("child") + expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nestedTitle.sourceProperty).toBe("nestedTitle") + expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nestedTitle.destinationProperty).toBe("nestedName") + + expect(dataMappingBuilder.nodes.array).toBeDefined() + expect(dataMappingBuilder.nodes.array.type).toBe(DataMappingNodeTypeEnum.Array) + expect(dataMappingBuilder.nodes.array.destinationProperty).toBe("list") + expect((dataMappingBuilder.nodes.array as DataMappingNode).nodes.rank.sourceProperty).toBe("rank") + expect((dataMappingBuilder.nodes.array as DataMappingNode).nodes.rank.destinationProperty).toBe("position") + + expect(dataMappingBuilder.nodes.children).toBeDefined() + expect(dataMappingBuilder.nodes.children.type).toBe(DataMappingNodeTypeEnum.Array) + expect(dataMappingBuilder.nodes.children.destinationProperty).toBe("infants") + expect((dataMappingBuilder.nodes.children as DataMappingLeaf).sourceProperty).toBe("children") + expect((dataMappingBuilder.nodes.children as DataMappingLeaf).destinationProperty).toBe("infants") + + }) + + it("should properly set the parents", () => { + class NestedSource2 { + nestedTitle2: string; + } + + class NestedSource { + nested2: NestedSource2; + } + + class Source { + nested: NestedSource; + } + + class NestedDestination2 { + nestedTitle2: string; + } + class NestedDestination { + nested2: NestedDestination2; + } + + class Destination { + nested: NestedDestination; + } + + const dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .addNestingLevel() + .setSourceProperty("nested") + .setDestinationProperty("nested") + .addNestingLevel() + .setSourceProperty("nested2") + .setDestinationProperty("nested2") + .add() + .setSourceProperty("nestedTitle2") + .setDestinationProperty("nestedTitle2") + .end() + .end() + .end() + .end(); + + expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2.sourceProperty).toBe("nested2"); + expect(((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2 as DataMappingNode).nodes.nestedTitle2.sourceProperty).toBe("nestedTitle2"); + + expect((((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2 as DataMappingNode).parent as DataMappingNode).sourceProperty).toBe("nested"); + + expect(((((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2 as DataMappingNode).nodes.nestedTitle2 as DataMappingLeaf).parent as DataMappingNode).sourceProperty).toBe("nested2"); }) }) \ No newline at end of file diff --git a/packages/data-transformer/src/builders/data-mapping.builder.ts b/packages/data-transformer/src/builders/data-mapping.builder.ts index 2ebac2023..21559c163 100644 --- a/packages/data-transformer/src/builders/data-mapping.builder.ts +++ b/packages/data-transformer/src/builders/data-mapping.builder.ts @@ -8,11 +8,8 @@ import { import { DataAfterRowTransformerInterceptorAlreadyAddedError } from "../errors/data-after-row-transformer-interceptor-already-added.error"; -import {DataTransformerProperty} from "../transformers/data-transformer.property"; import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; -import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; import {BaseDataMappingNode} from "../mapping-nodes/base-data-mapping.node"; -import {ArrayDataMappingNode} from "../mapping-nodes/array-data-mapping.node"; import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; export class DataMappingBuilder extends BaseDataMappingNode{ @@ -80,9 +77,14 @@ export class DataMappingBuilder extends BaseDataMappingNode{ return new DataMappingNode(this, this); } - public addArray() { + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.Array); + } + + public addArrayOfObjects(): DataMappingNode { return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); } + /** * This method is called at the end just to make it nice since all the nodes will have one, it's nice * that the builder has one too. diff --git a/packages/data-transformer/src/enums/enums.ts b/packages/data-transformer/src/enums/enums.ts index e69de29bb..d8e6e1a82 100644 --- a/packages/data-transformer/src/enums/enums.ts +++ b/packages/data-transformer/src/enums/enums.ts @@ -0,0 +1 @@ +export * from "./data-mapping-node-type.enum"; \ No newline at end of file diff --git a/packages/data-transformer/src/errors/errors.ts b/packages/data-transformer/src/errors/errors.ts index 2e08cba08..742001c2d 100644 --- a/packages/data-transformer/src/errors/errors.ts +++ b/packages/data-transformer/src/errors/errors.ts @@ -1,5 +1,7 @@ export * from "./data-after-row-transformer-interceptor-already-added.error"; export * from "./data-before-row-transformer-interceptor-already-added.error"; export * from "./data-normalizer-already-added.error"; +export * from "./data-transformer-interceptor-not-found.error"; export * from "./data-transformer-source-property-not-found.error"; -export * from "./normalizer-invalid-source-type.error"; \ No newline at end of file +export * from "./normalizer-invalid-source-type.error"; +export * from "./undefined-source-property.error"; \ No newline at end of file diff --git a/packages/data-transformer/src/errors/undefined-source-property.error.ts b/packages/data-transformer/src/errors/undefined-source-property.error.ts index 2c1c364b3..fd6cba445 100644 --- a/packages/data-transformer/src/errors/undefined-source-property.error.ts +++ b/packages/data-transformer/src/errors/undefined-source-property.error.ts @@ -1,14 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; -import {ArrayDataMappingNode} from "../mapping-nodes/array-data-mapping.node"; /** * This Error is thrown when you are trying to add a Node which has an undefined sourceProperty value. */ export class UndefinedSourcePropertyError extends LoggableError { - public constructor(node: DataMappingLeaf | DataMappingNode | ArrayDataMappingNode) { + public constructor(node: DataMappingLeaf | DataMappingNode) { super("The `sourceProperty` property of the Node cannot be undefined to be added as a Node to its parent.", { node, }); diff --git a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts deleted file mode 100644 index 5ab09d03c..000000000 --- a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {DataMappingNode} from "./data-mapping.node"; -import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; -import {DataMappingBuilder} from "../builders/data-mapping.builder"; -import {DataMappingLeaf} from "./data-mapping.leaf"; -import {BaseDataMappingNode} from "./base-data-mapping.node"; - -/** - * We need an array node because the behaviour when mapping an array is different. For each element in the source property, - * we will - */ -export class ArrayDataMappingNode extends BaseDataMappingNode{ - public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Array; - - public sourceProperty!: string; - public destinationProperty!: string; - - constructor(public readonly root: DataMappingBuilder, - public readonly parent: DataMappingNode | DataMappingBuilder) { - super(); - } - - public setSourceProperty(sourceProperty: string): ArrayDataMappingNode { - this.sourceProperty = sourceProperty; - return this; - } - public setDestinationProperty(destinationProperty: string): ArrayDataMappingNode { - this.destinationProperty = destinationProperty; - return this; - } - - /** - * You don't necessarily have to call the set method. If you have an array of - * simply types: string[] for example, you can simply skip this. The sourceProperty will be directly - * assigned to destinationProperty. - * - * For now, you won't be able to normalize each individual inside of it though. - */ - public set() { - return new DataMappingNode(this.root, this); - } - - - public end(): DataMappingNode | DataMappingBuilder { - this.parent.addNode(this) - - return this.parent; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts index 16cc88328..15551b632 100644 --- a/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts +++ b/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts @@ -1,12 +1,10 @@ import {DataMappingNode} from "./data-mapping.node"; import {DataMappingLeaf} from "./data-mapping.leaf"; import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; -import {ArrayDataMappingNode} from "./array-data-mapping.node"; export abstract class BaseDataMappingNode { public nodes: {[sourceProperty in string]: DataMappingNode | DataMappingLeaf} = {}; - /** * This method is called by the node itself to tell its parent that it has been build and is ready to be added. * We use this mechanism to force the `end()` method on the leaf to be called so we can do some validations before diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts index ebe73fc8a..6f3aec19e 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts @@ -6,8 +6,6 @@ import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {DataMappingNode} from "./data-mapping.node"; export class DataMappingLeaf { - public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Leaf; - public sourceProperty!: string; public destinationProperty!: string; @@ -20,7 +18,8 @@ export class DataMappingLeaf { public constructor( private readonly root: DataMappingBuilder, - private readonly parent: DataMappingNode | DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder, + public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Leaf, ) { } diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts index f06097ad5..fc8588f3e 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts @@ -1,10 +1,7 @@ import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; import {DataMappingLeaf} from "./data-mapping.leaf"; import {DataMappingBuilder} from "../builders/data-mapping.builder"; -import {DataTransformerProperty} from "../transformers/data-transformer.property"; -import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; import {BaseDataMappingNode} from "./base-data-mapping.node"; -import {ArrayDataMappingNode} from "./array-data-mapping.node"; export class DataMappingNode extends BaseDataMappingNode { public sourceProperty!: string; @@ -34,7 +31,11 @@ export class DataMappingNode extends BaseDataMappingNode { return new DataMappingNode(this.root, this); } - public addArray(): DataMappingNode { + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.Array); + } + + public addArrayOfObjects(): DataMappingNode { return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); } diff --git a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts index d9ab55d25..7a31c999f 100644 --- a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts +++ b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts @@ -1,3 +1,3 @@ -export * from "./array-data-mapping.node"; +export * from "./base-data-mapping.node"; +export * from "./data-mapping.leaf"; export * from "./data-mapping.node"; -export * from "../builders/data-mapping.builder"; \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/transformers.ts b/packages/data-transformer/src/transformers/transformers.ts index 458cda5bd..3f6f77c90 100644 --- a/packages/data-transformer/src/transformers/transformers.ts +++ b/packages/data-transformer/src/transformers/transformers.ts @@ -1,4 +1,3 @@ +export * from "./data.transformer"; export * from "./data-transformer.builder"; export * from "./data-transformer.property"; - -export * from "./data.transformer"; \ No newline at end of file From e20df00bdbddee9c93f829e74f85add0f2f0ed6a Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 13:12:32 -0800 Subject: [PATCH 05/20] - Updated the naming from 'transformer' to 'mapping' --- package.json | 2 +- .../package-lock.json | 0 .../package.json | 11 +- packages/data-mapping/readme.md | 1 + .../src/builders/builders.ts | 0 .../src/builders/data-mapping.builder.spec.ts | 4 +- .../src/builders/data-mapping.builder.ts | 143 ++++++++++++++++ .../src/data-mapping.module.keyname.ts | 1 + .../src/data-mapping.module.ts} | 13 +- .../src/enums/data-mapping-node-type.enum.ts | 0 .../src/enums/enums.ts | 0 ...sformer-interceptor-already-added.error.ts | 4 +- ...sformer-interceptor-already-added.error.ts | 4 +- .../data-normalizer-already-added.error.ts | 0 ...transformer-interceptor-not-found.error.ts | 4 +- ...sformer-source-property-not-found.error.ts | 0 .../src/errors/errors.ts | 0 .../normalizer-invalid-source-type.error.ts | 0 .../errors/undefined-source-property.error.ts | 4 +- .../default-data-mapping.interceptor.ts | 22 +++ .../src/interceptors/interceptors.ts | 1 + .../data-mapping-interceptor.interface.ts | 22 +++ .../interfaces/data-normalizer.interface.ts | 0 .../data-mapping/src/interfaces/interfaces.ts | 2 + .../src/mappers/data.mapper.spec.ts} | 114 ++++++------- .../data-mapping/src/mappers/data.mapper.ts | 95 +++++++++++ packages/data-mapping/src/mappers/mappers.ts | 1 + .../src/nodes}/base-data-mapping.node.ts | 0 .../src/nodes/data-mapping.leaf.spec.ts | 58 +++++++ .../src/nodes}/data-mapping.leaf.ts | 98 ++++++++++- .../src/nodes/data-mapping.node.ts | 132 +++++++++++++++ .../src/nodes/nodes.ts} | 0 .../base-normalizer.options.ts | 0 .../lowercase-normalizer.options.ts | 0 .../normalizer-options/normalizer-options.ts | 0 .../normalizers/lowercase.normalizer.spec.ts | 0 .../src/normalizers/lowercase.normalizer.ts | 0 .../src/normalizers/normalizers.ts | 0 .../src/test.spec.ts | 0 ...ata-mapping-interceptor-unique-key.type.ts | 1 + .../types/data-normalizer-unique-key.type.ts | 0 packages/data-mapping/src/types/types.ts | 2 + .../tsconfig.cjs.json | 0 .../tsconfig.json | 0 packages/data-transformer/readme.md | 1 - .../src/builders/data-mapping.builder.ts | 96 ----------- .../src/data-transformer.module.keyname.ts | 1 - .../default-data-transformer.interceptor.ts | 23 --- .../src/interceptors/interceptors.ts | 1 - .../data-transformer-interceptor.interface.ts | 24 --- .../src/interfaces/interfaces.ts | 2 - .../src/mapping-nodes/data-mapping.node.ts | 49 ------ .../data-transformer.builder.spec.ts | 131 --------------- .../transformers/data-transformer.builder.ts | 156 ------------------ .../data-transformer.property.spec.ts | 69 -------- .../transformers/data-transformer.property.ts | 69 -------- .../src/transformers/data.transformer.ts | 104 ------------ .../src/transformers/transformers.ts | 3 - ...transformer-interceptor-unique-key.type.ts | 1 - .../src/types/data-transformer.row.ts | 1 - packages/data-transformer/src/types/types.ts | 3 - 61 files changed, 654 insertions(+), 819 deletions(-) rename packages/{data-transformer => data-mapping}/package-lock.json (100%) rename packages/{data-transformer => data-mapping}/package.json (83%) create mode 100644 packages/data-mapping/readme.md rename packages/{data-transformer => data-mapping}/src/builders/builders.ts (100%) rename packages/{data-transformer => data-mapping}/src/builders/data-mapping.builder.spec.ts (98%) create mode 100644 packages/data-mapping/src/builders/data-mapping.builder.ts create mode 100644 packages/data-mapping/src/data-mapping.module.keyname.ts rename packages/{data-transformer/src/data-transformer.module.ts => data-mapping/src/data-mapping.module.ts} (55%) rename packages/{data-transformer => data-mapping}/src/enums/data-mapping-node-type.enum.ts (100%) rename packages/{data-transformer => data-mapping}/src/enums/enums.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/data-after-row-transformer-interceptor-already-added.error.ts (76%) rename packages/{data-transformer => data-mapping}/src/errors/data-before-row-transformer-interceptor-already-added.error.ts (76%) rename packages/{data-transformer => data-mapping}/src/errors/data-normalizer-already-added.error.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/data-transformer-interceptor-not-found.error.ts (76%) rename packages/{data-transformer => data-mapping}/src/errors/data-transformer-source-property-not-found.error.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/errors.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/normalizer-invalid-source-type.error.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/undefined-source-property.error.ts (84%) create mode 100644 packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts create mode 100644 packages/data-mapping/src/interceptors/interceptors.ts create mode 100644 packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts rename packages/{data-transformer => data-mapping}/src/interfaces/data-normalizer.interface.ts (100%) create mode 100644 packages/data-mapping/src/interfaces/interfaces.ts rename packages/{data-transformer/src/transformers/data.transformer.spec.ts => data-mapping/src/mappers/data.mapper.spec.ts} (70%) create mode 100644 packages/data-mapping/src/mappers/data.mapper.ts create mode 100644 packages/data-mapping/src/mappers/mappers.ts rename packages/{data-transformer/src/mapping-nodes => data-mapping/src/nodes}/base-data-mapping.node.ts (100%) create mode 100644 packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts rename packages/{data-transformer/src/mapping-nodes => data-mapping/src/nodes}/data-mapping.leaf.ts (56%) create mode 100644 packages/data-mapping/src/nodes/data-mapping.node.ts rename packages/{data-transformer/src/mapping-nodes/mapping-nodes.ts => data-mapping/src/nodes/nodes.ts} (100%) rename packages/{data-transformer => data-mapping}/src/normalizer-options/base-normalizer.options.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizer-options/lowercase-normalizer.options.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizer-options/normalizer-options.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizers/lowercase.normalizer.spec.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizers/lowercase.normalizer.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizers/normalizers.ts (100%) rename packages/{data-transformer => data-mapping}/src/test.spec.ts (100%) create mode 100644 packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts rename packages/{data-transformer => data-mapping}/src/types/data-normalizer-unique-key.type.ts (100%) create mode 100644 packages/data-mapping/src/types/types.ts rename packages/{data-transformer => data-mapping}/tsconfig.cjs.json (100%) rename packages/{data-transformer => data-mapping}/tsconfig.json (100%) delete mode 100644 packages/data-transformer/readme.md delete mode 100644 packages/data-transformer/src/builders/data-mapping.builder.ts delete mode 100644 packages/data-transformer/src/data-transformer.module.keyname.ts delete mode 100644 packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts delete mode 100644 packages/data-transformer/src/interceptors/interceptors.ts delete mode 100644 packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts delete mode 100644 packages/data-transformer/src/interfaces/interfaces.ts delete mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.node.ts delete mode 100644 packages/data-transformer/src/transformers/data-transformer.builder.spec.ts delete mode 100644 packages/data-transformer/src/transformers/data-transformer.builder.ts delete mode 100644 packages/data-transformer/src/transformers/data-transformer.property.spec.ts delete mode 100644 packages/data-transformer/src/transformers/data-transformer.property.ts delete mode 100644 packages/data-transformer/src/transformers/data.transformer.ts delete mode 100644 packages/data-transformer/src/transformers/transformers.ts delete mode 100644 packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts delete mode 100644 packages/data-transformer/src/types/data-transformer.row.ts delete mode 100644 packages/data-transformer/src/types/types.ts diff --git a/package.json b/package.json index 6e68b1d64..bf0ed758f 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@pristine-ts/common": "file:packages/common", "@pristine-ts/configuration": "file:packages/configuration", "@pristine-ts/core": "file:packages/core", - "@pristine-ts/data-transformer": "file:packages/data-transformer", + "@pristine-ts/data-mapping": "file:packages/data-mapping", "@pristine-ts/e2e": "file:tests/e2e", "@pristine-ts/express": "file:packages/express", "@pristine-ts/file": "file:packages/file", diff --git a/packages/data-transformer/package-lock.json b/packages/data-mapping/package-lock.json similarity index 100% rename from packages/data-transformer/package-lock.json rename to packages/data-mapping/package-lock.json diff --git a/packages/data-transformer/package.json b/packages/data-mapping/package.json similarity index 83% rename from packages/data-transformer/package.json rename to packages/data-mapping/package.json index d3466a1f4..54fa52cd5 100644 --- a/packages/data-transformer/package.json +++ b/packages/data-mapping/package.json @@ -1,10 +1,10 @@ { - "name": "@pristine-ts/data-transformer", + "name": "@pristine-ts/data-mapper", "version": "0.0.276", "description": "", - "module": "dist/lib/esm/data-transformer.module.js", - "main": "dist/lib/cjs/data-transformer.module.js", - "types": "dist/types/data-transformer.module.d.ts", + "module": "dist/lib/esm/data-mapper.module.js", + "main": "dist/lib/cjs/data-mapper.module.js", + "types": "dist/types/data-mapper.module.d.ts", "scripts": { "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json", "prepublish": "npm run build", @@ -23,7 +23,8 @@ "@pristine-ts/common": "file:../common", "@pristine-ts/logging": "file:../logging", "@pristine-ts/metadata": "~1.0.2", - "class-transformer": "^0.5.1" + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" }, "jest": { "transform": { diff --git a/packages/data-mapping/readme.md b/packages/data-mapping/readme.md new file mode 100644 index 000000000..ec59df714 --- /dev/null +++ b/packages/data-mapping/readme.md @@ -0,0 +1 @@ +# Data Mapping module. \ No newline at end of file diff --git a/packages/data-transformer/src/builders/builders.ts b/packages/data-mapping/src/builders/builders.ts similarity index 100% rename from packages/data-transformer/src/builders/builders.ts rename to packages/data-mapping/src/builders/builders.ts diff --git a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts b/packages/data-mapping/src/builders/data-mapping.builder.spec.ts similarity index 98% rename from packages/data-transformer/src/builders/data-mapping.builder.spec.ts rename to packages/data-mapping/src/builders/data-mapping.builder.spec.ts index 5682f7fd7..2588dafcd 100644 --- a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts +++ b/packages/data-mapping/src/builders/data-mapping.builder.spec.ts @@ -1,8 +1,8 @@ import {DataMappingBuilder} from "./data-mapping.builder"; import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; -import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; -import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; +import {DataMappingNode} from "../nodes/data-mapping.node"; +import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; describe("Data Mapping Builder", () => { it("should properly build a simple DataMappingBuilder", () => { diff --git a/packages/data-mapping/src/builders/data-mapping.builder.ts b/packages/data-mapping/src/builders/data-mapping.builder.ts new file mode 100644 index 000000000..e678ba8a4 --- /dev/null +++ b/packages/data-mapping/src/builders/data-mapping.builder.ts @@ -0,0 +1,143 @@ +import {DataMappingNode} from "../nodes/data-mapping.node"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; +import { + DataBeforeRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-before-row-transformer-interceptor-already-added.error"; +import { + DataAfterRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-after-row-transformer-interceptor-already-added.error"; +import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; +import {BaseDataMappingNode} from "../nodes/base-data-mapping.node"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; + +export class DataMappingBuilder extends BaseDataMappingNode{ + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + public beforeMappingInterceptors: { key: DataMappingInterceptorUniqueKeyType, options: any}[] = []; + public afterMappingInterceptors: { key: DataMappingInterceptorUniqueKeyType, options: any}[] = []; + + /** + * This method adds a normalizer to the root that will be applied on each node (unless they explicitly exclude to do + * so). + * + * @param normalizerUniqueKey + * @param options + */ + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingBuilder { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + return this; + } + + /** + * This method returns whether there's a normalizer for the specified key or not. + * + * @param normalizerUniqueKey + */ + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + /** + * This method adds an interceptor that will be executed **before** the object is mapped. + * + * @param key + * @param options + */ + public addBeforeMappingInterceptor(key: DataMappingInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasBeforeMappingInterceptor(key)) { + throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) + } + + this.beforeMappingInterceptors.push({ + key, + options, + }); + + return this; + } + + /** + * This method returns whether a **before** interceptor already exists. + * @param key + */ + public hasBeforeMappingInterceptor(key: DataMappingInterceptorUniqueKeyType): boolean { + return this.beforeMappingInterceptors.find(element => element.key === key) !== undefined; + } + + /** + * This method adds an interceptor that will be executed **after** the object is mapped. + * + * @param key + * @param options + */ + public addAfterMappingInterceptor(key: DataMappingInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasAfterMappingInterceptor(key)) { + throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) + } + + this.afterMappingInterceptors.push({ + key, + options, + }); + + return this; + } + + /** + * This method returns whether a **after** interceptor already exists. + * @param key + */ + public hasAfterMappingInterceptor(key: DataMappingInterceptorUniqueKeyType): boolean { + return this.afterMappingInterceptors.find(element => element.key === key) !== undefined; + } + + /** + * This property creates a new DataMappingLeaf and returns it. It doesn't add it yet. To do so, the `end()` method + * must be called. + */ + public add() { + return new DataMappingLeaf(this, this); + } + + /** + * This method adds a nesting level. This should be used when the property contains an object and you want to map + * this object into another object. + */ + public addNestingLevel() { + return new DataMappingNode(this, this); + } + + /** + * This method adds an array of Scalar allowing you to apply the normalizer on each scalar in the array. The + * `sourceProperty` and `destinationProperty` correspond to the name of the property that is an array. But, the + * values in the array will be normalized using the normalizer. + * + */ + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.Array); + } + + /** + * This method adds an array of objects allowing to define a node for each property in the object. Each object in + * the array will be treated as being the same. + */ + public addArrayOfObjects(): DataMappingNode { + return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); + } + + /** + * This method is called at the end just to make it nice since all the nodes will have one, it's nice + * that the builder has one too. + */ + public end(): DataMappingBuilder { + return this; + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/data-mapping.module.keyname.ts b/packages/data-mapping/src/data-mapping.module.keyname.ts new file mode 100644 index 000000000..09ad80b0f --- /dev/null +++ b/packages/data-mapping/src/data-mapping.module.keyname.ts @@ -0,0 +1 @@ +export const DataMappingModuleKeyname: string = "pristine.data-transformer"; diff --git a/packages/data-transformer/src/data-transformer.module.ts b/packages/data-mapping/src/data-mapping.module.ts similarity index 55% rename from packages/data-transformer/src/data-transformer.module.ts rename to packages/data-mapping/src/data-mapping.module.ts index 5d4ab2b1f..faf05f0ae 100644 --- a/packages/data-transformer/src/data-transformer.module.ts +++ b/packages/data-mapping/src/data-mapping.module.ts @@ -1,19 +1,20 @@ import {ModuleInterface} from "@pristine-ts/common"; import {LoggingModule} from "@pristine-ts/logging"; -import {DataTransformerModuleKeyname} from "./data-transformer.module.keyname"; -import { EnvironmentVariableResolver, NumberResolver} from "@pristine-ts/configuration"; -import {DataTransformerBuilder} from "./transformers/data-transformer.builder"; +import {DataMappingModuleKeyname} from "./data-mapping.module.keyname"; +export * from "./builders/builders"; +export * from "./enums/enums"; export * from "./errors/errors"; export * from "./interceptors/interceptors"; export * from "./interfaces/interfaces"; +export * from "./mappers/mappers"; +export * from "./nodes/nodes"; export * from "./normalizer-options/normalizer-options"; export * from "./normalizers/normalizers"; -export * from "./transformers/transformers"; export * from "./types/types"; -export const DataTransformerModule: ModuleInterface = { - keyname: DataTransformerModuleKeyname, +export const DataMappingModule: ModuleInterface = { + keyname: DataMappingModuleKeyname, importModules: [ LoggingModule, ], diff --git a/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts b/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts similarity index 100% rename from packages/data-transformer/src/enums/data-mapping-node-type.enum.ts rename to packages/data-mapping/src/enums/data-mapping-node-type.enum.ts diff --git a/packages/data-transformer/src/enums/enums.ts b/packages/data-mapping/src/enums/enums.ts similarity index 100% rename from packages/data-transformer/src/enums/enums.ts rename to packages/data-mapping/src/enums/enums.ts diff --git a/packages/data-transformer/src/errors/data-after-row-transformer-interceptor-already-added.error.ts b/packages/data-mapping/src/errors/data-after-row-transformer-interceptor-already-added.error.ts similarity index 76% rename from packages/data-transformer/src/errors/data-after-row-transformer-interceptor-already-added.error.ts rename to packages/data-mapping/src/errors/data-after-row-transformer-interceptor-already-added.error.ts index 576d3f6c4..1dab18212 100644 --- a/packages/data-transformer/src/errors/data-after-row-transformer-interceptor-already-added.error.ts +++ b/packages/data-mapping/src/errors/data-after-row-transformer-interceptor-already-added.error.ts @@ -1,13 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {Request} from "@pristine-ts/common"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; /** * This Error is thrown when the before row interceptor is added more than once to the builder. */ export class DataAfterRowTransformerInterceptorAlreadyAddedError extends LoggableError { - public constructor(message: string, uniqueKey: DataTransformerInterceptorUniqueKeyType, options?: any) { + public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { uniqueKey, options, diff --git a/packages/data-transformer/src/errors/data-before-row-transformer-interceptor-already-added.error.ts b/packages/data-mapping/src/errors/data-before-row-transformer-interceptor-already-added.error.ts similarity index 76% rename from packages/data-transformer/src/errors/data-before-row-transformer-interceptor-already-added.error.ts rename to packages/data-mapping/src/errors/data-before-row-transformer-interceptor-already-added.error.ts index 0cf89a075..716211e51 100644 --- a/packages/data-transformer/src/errors/data-before-row-transformer-interceptor-already-added.error.ts +++ b/packages/data-mapping/src/errors/data-before-row-transformer-interceptor-already-added.error.ts @@ -1,13 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {Request} from "@pristine-ts/common"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; /** * This Error is thrown when the after row interceptor is added more than once to the builder. */ export class DataBeforeRowTransformerInterceptorAlreadyAddedError extends LoggableError { - public constructor(message: string, uniqueKey: DataTransformerInterceptorUniqueKeyType, options?: any) { + public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { uniqueKey, options, diff --git a/packages/data-transformer/src/errors/data-normalizer-already-added.error.ts b/packages/data-mapping/src/errors/data-normalizer-already-added.error.ts similarity index 100% rename from packages/data-transformer/src/errors/data-normalizer-already-added.error.ts rename to packages/data-mapping/src/errors/data-normalizer-already-added.error.ts diff --git a/packages/data-transformer/src/errors/data-transformer-interceptor-not-found.error.ts b/packages/data-mapping/src/errors/data-transformer-interceptor-not-found.error.ts similarity index 76% rename from packages/data-transformer/src/errors/data-transformer-interceptor-not-found.error.ts rename to packages/data-mapping/src/errors/data-transformer-interceptor-not-found.error.ts index 766a827e9..0589fa6ce 100644 --- a/packages/data-transformer/src/errors/data-transformer-interceptor-not-found.error.ts +++ b/packages/data-mapping/src/errors/data-transformer-interceptor-not-found.error.ts @@ -1,13 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {Request} from "@pristine-ts/common"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; /** * This Error is thrown if the Data Transformer Class is not found in the list of available interceptors. It might be missing a tag. */ export class DataTransformerInterceptorNotFoundError extends LoggableError { - public constructor(message: string, uniqueKey: DataTransformerInterceptorUniqueKeyType, options?: any) { + public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { uniqueKey, options, diff --git a/packages/data-transformer/src/errors/data-transformer-source-property-not-found.error.ts b/packages/data-mapping/src/errors/data-transformer-source-property-not-found.error.ts similarity index 100% rename from packages/data-transformer/src/errors/data-transformer-source-property-not-found.error.ts rename to packages/data-mapping/src/errors/data-transformer-source-property-not-found.error.ts diff --git a/packages/data-transformer/src/errors/errors.ts b/packages/data-mapping/src/errors/errors.ts similarity index 100% rename from packages/data-transformer/src/errors/errors.ts rename to packages/data-mapping/src/errors/errors.ts diff --git a/packages/data-transformer/src/errors/normalizer-invalid-source-type.error.ts b/packages/data-mapping/src/errors/normalizer-invalid-source-type.error.ts similarity index 100% rename from packages/data-transformer/src/errors/normalizer-invalid-source-type.error.ts rename to packages/data-mapping/src/errors/normalizer-invalid-source-type.error.ts diff --git a/packages/data-transformer/src/errors/undefined-source-property.error.ts b/packages/data-mapping/src/errors/undefined-source-property.error.ts similarity index 84% rename from packages/data-transformer/src/errors/undefined-source-property.error.ts rename to packages/data-mapping/src/errors/undefined-source-property.error.ts index fd6cba445..e4d21cea7 100644 --- a/packages/data-transformer/src/errors/undefined-source-property.error.ts +++ b/packages/data-mapping/src/errors/undefined-source-property.error.ts @@ -1,6 +1,6 @@ import {LoggableError} from "@pristine-ts/common"; -import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; -import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; +import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; +import {DataMappingNode} from "../nodes/data-mapping.node"; /** * This Error is thrown when you are trying to add a Node which has an undefined sourceProperty value. diff --git a/packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts b/packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts new file mode 100644 index 000000000..70021fd86 --- /dev/null +++ b/packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts @@ -0,0 +1,22 @@ +import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; +import {moduleScoped, tag} from "@pristine-ts/common"; +import {DataMappingModuleKeyname} from "../data-mapping.module.keyname"; +import {injectable} from "tsyringe"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; + +@tag("DataTransformerInterceptor") +@moduleScoped(DataMappingModuleKeyname) +@injectable() +export class DefaultDataMappingInterceptor implements DataMappingInterceptorInterface { + async afterMapping(row: any): Promise { + return row; + } + + async beforeMapping(row: any): Promise { + return row; + } + + getUniqueKey(): DataMappingInterceptorUniqueKeyType { + return DefaultDataMappingInterceptor.name; + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/interceptors/interceptors.ts b/packages/data-mapping/src/interceptors/interceptors.ts new file mode 100644 index 000000000..e97b987e1 --- /dev/null +++ b/packages/data-mapping/src/interceptors/interceptors.ts @@ -0,0 +1 @@ +export * from "./default-data-mapping.interceptor"; \ No newline at end of file diff --git a/packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts b/packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts new file mode 100644 index 000000000..143372998 --- /dev/null +++ b/packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts @@ -0,0 +1,22 @@ +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; + +export interface DataMappingInterceptorInterface { + /** + * Every data mapping interceptor must define a unique key. Then, during the mapping, the schema can specify which + * interceptors must be called. + */ + getUniqueKey(): DataMappingInterceptorUniqueKeyType; + + /** + * This method is called before the row is being mapped and normalized. It allows you to combine fields for example if that's what you want. + * @param row + */ + beforeMapping(row: any): Promise; + + /** + * This method is called after the row is being mapped and normalized. It can allow you to apply operations on each + * field or combine fields for example. + * @param row + */ + afterMapping(row: any): Promise; +} \ No newline at end of file diff --git a/packages/data-transformer/src/interfaces/data-normalizer.interface.ts b/packages/data-mapping/src/interfaces/data-normalizer.interface.ts similarity index 100% rename from packages/data-transformer/src/interfaces/data-normalizer.interface.ts rename to packages/data-mapping/src/interfaces/data-normalizer.interface.ts diff --git a/packages/data-mapping/src/interfaces/interfaces.ts b/packages/data-mapping/src/interfaces/interfaces.ts new file mode 100644 index 000000000..224b612f3 --- /dev/null +++ b/packages/data-mapping/src/interfaces/interfaces.ts @@ -0,0 +1,2 @@ +export * from "./data-normalizer.interface"; +export * from "./data-mapping-interceptor.interface"; \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data.transformer.spec.ts b/packages/data-mapping/src/mappers/data.mapper.spec.ts similarity index 70% rename from packages/data-transformer/src/transformers/data.transformer.spec.ts rename to packages/data-mapping/src/mappers/data.mapper.spec.ts index 691822064..dda706709 100644 --- a/packages/data-transformer/src/transformers/data.transformer.spec.ts +++ b/packages/data-mapping/src/mappers/data.mapper.spec.ts @@ -1,16 +1,18 @@ -import "reflect-metadata" -import {DataTransformer} from "./data.transformer"; +import {DataTransformer} from "../transformers/data.transformer"; import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; -import {DataTransformerBuilder} from "./data-transformer.builder"; -import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; +import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; import {DataTransformerRow} from "../types/data-transformer.row"; -import 'jest-extended'; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; import {property} from "@pristine-ts/metadata"; -describe('Data Transformer', () => { +describe("Data Mapper", () =>{ + it("should map a very complex object into another complex object. Then, it should export the builder, import the builder and make sure it still maps everything properly.", async () => { + + }) + it("should properly transform", async () => { const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); @@ -23,18 +25,18 @@ describe('Data Transformer', () => { const dataTransformerBuilder = new DataTransformerBuilder(); dataTransformerBuilder .add() - .setSourceProperty("NAME") - .setDestinationProperty("name") - .end() + .setSourceProperty("NAME") + .setDestinationProperty("name") + .end() .add() - .setSourceProperty("province") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() + .setSourceProperty("province") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") + .end() .add() - .setSourceProperty("TOTAL") - .setDestinationProperty("total") - .end(); + .setSourceProperty("TOTAL") + .setDestinationProperty("total") + .end(); const destination = await dataTransformer.transform(dataTransformerBuilder, source); @@ -56,18 +58,18 @@ describe('Data Transformer', () => { const dataTransformerBuilder = new DataTransformerBuilder(); dataTransformerBuilder .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() + .setSourceProperty("1") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") + .end() .add() - .setSourceProperty("2") - .setDestinationProperty("total") - .end(); + .setSourceProperty("2") + .setDestinationProperty("total") + .end(); const destination = await dataTransformer.transform(dataTransformerBuilder, source); @@ -80,36 +82,36 @@ describe('Data Transformer', () => { }) it("should properly call the before row transformers and respect the order of calls", async () => { - const firstInterceptor: DataTransformerInterceptorInterface = { - async beforeRowTransform(row: DataTransformerRow): Promise { + const firstInterceptor: DataMappingInterceptorInterface = { + async beforeMapping(row: DataTransformerRow): Promise { return row; }, - async afterRowTransform(row: DataTransformerRow): Promise { + async afterMapping(row: DataTransformerRow): Promise { return { "after": row["name"] + row["province"] + row["total"] + row["added_property"], }; }, - getUniqueKey(): DataTransformerInterceptorUniqueKeyType { + getUniqueKey(): DataMappingInterceptorUniqueKeyType { return "first_interceptor"; }, } - const secondInterceptor: DataTransformerInterceptorInterface = { - async beforeRowTransform(row: DataTransformerRow): Promise { + const secondInterceptor: DataMappingInterceptorInterface = { + async beforeMapping(row: DataTransformerRow): Promise { row[3] = "Property added in the beforeRowTransform"; return row; }, - async afterRowTransform(row: DataTransformerRow): Promise { + async afterMapping(row: DataTransformerRow): Promise { return row; }, - getUniqueKey(): DataTransformerInterceptorUniqueKeyType { + getUniqueKey(): DataMappingInterceptorUniqueKeyType { return "second_interceptor"; }, } - const beforeRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "beforeRowTransform") - const afterRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "afterRowTransform") - const beforeRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "beforeRowTransform") - const afterRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "afterRowTransform") + const beforeRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "beforeMapping") + const afterRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "afterMapping") + const beforeRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "beforeMapping") + const afterRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "afterMapping") const dataTransformer = new DataTransformer([new LowercaseNormalizer()], [ firstInterceptor, @@ -127,22 +129,22 @@ describe('Data Transformer', () => { .addAfterRowTransformInterceptor("first_interceptor") .addAfterRowTransformInterceptor("second_interceptor") .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() + .setSourceProperty("1") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") + .end() .add() - .setSourceProperty("2") - .setDestinationProperty("total") - .end() + .setSourceProperty("2") + .setDestinationProperty("total") + .end() .add() - .setSourceProperty("3") - .setDestinationProperty("added_property") - .end(); + .setSourceProperty("3") + .setDestinationProperty("added_property") + .end(); const transformedData: any[] = await dataTransformer.transform(dataTransformerBuilder, source); @@ -164,7 +166,7 @@ describe('Data Transformer', () => { .setDestinationProperty("name") .end() - await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); + await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); }) it("should throw properly when after row transformer cannot be found", async () => { @@ -178,7 +180,7 @@ describe('Data Transformer', () => { .setDestinationProperty("name") .end() - await expect(dataTransformer.transform(dataTransformerBuilder, [{"0": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); + await expect(dataTransformer.transform(dataTransformerBuilder, [{"0": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); }) it("should throw properly when an element is not optional and not found in the source", async () => { const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); @@ -190,7 +192,7 @@ describe('Data Transformer', () => { .setDestinationProperty("name") .end() - await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); + await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); }) it("should properly type the return object when a destinationType is passed", async () => { @@ -251,4 +253,4 @@ describe('Data Transformer', () => { expect(destination.name).toBe("title"); }) -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/data.mapper.ts b/packages/data-mapping/src/mappers/data.mapper.ts new file mode 100644 index 000000000..ba9ee4f67 --- /dev/null +++ b/packages/data-mapping/src/mappers/data.mapper.ts @@ -0,0 +1,95 @@ +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; +import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; +import {moduleScoped} from "@pristine-ts/common"; +import {DataMappingModuleKeyname} from "../data-mapping.module.keyname"; +import {injectable, injectAll} from "tsyringe"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {ClassConstructor, plainToInstance} from "class-transformer"; +import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; + +@moduleScoped(DataMappingModuleKeyname) +@injectable() +export class DataMapper { + private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface } = {} + private readonly dataTransformerInterceptorsMap: { [key in DataMappingInterceptorUniqueKeyType]: DataMappingInterceptorInterface } = {} + + public constructor(@injectAll("DataNormalizerInterface") private readonly dataNormalizers: DataNormalizerInterface[], + @injectAll("DataTransformerInterceptor") private readonly dataTransformerInterceptors: DataMappingInterceptorInterface[],) { + dataNormalizers.forEach(dataNormalizer => { + this.dataNormalizersMap[dataNormalizer.getUniqueKey()] = dataNormalizer; + }) + + dataTransformerInterceptors.forEach(interceptor => { + this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; + }); + } + + /** + * This method takes an array of source and maps each item. + * + * @param builder + * @param source + * @param destinationType + */ + public async mapAll(builder: DataMappingBuilder, source: any[], destinationType?: ClassConstructor): Promise { + return source.map(element => this.map(builder, element, destinationType)); + } + + /** + * This method takes a builder, a source and maps it according to the builder. You can pass a `destinationType (optional)` + * that is an object that will be constructed. + * + * @param builder + * @param source + * @param destinationType + */ + public async map(builder: DataMappingBuilder, source: any, destinationType?: ClassConstructor): Promise { + const globalNormalizers = builder.normalizers; + + let destination = {}; + + if(destinationType) { + destination = plainToInstance(destinationType, {}); + } + + let interceptedSource = source; + + // Execute the before interceptors. + for (const element of builder.beforeMappingInterceptors) { + const interceptor = this.dataTransformerInterceptorsMap[element.key]; + + if (interceptor === undefined) { + throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); + } + + // todo: Pass the options when we start using them. + interceptedSource = await interceptor.beforeMapping(interceptedSource); + } + + // Loop over the properties defined in the builder + for (const key in builder.nodes) { + if(builder.nodes.hasOwnProperty(key) === false) { + continue; + } + + const node = builder.nodes[key]; + await node.map(interceptedSource, destination, this.dataNormalizersMap); + } + + // Execute the before interceptors. + for (const element of builder.afterMappingInterceptors) { + const interceptor: DataMappingInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; + + if (interceptor === undefined) { + throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); + } + + // todo pass the options when we start using it. + destination = await interceptor.afterMapping(destination); + } + + return destination; + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/mappers.ts b/packages/data-mapping/src/mappers/mappers.ts new file mode 100644 index 000000000..209812d7e --- /dev/null +++ b/packages/data-mapping/src/mappers/mappers.ts @@ -0,0 +1 @@ +export * from "./data.mapper"; \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts b/packages/data-mapping/src/nodes/base-data-mapping.node.ts similarity index 100% rename from packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts rename to packages/data-mapping/src/nodes/base-data-mapping.node.ts diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts new file mode 100644 index 000000000..4154b8739 --- /dev/null +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts @@ -0,0 +1,58 @@ +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; + +describe("Data Mapping Leaf", () => { + it("should map the property corresponding to the leaf from the source to the destination while also applying the normalizers and excluding the specified normalizers", async () => { + class Source { + title: string = "TITLE"; + } + + class Destination { + name: string; + } + + class PrependUnderscoresNormalizer implements DataNormalizerInterface { + getUniqueKey(): DataNormalizerUniqueKey { + return PrependUnderscoresNormalizer.name; + } + + normalize(source: any, options?: {}): string { + return "__" + source; + } + } + + class AppendUnderscoresNormalizer implements DataNormalizerInterface { + getUniqueKey(): DataNormalizerUniqueKey { + return AppendUnderscoresNormalizer.name; + } + + normalize(source: any, options?: {}): string { + return source + "__"; + } + } + + const dataBuilder = new DataMappingBuilder(); + dataBuilder.addNormalizer(PrependUnderscoresNormalizer.name); + dataBuilder.addNormalizer(AppendUnderscoresNormalizer.name); + + const leaf = new DataMappingLeaf(dataBuilder, dataBuilder); + leaf.setSourceProperty("title"); + leaf.setDestinationProperty("name"); + leaf.addNormalizer(LowercaseNormalizer.name) + leaf.excludeNormalizer(PrependUnderscoresNormalizer.name) + + const destination = new Destination(); + + await leaf.map(new Source(), destination, { + [LowercaseNormalizer.name]: new LowercaseNormalizer(), + [PrependUnderscoresNormalizer.name]: new PrependUnderscoresNormalizer(), + [AppendUnderscoresNormalizer.name]: new AppendUnderscoresNormalizer(), + }); + + expect(destination.name).toBeDefined() + expect(destination.name).toBe("title__"); + }) +}); \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.ts similarity index 56% rename from packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts rename to packages/data-mapping/src/nodes/data-mapping.leaf.ts index 6f3aec19e..b5411d9d2 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.ts @@ -1,19 +1,35 @@ import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {DataMappingNode} from "./data-mapping.node"; +import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; export class DataMappingLeaf { + /** + * This property represents the property referenced in the `source` object. + */ public sourceProperty!: string; + /** + * This property represents the property referenced in the `destination` object. + */ public destinationProperty!: string; + /** + * This property contains an array of Normalizers to apply sequentially when mapping this property. + */ public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + /** + * This property contains an array of Normalizers that must be excluded from normalizers defined by parents. + */ public excludedNormalizers: Set = new Set(); + /** + * This method specified whether it's possible that this element not be present in the `source` object. + */ public isOptional: boolean = false; public constructor( @@ -23,16 +39,41 @@ export class DataMappingLeaf { ) { } + /** + * This is a setter for `sourceProperty`. + * @param sourceProperty + */ public setSourceProperty(sourceProperty: string): DataMappingLeaf { this.sourceProperty = sourceProperty; return this; } + /** + * This is a setter for `destinationProperty`. + * @param destinationProperty + */ public setDestinationProperty(destinationProperty: string): DataMappingLeaf { this.destinationProperty = destinationProperty; return this; } + /** + * This is a setter for `isOptional`. + * @param isOptional + */ + public setIsOptional(isOptional: boolean): DataMappingLeaf { + this.isOptional = isOptional; + + return this; + } + + /** + * This methods adds a normalizer but checks that this normalizer hasn't been added already (either at the root) or + * directly on this leaf. + * + * @param normalizerUniqueKey + * @param options + */ public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingLeaf { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) @@ -50,10 +91,18 @@ export class DataMappingLeaf { return this; } + /** + * This method simply returns whether the normalizer was already added to this node. + * @param normalizerUniqueKey + */ public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; } + /** + * This method adds a normalizer that must be excluded from the normalizers applied at a higher level.à + * @param normalizerUniqueKey + */ public excludeNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): DataMappingLeaf { if(this.excludedNormalizers.has(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The EXCLUDED data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey) @@ -64,12 +113,9 @@ export class DataMappingLeaf { return this; } - public setIsOptional(isOptional: boolean): DataMappingLeaf { - this.isOptional = isOptional; - - return this; - } - + /** + * This method adds this node to its parent and returns the parent. + */ public end(): DataMappingNode | DataMappingBuilder { // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. this.parent.addNode(this) @@ -77,6 +123,41 @@ export class DataMappingLeaf { return this.parent; } + /** + * This method maps the `sourceProperty` from the `source` object and maps it to the `destinationProperty` of the + * `destination` object while applying the normalizers. + * @param source + * @param destination + * @param normalizersMap + */ + public async map(source: any, destination: any, normalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface }) { + if(source.hasOwnProperty(this.sourceProperty) === false) { + if(this.isOptional) { + return + } + + throw new DataTransformerSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) + } + + let value = source[this.sourceProperty]; + const normalizers = this.root.normalizers.filter(element => this.excludedNormalizers.has(element.key) === false); + normalizers.push(...this.normalizers); + + normalizers.forEach(element => { + const normalizer = normalizersMap[element.key]; + value = normalizer.normalize(value, element.options); + }) + + destination[this.destinationProperty] = value; + + return; + } + + /** + * This method imports a schema. + * + * @param schema + */ public import(schema: any) { this.normalizers = schema.normalizers; @@ -92,6 +173,9 @@ export class DataMappingLeaf { this.destinationProperty = schema.destinationProperty; } + /** + * This method exports this node. + */ public export(): any { return { "_type": this.type, diff --git a/packages/data-mapping/src/nodes/data-mapping.node.ts b/packages/data-mapping/src/nodes/data-mapping.node.ts new file mode 100644 index 000000000..e683c24d1 --- /dev/null +++ b/packages/data-mapping/src/nodes/data-mapping.node.ts @@ -0,0 +1,132 @@ +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {BaseDataMappingNode} from "./base-data-mapping.node"; +import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; + +export class DataMappingNode extends BaseDataMappingNode { + /** + * This property represents the property referenced in the `source` object. + */ + public sourceProperty!: string; + + /** + * This property represents the property referenced in the `destination` object. + */ + public destinationProperty!: string; + + /** + * This method specified whether it's possible that this element not be present in the `source` object. + */ + public isOptional: boolean = false; + + constructor(public readonly root: DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder, + public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node, + ) { + super(); + } + + /** + * This is a setter for `sourceProperty`. + * @param sourceProperty + */ + public setSourceProperty(sourceProperty: string): DataMappingNode { + this.sourceProperty = sourceProperty; + return this; + } + + /** + * This is a setter for `destinationProperty`. + * @param destinationProperty + */ + public setDestinationProperty(destinationProperty: string): DataMappingNode { + this.destinationProperty = destinationProperty; + return this; + } + + /** + * This is a setter for `isOptional`. + * @param isOptional + */ + public setIsOptional(isOptional: boolean): DataMappingNode { + this.isOptional = isOptional; + + return this; + } + + /** + * This property creates a new DataMappingLeaf and returns it. It doesn't add it yet. To do so, the `end()` method + * must be called. + */ + public add() { + return new DataMappingLeaf(this.root, this); + } + + /** + * This method adds a nesting level. This should be used when the property contains an object and you want to map + * this object into another object. + */ + public addNestingLevel() { + return new DataMappingNode(this.root, this); + } + + /** + * This method adds an array of Scalar allowing you to apply the normalizer on each scalar in the array. The + * `sourceProperty` and `destinationProperty` correspond to the name of the property that is an array. But, the + * values in the array will be normalized using the normalizer. + * + */ + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.Array); + } + + /** + * This method adds an array of objects allowing to define a node for each property in the object. Each object in + * the array will be treated as being the same. + */ + public addArrayOfObjects(): DataMappingNode { + return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); + } + + /** + * This method adds this node to its parent and returns the parent. + */ + public end(): DataMappingNode | DataMappingBuilder { + // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. + this.parent.addNode(this) + + return this.parent; + } + + public async map(source: any, destination: any, normalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface }) { + if(source.hasOwnProperty(this.sourceProperty) === false) { + if(this.isOptional) { + return + } + + throw new DataTransformerSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) + } + + let sourceElement = source[this.sourceProperty]; + + if(destination[this.destinationProperty] === undefined) { + // todo: we need to get the expected Type of the `destination[this.destinationProperty]` and actually instantiate it. + destination[this.destinationProperty] = {}; + } + + let destinationElement = destination[this.destinationProperty]; + + for (let key in this.nodes) { + if(this.nodes.hasOwnProperty(key) === false) { + continue; + } + + const node = this.nodes[key]; + + await node.map(sourceElement, destinationElement, normalizersMap); + } + } +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts b/packages/data-mapping/src/nodes/nodes.ts similarity index 100% rename from packages/data-transformer/src/mapping-nodes/mapping-nodes.ts rename to packages/data-mapping/src/nodes/nodes.ts diff --git a/packages/data-transformer/src/normalizer-options/base-normalizer.options.ts b/packages/data-mapping/src/normalizer-options/base-normalizer.options.ts similarity index 100% rename from packages/data-transformer/src/normalizer-options/base-normalizer.options.ts rename to packages/data-mapping/src/normalizer-options/base-normalizer.options.ts diff --git a/packages/data-transformer/src/normalizer-options/lowercase-normalizer.options.ts b/packages/data-mapping/src/normalizer-options/lowercase-normalizer.options.ts similarity index 100% rename from packages/data-transformer/src/normalizer-options/lowercase-normalizer.options.ts rename to packages/data-mapping/src/normalizer-options/lowercase-normalizer.options.ts diff --git a/packages/data-transformer/src/normalizer-options/normalizer-options.ts b/packages/data-mapping/src/normalizer-options/normalizer-options.ts similarity index 100% rename from packages/data-transformer/src/normalizer-options/normalizer-options.ts rename to packages/data-mapping/src/normalizer-options/normalizer-options.ts diff --git a/packages/data-transformer/src/normalizers/lowercase.normalizer.spec.ts b/packages/data-mapping/src/normalizers/lowercase.normalizer.spec.ts similarity index 100% rename from packages/data-transformer/src/normalizers/lowercase.normalizer.spec.ts rename to packages/data-mapping/src/normalizers/lowercase.normalizer.spec.ts diff --git a/packages/data-transformer/src/normalizers/lowercase.normalizer.ts b/packages/data-mapping/src/normalizers/lowercase.normalizer.ts similarity index 100% rename from packages/data-transformer/src/normalizers/lowercase.normalizer.ts rename to packages/data-mapping/src/normalizers/lowercase.normalizer.ts diff --git a/packages/data-transformer/src/normalizers/normalizers.ts b/packages/data-mapping/src/normalizers/normalizers.ts similarity index 100% rename from packages/data-transformer/src/normalizers/normalizers.ts rename to packages/data-mapping/src/normalizers/normalizers.ts diff --git a/packages/data-transformer/src/test.spec.ts b/packages/data-mapping/src/test.spec.ts similarity index 100% rename from packages/data-transformer/src/test.spec.ts rename to packages/data-mapping/src/test.spec.ts diff --git a/packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts b/packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts new file mode 100644 index 000000000..7791ad01b --- /dev/null +++ b/packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts @@ -0,0 +1 @@ +export type DataMappingInterceptorUniqueKeyType = string; \ No newline at end of file diff --git a/packages/data-transformer/src/types/data-normalizer-unique-key.type.ts b/packages/data-mapping/src/types/data-normalizer-unique-key.type.ts similarity index 100% rename from packages/data-transformer/src/types/data-normalizer-unique-key.type.ts rename to packages/data-mapping/src/types/data-normalizer-unique-key.type.ts diff --git a/packages/data-mapping/src/types/types.ts b/packages/data-mapping/src/types/types.ts new file mode 100644 index 000000000..ee9ebfee7 --- /dev/null +++ b/packages/data-mapping/src/types/types.ts @@ -0,0 +1,2 @@ +export * from "./data-normalizer-unique-key.type"; +export * from "./data-mapping-interceptor-unique-key.type"; \ No newline at end of file diff --git a/packages/data-transformer/tsconfig.cjs.json b/packages/data-mapping/tsconfig.cjs.json similarity index 100% rename from packages/data-transformer/tsconfig.cjs.json rename to packages/data-mapping/tsconfig.cjs.json diff --git a/packages/data-transformer/tsconfig.json b/packages/data-mapping/tsconfig.json similarity index 100% rename from packages/data-transformer/tsconfig.json rename to packages/data-mapping/tsconfig.json diff --git a/packages/data-transformer/readme.md b/packages/data-transformer/readme.md deleted file mode 100644 index a99a4cab4..000000000 --- a/packages/data-transformer/readme.md +++ /dev/null @@ -1 +0,0 @@ -# Data Transformer module. \ No newline at end of file diff --git a/packages/data-transformer/src/builders/data-mapping.builder.ts b/packages/data-transformer/src/builders/data-mapping.builder.ts deleted file mode 100644 index 21559c163..000000000 --- a/packages/data-transformer/src/builders/data-mapping.builder.ts +++ /dev/null @@ -1,96 +0,0 @@ -import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import { - DataBeforeRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-before-row-transformer-interceptor-already-added.error"; -import { - DataAfterRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-after-row-transformer-interceptor-already-added.error"; -import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; -import {BaseDataMappingNode} from "../mapping-nodes/base-data-mapping.node"; -import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; - -export class DataMappingBuilder extends BaseDataMappingNode{ - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingBuilder { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingBuilder { - if(this.hasBeforeRowTransformInterceptor(key)) { - throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) - } - - this.beforeRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingBuilder { - if(this.hasAfterRowTransformInterceptor(key)) { - throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) - } - - this.afterRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public add() { - return new DataMappingLeaf(this, this); - } - - - public addNestingLevel() { - return new DataMappingNode(this, this); - } - - public addArrayOfScalar(): DataMappingLeaf { - return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.Array); - } - - public addArrayOfObjects(): DataMappingNode { - return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); - } - - /** - * This method is called at the end just to make it nice since all the nodes will have one, it's nice - * that the builder has one too. - */ - public end(): DataMappingBuilder { - return this; - } - -} \ No newline at end of file diff --git a/packages/data-transformer/src/data-transformer.module.keyname.ts b/packages/data-transformer/src/data-transformer.module.keyname.ts deleted file mode 100644 index adfcf2047..000000000 --- a/packages/data-transformer/src/data-transformer.module.keyname.ts +++ /dev/null @@ -1 +0,0 @@ -export const DataTransformerModuleKeyname: string = "pristine.data-transformer"; diff --git a/packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts b/packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts deleted file mode 100644 index 7396b4001..000000000 --- a/packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; -import {moduleScoped, tag} from "@pristine-ts/common"; -import {DataTransformerModuleKeyname} from "../data-transformer.module.keyname"; -import {injectable} from "tsyringe"; -import {DataTransformerRow} from "../types/data-transformer.row"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; - -@tag("DataTransformerInterceptor") -@moduleScoped(DataTransformerModuleKeyname) -@injectable() -export class DefaultDataTransformerInterceptor implements DataTransformerInterceptorInterface { - async afterRowTransform(row: DataTransformerRow): Promise { - return row; - } - - async beforeRowTransform(row: DataTransformerRow): Promise { - return row; - } - - getUniqueKey(): DataTransformerInterceptorUniqueKeyType { - return DefaultDataTransformerInterceptor.name; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/interceptors/interceptors.ts b/packages/data-transformer/src/interceptors/interceptors.ts deleted file mode 100644 index 14629e7c1..000000000 --- a/packages/data-transformer/src/interceptors/interceptors.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./default-data-transformer.interceptor"; \ No newline at end of file diff --git a/packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts b/packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts deleted file mode 100644 index afbe878eb..000000000 --- a/packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {DataTransformerRow} from "../types/data-transformer.row"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; - -export interface DataTransformerInterceptorInterface { - /** - * Every data transformer interceptor must define a unique key. Then, during the transformation, the schema can specify which - * interceptors must be called. - */ - getUniqueKey(): DataTransformerInterceptorUniqueKeyType; - - /** - * This method is called before the row is being transformed and normalized. It allows you to combine fields for example if that's what you want. - * @param row - */ - beforeRowTransform(row: DataTransformerRow): Promise; - - /** - * This method is called after the row is being transformed and normalized. It can allow you to apply operations on each - * field or combine fields for example. - * @param row - */ - afterRowTransform(row: DataTransformerRow): Promise; -} \ No newline at end of file diff --git a/packages/data-transformer/src/interfaces/interfaces.ts b/packages/data-transformer/src/interfaces/interfaces.ts deleted file mode 100644 index c336d245c..000000000 --- a/packages/data-transformer/src/interfaces/interfaces.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./data-normalizer.interface"; -export * from "./data-transformer-interceptor.interface"; \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts deleted file mode 100644 index fc8588f3e..000000000 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; -import {DataMappingLeaf} from "./data-mapping.leaf"; -import {DataMappingBuilder} from "../builders/data-mapping.builder"; -import {BaseDataMappingNode} from "./base-data-mapping.node"; - -export class DataMappingNode extends BaseDataMappingNode { - public sourceProperty!: string; - public destinationProperty!: string; - - constructor(public readonly root: DataMappingBuilder, - public readonly parent: DataMappingNode | DataMappingBuilder, - public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node, - ) { - super(); - } - - public setSourceProperty(sourceProperty: string): DataMappingNode { - this.sourceProperty = sourceProperty; - return this; - } - public setDestinationProperty(destinationProperty: string): DataMappingNode { - this.destinationProperty = destinationProperty; - return this; - } - - public add() { - return new DataMappingLeaf(this.root, this); - } - - public addNestingLevel() { - return new DataMappingNode(this.root, this); - } - - public addArrayOfScalar(): DataMappingLeaf { - return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.Array); - } - - public addArrayOfObjects(): DataMappingNode { - return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); - } - - public end(): DataMappingNode | DataMappingBuilder { - // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. - this.parent.addNode(this) - - //@ts-ignore - return this.parent; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.spec.ts b/packages/data-transformer/src/transformers/data-transformer.builder.spec.ts deleted file mode 100644 index 10ee82ea4..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.builder.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import "reflect-metadata" -import {DataTransformerBuilder} from "./data-transformer.builder"; -import {DataTransformerProperty} from "./data-transformer.property"; -import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; - -describe('Data Transformer Builder', () => { - let dataTransformerBuilder: DataTransformerBuilder; - - beforeEach(() => { - dataTransformerBuilder = new DataTransformerBuilder(); - }) - - it("should properly add a normalizer", () => { - const options = { - "optionA": true, - }; - - dataTransformerBuilder.addNormalizer("normalizer", options); - - expect(dataTransformerBuilder.normalizers.length).toBe(1); - expect(dataTransformerBuilder.normalizers[0].key).toBe("normalizer") - expect(dataTransformerBuilder.normalizers[0].options).toBe(options); - }) - - it("should return if it has a normalizer or not", () => { - const options = { - "optionA": true, - }; - - dataTransformerBuilder.addNormalizer("normalizer", options); - - expect(dataTransformerBuilder.hasNormalizer("normalizer")).toBeTruthy(); - expect(dataTransformerBuilder.hasNormalizer("dafds")).toBeFalsy(); - }) - - it("should properly add a new data transformer property", () => { - expect(dataTransformerBuilder.add() instanceof DataTransformerProperty).toBeTruthy() - }) - - it("should support being exported and then re-imported", () => { - const dataTransformerBuilder = new DataTransformerBuilder(); - const builder = dataTransformerBuilder - .addBeforeRowTransformInterceptor("first_interceptor") - .addBeforeRowTransformInterceptor("second_interceptor") - .addAfterRowTransformInterceptor("first_interceptor") - .addAfterRowTransformInterceptor("second_interceptor") - .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .excludeNormalizer(LowercaseNormalizer.name) - .end() - .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() - .add() - .setSourceProperty("2") - .setDestinationProperty("total") - .end(); - - const exportedBuilder = builder.export(); - - - const serializedObject: any = { - "normalizers": [], - "beforeRowTransformInterceptors":[{"key":"first_interceptor"},{"key":"second_interceptor"}], - "afterRowTransformInterceptors":[{"key":"first_interceptor"},{"key":"second_interceptor"}], - "properties":{ - "0":{ - "sourceProperty":"0", - "destinationProperty":"name", - "isOptional": false, - "normalizers": [], - "excludedNormalizers": { - [LowercaseNormalizer.name]: true, - } - }, - "1": { - "sourceProperty":"1", - "destinationProperty":"province", - "isOptional": false, - "normalizers": [ - { - "key": LowercaseNormalizer.name - } - ], - "excludedNormalizers": {} - }, - "2": { - "sourceProperty": "2", - "destinationProperty": "total", - "isOptional": false, - "normalizers": [], - "excludedNormalizers": {}, - } - }, - }; - - const stringifiedObject = JSON.stringify(serializedObject); - - expect(exportedBuilder).toBe(stringifiedObject); - - // Create a new builder and import the serializedObject. - - const importedBuilder = new DataTransformerBuilder(); - importedBuilder.import(stringifiedObject); - - expect(importedBuilder.normalizers.length).toBe(0); - expect(importedBuilder.beforeRowTransformInterceptors.length).toBe(2); - expect(importedBuilder.afterRowTransformInterceptors.length).toBe(2); - expect(Object.keys(importedBuilder.properties).length).toBe(3); - expect(importedBuilder.properties[0].normalizers.length).toBe(0) - expect(importedBuilder.properties[0].excludedNormalizers.size).toBe(1) - expect(importedBuilder.properties[0].isOptional).toBeFalsy(); - expect(importedBuilder.properties[0].sourceProperty).toBe("0"); - expect(importedBuilder.properties[0].destinationProperty).toBe("name"); - - expect(importedBuilder.properties[1].normalizers.length).toBe(1) - expect(importedBuilder.properties[1].excludedNormalizers.size).toBe(0) - expect(importedBuilder.properties[1].isOptional).toBeFalsy(); - expect(importedBuilder.properties[1].sourceProperty).toBe("1"); - expect(importedBuilder.properties[1].destinationProperty).toBe("province"); - - expect(importedBuilder.properties[2].normalizers.length).toBe(0) - expect(importedBuilder.properties[2].excludedNormalizers.size).toBe(0) - expect(importedBuilder.properties[2].isOptional).toBeFalsy(); - expect(importedBuilder.properties[2].sourceProperty).toBe("2"); - expect(importedBuilder.properties[2].destinationProperty).toBe("total"); - }) -}); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.ts b/packages/data-transformer/src/transformers/data-transformer.builder.ts deleted file mode 100644 index 49d1b627b..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.builder.ts +++ /dev/null @@ -1,156 +0,0 @@ -import {injectable} from "tsyringe"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerProperty} from "./data-transformer.property"; -import {moduleScoped} from "@pristine-ts/common"; -import {DataTransformerModuleKeyname} from "../data-transformer.module.keyname"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import { - DataAfterRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-after-row-transformer-interceptor-already-added.error"; -import { - DataBeforeRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-before-row-transformer-interceptor-already-added.error"; - -@moduleScoped(DataTransformerModuleKeyname) -@injectable() -export class DataTransformerBuilder { - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - - public properties: {[sourceProperty in string]: DataTransformerProperty} = {} - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerBuilder { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataTransformerBuilder { - if(this.hasBeforeRowTransformInterceptor(key)) { - throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this builder.", key, options) - } - - this.beforeRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataTransformerBuilder { - if(this.hasAfterRowTransformInterceptor(key)) { - throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this builder.", key, options) - } - - this.afterRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public add() { - return new DataTransformerProperty(this); - } - - public addNewProperty(property: DataTransformerProperty) { - this.properties[property.sourceProperty] = property; - } - - public import(jsonString: string) { - const object = JSON.parse(jsonString); - - if(object.hasOwnProperty("normalizers") && Array.isArray(object.normalizers)) { - this.normalizers = object.normalizers; - } - - if(object.hasOwnProperty("beforeRowTransformInterceptors") && Array.isArray(object.beforeRowTransformInterceptors)) { - this.beforeRowTransformInterceptors = object.beforeRowTransformInterceptors; - } - - if(object.hasOwnProperty("afterRowTransformInterceptors") && Array.isArray(object.afterRowTransformInterceptors)) { - this.afterRowTransformInterceptors = object.afterRowTransformInterceptors; - } - - if(object.hasOwnProperty("properties") && typeof object.properties === "object") { - for(const key in object.properties) { - if(object.properties.hasOwnProperty(key) === false) { - continue; - } - - const property = object.properties[key]; - const newProperty = this.add(); - - newProperty.normalizers = property.normalizers; - if(property.hasOwnProperty("excludedNormalizers")) { - for(const item in property.excludedNormalizers) { - newProperty.excludeNormalizer(item); - } - } - - newProperty.isOptional = property.isOptional; - newProperty.sourceProperty = property.sourceProperty; - newProperty.destinationProperty = property.destinationProperty; - - newProperty.end(); - } - } - - return this; - } - - public export(): string { - const properties: any = {}; - - for (const key in this.properties) { - if(this.properties.hasOwnProperty(key) === false) { - continue; - } - - const property = this.properties[key]; - - const excludedNormalizers: any = {} - - for(const element of property.excludedNormalizers.values()) { - excludedNormalizers[element] = true; - } - - properties[key] = { - "sourceProperty": property.sourceProperty, - "destinationProperty": property.destinationProperty, - "isOptional": property.isOptional, - "normalizers": property.normalizers, - "excludedNormalizers": excludedNormalizers, - }; - } - - return JSON.stringify({ - "normalizers": this.normalizers, - "beforeRowTransformInterceptors": this.beforeRowTransformInterceptors, - "afterRowTransformInterceptors": this.afterRowTransformInterceptors, - "properties": properties, - }); - } -} diff --git a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts b/packages/data-transformer/src/transformers/data-transformer.property.spec.ts deleted file mode 100644 index e28356aa7..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {DataTransformerProperty} from "./data-transformer.property"; -import {DataTransformerBuilder} from "./data-transformer.builder"; - -describe('Data Transformer Property', () => { - const dataTransformerBuilder: DataTransformerBuilder = new DataTransformerBuilder(); - let dataTransformerProperty: DataTransformerProperty; - - beforeEach(() => { - dataTransformerProperty = new DataTransformerProperty(dataTransformerBuilder); - }) - - it("should set the Source Property correctly", () => { - dataTransformerProperty.setSourceProperty("property"); - - expect(dataTransformerProperty.sourceProperty).toBe("property") - }) - - it("should set the Destination Property correctly", () => { - dataTransformerProperty.setDestinationProperty("property"); - - expect(dataTransformerProperty.destinationProperty).toBe("property") - }) - - it("should add a normalizer correctly", () => { - const options = { - "optionA": true, - }; - - dataTransformerProperty.addNormalizer("normalizer", options); - - expect(dataTransformerProperty.normalizers.length).toBe(1); - expect(dataTransformerProperty.normalizers[0].key).toBe("normalizer") - expect(dataTransformerProperty.normalizers[0].options).toBe(options) - }) - - it("should return correctly if it has a normalizer correctly", () => { - const options = { - "optionA": true, - }; - - dataTransformerProperty.addNormalizer("normalizer", options); - - expect(dataTransformerProperty.hasNormalizer("normalizer")).toBeTruthy(); - expect(dataTransformerProperty.hasNormalizer("dafds")).toBeFalsy(); - }) - - it("should add an excluded normalizer correctly", () => { - const options = { - "optionA": true, - }; - - dataTransformerProperty.excludeNormalizer("normalizer"); - - expect(dataTransformerProperty.excludedNormalizers.size).toBe(1); - expect(dataTransformerProperty.excludedNormalizers.has("normalizer")).toBeTruthy() - }) - - it("should set the IsOptional property", () => { - dataTransformerProperty.setIsOptional(true); - - expect(dataTransformerProperty.isOptional).toBeTruthy(); - }) - - it("should properly return the builder when calling end()", () => { - expect(dataTransformerProperty.end()).toBe(dataTransformerBuilder); - }) - - it("should properly accept a list of sourceProperties", () => {}) -}); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.property.ts b/packages/data-transformer/src/transformers/data-transformer.property.ts deleted file mode 100644 index ef54b3c4b..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.property.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerBuilder} from "./data-transformer.builder"; - -export class DataTransformerProperty { - public sourceProperty!: string; - public destinationProperty!: string; - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public excludedNormalizers: Set = new Set(); - public isOptional: boolean = false; - - public children: {[key in (string|number)]: DataTransformerProperty} = {}; - - public constructor(private readonly builder: DataTransformerBuilder) { - } - - public setSourceProperty(sourceProperty: string): DataTransformerProperty { - this.sourceProperty = sourceProperty; - return this; - } - public setDestinationProperty(destinationProperty: string): DataTransformerProperty { - this.destinationProperty = destinationProperty; - return this; - } - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerProperty { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) - } - - if(this.builder.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the builder and cannot be also added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public excludeNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): DataTransformerProperty { - if(this.excludedNormalizers.has(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The EXCLUDED data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey) - } - - this.excludedNormalizers.add(normalizerUniqueKey); - - return this; - } - - public setIsOptional(isOptional: boolean): DataTransformerProperty { - this.isOptional = isOptional; - - return this; - } - - public end(): DataTransformerBuilder { - this.builder.addNewProperty(this); - - return this.builder; - } - -} \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data.transformer.ts b/packages/data-transformer/src/transformers/data.transformer.ts deleted file mode 100644 index 3a9fe8d06..000000000 --- a/packages/data-transformer/src/transformers/data.transformer.ts +++ /dev/null @@ -1,104 +0,0 @@ -import {injectable, injectAll} from "tsyringe"; -import {moduleScoped, tag} from "@pristine-ts/common"; -import {DataTransformerModuleKeyname} from "../data-transformer.module.keyname"; -import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; -import {DataTransformerBuilder} from "./data-transformer.builder"; -import {DataTransformerProperty} from "./data-transformer.property"; -import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataTransformerRow} from "../types/data-transformer.row"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; -import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; -import {ClassMetadata} from "@pristine-ts/metadata"; -import {ClassConstructor, plainToInstance} from "class-transformer"; - -@moduleScoped(DataTransformerModuleKeyname) -@injectable() -export class DataTransformer { - private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface } = {} - private readonly dataTransformerInterceptorsMap: { [key in DataTransformerInterceptorUniqueKeyType]: DataTransformerInterceptorInterface } = {} - - public constructor(@injectAll("DataNormalizerInterface") private readonly dataNormalizers: DataNormalizerInterface[], - @injectAll("DataTransformerInterceptor") private readonly dataTransformerInterceptors: DataTransformerInterceptorInterface[],) { - dataNormalizers.forEach(dataNormalizer => { - this.dataNormalizersMap[dataNormalizer.getUniqueKey()] = dataNormalizer; - }) - - dataTransformerInterceptors.forEach(interceptor => { - this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; - }); - } - - public async transformRows(builder: DataTransformerBuilder, rows: DataTransformerRow[], destinationType?: ClassConstructor): Promise { - return rows.map(row => this.transform(builder, row, destinationType)); - } - - public async transform(builder: DataTransformerBuilder, source: DataTransformerRow, destinationType?: ClassConstructor): Promise { - const globalNormalizers = builder.normalizers; - - let row: DataTransformerRow = {}; - - if(destinationType) { - row = plainToInstance(destinationType, {}); - } - - let interceptedInputRow = source; - - // Execute the before row interceptors. - for (const element of builder.beforeRowTransformInterceptors) { - const interceptor = this.dataTransformerInterceptorsMap[element.key]; - - if (interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } - - // todo: Pass the options when we start using them. - interceptedInputRow = await interceptor.beforeRowTransform(interceptedInputRow); - } - - // Loop over the properties defined in the builder - for (const key in builder.properties) { - if (builder.properties.hasOwnProperty(key) === false) { - continue; - } - - const property: DataTransformerProperty = builder.properties[key]; - if (interceptedInputRow.hasOwnProperty(property.sourceProperty) === false) { - if (property.isOptional) { - continue; - } - - throw new DataTransformerSourcePropertyNotFoundError("The property '" + key + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", key) - } - - let value = interceptedInputRow[property.sourceProperty]; - - // Remove the normalizers part of the excludedNormalizers - const normalizers = globalNormalizers.filter(element => property.excludedNormalizers.has(element.key) === false); - normalizers.push(...property.normalizers); - - normalizers.forEach(element => { - const dataNormalizer = this.dataNormalizersMap[element.key]; - value = dataNormalizer.normalize(value, element.options); - }) - - // Assign the resulting value in the destination - row[property.destinationProperty] = value; - } - - // Execute the before row interceptors. - for (const element of builder.afterRowTransformInterceptors) { - const interceptor: DataTransformerInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; - - if (interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } - - // todo pass the options when we start using it. - row = await interceptor.afterRowTransform(row); - } - - return row; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/transformers.ts b/packages/data-transformer/src/transformers/transformers.ts deleted file mode 100644 index 3f6f77c90..000000000 --- a/packages/data-transformer/src/transformers/transformers.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./data.transformer"; -export * from "./data-transformer.builder"; -export * from "./data-transformer.property"; diff --git a/packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts b/packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts deleted file mode 100644 index ca2901216..000000000 --- a/packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts +++ /dev/null @@ -1 +0,0 @@ -export type DataTransformerInterceptorUniqueKeyType = string; \ No newline at end of file diff --git a/packages/data-transformer/src/types/data-transformer.row.ts b/packages/data-transformer/src/types/data-transformer.row.ts deleted file mode 100644 index bb6ffc7de..000000000 --- a/packages/data-transformer/src/types/data-transformer.row.ts +++ /dev/null @@ -1 +0,0 @@ -export type DataTransformerRow = {[key in string]: any} | any; \ No newline at end of file diff --git a/packages/data-transformer/src/types/types.ts b/packages/data-transformer/src/types/types.ts deleted file mode 100644 index ccceae527..000000000 --- a/packages/data-transformer/src/types/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./data-normalizer-unique-key.type"; -export * from "./data-transformer.row"; -export * from "./data-transformer-interceptor-unique-key.type"; \ No newline at end of file From b21888d214edc63a0b697706b9f834cbb8f93e16 Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 14:36:20 -0800 Subject: [PATCH 06/20] - Continued improvements. --- .../src/builders/data-mapping.builder.ts | 4 +- .../src/enums/data-mapping-node-type.enum.ts | 3 +- ...node-invalid-source-property-type.error.ts | 19 +++ packages/data-mapping/src/errors/errors.ts | 1 + .../src/mappers/data.mapper.spec.ts | 3 - .../src/nodes/base-data-mapping.node.ts | 2 + .../src/nodes/data-mapping.leaf.spec.ts | 47 +++++++ .../src/nodes/data-mapping.leaf.ts | 28 ++++- .../src/nodes/data-mapping.node.ts | 115 +++++++++++++++++- 9 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts diff --git a/packages/data-mapping/src/builders/data-mapping.builder.ts b/packages/data-mapping/src/builders/data-mapping.builder.ts index e678ba8a4..f76813a20 100644 --- a/packages/data-mapping/src/builders/data-mapping.builder.ts +++ b/packages/data-mapping/src/builders/data-mapping.builder.ts @@ -122,7 +122,7 @@ export class DataMappingBuilder extends BaseDataMappingNode{ * */ public addArrayOfScalar(): DataMappingLeaf { - return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.Array); + return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.ScalarArray); } /** @@ -130,7 +130,7 @@ export class DataMappingBuilder extends BaseDataMappingNode{ * the array will be treated as being the same. */ public addArrayOfObjects(): DataMappingNode { - return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); + return new DataMappingNode(this, this, DataMappingNodeTypeEnum.ObjectArray); } /** diff --git a/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts b/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts index 8a04000b3..abfe0423e 100644 --- a/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts +++ b/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts @@ -1,5 +1,6 @@ export enum DataMappingNodeTypeEnum { - Array = "DATA_MAPPING_NODE_ARRAY", + ScalarArray = "DATA_MAPPING_NODE_SCALAR_ARRAY", + ObjectArray = "DATA_MAPPING_NODE_OBJECT_ARRAY", Node = "DATA_MAPPING_NODE", Leaf = "DATA_MAPPING_LEAF", } \ No newline at end of file diff --git a/packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts b/packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts new file mode 100644 index 000000000..1fabdd18b --- /dev/null +++ b/packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts @@ -0,0 +1,19 @@ +import {LoggableError} from "@pristine-ts/common"; + +/** + * This Error is thrown when a node is of type array but the `source[sourceProperty]` doesn't actually contain an array. + */ +export class ArrayDataMappingNodeInvalidSourcePropertyTypeError extends LoggableError { + + public constructor(message: string, sourceProperty: string) { + super(message, { + sourceProperty, + }); + + + // Set the prototype explicitly. + // As specified in the documentation in TypeScript + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, ArrayDataMappingNodeInvalidSourcePropertyTypeError.prototype); + } +} diff --git a/packages/data-mapping/src/errors/errors.ts b/packages/data-mapping/src/errors/errors.ts index 742001c2d..02c2bbc99 100644 --- a/packages/data-mapping/src/errors/errors.ts +++ b/packages/data-mapping/src/errors/errors.ts @@ -1,3 +1,4 @@ +export * from "./array-data-mapping-node-invalid-source-property-type.error"; export * from "./data-after-row-transformer-interceptor-already-added.error"; export * from "./data-before-row-transformer-interceptor-already-added.error"; export * from "./data-normalizer-already-added.error"; diff --git a/packages/data-mapping/src/mappers/data.mapper.spec.ts b/packages/data-mapping/src/mappers/data.mapper.spec.ts index dda706709..d92488849 100644 --- a/packages/data-mapping/src/mappers/data.mapper.spec.ts +++ b/packages/data-mapping/src/mappers/data.mapper.spec.ts @@ -1,8 +1,5 @@ -import {DataTransformer} from "../transformers/data.transformer"; import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; -import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; -import {DataTransformerRow} from "../types/data-transformer.row"; import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; diff --git a/packages/data-mapping/src/nodes/base-data-mapping.node.ts b/packages/data-mapping/src/nodes/base-data-mapping.node.ts index 15551b632..7910183e0 100644 --- a/packages/data-mapping/src/nodes/base-data-mapping.node.ts +++ b/packages/data-mapping/src/nodes/base-data-mapping.node.ts @@ -19,4 +19,6 @@ export abstract class BaseDataMappingNode { this.nodes[node.sourceProperty] = node; } + + } \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts index 4154b8739..dd4f09d45 100644 --- a/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts @@ -3,6 +3,7 @@ import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; describe("Data Mapping Leaf", () => { it("should map the property corresponding to the leaf from the source to the destination while also applying the normalizers and excluding the specified normalizers", async () => { @@ -55,4 +56,50 @@ describe("Data Mapping Leaf", () => { expect(destination.name).toBeDefined() expect(destination.name).toBe("title__"); }) + + it("should properly map an array of scalars", async () => { + class Source { + array: string[] = [ + "-4", + "100", + "9350", + "5", + ] + } + + class Destination { + name: number[] = []; + } + + class ConvertToNumber implements DataNormalizerInterface { + getUniqueKey(): DataNormalizerUniqueKey { + return ConvertToNumber.name; + } + + normalize(source: any, options?: {}): number { + return parseInt(source); + } + } + + const dataBuilder = new DataMappingBuilder(); + dataBuilder.addNormalizer(ConvertToNumber.name); + + const leaf = new DataMappingLeaf(dataBuilder, dataBuilder, DataMappingNodeTypeEnum.ScalarArray); + leaf.setSourceProperty("array"); + leaf.setDestinationProperty("name"); + + const destination = new Destination(); + + await leaf.map(new Source(), destination, { + [ConvertToNumber.name]: new ConvertToNumber(), + }); + + expect(destination.name).toBeDefined() + expect(Array.isArray(destination.name)).toBeTruthy() + expect(destination.name.length).toBe(4); + expect(destination.name[0]).toBe(-4); + expect(destination.name[1]).toBe(100); + expect(destination.name[2]).toBe(9350); + expect(destination.name[3]).toBe(5); + }) }); \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.ts index b5411d9d2..a2eed3b7f 100644 --- a/packages/data-mapping/src/nodes/data-mapping.leaf.ts +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.ts @@ -5,6 +5,9 @@ import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {DataMappingNode} from "./data-mapping.node"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import { + ArrayDataMappingNodeInvalidSourcePropertyTypeError +} from "../errors/array-data-mapping-node-invalid-source-property-type.error"; export class DataMappingLeaf { /** @@ -126,6 +129,7 @@ export class DataMappingLeaf { /** * This method maps the `sourceProperty` from the `source` object and maps it to the `destinationProperty` of the * `destination` object while applying the normalizers. + * * @param source * @param destination * @param normalizersMap @@ -139,10 +143,32 @@ export class DataMappingLeaf { throw new DataTransformerSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) } - let value = source[this.sourceProperty]; const normalizers = this.root.normalizers.filter(element => this.excludedNormalizers.has(element.key) === false); normalizers.push(...this.normalizers); + if(this.type === DataMappingNodeTypeEnum.ScalarArray) { + // This means that the source[propertyKey] contains an array of objects and each object should be mapped + const array = source[this.sourceProperty]; + + if(Array.isArray(array) === false) { + throw new ArrayDataMappingNodeInvalidSourcePropertyTypeError(`According to your schema, the property '${this.sourceProperty}' in the source object must contain an Array of Scalar. Instead, it contains: '${typeof array}'.`, this.sourceProperty); + } + + destination[this.destinationProperty] = []; + + for (let value of array) { + normalizers.forEach(element => { + const normalizer = normalizersMap[element.key]; + value = normalizer.normalize(value, element.options); + }) + + destination[this.destinationProperty].push(value); + } + + return; + } + + let value = source[this.sourceProperty]; normalizers.forEach(element => { const normalizer = normalizersMap[element.key]; value = normalizer.normalize(value, element.options); diff --git a/packages/data-mapping/src/nodes/data-mapping.node.ts b/packages/data-mapping/src/nodes/data-mapping.node.ts index e683c24d1..8b4f19f34 100644 --- a/packages/data-mapping/src/nodes/data-mapping.node.ts +++ b/packages/data-mapping/src/nodes/data-mapping.node.ts @@ -5,6 +5,9 @@ import {BaseDataMappingNode} from "./base-data-mapping.node"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import { + ArrayDataMappingNodeInvalidSourcePropertyTypeError +} from "../errors/array-data-mapping-node-invalid-source-property-type.error"; export class DataMappingNode extends BaseDataMappingNode { /** @@ -80,7 +83,7 @@ export class DataMappingNode extends BaseDataMappingNode { * */ public addArrayOfScalar(): DataMappingLeaf { - return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.Array); + return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.ScalarArray); } /** @@ -88,7 +91,7 @@ export class DataMappingNode extends BaseDataMappingNode { * the array will be treated as being the same. */ public addArrayOfObjects(): DataMappingNode { - return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); + return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.ObjectArray); } /** @@ -101,6 +104,14 @@ export class DataMappingNode extends BaseDataMappingNode { return this.parent; } + /** + * This method maps the `sourceProperty` from the `source` object and maps it to the `destinationProperty` of the + * `destination` object while applying the normalizers. + * + * @param source + * @param destination + * @param normalizersMap + */ public async map(source: any, destination: any, normalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface }) { if(source.hasOwnProperty(this.sourceProperty) === false) { if(this.isOptional) { @@ -113,12 +124,45 @@ export class DataMappingNode extends BaseDataMappingNode { let sourceElement = source[this.sourceProperty]; if(destination[this.destinationProperty] === undefined) { - // todo: we need to get the expected Type of the `destination[this.destinationProperty]` and actually instantiate it. - destination[this.destinationProperty] = {}; + if(this.type === DataMappingNodeTypeEnum.ObjectArray) { + destination[this.destinationProperty] = []; + } else { + // todo: we need to get the expected Type of the `destination[this.destinationProperty]` and actually instantiate it. + destination[this.destinationProperty] = {}; + } } let destinationElement = destination[this.destinationProperty]; + if(this.type === DataMappingNodeTypeEnum.ObjectArray) { + // This means that the source[propertyKey] contains an array of objects and each object should be mapped + const array = source[this.sourceProperty]; + + if(Array.isArray(array) === false) { + throw new ArrayDataMappingNodeInvalidSourcePropertyTypeError(`According to your schema, the property '${this.sourceProperty}' in the source object must contain an Array of objects. Instead, it contains: '${typeof array}'.`, this.sourceProperty); + } + + for (let element of array) { + // todo: we need to get the expected Type of the object in the array in the Destination object + let dest = {}; + + for (let key in this.nodes) { + if(this.nodes.hasOwnProperty(key) === false) { + continue; + } + + const node = this.nodes[key]; + + await node.map(element, dest, normalizersMap); + } + + destinationElement.push(dest); + } + + return; + } + + // When the current node is not an array, we simply iterate for (let key in this.nodes) { if(this.nodes.hasOwnProperty(key) === false) { continue; @@ -129,4 +173,67 @@ export class DataMappingNode extends BaseDataMappingNode { await node.map(sourceElement, destinationElement, normalizersMap); } } + + /** + * This method imports a schema. + * + * @param schema + */ + public import(schema: any) { + this.sourceProperty = schema.sourceProperty; + this.destinationProperty = schema.destinationProperty; + this.isOptional = schema.isOptional; + this.nodes = {}; + + const nodes = schema.nodes; + + for(let key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + const nodeInfo = nodes[key]; + + const type: DataMappingNodeTypeEnum = nodeInfo["_type"]; + + switch (type) { + case DataMappingNodeTypeEnum.Leaf: + const leaf = new DataMappingLeaf(this.root, this, type); + leaf.import(nodeInfo); + this.nodes[leaf.sourceProperty] = leaf; + continue; + + case DataMappingNodeTypeEnum.Node: + case DataMappingNodeTypeEnum.ScalarArray: + case DataMappingNodeTypeEnum.ObjectArray: + const node = new DataMappingNode(this.root, this, type); + node.import(nodeInfo); + this.nodes[node.sourceProperty] = node; + continue; + } + } + } + + /** + * This method exports this node. + */ + public export() { + const nodes = this.nodes; + + for (let key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + nodes[key] = nodes[key].export(); + } + + return { + "_type": this.type, + "sourceProperty": this.sourceProperty, + "destinationProperty": this.destinationProperty, + "isOptional": this.isOptional, + "nodes": nodes, + } + } } \ No newline at end of file From f4c16691138dcc2ee1d0483afe4cb771a91edfa1 Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 15:55:57 -0800 Subject: [PATCH 07/20] - Testing the final validation. --- packages/data-mapping/package-lock.json | 136 +++++++++++++++++- packages/data-mapping/package.json | 1 + .../src/builders/data-mapping.builder.ts | 62 ++++++++ .../src/mappers/data.mapper.spec.ts | 136 +++++++++++++++++- .../src/nodes/data-mapping.leaf.ts | 8 +- .../src/nodes/data-mapping.node.ts | 2 +- 6 files changed, 338 insertions(+), 7 deletions(-) diff --git a/packages/data-mapping/package-lock.json b/packages/data-mapping/package-lock.json index 301677e39..0a77c046c 100644 --- a/packages/data-mapping/package-lock.json +++ b/packages/data-mapping/package-lock.json @@ -1,18 +1,20 @@ { - "name": "@pristine-ts/data-transformer", + "name": "@pristine-ts/data-mapper", "version": "0.0.276", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "@pristine-ts/data-transformer", + "name": "@pristine-ts/data-mapper", "version": "0.0.276", "license": "ISC", "dependencies": { + "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/common": "file:../common", "@pristine-ts/logging": "file:../logging", "@pristine-ts/metadata": "~1.0.2", - "class-transformer": "^0.5.1" + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" } }, "../common": { @@ -35,6 +37,29 @@ "date-fns": "^2.30.0" } }, + "node_modules/@babel/runtime": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", + "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@pristine-ts/class-validator": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@pristine-ts/class-validator/-/class-validator-1.0.22.tgz", + "integrity": "sha512-Fuvl2rq3wz/lO6gcoBHt3FZjr1diHBfz2Ug5wCqZceQZIHeUWwHM3T6GxazPDOjXPIH1yNGZo07e5o3Uma0ibw==", + "dependencies": { + "@pristine-ts/metadata": "^1.0.2", + "date-fns": "^2.28.0", + "libphonenumber-js": "^1.10.6", + "reflect-metadata": "^0.2.1", + "validator": "^13.7.0" + } + }, "node_modules/@pristine-ts/common": { "resolved": "../common", "link": true @@ -56,13 +81,82 @@ "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.10.53", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.53.tgz", + "integrity": "sha512-sDTnnqlWK4vH4AlDQuswz3n4Hx7bIQWTpIcScJX+Sp7St3LXHmfiax/ZFfyYxHmkdCvydOLSuvtAO/XpXiSySw==" + }, "node_modules/reflect-metadata": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tsyringe": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.8.0.tgz", + "integrity": "sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } } }, "dependencies": { + "@babel/runtime": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", + "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@pristine-ts/class-validator": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@pristine-ts/class-validator/-/class-validator-1.0.22.tgz", + "integrity": "sha512-Fuvl2rq3wz/lO6gcoBHt3FZjr1diHBfz2Ug5wCqZceQZIHeUWwHM3T6GxazPDOjXPIH1yNGZo07e5o3Uma0ibw==", + "requires": { + "@pristine-ts/metadata": "^1.0.2", + "date-fns": "^2.28.0", + "libphonenumber-js": "^1.10.6", + "reflect-metadata": "^0.2.1", + "validator": "^13.7.0" + } + }, "@pristine-ts/common": { "version": "file:../common", "requires": { @@ -92,10 +186,46 @@ "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, + "libphonenumber-js": { + "version": "1.10.53", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.53.tgz", + "integrity": "sha512-sDTnnqlWK4vH4AlDQuswz3n4Hx7bIQWTpIcScJX+Sp7St3LXHmfiax/ZFfyYxHmkdCvydOLSuvtAO/XpXiSySw==" + }, "reflect-metadata": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tsyringe": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.8.0.tgz", + "integrity": "sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==", + "requires": { + "tslib": "^1.9.3" + } + }, + "validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==" } } } diff --git a/packages/data-mapping/package.json b/packages/data-mapping/package.json index 54fa52cd5..3e94edc63 100644 --- a/packages/data-mapping/package.json +++ b/packages/data-mapping/package.json @@ -23,6 +23,7 @@ "@pristine-ts/common": "file:../common", "@pristine-ts/logging": "file:../logging", "@pristine-ts/metadata": "~1.0.2", + "@pristine-ts/class-validator": "^1.0.22", "class-transformer": "^0.5.1", "tsyringe": "^4.8.0" }, diff --git a/packages/data-mapping/src/builders/data-mapping.builder.ts b/packages/data-mapping/src/builders/data-mapping.builder.ts index f76813a20..f08deefd6 100644 --- a/packages/data-mapping/src/builders/data-mapping.builder.ts +++ b/packages/data-mapping/src/builders/data-mapping.builder.ts @@ -140,4 +140,66 @@ export class DataMappingBuilder extends BaseDataMappingNode{ public end(): DataMappingBuilder { return this; } + + /** + * This method imports a schema. + * + * @param schema + */ + public import(schema: any) { + this.normalizers = schema.normalizers; + this.beforeMappingInterceptors = schema.beforeMappingInterceptors; + this.afterMappingInterceptors = schema.afterMappingInterceptors; + + const nodes = schema.nodes; + + for(let key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + const nodeInfo = nodes[key]; + + const type: DataMappingNodeTypeEnum = nodeInfo["_type"]; + + switch (type) { + case DataMappingNodeTypeEnum.ScalarArray: + case DataMappingNodeTypeEnum.Leaf: + const leaf = new DataMappingLeaf(this, this, type); + leaf.import(nodeInfo); + this.nodes[leaf.sourceProperty] = leaf; + continue; + + case DataMappingNodeTypeEnum.Node: + case DataMappingNodeTypeEnum.ObjectArray: + const node = new DataMappingNode(this, this, type); + node.import(nodeInfo); + this.nodes[node.sourceProperty] = node; + continue; + } + } + } + + /** + * This method exports this node. + */ + public export() { + const nodes = this.nodes; + + for (let key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + nodes[key] = nodes[key].export(); + } + + return { + "nodes": nodes, + "normalizers":this.normalizers, + "beforeMappingInterceptors": this.beforeMappingInterceptors, + "afterMappingInterceptors": this.afterMappingInterceptors, + + } + } } \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/data.mapper.spec.ts b/packages/data-mapping/src/mappers/data.mapper.spec.ts index d92488849..1041e4966 100644 --- a/packages/data-mapping/src/mappers/data.mapper.spec.ts +++ b/packages/data-mapping/src/mappers/data.mapper.spec.ts @@ -4,13 +4,145 @@ import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interce import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; import {property} from "@pristine-ts/metadata"; +import "jest-extended" +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataMapper} from "./data.mapper"; +import {Type} from "class-transformer"; describe("Data Mapper", () =>{ it("should map a very complex object into another complex object. Then, it should export the builder, import the builder and make sure it still maps everything properly.", async () => { + class ArraySource { + rank: number; + } + + class NestedSource { + nestedTitle: string; + } + + class Source { + title: string; + + nested: NestedSource; + + array: ArraySource[] = []; + + children: string[] = []; + } + + class ArrayDestination { + position: number; + } + + class NestedDestination { + nestedName: string; + } + + class Destination { + name: string; + + @Type(() => NestedDestination) + child: NestedDestination; + + @Type(() => ArrayDestination) + list: ArrayDestination[]; + + infants: string[] = [] + } + + let dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .addNormalizer(LowercaseNormalizer.name) + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .addNestingLevel() + .setSourceProperty("nested") + .setDestinationProperty("child") + .add() + .setSourceProperty("nestedTitle") + .setDestinationProperty("nestedName") + .end() + .end() + .addArrayOfObjects() + .setSourceProperty("array") + .setDestinationProperty("list") + .add() + .setSourceProperty("rank") + .setDestinationProperty("position") + .end() + .end() + .addArrayOfScalar() + .setSourceProperty("children") + .setDestinationProperty("infants") + .end() + .end(); + const source = new Source(); + source.title = "TITLE"; + source.children = ["Etienne", "Antoine", "Olivier"]; + source.nested = new NestedSource(); + source.nested.nestedTitle = "NESTED_TITLE"; + source.array = []; + source.array[0] = new ArraySource() + source.array[0].rank = 1 + source.array[1] = new ArraySource() + source.array[1].rank = 2 + + + let dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const destination: Destination = await dataMapper.map(dataMappingBuilder, source, Destination); + + // Check that the mapping was properly done. + expect(destination instanceof Destination).toBeTruthy(); + + expect(destination.name).toBe("TITLE"); + expect(destination.infants.length).toBe(3); + expect(destination.infants[0]).toBe("etienne") + expect(destination.infants[1]).toBe("antoine") + expect(destination.infants[2]).toBe("olivier") + expect(destination.child).toBeDefined() + //expect(destination.child instanceof NestedDestination).toBeTruthy(); + expect(destination.child.nestedName).toBe("nested_title") + expect(destination.list).toBeDefined() + expect(destination.list.length).toBe(2) + //expect(destination.list[0] instanceof ArrayDestination).toBeTruthy() + expect(destination.list[0].position).toBe(1) + //expect(destination.list[1] instanceof ArrayDestination).toBeTruthy() + expect(destination.list[1].position).toBe(2) + + // Make sure that the import and export work and still map properly + const schema = dataMappingBuilder.export(); + + dataMapper = new DataMapper([new LowercaseNormalizer()], []); + dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder.import(schema); + + const destinationAfterExportAndReimport = await dataMapper.map(dataMappingBuilder, source, Destination); + + // Check AGAIN that the mapping was properly done. + expect(destinationAfterExportAndReimport instanceof Destination).toBeTruthy(); + + expect(destinationAfterExportAndReimport.name).toBe("TITLE"); + expect(destinationAfterExportAndReimport.infants.length).toBe(3); + expect(destinationAfterExportAndReimport.infants[0]).toBe("etienne") + expect(destinationAfterExportAndReimport.infants[1]).toBe("antoine") + expect(destinationAfterExportAndReimport.infants[2]).toBe("olivier") + expect(destinationAfterExportAndReimport.child).toBeDefined() + //expect(destinationAfterExportAndReimport.child instanceof NestedDestination).toBeTruthy(); + expect(destinationAfterExportAndReimport.child.nestedName).toBe("nested_title") + expect(destinationAfterExportAndReimport.list).toBeDefined() + expect(destinationAfterExportAndReimport.list.length).toBe(2) + //expect(destinationAfterExportAndReimport.list[0] instanceof ArrayDestination).toBeTruthy() + expect(destinationAfterExportAndReimport.list[0].position).toBe(1) + //expect(destinationAfterExportAndReimport.list[1] instanceof ArrayDestination).toBeTruthy() + expect(destinationAfterExportAndReimport.list[1].position).toBe(2) }) - it("should properly transform", async () => { + /*it("should properly transform", async () => { const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); const source = [{ @@ -249,5 +381,5 @@ describe("Data Mapper", () =>{ const destination = await dataTransformer.transform(dataTransformerBuilder, source, Destination); expect(destination.name).toBe("title"); - }) + })*/ }) \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.ts index a2eed3b7f..195ff107c 100644 --- a/packages/data-mapping/src/nodes/data-mapping.leaf.ts +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.ts @@ -203,13 +203,19 @@ export class DataMappingLeaf { * This method exports this node. */ public export(): any { + const excludedNormalizers: any = {} + + for(const element of this.excludedNormalizers.values()) { + excludedNormalizers[element] = true; + } + return { "_type": this.type, "sourceProperty": this.sourceProperty, "destinationProperty": this.destinationProperty, "isOptional": this.isOptional, "normalizers": this.normalizers, - "excludedNormalizers": this.excludedNormalizers, + "excludedNormalizers": excludedNormalizers, } } } \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.node.ts b/packages/data-mapping/src/nodes/data-mapping.node.ts index 8b4f19f34..f97202f83 100644 --- a/packages/data-mapping/src/nodes/data-mapping.node.ts +++ b/packages/data-mapping/src/nodes/data-mapping.node.ts @@ -197,6 +197,7 @@ export class DataMappingNode extends BaseDataMappingNode { const type: DataMappingNodeTypeEnum = nodeInfo["_type"]; switch (type) { + case DataMappingNodeTypeEnum.ScalarArray: case DataMappingNodeTypeEnum.Leaf: const leaf = new DataMappingLeaf(this.root, this, type); leaf.import(nodeInfo); @@ -204,7 +205,6 @@ export class DataMappingNode extends BaseDataMappingNode { continue; case DataMappingNodeTypeEnum.Node: - case DataMappingNodeTypeEnum.ScalarArray: case DataMappingNodeTypeEnum.ObjectArray: const node = new DataMappingNode(this.root, this, type); node.import(nodeInfo); From ab1cf133367df72b287c664bd7588759d6041e58 Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Wed, 17 Jan 2024 10:00:17 -0800 Subject: [PATCH 08/20] - Added support for instantiation of an object in the transformation. --- packages/data-transformer/package-lock.json | 40 ++++++- packages/data-transformer/package.json | 4 +- .../transformers/data-transformer.builder.ts | 1 + .../src/transformers/data.transformer.spec.ts | 30 +++++ .../src/transformers/data.transformer.ts | 103 +++++++++--------- 5 files changed, 125 insertions(+), 53 deletions(-) diff --git a/packages/data-transformer/package-lock.json b/packages/data-transformer/package-lock.json index 6d6ac0810..301677e39 100644 --- a/packages/data-transformer/package-lock.json +++ b/packages/data-transformer/package-lock.json @@ -10,7 +10,9 @@ "license": "ISC", "dependencies": { "@pristine-ts/common": "file:../common", - "@pristine-ts/logging": "file:../logging" + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1" } }, "../common": { @@ -40,6 +42,24 @@ "node_modules/@pristine-ts/logging": { "resolved": "../logging", "link": true + }, + "node_modules/@pristine-ts/metadata": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pristine-ts/metadata/-/metadata-1.0.2.tgz", + "integrity": "sha512-flztBNC8Rf8IqrapZCrz86IUc/wf+L13gRL+R3Z5wCQxldG5GTGd+eJYEgDfzKa7crxoofz8Y1+2kJDj4nScdg==", + "dependencies": { + "reflect-metadata": "^0.2.1" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" } }, "dependencies": { @@ -58,6 +78,24 @@ "@pristine-ts/configuration": "file:../configuration", "date-fns": "^2.30.0" } + }, + "@pristine-ts/metadata": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pristine-ts/metadata/-/metadata-1.0.2.tgz", + "integrity": "sha512-flztBNC8Rf8IqrapZCrz86IUc/wf+L13gRL+R3Z5wCQxldG5GTGd+eJYEgDfzKa7crxoofz8Y1+2kJDj4nScdg==", + "requires": { + "reflect-metadata": "^0.2.1" + } + }, + "class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" } } } diff --git a/packages/data-transformer/package.json b/packages/data-transformer/package.json index 0c23c3707..d3466a1f4 100644 --- a/packages/data-transformer/package.json +++ b/packages/data-transformer/package.json @@ -21,7 +21,9 @@ }, "dependencies": { "@pristine-ts/common": "file:../common", - "@pristine-ts/logging": "file:../logging" + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1" }, "jest": { "transform": { diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.ts b/packages/data-transformer/src/transformers/data-transformer.builder.ts index 49d1b627b..4e6619996 100644 --- a/packages/data-transformer/src/transformers/data-transformer.builder.ts +++ b/packages/data-transformer/src/transformers/data-transformer.builder.ts @@ -21,6 +21,7 @@ export class DataTransformerBuilder { public properties: {[sourceProperty in string]: DataTransformerProperty} = {} + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerBuilder { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); diff --git a/packages/data-transformer/src/transformers/data.transformer.spec.ts b/packages/data-transformer/src/transformers/data.transformer.spec.ts index 6c7280571..6ee221026 100644 --- a/packages/data-transformer/src/transformers/data.transformer.spec.ts +++ b/packages/data-transformer/src/transformers/data.transformer.spec.ts @@ -8,6 +8,7 @@ import {DataTransformerRow} from "../types/data-transformer.row"; import 'jest-extended'; import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; +import {property} from "@pristine-ts/metadata"; describe('Data Transformer', () => { it("should properly transform", async () => { @@ -191,4 +192,33 @@ describe('Data Transformer', () => { await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); }) + + it("should properly type the return object when a destinationType is passed", async () => { + class Source { + @property() + title: string; + } + + class Destination { + @property() + name: string; + } + + const source = new Source(); + source.title = "TITLE"; + + const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + + const dataTransformerBuilder = new DataTransformerBuilder(); + dataTransformerBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .addNormalizer(LowercaseNormalizer.name) + .end() + + const destination = await dataTransformer.transform(dataTransformerBuilder, source, Destination); + + expect(destination.name).toBe("title"); + }) }); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data.transformer.ts b/packages/data-transformer/src/transformers/data.transformer.ts index 2569d97b0..3a9fe8d06 100644 --- a/packages/data-transformer/src/transformers/data.transformer.ts +++ b/packages/data-transformer/src/transformers/data.transformer.ts @@ -10,12 +10,14 @@ import {DataTransformerRow} from "../types/data-transformer.row"; import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; +import {ClassMetadata} from "@pristine-ts/metadata"; +import {ClassConstructor, plainToInstance} from "class-transformer"; @moduleScoped(DataTransformerModuleKeyname) @injectable() export class DataTransformer { - private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface} = {} - private readonly dataTransformerInterceptorsMap: { [key in DataTransformerInterceptorUniqueKeyType]: DataTransformerInterceptorInterface} = {} + private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface } = {} + private readonly dataTransformerInterceptorsMap: { [key in DataTransformerInterceptorUniqueKeyType]: DataTransformerInterceptorInterface } = {} public constructor(@injectAll("DataNormalizerInterface") private readonly dataNormalizers: DataNormalizerInterface[], @injectAll("DataTransformerInterceptor") private readonly dataTransformerInterceptors: DataTransformerInterceptorInterface[],) { @@ -24,80 +26,79 @@ export class DataTransformer { }) dataTransformerInterceptors.forEach(interceptor => { - this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; + this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; }); } - public async transform(builder: DataTransformerBuilder, source: (DataTransformerRow)[]): Promise { - const globalNormalizers = builder.normalizers; + public async transformRows(builder: DataTransformerBuilder, rows: DataTransformerRow[], destinationType?: ClassConstructor): Promise { + return rows.map(row => this.transform(builder, row, destinationType)); + } - const destination: DataTransformerRow[] = []; + public async transform(builder: DataTransformerBuilder, source: DataTransformerRow, destinationType?: ClassConstructor): Promise { + const globalNormalizers = builder.normalizers; let row: DataTransformerRow = {}; - for(const key in source) { - if(source.hasOwnProperty(key) === false) { - continue; - } - let interceptedInputRow = source[key]; + if(destinationType) { + row = plainToInstance(destinationType, {}); + } - // Execute the before row interceptors. - for(const element of builder.beforeRowTransformInterceptors) { - const interceptor = this.dataTransformerInterceptorsMap[element.key]; + let interceptedInputRow = source; - if(interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } + // Execute the before row interceptors. + for (const element of builder.beforeRowTransformInterceptors) { + const interceptor = this.dataTransformerInterceptorsMap[element.key]; - // todo: Pass the options when we start using them. - interceptedInputRow = await interceptor.beforeRowTransform(interceptedInputRow); + if (interceptor === undefined) { + throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); } - // Loop over the properties defined in the builder - for (const key in builder.properties) { - if(builder.properties.hasOwnProperty(key) === false) { - continue; - } + // todo: Pass the options when we start using them. + interceptedInputRow = await interceptor.beforeRowTransform(interceptedInputRow); + } - const property: DataTransformerProperty = builder.properties[key]; - if(interceptedInputRow.hasOwnProperty(property.sourceProperty) === false) { - if(property.isOptional) { - continue; - } + // Loop over the properties defined in the builder + for (const key in builder.properties) { + if (builder.properties.hasOwnProperty(key) === false) { + continue; + } - throw new DataTransformerSourcePropertyNotFoundError("The property '" + key + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", key) + const property: DataTransformerProperty = builder.properties[key]; + if (interceptedInputRow.hasOwnProperty(property.sourceProperty) === false) { + if (property.isOptional) { + continue; } - let value = interceptedInputRow[property.sourceProperty]; + throw new DataTransformerSourcePropertyNotFoundError("The property '" + key + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", key) + } - // Remove the normalizers part of the excludedNormalizers - const normalizers = globalNormalizers.filter(element => property.excludedNormalizers.has(element.key) === false); - normalizers.push(...property.normalizers); + let value = interceptedInputRow[property.sourceProperty]; - normalizers.forEach(element => { - const dataNormalizer = this.dataNormalizersMap[element.key]; - value = dataNormalizer.normalize(value, element.options); - }) + // Remove the normalizers part of the excludedNormalizers + const normalizers = globalNormalizers.filter(element => property.excludedNormalizers.has(element.key) === false); + normalizers.push(...property.normalizers); - // Assign the resulting value in the destination - row[property.destinationProperty] = value; - } + normalizers.forEach(element => { + const dataNormalizer = this.dataNormalizersMap[element.key]; + value = dataNormalizer.normalize(value, element.options); + }) - // Execute the before row interceptors. - for(const element of builder.afterRowTransformInterceptors) { - const interceptor: DataTransformerInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; + // Assign the resulting value in the destination + row[property.destinationProperty] = value; + } - if(interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } + // Execute the before row interceptors. + for (const element of builder.afterRowTransformInterceptors) { + const interceptor: DataTransformerInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; - // todo pass the options when we start using it. - row = await interceptor.afterRowTransform(row); + if (interceptor === undefined) { + throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); } - destination.push(row); + // todo pass the options when we start using it. + row = await interceptor.afterRowTransform(row); } - return destination; + return row; } } \ No newline at end of file From c9613cae9015a18e315991299db9f7706aad8a5d Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Wed, 17 Jan 2024 16:05:45 -0800 Subject: [PATCH 09/20] - Very early draft of starting the data-mapping tree structure instead. This will support multiple nesting levels as well as arrays. --- .../data-transformer/src/builders/builders.ts | 0 .../src/builders/data-mapping.builder.ts | 3 + .../src/enums/data-mapping-node-type.enum.ts | 5 ++ packages/data-transformer/src/enums/enums.ts | 0 .../mapping-nodes/array-data-mapping.node.ts | 12 +++ .../src/mapping-nodes/data-mapping.leaf.ts | 83 +++++++++++++++++++ .../src/mapping-nodes/data-mapping.node.ts | 21 +++++ .../src/mapping-nodes/data-mapping.tree.ts | 79 ++++++++++++++++++ .../src/mapping-nodes/mapping-nodes.ts | 3 + .../transformers/data-transformer.builder.ts | 1 - .../data-transformer.property.spec.ts | 2 + .../transformers/data-transformer.property.ts | 4 + .../src/transformers/data.transformer.spec.ts | 30 +++++++ 13 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 packages/data-transformer/src/builders/builders.ts create mode 100644 packages/data-transformer/src/builders/data-mapping.builder.ts create mode 100644 packages/data-transformer/src/enums/data-mapping-node-type.enum.ts create mode 100644 packages/data-transformer/src/enums/enums.ts create mode 100644 packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts create mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts create mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.node.ts create mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts create mode 100644 packages/data-transformer/src/mapping-nodes/mapping-nodes.ts diff --git a/packages/data-transformer/src/builders/builders.ts b/packages/data-transformer/src/builders/builders.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/data-transformer/src/builders/data-mapping.builder.ts b/packages/data-transformer/src/builders/data-mapping.builder.ts new file mode 100644 index 000000000..9274d4b05 --- /dev/null +++ b/packages/data-transformer/src/builders/data-mapping.builder.ts @@ -0,0 +1,3 @@ +export class DataMappingBuilder { + +} \ No newline at end of file diff --git a/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts b/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts new file mode 100644 index 000000000..39cf495d4 --- /dev/null +++ b/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts @@ -0,0 +1,5 @@ +export enum DataMappingNodeTypeEnum { + ArrayDataMappingNode = "ARRAY_DATA_MAPPING_NODE", + Node = "DATA_MAPPING_NODE", + Leaf = "DATA_MAPPING_LEAF", +} \ No newline at end of file diff --git a/packages/data-transformer/src/enums/enums.ts b/packages/data-transformer/src/enums/enums.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts new file mode 100644 index 000000000..7a2a664e0 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts @@ -0,0 +1,12 @@ +import {DataMappingNode} from "./data-mapping.node"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; + +/** + * We need an array node because the behaviour when mapping an array is different. For each element in the source property, + * we will + */ +export class ArrayDataMappingNode extends DataMappingNode { + public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.ArrayDataMappingNode; + + +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts new file mode 100644 index 000000000..927a6fbe7 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts @@ -0,0 +1,83 @@ +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataMappingNode} from "./data-mapping.node"; +import {DataMappingTree} from "./data-mapping.tree"; + +export class DataMappingLeaf { + public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Leaf; + + public sourceProperties: string[] = []; + + public destinationProperty: string; + + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + + public excludedNormalizers: Set = new Set(); + + public isOptional: boolean; + + public constructor( + private readonly parent: DataMappingNode, + private readonly root: DataMappingTree, + ) { + } + + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingLeaf { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) + } + + if(this.root.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the root and cannot be also added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + + return this; + } + + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + public excludeNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): DataMappingLeaf { + if(this.excludedNormalizers.has(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The EXCLUDED data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey) + } + + this.excludedNormalizers.add(normalizerUniqueKey); + + return this; + } + + public import(schema: any) { + this.normalizers = schema.normalizers; + + this.excludedNormalizers = new Set() + if(schema.hasOwnProperty("excludedNormalizers")) { + for(const item in schema.excludedNormalizers) { + this.excludeNormalizer(item); + } + } + + this.isOptional = schema.isOptional; + this.sourceProperties = schema.sourceProperties; + this.destinationProperty = schema.destinationProperty; + } + + public export(): any { + return { + "sourceProperties": this.sourceProperties, + "destinationProperty": this.destinationProperty, + "isOptional": this.isOptional, + "normalizers": this.normalizers, + "excludedNormalizers": this.excludedNormalizers, + } + } +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts new file mode 100644 index 000000000..02f7aff76 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts @@ -0,0 +1,21 @@ +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingLeaf} from "./data-mapping.leaf"; + +export class DataMappingNode { + public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node; + public nodes: DataMappingNode[] = []; + public leaves: DataMappingLeaf[] = []; + + public parent?: DataMappingNode; + + constructor(private readonly root: DataMappingTree) { + } + + /** + * The DataMappingNode can only have one sourceProperty assigned. We need one sourceProperty to understand how to + * navigate through the source object to pass the exact properties to the leaf nodes; + */ + public sourceProperty: string; + + public destinationProperty: string; +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts new file mode 100644 index 000000000..6ea5f6e22 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts @@ -0,0 +1,79 @@ +import {DataMappingNode} from "./data-mapping.node"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import { + DataBeforeRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-before-row-transformer-interceptor-already-added.error"; +import { + DataAfterRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-after-row-transformer-interceptor-already-added.error"; +import {DataTransformerProperty} from "../transformers/data-transformer.property"; +import {DataMappingLeaf} from "./data-mapping.leaf"; + +export class DataMappingTree extends DataMappingNode { + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; + public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; + + + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingTree { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + return this; + } + + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingTree { + if(this.hasBeforeRowTransformInterceptor(key)) { + throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) + } + + this.beforeRowTransformInterceptors.push({ + key, + options, + }); + + return this; + } + + public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { + return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; + } + + public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingTree { + if(this.hasAfterRowTransformInterceptor(key)) { + throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) + } + + this.afterRowTransformInterceptors.push({ + key, + options, + }); + + return this; + } + + public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { + return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; + } + + public add() { + return new DataMappingLeaf(this); + } + + public addArray() {} + + public addNestingLevel() { + return new DataMappingNode(); + } +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts new file mode 100644 index 000000000..454d7ad47 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts @@ -0,0 +1,3 @@ +export * from "./array-data-mapping.node"; +export * from "./data-mapping.node"; +export * from "./data-mapping.tree"; \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.ts b/packages/data-transformer/src/transformers/data-transformer.builder.ts index 4e6619996..49d1b627b 100644 --- a/packages/data-transformer/src/transformers/data-transformer.builder.ts +++ b/packages/data-transformer/src/transformers/data-transformer.builder.ts @@ -21,7 +21,6 @@ export class DataTransformerBuilder { public properties: {[sourceProperty in string]: DataTransformerProperty} = {} - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerBuilder { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); diff --git a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts b/packages/data-transformer/src/transformers/data-transformer.property.spec.ts index 576cf44e4..e28356aa7 100644 --- a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts +++ b/packages/data-transformer/src/transformers/data-transformer.property.spec.ts @@ -64,4 +64,6 @@ describe('Data Transformer Property', () => { it("should properly return the builder when calling end()", () => { expect(dataTransformerProperty.end()).toBe(dataTransformerBuilder); }) + + it("should properly accept a list of sourceProperties", () => {}) }); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.property.ts b/packages/data-transformer/src/transformers/data-transformer.property.ts index 2cd4b51bb..63d85012f 100644 --- a/packages/data-transformer/src/transformers/data-transformer.property.ts +++ b/packages/data-transformer/src/transformers/data-transformer.property.ts @@ -9,6 +9,8 @@ export class DataTransformerProperty { public excludedNormalizers: Set = new Set(); public isOptional: boolean = false; + public children: {[key in (string|number)]: DataTransformerProperty} = {}; + public constructor(private readonly builder: DataTransformerBuilder) { } @@ -21,6 +23,8 @@ export class DataTransformerProperty { return this; } + public setChildProperty(key:) + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerProperty { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) diff --git a/packages/data-transformer/src/transformers/data.transformer.spec.ts b/packages/data-transformer/src/transformers/data.transformer.spec.ts index 6ee221026..691822064 100644 --- a/packages/data-transformer/src/transformers/data.transformer.spec.ts +++ b/packages/data-transformer/src/transformers/data.transformer.spec.ts @@ -221,4 +221,34 @@ describe('Data Transformer', () => { expect(destination.name).toBe("title"); }) + + + it("should properly type the nested objects", async () => { + class Source { + @property() + title: string; + } + + class Destination { + @property() + name: string; + } + + const source = new Source(); + source.title = "TITLE"; + + const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + + const dataTransformerBuilder = new DataTransformerBuilder(); + dataTransformerBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .addNormalizer(LowercaseNormalizer.name) + .end() + + const destination = await dataTransformer.transform(dataTransformerBuilder, source, Destination); + + expect(destination.name).toBe("title"); + }) }); \ No newline at end of file From 7ef74d3f75d9b422a8ee948599270e96e94ae005 Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Wed, 17 Jan 2024 20:43:05 -0800 Subject: [PATCH 10/20] - Continuing progress. --- .../src/builders/data-mapping.builder.spec.ts | 134 ++++++++++++++++++ .../src/builders/data-mapping.builder.ts | 93 +++++++++++- .../src/enums/data-mapping-node-type.enum.ts | 2 +- .../errors/undefined-source-property.error.ts | 21 +++ .../mapping-nodes/array-data-mapping.node.ts | 40 +++++- .../mapping-nodes/base-data-mapping.node.ts | 24 ++++ .../src/mapping-nodes/data-mapping.leaf.ts | 39 +++-- .../src/mapping-nodes/data-mapping.node.ts | 51 +++++-- .../src/mapping-nodes/data-mapping.tree.ts | 79 ----------- .../src/mapping-nodes/mapping-nodes.ts | 2 +- .../transformers/data-transformer.property.ts | 2 - 11 files changed, 381 insertions(+), 106 deletions(-) create mode 100644 packages/data-transformer/src/builders/data-mapping.builder.spec.ts create mode 100644 packages/data-transformer/src/errors/undefined-source-property.error.ts create mode 100644 packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts delete mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts diff --git a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts b/packages/data-transformer/src/builders/data-mapping.builder.spec.ts new file mode 100644 index 000000000..aa3567675 --- /dev/null +++ b/packages/data-transformer/src/builders/data-mapping.builder.spec.ts @@ -0,0 +1,134 @@ +import {DataMappingBuilder} from "./data-mapping.builder"; +import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; +import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; + +describe("Data Mapping Builder", () => { + it("should properly build a simple DataMappingBuilder", () => { + class Source { + title: string; + + rank: number; + + firstName: string; + + lastName: string; + } + + class Destination { + name: string; + + position: number; + + familyName: string; + } + + const dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .add() + .setSourceProperty("rank") + .setDestinationProperty("position") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .add() + .setSourceProperty("lastName") + .setDestinationProperty("familyName") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .end() + + expect(dataMappingBuilder.nodes.title).toBeDefined() + expect(dataMappingBuilder.nodes.title.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.title.destinationProperty).toBe("name") + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.rank).toBeDefined() + expect(dataMappingBuilder.nodes.rank.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.rank.destinationProperty).toBe("position") + expect((dataMappingBuilder.nodes.rank as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.rank as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.lastName).toBeDefined() + expect(dataMappingBuilder.nodes.lastName.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.lastName.destinationProperty).toBe("familyName") + expect((dataMappingBuilder.nodes.lastName as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.lastName as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.firstName).toBeUndefined() + }) + it("should properly build a complex DataMappingBuilder", () => { + class ArraySource { + rank: number; + } + + class NestedSource { + nestedTitle: string; + } + + class Source { + title: string; + + nested: NestedSource; + + array: ArraySource[] = []; + } + + class ArrayDestination { + position: number; + } + + class NestedDestination { + nestedName: string; + } + + class Destination { + name: string; + + child: NestedDestination; + + list: ArrayDestination[]; + } + + const dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .addNestingLevel() + .setSourceProperty("nested") + .setDestinationProperty("child") + .add() + .setSourceProperty("nestedTitle") + .setDestinationProperty("nestedName") + .end() + .end() + .addArray() + .setSourceProperty("array") + .setDestinationProperty("list") + .add() + .setSourceProperty("rank") + .setDestinationProperty("position") + .end() + .end() + .end(); + + expect(dataMappingBuilder.nodes.title).toBeDefined() + expect(dataMappingBuilder.nodes.title.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.title.destinationProperty).toBe("name") + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + + }) +}) \ No newline at end of file diff --git a/packages/data-transformer/src/builders/data-mapping.builder.ts b/packages/data-transformer/src/builders/data-mapping.builder.ts index 9274d4b05..2ebac2023 100644 --- a/packages/data-transformer/src/builders/data-mapping.builder.ts +++ b/packages/data-transformer/src/builders/data-mapping.builder.ts @@ -1,3 +1,94 @@ -export class DataMappingBuilder { +import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import { + DataBeforeRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-before-row-transformer-interceptor-already-added.error"; +import { + DataAfterRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-after-row-transformer-interceptor-already-added.error"; +import {DataTransformerProperty} from "../transformers/data-transformer.property"; +import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; +import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; +import {BaseDataMappingNode} from "../mapping-nodes/base-data-mapping.node"; +import {ArrayDataMappingNode} from "../mapping-nodes/array-data-mapping.node"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; + +export class DataMappingBuilder extends BaseDataMappingNode{ + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; + public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; + + + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingBuilder { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + return this; + } + + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasBeforeRowTransformInterceptor(key)) { + throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) + } + + this.beforeRowTransformInterceptors.push({ + key, + options, + }); + + return this; + } + + public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { + return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; + } + + public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasAfterRowTransformInterceptor(key)) { + throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) + } + + this.afterRowTransformInterceptors.push({ + key, + options, + }); + + return this; + } + + public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { + return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; + } + + public add() { + return new DataMappingLeaf(this, this); + } + + + public addNestingLevel() { + return new DataMappingNode(this, this); + } + + public addArray() { + return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); + } + /** + * This method is called at the end just to make it nice since all the nodes will have one, it's nice + * that the builder has one too. + */ + public end(): DataMappingBuilder { + return this; + } } \ No newline at end of file diff --git a/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts b/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts index 39cf495d4..8a04000b3 100644 --- a/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts +++ b/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts @@ -1,5 +1,5 @@ export enum DataMappingNodeTypeEnum { - ArrayDataMappingNode = "ARRAY_DATA_MAPPING_NODE", + Array = "DATA_MAPPING_NODE_ARRAY", Node = "DATA_MAPPING_NODE", Leaf = "DATA_MAPPING_LEAF", } \ No newline at end of file diff --git a/packages/data-transformer/src/errors/undefined-source-property.error.ts b/packages/data-transformer/src/errors/undefined-source-property.error.ts new file mode 100644 index 000000000..2c1c364b3 --- /dev/null +++ b/packages/data-transformer/src/errors/undefined-source-property.error.ts @@ -0,0 +1,21 @@ +import {LoggableError} from "@pristine-ts/common"; +import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; +import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; +import {ArrayDataMappingNode} from "../mapping-nodes/array-data-mapping.node"; + +/** + * This Error is thrown when you are trying to add a Node which has an undefined sourceProperty value. + */ +export class UndefinedSourcePropertyError extends LoggableError { + + public constructor(node: DataMappingLeaf | DataMappingNode | ArrayDataMappingNode) { + super("The `sourceProperty` property of the Node cannot be undefined to be added as a Node to its parent.", { + node, + }); + + // Set the prototype explicitly. + // As specified in the documentation in TypeScript + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, UndefinedSourcePropertyError.prototype); + } +} diff --git a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts index 7a2a664e0..5ab09d03c 100644 --- a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts +++ b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts @@ -1,12 +1,48 @@ import {DataMappingNode} from "./data-mapping.node"; import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {BaseDataMappingNode} from "./base-data-mapping.node"; /** * We need an array node because the behaviour when mapping an array is different. For each element in the source property, * we will */ -export class ArrayDataMappingNode extends DataMappingNode { - public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.ArrayDataMappingNode; +export class ArrayDataMappingNode extends BaseDataMappingNode{ + public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Array; + public sourceProperty!: string; + public destinationProperty!: string; + constructor(public readonly root: DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder) { + super(); + } + + public setSourceProperty(sourceProperty: string): ArrayDataMappingNode { + this.sourceProperty = sourceProperty; + return this; + } + public setDestinationProperty(destinationProperty: string): ArrayDataMappingNode { + this.destinationProperty = destinationProperty; + return this; + } + + /** + * You don't necessarily have to call the set method. If you have an array of + * simply types: string[] for example, you can simply skip this. The sourceProperty will be directly + * assigned to destinationProperty. + * + * For now, you won't be able to normalize each individual inside of it though. + */ + public set() { + return new DataMappingNode(this.root, this); + } + + + public end(): DataMappingNode | DataMappingBuilder { + this.parent.addNode(this) + + return this.parent; + } } \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts new file mode 100644 index 000000000..16cc88328 --- /dev/null +++ b/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts @@ -0,0 +1,24 @@ +import {DataMappingNode} from "./data-mapping.node"; +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; +import {ArrayDataMappingNode} from "./array-data-mapping.node"; + +export abstract class BaseDataMappingNode { + public nodes: {[sourceProperty in string]: DataMappingNode | DataMappingLeaf} = {}; + + + /** + * This method is called by the node itself to tell its parent that it has been build and is ready to be added. + * We use this mechanism to force the `end()` method on the leaf to be called so we can do some validations before + * adding it to the tree. + * + * @param node + */ + public addNode(node: DataMappingLeaf | DataMappingNode) { + if(node.sourceProperty === undefined) { + throw new UndefinedSourcePropertyError(node); + } + + this.nodes[node.sourceProperty] = node; + } +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts index 927a6fbe7..ebe73fc8a 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts @@ -4,27 +4,36 @@ import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-adde import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {DataMappingNode} from "./data-mapping.node"; -import {DataMappingTree} from "./data-mapping.tree"; export class DataMappingLeaf { public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Leaf; - public sourceProperties: string[] = []; + public sourceProperty!: string; - public destinationProperty: string; + public destinationProperty!: string; public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; public excludedNormalizers: Set = new Set(); - public isOptional: boolean; + public isOptional: boolean = false; public constructor( - private readonly parent: DataMappingNode, - private readonly root: DataMappingTree, + private readonly root: DataMappingBuilder, + private readonly parent: DataMappingNode | DataMappingBuilder, ) { } + public setSourceProperty(sourceProperty: string): DataMappingLeaf { + this.sourceProperty = sourceProperty; + return this; + } + + public setDestinationProperty(destinationProperty: string): DataMappingLeaf { + this.destinationProperty = destinationProperty; + return this; + } + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingLeaf { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) @@ -56,6 +65,19 @@ export class DataMappingLeaf { return this; } + public setIsOptional(isOptional: boolean): DataMappingLeaf { + this.isOptional = isOptional; + + return this; + } + + public end(): DataMappingNode | DataMappingBuilder { + // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. + this.parent.addNode(this) + + return this.parent; + } + public import(schema: any) { this.normalizers = schema.normalizers; @@ -67,13 +89,14 @@ export class DataMappingLeaf { } this.isOptional = schema.isOptional; - this.sourceProperties = schema.sourceProperties; + this.sourceProperty = schema.sourceProperty; this.destinationProperty = schema.destinationProperty; } public export(): any { return { - "sourceProperties": this.sourceProperties, + "_type": this.type, + "sourceProperty": this.sourceProperty, "destinationProperty": this.destinationProperty, "isOptional": this.isOptional, "normalizers": this.normalizers, diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts index 02f7aff76..f06097ad5 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts @@ -1,21 +1,48 @@ import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; import {DataMappingLeaf} from "./data-mapping.leaf"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataTransformerProperty} from "../transformers/data-transformer.property"; +import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; +import {BaseDataMappingNode} from "./base-data-mapping.node"; +import {ArrayDataMappingNode} from "./array-data-mapping.node"; -export class DataMappingNode { - public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node; - public nodes: DataMappingNode[] = []; - public leaves: DataMappingLeaf[] = []; +export class DataMappingNode extends BaseDataMappingNode { + public sourceProperty!: string; + public destinationProperty!: string; - public parent?: DataMappingNode; + constructor(public readonly root: DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder, + public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node, + ) { + super(); + } - constructor(private readonly root: DataMappingTree) { + public setSourceProperty(sourceProperty: string): DataMappingNode { + this.sourceProperty = sourceProperty; + return this; + } + public setDestinationProperty(destinationProperty: string): DataMappingNode { + this.destinationProperty = destinationProperty; + return this; } - /** - * The DataMappingNode can only have one sourceProperty assigned. We need one sourceProperty to understand how to - * navigate through the source object to pass the exact properties to the leaf nodes; - */ - public sourceProperty: string; + public add() { + return new DataMappingLeaf(this.root, this); + } + + public addNestingLevel() { + return new DataMappingNode(this.root, this); + } - public destinationProperty: string; + public addArray(): DataMappingNode { + return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); + } + + public end(): DataMappingNode | DataMappingBuilder { + // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. + this.parent.addNode(this) + + //@ts-ignore + return this.parent; + } } \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts deleted file mode 100644 index 6ea5f6e22..000000000 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.tree.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {DataMappingNode} from "./data-mapping.node"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import { - DataBeforeRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-before-row-transformer-interceptor-already-added.error"; -import { - DataAfterRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-after-row-transformer-interceptor-already-added.error"; -import {DataTransformerProperty} from "../transformers/data-transformer.property"; -import {DataMappingLeaf} from "./data-mapping.leaf"; - -export class DataMappingTree extends DataMappingNode { - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingTree { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingTree { - if(this.hasBeforeRowTransformInterceptor(key)) { - throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) - } - - this.beforeRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingTree { - if(this.hasAfterRowTransformInterceptor(key)) { - throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) - } - - this.afterRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public add() { - return new DataMappingLeaf(this); - } - - public addArray() {} - - public addNestingLevel() { - return new DataMappingNode(); - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts index 454d7ad47..d9ab55d25 100644 --- a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts +++ b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts @@ -1,3 +1,3 @@ export * from "./array-data-mapping.node"; export * from "./data-mapping.node"; -export * from "./data-mapping.tree"; \ No newline at end of file +export * from "../builders/data-mapping.builder"; \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.property.ts b/packages/data-transformer/src/transformers/data-transformer.property.ts index 63d85012f..ef54b3c4b 100644 --- a/packages/data-transformer/src/transformers/data-transformer.property.ts +++ b/packages/data-transformer/src/transformers/data-transformer.property.ts @@ -23,8 +23,6 @@ export class DataTransformerProperty { return this; } - public setChildProperty(key:) - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerProperty { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) From 6356e877af9a41ec568497980f5603d709e813f5 Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 09:02:25 -0800 Subject: [PATCH 11/20] - Added the tests that ensures that the tree is properly built. --- .../data-transformer/src/builders/builders.ts | 1 + .../src/builders/data-mapping.builder.spec.ts | 77 ++++++++++++++++++- .../src/builders/data-mapping.builder.ts | 10 ++- packages/data-transformer/src/enums/enums.ts | 1 + .../data-transformer/src/errors/errors.ts | 4 +- .../errors/undefined-source-property.error.ts | 3 +- .../mapping-nodes/array-data-mapping.node.ts | 48 ------------ .../mapping-nodes/base-data-mapping.node.ts | 2 - .../src/mapping-nodes/data-mapping.leaf.ts | 5 +- .../src/mapping-nodes/data-mapping.node.ts | 9 ++- .../src/mapping-nodes/mapping-nodes.ts | 4 +- .../src/transformers/transformers.ts | 3 +- 12 files changed, 98 insertions(+), 69 deletions(-) delete mode 100644 packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts diff --git a/packages/data-transformer/src/builders/builders.ts b/packages/data-transformer/src/builders/builders.ts index e69de29bb..c9cfed586 100644 --- a/packages/data-transformer/src/builders/builders.ts +++ b/packages/data-transformer/src/builders/builders.ts @@ -0,0 +1 @@ +export * from "./data-mapping.builder"; \ No newline at end of file diff --git a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts b/packages/data-transformer/src/builders/data-mapping.builder.spec.ts index aa3567675..5682f7fd7 100644 --- a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts +++ b/packages/data-transformer/src/builders/data-mapping.builder.spec.ts @@ -79,6 +79,8 @@ describe("Data Mapping Builder", () => { nested: NestedSource; array: ArraySource[] = []; + + children: string[] = []; } class ArrayDestination { @@ -95,6 +97,8 @@ describe("Data Mapping Builder", () => { child: NestedDestination; list: ArrayDestination[]; + + infants: string[] = [] } const dataMappingBuilder = new DataMappingBuilder(); @@ -113,7 +117,7 @@ describe("Data Mapping Builder", () => { .setDestinationProperty("nestedName") .end() .end() - .addArray() + .addArrayOfObjects() .setSourceProperty("array") .setDestinationProperty("list") .add() @@ -121,6 +125,10 @@ describe("Data Mapping Builder", () => { .setDestinationProperty("position") .end() .end() + .addArrayOfScalar() + .setSourceProperty("children") + .setDestinationProperty("infants") + .end() .end(); expect(dataMappingBuilder.nodes.title).toBeDefined() @@ -129,6 +137,73 @@ describe("Data Mapping Builder", () => { expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.size).toBe(1) expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + expect(dataMappingBuilder.nodes.nested).toBeDefined() + expect(dataMappingBuilder.nodes.nested.type).toBe(DataMappingNodeTypeEnum.Node) + expect(dataMappingBuilder.nodes.nested.destinationProperty).toBe("child") + expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nestedTitle.sourceProperty).toBe("nestedTitle") + expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nestedTitle.destinationProperty).toBe("nestedName") + + expect(dataMappingBuilder.nodes.array).toBeDefined() + expect(dataMappingBuilder.nodes.array.type).toBe(DataMappingNodeTypeEnum.Array) + expect(dataMappingBuilder.nodes.array.destinationProperty).toBe("list") + expect((dataMappingBuilder.nodes.array as DataMappingNode).nodes.rank.sourceProperty).toBe("rank") + expect((dataMappingBuilder.nodes.array as DataMappingNode).nodes.rank.destinationProperty).toBe("position") + + expect(dataMappingBuilder.nodes.children).toBeDefined() + expect(dataMappingBuilder.nodes.children.type).toBe(DataMappingNodeTypeEnum.Array) + expect(dataMappingBuilder.nodes.children.destinationProperty).toBe("infants") + expect((dataMappingBuilder.nodes.children as DataMappingLeaf).sourceProperty).toBe("children") + expect((dataMappingBuilder.nodes.children as DataMappingLeaf).destinationProperty).toBe("infants") + + }) + + it("should properly set the parents", () => { + class NestedSource2 { + nestedTitle2: string; + } + + class NestedSource { + nested2: NestedSource2; + } + + class Source { + nested: NestedSource; + } + + class NestedDestination2 { + nestedTitle2: string; + } + class NestedDestination { + nested2: NestedDestination2; + } + + class Destination { + nested: NestedDestination; + } + + const dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .addNestingLevel() + .setSourceProperty("nested") + .setDestinationProperty("nested") + .addNestingLevel() + .setSourceProperty("nested2") + .setDestinationProperty("nested2") + .add() + .setSourceProperty("nestedTitle2") + .setDestinationProperty("nestedTitle2") + .end() + .end() + .end() + .end(); + + expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2.sourceProperty).toBe("nested2"); + expect(((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2 as DataMappingNode).nodes.nestedTitle2.sourceProperty).toBe("nestedTitle2"); + + expect((((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2 as DataMappingNode).parent as DataMappingNode).sourceProperty).toBe("nested"); + + expect(((((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2 as DataMappingNode).nodes.nestedTitle2 as DataMappingLeaf).parent as DataMappingNode).sourceProperty).toBe("nested2"); }) }) \ No newline at end of file diff --git a/packages/data-transformer/src/builders/data-mapping.builder.ts b/packages/data-transformer/src/builders/data-mapping.builder.ts index 2ebac2023..21559c163 100644 --- a/packages/data-transformer/src/builders/data-mapping.builder.ts +++ b/packages/data-transformer/src/builders/data-mapping.builder.ts @@ -8,11 +8,8 @@ import { import { DataAfterRowTransformerInterceptorAlreadyAddedError } from "../errors/data-after-row-transformer-interceptor-already-added.error"; -import {DataTransformerProperty} from "../transformers/data-transformer.property"; import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; -import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; import {BaseDataMappingNode} from "../mapping-nodes/base-data-mapping.node"; -import {ArrayDataMappingNode} from "../mapping-nodes/array-data-mapping.node"; import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; export class DataMappingBuilder extends BaseDataMappingNode{ @@ -80,9 +77,14 @@ export class DataMappingBuilder extends BaseDataMappingNode{ return new DataMappingNode(this, this); } - public addArray() { + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.Array); + } + + public addArrayOfObjects(): DataMappingNode { return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); } + /** * This method is called at the end just to make it nice since all the nodes will have one, it's nice * that the builder has one too. diff --git a/packages/data-transformer/src/enums/enums.ts b/packages/data-transformer/src/enums/enums.ts index e69de29bb..d8e6e1a82 100644 --- a/packages/data-transformer/src/enums/enums.ts +++ b/packages/data-transformer/src/enums/enums.ts @@ -0,0 +1 @@ +export * from "./data-mapping-node-type.enum"; \ No newline at end of file diff --git a/packages/data-transformer/src/errors/errors.ts b/packages/data-transformer/src/errors/errors.ts index 2e08cba08..742001c2d 100644 --- a/packages/data-transformer/src/errors/errors.ts +++ b/packages/data-transformer/src/errors/errors.ts @@ -1,5 +1,7 @@ export * from "./data-after-row-transformer-interceptor-already-added.error"; export * from "./data-before-row-transformer-interceptor-already-added.error"; export * from "./data-normalizer-already-added.error"; +export * from "./data-transformer-interceptor-not-found.error"; export * from "./data-transformer-source-property-not-found.error"; -export * from "./normalizer-invalid-source-type.error"; \ No newline at end of file +export * from "./normalizer-invalid-source-type.error"; +export * from "./undefined-source-property.error"; \ No newline at end of file diff --git a/packages/data-transformer/src/errors/undefined-source-property.error.ts b/packages/data-transformer/src/errors/undefined-source-property.error.ts index 2c1c364b3..fd6cba445 100644 --- a/packages/data-transformer/src/errors/undefined-source-property.error.ts +++ b/packages/data-transformer/src/errors/undefined-source-property.error.ts @@ -1,14 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; -import {ArrayDataMappingNode} from "../mapping-nodes/array-data-mapping.node"; /** * This Error is thrown when you are trying to add a Node which has an undefined sourceProperty value. */ export class UndefinedSourcePropertyError extends LoggableError { - public constructor(node: DataMappingLeaf | DataMappingNode | ArrayDataMappingNode) { + public constructor(node: DataMappingLeaf | DataMappingNode) { super("The `sourceProperty` property of the Node cannot be undefined to be added as a Node to its parent.", { node, }); diff --git a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts deleted file mode 100644 index 5ab09d03c..000000000 --- a/packages/data-transformer/src/mapping-nodes/array-data-mapping.node.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {DataMappingNode} from "./data-mapping.node"; -import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; -import {DataMappingBuilder} from "../builders/data-mapping.builder"; -import {DataMappingLeaf} from "./data-mapping.leaf"; -import {BaseDataMappingNode} from "./base-data-mapping.node"; - -/** - * We need an array node because the behaviour when mapping an array is different. For each element in the source property, - * we will - */ -export class ArrayDataMappingNode extends BaseDataMappingNode{ - public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Array; - - public sourceProperty!: string; - public destinationProperty!: string; - - constructor(public readonly root: DataMappingBuilder, - public readonly parent: DataMappingNode | DataMappingBuilder) { - super(); - } - - public setSourceProperty(sourceProperty: string): ArrayDataMappingNode { - this.sourceProperty = sourceProperty; - return this; - } - public setDestinationProperty(destinationProperty: string): ArrayDataMappingNode { - this.destinationProperty = destinationProperty; - return this; - } - - /** - * You don't necessarily have to call the set method. If you have an array of - * simply types: string[] for example, you can simply skip this. The sourceProperty will be directly - * assigned to destinationProperty. - * - * For now, you won't be able to normalize each individual inside of it though. - */ - public set() { - return new DataMappingNode(this.root, this); - } - - - public end(): DataMappingNode | DataMappingBuilder { - this.parent.addNode(this) - - return this.parent; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts index 16cc88328..15551b632 100644 --- a/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts +++ b/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts @@ -1,12 +1,10 @@ import {DataMappingNode} from "./data-mapping.node"; import {DataMappingLeaf} from "./data-mapping.leaf"; import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; -import {ArrayDataMappingNode} from "./array-data-mapping.node"; export abstract class BaseDataMappingNode { public nodes: {[sourceProperty in string]: DataMappingNode | DataMappingLeaf} = {}; - /** * This method is called by the node itself to tell its parent that it has been build and is ready to be added. * We use this mechanism to force the `end()` method on the leaf to be called so we can do some validations before diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts index ebe73fc8a..6f3aec19e 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts @@ -6,8 +6,6 @@ import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {DataMappingNode} from "./data-mapping.node"; export class DataMappingLeaf { - public type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Leaf; - public sourceProperty!: string; public destinationProperty!: string; @@ -20,7 +18,8 @@ export class DataMappingLeaf { public constructor( private readonly root: DataMappingBuilder, - private readonly parent: DataMappingNode | DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder, + public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Leaf, ) { } diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts index f06097ad5..fc8588f3e 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts +++ b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts @@ -1,10 +1,7 @@ import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; import {DataMappingLeaf} from "./data-mapping.leaf"; import {DataMappingBuilder} from "../builders/data-mapping.builder"; -import {DataTransformerProperty} from "../transformers/data-transformer.property"; -import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; import {BaseDataMappingNode} from "./base-data-mapping.node"; -import {ArrayDataMappingNode} from "./array-data-mapping.node"; export class DataMappingNode extends BaseDataMappingNode { public sourceProperty!: string; @@ -34,7 +31,11 @@ export class DataMappingNode extends BaseDataMappingNode { return new DataMappingNode(this.root, this); } - public addArray(): DataMappingNode { + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.Array); + } + + public addArrayOfObjects(): DataMappingNode { return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); } diff --git a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts index d9ab55d25..7a31c999f 100644 --- a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts +++ b/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts @@ -1,3 +1,3 @@ -export * from "./array-data-mapping.node"; +export * from "./base-data-mapping.node"; +export * from "./data-mapping.leaf"; export * from "./data-mapping.node"; -export * from "../builders/data-mapping.builder"; \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/transformers.ts b/packages/data-transformer/src/transformers/transformers.ts index 458cda5bd..3f6f77c90 100644 --- a/packages/data-transformer/src/transformers/transformers.ts +++ b/packages/data-transformer/src/transformers/transformers.ts @@ -1,4 +1,3 @@ +export * from "./data.transformer"; export * from "./data-transformer.builder"; export * from "./data-transformer.property"; - -export * from "./data.transformer"; \ No newline at end of file From 0e031fb38475cd694484363aae1100118bc5efd0 Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 13:12:32 -0800 Subject: [PATCH 12/20] - Updated the naming from 'transformer' to 'mapping' --- package.json | 2 +- .../package-lock.json | 0 .../package.json | 11 +- packages/data-mapping/readme.md | 1 + .../src/builders/builders.ts | 0 .../src/builders/data-mapping.builder.spec.ts | 4 +- .../src/builders/data-mapping.builder.ts | 143 ++++++++++++++++ .../src/data-mapping.module.keyname.ts | 1 + .../src/data-mapping.module.ts} | 13 +- .../src/enums/data-mapping-node-type.enum.ts | 0 .../src/enums/enums.ts | 0 ...sformer-interceptor-already-added.error.ts | 4 +- ...sformer-interceptor-already-added.error.ts | 4 +- .../data-normalizer-already-added.error.ts | 0 ...transformer-interceptor-not-found.error.ts | 4 +- ...sformer-source-property-not-found.error.ts | 0 .../src/errors/errors.ts | 0 .../normalizer-invalid-source-type.error.ts | 0 .../errors/undefined-source-property.error.ts | 4 +- .../default-data-mapping.interceptor.ts | 22 +++ .../src/interceptors/interceptors.ts | 1 + .../data-mapping-interceptor.interface.ts | 22 +++ .../interfaces/data-normalizer.interface.ts | 0 .../data-mapping/src/interfaces/interfaces.ts | 2 + .../src/mappers/data.mapper.spec.ts} | 114 ++++++------- .../data-mapping/src/mappers/data.mapper.ts | 95 +++++++++++ packages/data-mapping/src/mappers/mappers.ts | 1 + .../src/nodes}/base-data-mapping.node.ts | 0 .../src/nodes/data-mapping.leaf.spec.ts | 58 +++++++ .../src/nodes}/data-mapping.leaf.ts | 98 ++++++++++- .../src/nodes/data-mapping.node.ts | 132 +++++++++++++++ .../src/nodes/nodes.ts} | 0 .../base-normalizer.options.ts | 0 .../lowercase-normalizer.options.ts | 0 .../normalizer-options/normalizer-options.ts | 0 .../normalizers/lowercase.normalizer.spec.ts | 0 .../src/normalizers/lowercase.normalizer.ts | 0 .../src/normalizers/normalizers.ts | 0 .../src/test.spec.ts | 0 ...ata-mapping-interceptor-unique-key.type.ts | 1 + .../types/data-normalizer-unique-key.type.ts | 0 packages/data-mapping/src/types/types.ts | 2 + .../tsconfig.cjs.json | 0 .../tsconfig.json | 0 packages/data-transformer/readme.md | 1 - .../src/builders/data-mapping.builder.ts | 96 ----------- .../src/data-transformer.module.keyname.ts | 1 - .../default-data-transformer.interceptor.ts | 23 --- .../src/interceptors/interceptors.ts | 1 - .../data-transformer-interceptor.interface.ts | 24 --- .../src/interfaces/interfaces.ts | 2 - .../src/mapping-nodes/data-mapping.node.ts | 49 ------ .../data-transformer.builder.spec.ts | 131 --------------- .../transformers/data-transformer.builder.ts | 156 ------------------ .../data-transformer.property.spec.ts | 69 -------- .../transformers/data-transformer.property.ts | 69 -------- .../src/transformers/data.transformer.ts | 104 ------------ .../src/transformers/transformers.ts | 3 - ...transformer-interceptor-unique-key.type.ts | 1 - .../src/types/data-transformer.row.ts | 1 - packages/data-transformer/src/types/types.ts | 3 - 61 files changed, 654 insertions(+), 819 deletions(-) rename packages/{data-transformer => data-mapping}/package-lock.json (100%) rename packages/{data-transformer => data-mapping}/package.json (83%) create mode 100644 packages/data-mapping/readme.md rename packages/{data-transformer => data-mapping}/src/builders/builders.ts (100%) rename packages/{data-transformer => data-mapping}/src/builders/data-mapping.builder.spec.ts (98%) create mode 100644 packages/data-mapping/src/builders/data-mapping.builder.ts create mode 100644 packages/data-mapping/src/data-mapping.module.keyname.ts rename packages/{data-transformer/src/data-transformer.module.ts => data-mapping/src/data-mapping.module.ts} (55%) rename packages/{data-transformer => data-mapping}/src/enums/data-mapping-node-type.enum.ts (100%) rename packages/{data-transformer => data-mapping}/src/enums/enums.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/data-after-row-transformer-interceptor-already-added.error.ts (76%) rename packages/{data-transformer => data-mapping}/src/errors/data-before-row-transformer-interceptor-already-added.error.ts (76%) rename packages/{data-transformer => data-mapping}/src/errors/data-normalizer-already-added.error.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/data-transformer-interceptor-not-found.error.ts (76%) rename packages/{data-transformer => data-mapping}/src/errors/data-transformer-source-property-not-found.error.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/errors.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/normalizer-invalid-source-type.error.ts (100%) rename packages/{data-transformer => data-mapping}/src/errors/undefined-source-property.error.ts (84%) create mode 100644 packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts create mode 100644 packages/data-mapping/src/interceptors/interceptors.ts create mode 100644 packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts rename packages/{data-transformer => data-mapping}/src/interfaces/data-normalizer.interface.ts (100%) create mode 100644 packages/data-mapping/src/interfaces/interfaces.ts rename packages/{data-transformer/src/transformers/data.transformer.spec.ts => data-mapping/src/mappers/data.mapper.spec.ts} (70%) create mode 100644 packages/data-mapping/src/mappers/data.mapper.ts create mode 100644 packages/data-mapping/src/mappers/mappers.ts rename packages/{data-transformer/src/mapping-nodes => data-mapping/src/nodes}/base-data-mapping.node.ts (100%) create mode 100644 packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts rename packages/{data-transformer/src/mapping-nodes => data-mapping/src/nodes}/data-mapping.leaf.ts (56%) create mode 100644 packages/data-mapping/src/nodes/data-mapping.node.ts rename packages/{data-transformer/src/mapping-nodes/mapping-nodes.ts => data-mapping/src/nodes/nodes.ts} (100%) rename packages/{data-transformer => data-mapping}/src/normalizer-options/base-normalizer.options.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizer-options/lowercase-normalizer.options.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizer-options/normalizer-options.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizers/lowercase.normalizer.spec.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizers/lowercase.normalizer.ts (100%) rename packages/{data-transformer => data-mapping}/src/normalizers/normalizers.ts (100%) rename packages/{data-transformer => data-mapping}/src/test.spec.ts (100%) create mode 100644 packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts rename packages/{data-transformer => data-mapping}/src/types/data-normalizer-unique-key.type.ts (100%) create mode 100644 packages/data-mapping/src/types/types.ts rename packages/{data-transformer => data-mapping}/tsconfig.cjs.json (100%) rename packages/{data-transformer => data-mapping}/tsconfig.json (100%) delete mode 100644 packages/data-transformer/readme.md delete mode 100644 packages/data-transformer/src/builders/data-mapping.builder.ts delete mode 100644 packages/data-transformer/src/data-transformer.module.keyname.ts delete mode 100644 packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts delete mode 100644 packages/data-transformer/src/interceptors/interceptors.ts delete mode 100644 packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts delete mode 100644 packages/data-transformer/src/interfaces/interfaces.ts delete mode 100644 packages/data-transformer/src/mapping-nodes/data-mapping.node.ts delete mode 100644 packages/data-transformer/src/transformers/data-transformer.builder.spec.ts delete mode 100644 packages/data-transformer/src/transformers/data-transformer.builder.ts delete mode 100644 packages/data-transformer/src/transformers/data-transformer.property.spec.ts delete mode 100644 packages/data-transformer/src/transformers/data-transformer.property.ts delete mode 100644 packages/data-transformer/src/transformers/data.transformer.ts delete mode 100644 packages/data-transformer/src/transformers/transformers.ts delete mode 100644 packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts delete mode 100644 packages/data-transformer/src/types/data-transformer.row.ts delete mode 100644 packages/data-transformer/src/types/types.ts diff --git a/package.json b/package.json index 6e68b1d64..bf0ed758f 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@pristine-ts/common": "file:packages/common", "@pristine-ts/configuration": "file:packages/configuration", "@pristine-ts/core": "file:packages/core", - "@pristine-ts/data-transformer": "file:packages/data-transformer", + "@pristine-ts/data-mapping": "file:packages/data-mapping", "@pristine-ts/e2e": "file:tests/e2e", "@pristine-ts/express": "file:packages/express", "@pristine-ts/file": "file:packages/file", diff --git a/packages/data-transformer/package-lock.json b/packages/data-mapping/package-lock.json similarity index 100% rename from packages/data-transformer/package-lock.json rename to packages/data-mapping/package-lock.json diff --git a/packages/data-transformer/package.json b/packages/data-mapping/package.json similarity index 83% rename from packages/data-transformer/package.json rename to packages/data-mapping/package.json index d3466a1f4..54fa52cd5 100644 --- a/packages/data-transformer/package.json +++ b/packages/data-mapping/package.json @@ -1,10 +1,10 @@ { - "name": "@pristine-ts/data-transformer", + "name": "@pristine-ts/data-mapper", "version": "0.0.276", "description": "", - "module": "dist/lib/esm/data-transformer.module.js", - "main": "dist/lib/cjs/data-transformer.module.js", - "types": "dist/types/data-transformer.module.d.ts", + "module": "dist/lib/esm/data-mapper.module.js", + "main": "dist/lib/cjs/data-mapper.module.js", + "types": "dist/types/data-mapper.module.d.ts", "scripts": { "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json", "prepublish": "npm run build", @@ -23,7 +23,8 @@ "@pristine-ts/common": "file:../common", "@pristine-ts/logging": "file:../logging", "@pristine-ts/metadata": "~1.0.2", - "class-transformer": "^0.5.1" + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" }, "jest": { "transform": { diff --git a/packages/data-mapping/readme.md b/packages/data-mapping/readme.md new file mode 100644 index 000000000..ec59df714 --- /dev/null +++ b/packages/data-mapping/readme.md @@ -0,0 +1 @@ +# Data Mapping module. \ No newline at end of file diff --git a/packages/data-transformer/src/builders/builders.ts b/packages/data-mapping/src/builders/builders.ts similarity index 100% rename from packages/data-transformer/src/builders/builders.ts rename to packages/data-mapping/src/builders/builders.ts diff --git a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts b/packages/data-mapping/src/builders/data-mapping.builder.spec.ts similarity index 98% rename from packages/data-transformer/src/builders/data-mapping.builder.spec.ts rename to packages/data-mapping/src/builders/data-mapping.builder.spec.ts index 5682f7fd7..2588dafcd 100644 --- a/packages/data-transformer/src/builders/data-mapping.builder.spec.ts +++ b/packages/data-mapping/src/builders/data-mapping.builder.spec.ts @@ -1,8 +1,8 @@ import {DataMappingBuilder} from "./data-mapping.builder"; import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; -import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; -import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; +import {DataMappingNode} from "../nodes/data-mapping.node"; +import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; describe("Data Mapping Builder", () => { it("should properly build a simple DataMappingBuilder", () => { diff --git a/packages/data-mapping/src/builders/data-mapping.builder.ts b/packages/data-mapping/src/builders/data-mapping.builder.ts new file mode 100644 index 000000000..e678ba8a4 --- /dev/null +++ b/packages/data-mapping/src/builders/data-mapping.builder.ts @@ -0,0 +1,143 @@ +import {DataMappingNode} from "../nodes/data-mapping.node"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; +import { + DataBeforeRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-before-row-transformer-interceptor-already-added.error"; +import { + DataAfterRowTransformerInterceptorAlreadyAddedError +} from "../errors/data-after-row-transformer-interceptor-already-added.error"; +import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; +import {BaseDataMappingNode} from "../nodes/base-data-mapping.node"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; + +export class DataMappingBuilder extends BaseDataMappingNode{ + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + public beforeMappingInterceptors: { key: DataMappingInterceptorUniqueKeyType, options: any}[] = []; + public afterMappingInterceptors: { key: DataMappingInterceptorUniqueKeyType, options: any}[] = []; + + /** + * This method adds a normalizer to the root that will be applied on each node (unless they explicitly exclude to do + * so). + * + * @param normalizerUniqueKey + * @param options + */ + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingBuilder { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + return this; + } + + /** + * This method returns whether there's a normalizer for the specified key or not. + * + * @param normalizerUniqueKey + */ + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + /** + * This method adds an interceptor that will be executed **before** the object is mapped. + * + * @param key + * @param options + */ + public addBeforeMappingInterceptor(key: DataMappingInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasBeforeMappingInterceptor(key)) { + throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) + } + + this.beforeMappingInterceptors.push({ + key, + options, + }); + + return this; + } + + /** + * This method returns whether a **before** interceptor already exists. + * @param key + */ + public hasBeforeMappingInterceptor(key: DataMappingInterceptorUniqueKeyType): boolean { + return this.beforeMappingInterceptors.find(element => element.key === key) !== undefined; + } + + /** + * This method adds an interceptor that will be executed **after** the object is mapped. + * + * @param key + * @param options + */ + public addAfterMappingInterceptor(key: DataMappingInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasAfterMappingInterceptor(key)) { + throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) + } + + this.afterMappingInterceptors.push({ + key, + options, + }); + + return this; + } + + /** + * This method returns whether a **after** interceptor already exists. + * @param key + */ + public hasAfterMappingInterceptor(key: DataMappingInterceptorUniqueKeyType): boolean { + return this.afterMappingInterceptors.find(element => element.key === key) !== undefined; + } + + /** + * This property creates a new DataMappingLeaf and returns it. It doesn't add it yet. To do so, the `end()` method + * must be called. + */ + public add() { + return new DataMappingLeaf(this, this); + } + + /** + * This method adds a nesting level. This should be used when the property contains an object and you want to map + * this object into another object. + */ + public addNestingLevel() { + return new DataMappingNode(this, this); + } + + /** + * This method adds an array of Scalar allowing you to apply the normalizer on each scalar in the array. The + * `sourceProperty` and `destinationProperty` correspond to the name of the property that is an array. But, the + * values in the array will be normalized using the normalizer. + * + */ + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.Array); + } + + /** + * This method adds an array of objects allowing to define a node for each property in the object. Each object in + * the array will be treated as being the same. + */ + public addArrayOfObjects(): DataMappingNode { + return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); + } + + /** + * This method is called at the end just to make it nice since all the nodes will have one, it's nice + * that the builder has one too. + */ + public end(): DataMappingBuilder { + return this; + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/data-mapping.module.keyname.ts b/packages/data-mapping/src/data-mapping.module.keyname.ts new file mode 100644 index 000000000..09ad80b0f --- /dev/null +++ b/packages/data-mapping/src/data-mapping.module.keyname.ts @@ -0,0 +1 @@ +export const DataMappingModuleKeyname: string = "pristine.data-transformer"; diff --git a/packages/data-transformer/src/data-transformer.module.ts b/packages/data-mapping/src/data-mapping.module.ts similarity index 55% rename from packages/data-transformer/src/data-transformer.module.ts rename to packages/data-mapping/src/data-mapping.module.ts index 5d4ab2b1f..faf05f0ae 100644 --- a/packages/data-transformer/src/data-transformer.module.ts +++ b/packages/data-mapping/src/data-mapping.module.ts @@ -1,19 +1,20 @@ import {ModuleInterface} from "@pristine-ts/common"; import {LoggingModule} from "@pristine-ts/logging"; -import {DataTransformerModuleKeyname} from "./data-transformer.module.keyname"; -import { EnvironmentVariableResolver, NumberResolver} from "@pristine-ts/configuration"; -import {DataTransformerBuilder} from "./transformers/data-transformer.builder"; +import {DataMappingModuleKeyname} from "./data-mapping.module.keyname"; +export * from "./builders/builders"; +export * from "./enums/enums"; export * from "./errors/errors"; export * from "./interceptors/interceptors"; export * from "./interfaces/interfaces"; +export * from "./mappers/mappers"; +export * from "./nodes/nodes"; export * from "./normalizer-options/normalizer-options"; export * from "./normalizers/normalizers"; -export * from "./transformers/transformers"; export * from "./types/types"; -export const DataTransformerModule: ModuleInterface = { - keyname: DataTransformerModuleKeyname, +export const DataMappingModule: ModuleInterface = { + keyname: DataMappingModuleKeyname, importModules: [ LoggingModule, ], diff --git a/packages/data-transformer/src/enums/data-mapping-node-type.enum.ts b/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts similarity index 100% rename from packages/data-transformer/src/enums/data-mapping-node-type.enum.ts rename to packages/data-mapping/src/enums/data-mapping-node-type.enum.ts diff --git a/packages/data-transformer/src/enums/enums.ts b/packages/data-mapping/src/enums/enums.ts similarity index 100% rename from packages/data-transformer/src/enums/enums.ts rename to packages/data-mapping/src/enums/enums.ts diff --git a/packages/data-transformer/src/errors/data-after-row-transformer-interceptor-already-added.error.ts b/packages/data-mapping/src/errors/data-after-row-transformer-interceptor-already-added.error.ts similarity index 76% rename from packages/data-transformer/src/errors/data-after-row-transformer-interceptor-already-added.error.ts rename to packages/data-mapping/src/errors/data-after-row-transformer-interceptor-already-added.error.ts index 576d3f6c4..1dab18212 100644 --- a/packages/data-transformer/src/errors/data-after-row-transformer-interceptor-already-added.error.ts +++ b/packages/data-mapping/src/errors/data-after-row-transformer-interceptor-already-added.error.ts @@ -1,13 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {Request} from "@pristine-ts/common"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; /** * This Error is thrown when the before row interceptor is added more than once to the builder. */ export class DataAfterRowTransformerInterceptorAlreadyAddedError extends LoggableError { - public constructor(message: string, uniqueKey: DataTransformerInterceptorUniqueKeyType, options?: any) { + public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { uniqueKey, options, diff --git a/packages/data-transformer/src/errors/data-before-row-transformer-interceptor-already-added.error.ts b/packages/data-mapping/src/errors/data-before-row-transformer-interceptor-already-added.error.ts similarity index 76% rename from packages/data-transformer/src/errors/data-before-row-transformer-interceptor-already-added.error.ts rename to packages/data-mapping/src/errors/data-before-row-transformer-interceptor-already-added.error.ts index 0cf89a075..716211e51 100644 --- a/packages/data-transformer/src/errors/data-before-row-transformer-interceptor-already-added.error.ts +++ b/packages/data-mapping/src/errors/data-before-row-transformer-interceptor-already-added.error.ts @@ -1,13 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {Request} from "@pristine-ts/common"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; /** * This Error is thrown when the after row interceptor is added more than once to the builder. */ export class DataBeforeRowTransformerInterceptorAlreadyAddedError extends LoggableError { - public constructor(message: string, uniqueKey: DataTransformerInterceptorUniqueKeyType, options?: any) { + public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { uniqueKey, options, diff --git a/packages/data-transformer/src/errors/data-normalizer-already-added.error.ts b/packages/data-mapping/src/errors/data-normalizer-already-added.error.ts similarity index 100% rename from packages/data-transformer/src/errors/data-normalizer-already-added.error.ts rename to packages/data-mapping/src/errors/data-normalizer-already-added.error.ts diff --git a/packages/data-transformer/src/errors/data-transformer-interceptor-not-found.error.ts b/packages/data-mapping/src/errors/data-transformer-interceptor-not-found.error.ts similarity index 76% rename from packages/data-transformer/src/errors/data-transformer-interceptor-not-found.error.ts rename to packages/data-mapping/src/errors/data-transformer-interceptor-not-found.error.ts index 766a827e9..0589fa6ce 100644 --- a/packages/data-transformer/src/errors/data-transformer-interceptor-not-found.error.ts +++ b/packages/data-mapping/src/errors/data-transformer-interceptor-not-found.error.ts @@ -1,13 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {Request} from "@pristine-ts/common"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; /** * This Error is thrown if the Data Transformer Class is not found in the list of available interceptors. It might be missing a tag. */ export class DataTransformerInterceptorNotFoundError extends LoggableError { - public constructor(message: string, uniqueKey: DataTransformerInterceptorUniqueKeyType, options?: any) { + public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { uniqueKey, options, diff --git a/packages/data-transformer/src/errors/data-transformer-source-property-not-found.error.ts b/packages/data-mapping/src/errors/data-transformer-source-property-not-found.error.ts similarity index 100% rename from packages/data-transformer/src/errors/data-transformer-source-property-not-found.error.ts rename to packages/data-mapping/src/errors/data-transformer-source-property-not-found.error.ts diff --git a/packages/data-transformer/src/errors/errors.ts b/packages/data-mapping/src/errors/errors.ts similarity index 100% rename from packages/data-transformer/src/errors/errors.ts rename to packages/data-mapping/src/errors/errors.ts diff --git a/packages/data-transformer/src/errors/normalizer-invalid-source-type.error.ts b/packages/data-mapping/src/errors/normalizer-invalid-source-type.error.ts similarity index 100% rename from packages/data-transformer/src/errors/normalizer-invalid-source-type.error.ts rename to packages/data-mapping/src/errors/normalizer-invalid-source-type.error.ts diff --git a/packages/data-transformer/src/errors/undefined-source-property.error.ts b/packages/data-mapping/src/errors/undefined-source-property.error.ts similarity index 84% rename from packages/data-transformer/src/errors/undefined-source-property.error.ts rename to packages/data-mapping/src/errors/undefined-source-property.error.ts index fd6cba445..e4d21cea7 100644 --- a/packages/data-transformer/src/errors/undefined-source-property.error.ts +++ b/packages/data-mapping/src/errors/undefined-source-property.error.ts @@ -1,6 +1,6 @@ import {LoggableError} from "@pristine-ts/common"; -import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; -import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; +import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; +import {DataMappingNode} from "../nodes/data-mapping.node"; /** * This Error is thrown when you are trying to add a Node which has an undefined sourceProperty value. diff --git a/packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts b/packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts new file mode 100644 index 000000000..70021fd86 --- /dev/null +++ b/packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts @@ -0,0 +1,22 @@ +import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; +import {moduleScoped, tag} from "@pristine-ts/common"; +import {DataMappingModuleKeyname} from "../data-mapping.module.keyname"; +import {injectable} from "tsyringe"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; + +@tag("DataTransformerInterceptor") +@moduleScoped(DataMappingModuleKeyname) +@injectable() +export class DefaultDataMappingInterceptor implements DataMappingInterceptorInterface { + async afterMapping(row: any): Promise { + return row; + } + + async beforeMapping(row: any): Promise { + return row; + } + + getUniqueKey(): DataMappingInterceptorUniqueKeyType { + return DefaultDataMappingInterceptor.name; + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/interceptors/interceptors.ts b/packages/data-mapping/src/interceptors/interceptors.ts new file mode 100644 index 000000000..e97b987e1 --- /dev/null +++ b/packages/data-mapping/src/interceptors/interceptors.ts @@ -0,0 +1 @@ +export * from "./default-data-mapping.interceptor"; \ No newline at end of file diff --git a/packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts b/packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts new file mode 100644 index 000000000..143372998 --- /dev/null +++ b/packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts @@ -0,0 +1,22 @@ +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; + +export interface DataMappingInterceptorInterface { + /** + * Every data mapping interceptor must define a unique key. Then, during the mapping, the schema can specify which + * interceptors must be called. + */ + getUniqueKey(): DataMappingInterceptorUniqueKeyType; + + /** + * This method is called before the row is being mapped and normalized. It allows you to combine fields for example if that's what you want. + * @param row + */ + beforeMapping(row: any): Promise; + + /** + * This method is called after the row is being mapped and normalized. It can allow you to apply operations on each + * field or combine fields for example. + * @param row + */ + afterMapping(row: any): Promise; +} \ No newline at end of file diff --git a/packages/data-transformer/src/interfaces/data-normalizer.interface.ts b/packages/data-mapping/src/interfaces/data-normalizer.interface.ts similarity index 100% rename from packages/data-transformer/src/interfaces/data-normalizer.interface.ts rename to packages/data-mapping/src/interfaces/data-normalizer.interface.ts diff --git a/packages/data-mapping/src/interfaces/interfaces.ts b/packages/data-mapping/src/interfaces/interfaces.ts new file mode 100644 index 000000000..224b612f3 --- /dev/null +++ b/packages/data-mapping/src/interfaces/interfaces.ts @@ -0,0 +1,2 @@ +export * from "./data-normalizer.interface"; +export * from "./data-mapping-interceptor.interface"; \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data.transformer.spec.ts b/packages/data-mapping/src/mappers/data.mapper.spec.ts similarity index 70% rename from packages/data-transformer/src/transformers/data.transformer.spec.ts rename to packages/data-mapping/src/mappers/data.mapper.spec.ts index 691822064..dda706709 100644 --- a/packages/data-transformer/src/transformers/data.transformer.spec.ts +++ b/packages/data-mapping/src/mappers/data.mapper.spec.ts @@ -1,16 +1,18 @@ -import "reflect-metadata" -import {DataTransformer} from "./data.transformer"; +import {DataTransformer} from "../transformers/data.transformer"; import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; -import {DataTransformerBuilder} from "./data-transformer.builder"; -import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; +import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; import {DataTransformerRow} from "../types/data-transformer.row"; -import 'jest-extended'; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; import {property} from "@pristine-ts/metadata"; -describe('Data Transformer', () => { +describe("Data Mapper", () =>{ + it("should map a very complex object into another complex object. Then, it should export the builder, import the builder and make sure it still maps everything properly.", async () => { + + }) + it("should properly transform", async () => { const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); @@ -23,18 +25,18 @@ describe('Data Transformer', () => { const dataTransformerBuilder = new DataTransformerBuilder(); dataTransformerBuilder .add() - .setSourceProperty("NAME") - .setDestinationProperty("name") - .end() + .setSourceProperty("NAME") + .setDestinationProperty("name") + .end() .add() - .setSourceProperty("province") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() + .setSourceProperty("province") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") + .end() .add() - .setSourceProperty("TOTAL") - .setDestinationProperty("total") - .end(); + .setSourceProperty("TOTAL") + .setDestinationProperty("total") + .end(); const destination = await dataTransformer.transform(dataTransformerBuilder, source); @@ -56,18 +58,18 @@ describe('Data Transformer', () => { const dataTransformerBuilder = new DataTransformerBuilder(); dataTransformerBuilder .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() + .setSourceProperty("1") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") + .end() .add() - .setSourceProperty("2") - .setDestinationProperty("total") - .end(); + .setSourceProperty("2") + .setDestinationProperty("total") + .end(); const destination = await dataTransformer.transform(dataTransformerBuilder, source); @@ -80,36 +82,36 @@ describe('Data Transformer', () => { }) it("should properly call the before row transformers and respect the order of calls", async () => { - const firstInterceptor: DataTransformerInterceptorInterface = { - async beforeRowTransform(row: DataTransformerRow): Promise { + const firstInterceptor: DataMappingInterceptorInterface = { + async beforeMapping(row: DataTransformerRow): Promise { return row; }, - async afterRowTransform(row: DataTransformerRow): Promise { + async afterMapping(row: DataTransformerRow): Promise { return { "after": row["name"] + row["province"] + row["total"] + row["added_property"], }; }, - getUniqueKey(): DataTransformerInterceptorUniqueKeyType { + getUniqueKey(): DataMappingInterceptorUniqueKeyType { return "first_interceptor"; }, } - const secondInterceptor: DataTransformerInterceptorInterface = { - async beforeRowTransform(row: DataTransformerRow): Promise { + const secondInterceptor: DataMappingInterceptorInterface = { + async beforeMapping(row: DataTransformerRow): Promise { row[3] = "Property added in the beforeRowTransform"; return row; }, - async afterRowTransform(row: DataTransformerRow): Promise { + async afterMapping(row: DataTransformerRow): Promise { return row; }, - getUniqueKey(): DataTransformerInterceptorUniqueKeyType { + getUniqueKey(): DataMappingInterceptorUniqueKeyType { return "second_interceptor"; }, } - const beforeRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "beforeRowTransform") - const afterRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "afterRowTransform") - const beforeRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "beforeRowTransform") - const afterRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "afterRowTransform") + const beforeRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "beforeMapping") + const afterRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "afterMapping") + const beforeRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "beforeMapping") + const afterRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "afterMapping") const dataTransformer = new DataTransformer([new LowercaseNormalizer()], [ firstInterceptor, @@ -127,22 +129,22 @@ describe('Data Transformer', () => { .addAfterRowTransformInterceptor("first_interceptor") .addAfterRowTransformInterceptor("second_interceptor") .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() + .setSourceProperty("1") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") + .end() .add() - .setSourceProperty("2") - .setDestinationProperty("total") - .end() + .setSourceProperty("2") + .setDestinationProperty("total") + .end() .add() - .setSourceProperty("3") - .setDestinationProperty("added_property") - .end(); + .setSourceProperty("3") + .setDestinationProperty("added_property") + .end(); const transformedData: any[] = await dataTransformer.transform(dataTransformerBuilder, source); @@ -164,7 +166,7 @@ describe('Data Transformer', () => { .setDestinationProperty("name") .end() - await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); + await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); }) it("should throw properly when after row transformer cannot be found", async () => { @@ -178,7 +180,7 @@ describe('Data Transformer', () => { .setDestinationProperty("name") .end() - await expect(dataTransformer.transform(dataTransformerBuilder, [{"0": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); + await expect(dataTransformer.transform(dataTransformerBuilder, [{"0": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); }) it("should throw properly when an element is not optional and not found in the source", async () => { const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); @@ -190,7 +192,7 @@ describe('Data Transformer', () => { .setDestinationProperty("name") .end() - await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); + await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); }) it("should properly type the return object when a destinationType is passed", async () => { @@ -251,4 +253,4 @@ describe('Data Transformer', () => { expect(destination.name).toBe("title"); }) -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/data.mapper.ts b/packages/data-mapping/src/mappers/data.mapper.ts new file mode 100644 index 000000000..ba9ee4f67 --- /dev/null +++ b/packages/data-mapping/src/mappers/data.mapper.ts @@ -0,0 +1,95 @@ +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; +import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; +import {moduleScoped} from "@pristine-ts/common"; +import {DataMappingModuleKeyname} from "../data-mapping.module.keyname"; +import {injectable, injectAll} from "tsyringe"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {ClassConstructor, plainToInstance} from "class-transformer"; +import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; + +@moduleScoped(DataMappingModuleKeyname) +@injectable() +export class DataMapper { + private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface } = {} + private readonly dataTransformerInterceptorsMap: { [key in DataMappingInterceptorUniqueKeyType]: DataMappingInterceptorInterface } = {} + + public constructor(@injectAll("DataNormalizerInterface") private readonly dataNormalizers: DataNormalizerInterface[], + @injectAll("DataTransformerInterceptor") private readonly dataTransformerInterceptors: DataMappingInterceptorInterface[],) { + dataNormalizers.forEach(dataNormalizer => { + this.dataNormalizersMap[dataNormalizer.getUniqueKey()] = dataNormalizer; + }) + + dataTransformerInterceptors.forEach(interceptor => { + this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; + }); + } + + /** + * This method takes an array of source and maps each item. + * + * @param builder + * @param source + * @param destinationType + */ + public async mapAll(builder: DataMappingBuilder, source: any[], destinationType?: ClassConstructor): Promise { + return source.map(element => this.map(builder, element, destinationType)); + } + + /** + * This method takes a builder, a source and maps it according to the builder. You can pass a `destinationType (optional)` + * that is an object that will be constructed. + * + * @param builder + * @param source + * @param destinationType + */ + public async map(builder: DataMappingBuilder, source: any, destinationType?: ClassConstructor): Promise { + const globalNormalizers = builder.normalizers; + + let destination = {}; + + if(destinationType) { + destination = plainToInstance(destinationType, {}); + } + + let interceptedSource = source; + + // Execute the before interceptors. + for (const element of builder.beforeMappingInterceptors) { + const interceptor = this.dataTransformerInterceptorsMap[element.key]; + + if (interceptor === undefined) { + throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); + } + + // todo: Pass the options when we start using them. + interceptedSource = await interceptor.beforeMapping(interceptedSource); + } + + // Loop over the properties defined in the builder + for (const key in builder.nodes) { + if(builder.nodes.hasOwnProperty(key) === false) { + continue; + } + + const node = builder.nodes[key]; + await node.map(interceptedSource, destination, this.dataNormalizersMap); + } + + // Execute the before interceptors. + for (const element of builder.afterMappingInterceptors) { + const interceptor: DataMappingInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; + + if (interceptor === undefined) { + throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); + } + + // todo pass the options when we start using it. + destination = await interceptor.afterMapping(destination); + } + + return destination; + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/mappers.ts b/packages/data-mapping/src/mappers/mappers.ts new file mode 100644 index 000000000..209812d7e --- /dev/null +++ b/packages/data-mapping/src/mappers/mappers.ts @@ -0,0 +1 @@ +export * from "./data.mapper"; \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts b/packages/data-mapping/src/nodes/base-data-mapping.node.ts similarity index 100% rename from packages/data-transformer/src/mapping-nodes/base-data-mapping.node.ts rename to packages/data-mapping/src/nodes/base-data-mapping.node.ts diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts new file mode 100644 index 000000000..4154b8739 --- /dev/null +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts @@ -0,0 +1,58 @@ +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; + +describe("Data Mapping Leaf", () => { + it("should map the property corresponding to the leaf from the source to the destination while also applying the normalizers and excluding the specified normalizers", async () => { + class Source { + title: string = "TITLE"; + } + + class Destination { + name: string; + } + + class PrependUnderscoresNormalizer implements DataNormalizerInterface { + getUniqueKey(): DataNormalizerUniqueKey { + return PrependUnderscoresNormalizer.name; + } + + normalize(source: any, options?: {}): string { + return "__" + source; + } + } + + class AppendUnderscoresNormalizer implements DataNormalizerInterface { + getUniqueKey(): DataNormalizerUniqueKey { + return AppendUnderscoresNormalizer.name; + } + + normalize(source: any, options?: {}): string { + return source + "__"; + } + } + + const dataBuilder = new DataMappingBuilder(); + dataBuilder.addNormalizer(PrependUnderscoresNormalizer.name); + dataBuilder.addNormalizer(AppendUnderscoresNormalizer.name); + + const leaf = new DataMappingLeaf(dataBuilder, dataBuilder); + leaf.setSourceProperty("title"); + leaf.setDestinationProperty("name"); + leaf.addNormalizer(LowercaseNormalizer.name) + leaf.excludeNormalizer(PrependUnderscoresNormalizer.name) + + const destination = new Destination(); + + await leaf.map(new Source(), destination, { + [LowercaseNormalizer.name]: new LowercaseNormalizer(), + [PrependUnderscoresNormalizer.name]: new PrependUnderscoresNormalizer(), + [AppendUnderscoresNormalizer.name]: new AppendUnderscoresNormalizer(), + }); + + expect(destination.name).toBeDefined() + expect(destination.name).toBe("title__"); + }) +}); \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.ts similarity index 56% rename from packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts rename to packages/data-mapping/src/nodes/data-mapping.leaf.ts index 6f3aec19e..b5411d9d2 100644 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.leaf.ts +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.ts @@ -1,19 +1,35 @@ import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {DataMappingNode} from "./data-mapping.node"; +import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; export class DataMappingLeaf { + /** + * This property represents the property referenced in the `source` object. + */ public sourceProperty!: string; + /** + * This property represents the property referenced in the `destination` object. + */ public destinationProperty!: string; + /** + * This property contains an array of Normalizers to apply sequentially when mapping this property. + */ public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + /** + * This property contains an array of Normalizers that must be excluded from normalizers defined by parents. + */ public excludedNormalizers: Set = new Set(); + /** + * This method specified whether it's possible that this element not be present in the `source` object. + */ public isOptional: boolean = false; public constructor( @@ -23,16 +39,41 @@ export class DataMappingLeaf { ) { } + /** + * This is a setter for `sourceProperty`. + * @param sourceProperty + */ public setSourceProperty(sourceProperty: string): DataMappingLeaf { this.sourceProperty = sourceProperty; return this; } + /** + * This is a setter for `destinationProperty`. + * @param destinationProperty + */ public setDestinationProperty(destinationProperty: string): DataMappingLeaf { this.destinationProperty = destinationProperty; return this; } + /** + * This is a setter for `isOptional`. + * @param isOptional + */ + public setIsOptional(isOptional: boolean): DataMappingLeaf { + this.isOptional = isOptional; + + return this; + } + + /** + * This methods adds a normalizer but checks that this normalizer hasn't been added already (either at the root) or + * directly on this leaf. + * + * @param normalizerUniqueKey + * @param options + */ public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingLeaf { if(this.hasNormalizer(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) @@ -50,10 +91,18 @@ export class DataMappingLeaf { return this; } + /** + * This method simply returns whether the normalizer was already added to this node. + * @param normalizerUniqueKey + */ public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; } + /** + * This method adds a normalizer that must be excluded from the normalizers applied at a higher level.à + * @param normalizerUniqueKey + */ public excludeNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): DataMappingLeaf { if(this.excludedNormalizers.has(normalizerUniqueKey)) { throw new DataNormalizerAlreadyAdded("The EXCLUDED data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey) @@ -64,12 +113,9 @@ export class DataMappingLeaf { return this; } - public setIsOptional(isOptional: boolean): DataMappingLeaf { - this.isOptional = isOptional; - - return this; - } - + /** + * This method adds this node to its parent and returns the parent. + */ public end(): DataMappingNode | DataMappingBuilder { // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. this.parent.addNode(this) @@ -77,6 +123,41 @@ export class DataMappingLeaf { return this.parent; } + /** + * This method maps the `sourceProperty` from the `source` object and maps it to the `destinationProperty` of the + * `destination` object while applying the normalizers. + * @param source + * @param destination + * @param normalizersMap + */ + public async map(source: any, destination: any, normalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface }) { + if(source.hasOwnProperty(this.sourceProperty) === false) { + if(this.isOptional) { + return + } + + throw new DataTransformerSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) + } + + let value = source[this.sourceProperty]; + const normalizers = this.root.normalizers.filter(element => this.excludedNormalizers.has(element.key) === false); + normalizers.push(...this.normalizers); + + normalizers.forEach(element => { + const normalizer = normalizersMap[element.key]; + value = normalizer.normalize(value, element.options); + }) + + destination[this.destinationProperty] = value; + + return; + } + + /** + * This method imports a schema. + * + * @param schema + */ public import(schema: any) { this.normalizers = schema.normalizers; @@ -92,6 +173,9 @@ export class DataMappingLeaf { this.destinationProperty = schema.destinationProperty; } + /** + * This method exports this node. + */ public export(): any { return { "_type": this.type, diff --git a/packages/data-mapping/src/nodes/data-mapping.node.ts b/packages/data-mapping/src/nodes/data-mapping.node.ts new file mode 100644 index 000000000..e683c24d1 --- /dev/null +++ b/packages/data-mapping/src/nodes/data-mapping.node.ts @@ -0,0 +1,132 @@ +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {BaseDataMappingNode} from "./base-data-mapping.node"; +import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; + +export class DataMappingNode extends BaseDataMappingNode { + /** + * This property represents the property referenced in the `source` object. + */ + public sourceProperty!: string; + + /** + * This property represents the property referenced in the `destination` object. + */ + public destinationProperty!: string; + + /** + * This method specified whether it's possible that this element not be present in the `source` object. + */ + public isOptional: boolean = false; + + constructor(public readonly root: DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder, + public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node, + ) { + super(); + } + + /** + * This is a setter for `sourceProperty`. + * @param sourceProperty + */ + public setSourceProperty(sourceProperty: string): DataMappingNode { + this.sourceProperty = sourceProperty; + return this; + } + + /** + * This is a setter for `destinationProperty`. + * @param destinationProperty + */ + public setDestinationProperty(destinationProperty: string): DataMappingNode { + this.destinationProperty = destinationProperty; + return this; + } + + /** + * This is a setter for `isOptional`. + * @param isOptional + */ + public setIsOptional(isOptional: boolean): DataMappingNode { + this.isOptional = isOptional; + + return this; + } + + /** + * This property creates a new DataMappingLeaf and returns it. It doesn't add it yet. To do so, the `end()` method + * must be called. + */ + public add() { + return new DataMappingLeaf(this.root, this); + } + + /** + * This method adds a nesting level. This should be used when the property contains an object and you want to map + * this object into another object. + */ + public addNestingLevel() { + return new DataMappingNode(this.root, this); + } + + /** + * This method adds an array of Scalar allowing you to apply the normalizer on each scalar in the array. The + * `sourceProperty` and `destinationProperty` correspond to the name of the property that is an array. But, the + * values in the array will be normalized using the normalizer. + * + */ + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.Array); + } + + /** + * This method adds an array of objects allowing to define a node for each property in the object. Each object in + * the array will be treated as being the same. + */ + public addArrayOfObjects(): DataMappingNode { + return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); + } + + /** + * This method adds this node to its parent and returns the parent. + */ + public end(): DataMappingNode | DataMappingBuilder { + // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. + this.parent.addNode(this) + + return this.parent; + } + + public async map(source: any, destination: any, normalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface }) { + if(source.hasOwnProperty(this.sourceProperty) === false) { + if(this.isOptional) { + return + } + + throw new DataTransformerSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) + } + + let sourceElement = source[this.sourceProperty]; + + if(destination[this.destinationProperty] === undefined) { + // todo: we need to get the expected Type of the `destination[this.destinationProperty]` and actually instantiate it. + destination[this.destinationProperty] = {}; + } + + let destinationElement = destination[this.destinationProperty]; + + for (let key in this.nodes) { + if(this.nodes.hasOwnProperty(key) === false) { + continue; + } + + const node = this.nodes[key]; + + await node.map(sourceElement, destinationElement, normalizersMap); + } + } +} \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/mapping-nodes.ts b/packages/data-mapping/src/nodes/nodes.ts similarity index 100% rename from packages/data-transformer/src/mapping-nodes/mapping-nodes.ts rename to packages/data-mapping/src/nodes/nodes.ts diff --git a/packages/data-transformer/src/normalizer-options/base-normalizer.options.ts b/packages/data-mapping/src/normalizer-options/base-normalizer.options.ts similarity index 100% rename from packages/data-transformer/src/normalizer-options/base-normalizer.options.ts rename to packages/data-mapping/src/normalizer-options/base-normalizer.options.ts diff --git a/packages/data-transformer/src/normalizer-options/lowercase-normalizer.options.ts b/packages/data-mapping/src/normalizer-options/lowercase-normalizer.options.ts similarity index 100% rename from packages/data-transformer/src/normalizer-options/lowercase-normalizer.options.ts rename to packages/data-mapping/src/normalizer-options/lowercase-normalizer.options.ts diff --git a/packages/data-transformer/src/normalizer-options/normalizer-options.ts b/packages/data-mapping/src/normalizer-options/normalizer-options.ts similarity index 100% rename from packages/data-transformer/src/normalizer-options/normalizer-options.ts rename to packages/data-mapping/src/normalizer-options/normalizer-options.ts diff --git a/packages/data-transformer/src/normalizers/lowercase.normalizer.spec.ts b/packages/data-mapping/src/normalizers/lowercase.normalizer.spec.ts similarity index 100% rename from packages/data-transformer/src/normalizers/lowercase.normalizer.spec.ts rename to packages/data-mapping/src/normalizers/lowercase.normalizer.spec.ts diff --git a/packages/data-transformer/src/normalizers/lowercase.normalizer.ts b/packages/data-mapping/src/normalizers/lowercase.normalizer.ts similarity index 100% rename from packages/data-transformer/src/normalizers/lowercase.normalizer.ts rename to packages/data-mapping/src/normalizers/lowercase.normalizer.ts diff --git a/packages/data-transformer/src/normalizers/normalizers.ts b/packages/data-mapping/src/normalizers/normalizers.ts similarity index 100% rename from packages/data-transformer/src/normalizers/normalizers.ts rename to packages/data-mapping/src/normalizers/normalizers.ts diff --git a/packages/data-transformer/src/test.spec.ts b/packages/data-mapping/src/test.spec.ts similarity index 100% rename from packages/data-transformer/src/test.spec.ts rename to packages/data-mapping/src/test.spec.ts diff --git a/packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts b/packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts new file mode 100644 index 000000000..7791ad01b --- /dev/null +++ b/packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts @@ -0,0 +1 @@ +export type DataMappingInterceptorUniqueKeyType = string; \ No newline at end of file diff --git a/packages/data-transformer/src/types/data-normalizer-unique-key.type.ts b/packages/data-mapping/src/types/data-normalizer-unique-key.type.ts similarity index 100% rename from packages/data-transformer/src/types/data-normalizer-unique-key.type.ts rename to packages/data-mapping/src/types/data-normalizer-unique-key.type.ts diff --git a/packages/data-mapping/src/types/types.ts b/packages/data-mapping/src/types/types.ts new file mode 100644 index 000000000..ee9ebfee7 --- /dev/null +++ b/packages/data-mapping/src/types/types.ts @@ -0,0 +1,2 @@ +export * from "./data-normalizer-unique-key.type"; +export * from "./data-mapping-interceptor-unique-key.type"; \ No newline at end of file diff --git a/packages/data-transformer/tsconfig.cjs.json b/packages/data-mapping/tsconfig.cjs.json similarity index 100% rename from packages/data-transformer/tsconfig.cjs.json rename to packages/data-mapping/tsconfig.cjs.json diff --git a/packages/data-transformer/tsconfig.json b/packages/data-mapping/tsconfig.json similarity index 100% rename from packages/data-transformer/tsconfig.json rename to packages/data-mapping/tsconfig.json diff --git a/packages/data-transformer/readme.md b/packages/data-transformer/readme.md deleted file mode 100644 index a99a4cab4..000000000 --- a/packages/data-transformer/readme.md +++ /dev/null @@ -1 +0,0 @@ -# Data Transformer module. \ No newline at end of file diff --git a/packages/data-transformer/src/builders/data-mapping.builder.ts b/packages/data-transformer/src/builders/data-mapping.builder.ts deleted file mode 100644 index 21559c163..000000000 --- a/packages/data-transformer/src/builders/data-mapping.builder.ts +++ /dev/null @@ -1,96 +0,0 @@ -import {DataMappingNode} from "../mapping-nodes/data-mapping.node"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import { - DataBeforeRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-before-row-transformer-interceptor-already-added.error"; -import { - DataAfterRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-after-row-transformer-interceptor-already-added.error"; -import {DataMappingLeaf} from "../mapping-nodes/data-mapping.leaf"; -import {BaseDataMappingNode} from "../mapping-nodes/base-data-mapping.node"; -import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; - -export class DataMappingBuilder extends BaseDataMappingNode{ - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingBuilder { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingBuilder { - if(this.hasBeforeRowTransformInterceptor(key)) { - throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) - } - - this.beforeRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataMappingBuilder { - if(this.hasAfterRowTransformInterceptor(key)) { - throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) - } - - this.afterRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public add() { - return new DataMappingLeaf(this, this); - } - - - public addNestingLevel() { - return new DataMappingNode(this, this); - } - - public addArrayOfScalar(): DataMappingLeaf { - return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.Array); - } - - public addArrayOfObjects(): DataMappingNode { - return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); - } - - /** - * This method is called at the end just to make it nice since all the nodes will have one, it's nice - * that the builder has one too. - */ - public end(): DataMappingBuilder { - return this; - } - -} \ No newline at end of file diff --git a/packages/data-transformer/src/data-transformer.module.keyname.ts b/packages/data-transformer/src/data-transformer.module.keyname.ts deleted file mode 100644 index adfcf2047..000000000 --- a/packages/data-transformer/src/data-transformer.module.keyname.ts +++ /dev/null @@ -1 +0,0 @@ -export const DataTransformerModuleKeyname: string = "pristine.data-transformer"; diff --git a/packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts b/packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts deleted file mode 100644 index 7396b4001..000000000 --- a/packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; -import {moduleScoped, tag} from "@pristine-ts/common"; -import {DataTransformerModuleKeyname} from "../data-transformer.module.keyname"; -import {injectable} from "tsyringe"; -import {DataTransformerRow} from "../types/data-transformer.row"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; - -@tag("DataTransformerInterceptor") -@moduleScoped(DataTransformerModuleKeyname) -@injectable() -export class DefaultDataTransformerInterceptor implements DataTransformerInterceptorInterface { - async afterRowTransform(row: DataTransformerRow): Promise { - return row; - } - - async beforeRowTransform(row: DataTransformerRow): Promise { - return row; - } - - getUniqueKey(): DataTransformerInterceptorUniqueKeyType { - return DefaultDataTransformerInterceptor.name; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/interceptors/interceptors.ts b/packages/data-transformer/src/interceptors/interceptors.ts deleted file mode 100644 index 14629e7c1..000000000 --- a/packages/data-transformer/src/interceptors/interceptors.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./default-data-transformer.interceptor"; \ No newline at end of file diff --git a/packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts b/packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts deleted file mode 100644 index afbe878eb..000000000 --- a/packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {DataTransformerRow} from "../types/data-transformer.row"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; - -export interface DataTransformerInterceptorInterface { - /** - * Every data transformer interceptor must define a unique key. Then, during the transformation, the schema can specify which - * interceptors must be called. - */ - getUniqueKey(): DataTransformerInterceptorUniqueKeyType; - - /** - * This method is called before the row is being transformed and normalized. It allows you to combine fields for example if that's what you want. - * @param row - */ - beforeRowTransform(row: DataTransformerRow): Promise; - - /** - * This method is called after the row is being transformed and normalized. It can allow you to apply operations on each - * field or combine fields for example. - * @param row - */ - afterRowTransform(row: DataTransformerRow): Promise; -} \ No newline at end of file diff --git a/packages/data-transformer/src/interfaces/interfaces.ts b/packages/data-transformer/src/interfaces/interfaces.ts deleted file mode 100644 index c336d245c..000000000 --- a/packages/data-transformer/src/interfaces/interfaces.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./data-normalizer.interface"; -export * from "./data-transformer-interceptor.interface"; \ No newline at end of file diff --git a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts b/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts deleted file mode 100644 index fc8588f3e..000000000 --- a/packages/data-transformer/src/mapping-nodes/data-mapping.node.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; -import {DataMappingLeaf} from "./data-mapping.leaf"; -import {DataMappingBuilder} from "../builders/data-mapping.builder"; -import {BaseDataMappingNode} from "./base-data-mapping.node"; - -export class DataMappingNode extends BaseDataMappingNode { - public sourceProperty!: string; - public destinationProperty!: string; - - constructor(public readonly root: DataMappingBuilder, - public readonly parent: DataMappingNode | DataMappingBuilder, - public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node, - ) { - super(); - } - - public setSourceProperty(sourceProperty: string): DataMappingNode { - this.sourceProperty = sourceProperty; - return this; - } - public setDestinationProperty(destinationProperty: string): DataMappingNode { - this.destinationProperty = destinationProperty; - return this; - } - - public add() { - return new DataMappingLeaf(this.root, this); - } - - public addNestingLevel() { - return new DataMappingNode(this.root, this); - } - - public addArrayOfScalar(): DataMappingLeaf { - return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.Array); - } - - public addArrayOfObjects(): DataMappingNode { - return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); - } - - public end(): DataMappingNode | DataMappingBuilder { - // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. - this.parent.addNode(this) - - //@ts-ignore - return this.parent; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.spec.ts b/packages/data-transformer/src/transformers/data-transformer.builder.spec.ts deleted file mode 100644 index 10ee82ea4..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.builder.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import "reflect-metadata" -import {DataTransformerBuilder} from "./data-transformer.builder"; -import {DataTransformerProperty} from "./data-transformer.property"; -import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; - -describe('Data Transformer Builder', () => { - let dataTransformerBuilder: DataTransformerBuilder; - - beforeEach(() => { - dataTransformerBuilder = new DataTransformerBuilder(); - }) - - it("should properly add a normalizer", () => { - const options = { - "optionA": true, - }; - - dataTransformerBuilder.addNormalizer("normalizer", options); - - expect(dataTransformerBuilder.normalizers.length).toBe(1); - expect(dataTransformerBuilder.normalizers[0].key).toBe("normalizer") - expect(dataTransformerBuilder.normalizers[0].options).toBe(options); - }) - - it("should return if it has a normalizer or not", () => { - const options = { - "optionA": true, - }; - - dataTransformerBuilder.addNormalizer("normalizer", options); - - expect(dataTransformerBuilder.hasNormalizer("normalizer")).toBeTruthy(); - expect(dataTransformerBuilder.hasNormalizer("dafds")).toBeFalsy(); - }) - - it("should properly add a new data transformer property", () => { - expect(dataTransformerBuilder.add() instanceof DataTransformerProperty).toBeTruthy() - }) - - it("should support being exported and then re-imported", () => { - const dataTransformerBuilder = new DataTransformerBuilder(); - const builder = dataTransformerBuilder - .addBeforeRowTransformInterceptor("first_interceptor") - .addBeforeRowTransformInterceptor("second_interceptor") - .addAfterRowTransformInterceptor("first_interceptor") - .addAfterRowTransformInterceptor("second_interceptor") - .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .excludeNormalizer(LowercaseNormalizer.name) - .end() - .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() - .add() - .setSourceProperty("2") - .setDestinationProperty("total") - .end(); - - const exportedBuilder = builder.export(); - - - const serializedObject: any = { - "normalizers": [], - "beforeRowTransformInterceptors":[{"key":"first_interceptor"},{"key":"second_interceptor"}], - "afterRowTransformInterceptors":[{"key":"first_interceptor"},{"key":"second_interceptor"}], - "properties":{ - "0":{ - "sourceProperty":"0", - "destinationProperty":"name", - "isOptional": false, - "normalizers": [], - "excludedNormalizers": { - [LowercaseNormalizer.name]: true, - } - }, - "1": { - "sourceProperty":"1", - "destinationProperty":"province", - "isOptional": false, - "normalizers": [ - { - "key": LowercaseNormalizer.name - } - ], - "excludedNormalizers": {} - }, - "2": { - "sourceProperty": "2", - "destinationProperty": "total", - "isOptional": false, - "normalizers": [], - "excludedNormalizers": {}, - } - }, - }; - - const stringifiedObject = JSON.stringify(serializedObject); - - expect(exportedBuilder).toBe(stringifiedObject); - - // Create a new builder and import the serializedObject. - - const importedBuilder = new DataTransformerBuilder(); - importedBuilder.import(stringifiedObject); - - expect(importedBuilder.normalizers.length).toBe(0); - expect(importedBuilder.beforeRowTransformInterceptors.length).toBe(2); - expect(importedBuilder.afterRowTransformInterceptors.length).toBe(2); - expect(Object.keys(importedBuilder.properties).length).toBe(3); - expect(importedBuilder.properties[0].normalizers.length).toBe(0) - expect(importedBuilder.properties[0].excludedNormalizers.size).toBe(1) - expect(importedBuilder.properties[0].isOptional).toBeFalsy(); - expect(importedBuilder.properties[0].sourceProperty).toBe("0"); - expect(importedBuilder.properties[0].destinationProperty).toBe("name"); - - expect(importedBuilder.properties[1].normalizers.length).toBe(1) - expect(importedBuilder.properties[1].excludedNormalizers.size).toBe(0) - expect(importedBuilder.properties[1].isOptional).toBeFalsy(); - expect(importedBuilder.properties[1].sourceProperty).toBe("1"); - expect(importedBuilder.properties[1].destinationProperty).toBe("province"); - - expect(importedBuilder.properties[2].normalizers.length).toBe(0) - expect(importedBuilder.properties[2].excludedNormalizers.size).toBe(0) - expect(importedBuilder.properties[2].isOptional).toBeFalsy(); - expect(importedBuilder.properties[2].sourceProperty).toBe("2"); - expect(importedBuilder.properties[2].destinationProperty).toBe("total"); - }) -}); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.ts b/packages/data-transformer/src/transformers/data-transformer.builder.ts deleted file mode 100644 index 49d1b627b..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.builder.ts +++ /dev/null @@ -1,156 +0,0 @@ -import {injectable} from "tsyringe"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerProperty} from "./data-transformer.property"; -import {moduleScoped} from "@pristine-ts/common"; -import {DataTransformerModuleKeyname} from "../data-transformer.module.keyname"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import { - DataAfterRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-after-row-transformer-interceptor-already-added.error"; -import { - DataBeforeRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-before-row-transformer-interceptor-already-added.error"; - -@moduleScoped(DataTransformerModuleKeyname) -@injectable() -export class DataTransformerBuilder { - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - - public properties: {[sourceProperty in string]: DataTransformerProperty} = {} - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerBuilder { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataTransformerBuilder { - if(this.hasBeforeRowTransformInterceptor(key)) { - throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this builder.", key, options) - } - - this.beforeRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataTransformerBuilder { - if(this.hasAfterRowTransformInterceptor(key)) { - throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this builder.", key, options) - } - - this.afterRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public add() { - return new DataTransformerProperty(this); - } - - public addNewProperty(property: DataTransformerProperty) { - this.properties[property.sourceProperty] = property; - } - - public import(jsonString: string) { - const object = JSON.parse(jsonString); - - if(object.hasOwnProperty("normalizers") && Array.isArray(object.normalizers)) { - this.normalizers = object.normalizers; - } - - if(object.hasOwnProperty("beforeRowTransformInterceptors") && Array.isArray(object.beforeRowTransformInterceptors)) { - this.beforeRowTransformInterceptors = object.beforeRowTransformInterceptors; - } - - if(object.hasOwnProperty("afterRowTransformInterceptors") && Array.isArray(object.afterRowTransformInterceptors)) { - this.afterRowTransformInterceptors = object.afterRowTransformInterceptors; - } - - if(object.hasOwnProperty("properties") && typeof object.properties === "object") { - for(const key in object.properties) { - if(object.properties.hasOwnProperty(key) === false) { - continue; - } - - const property = object.properties[key]; - const newProperty = this.add(); - - newProperty.normalizers = property.normalizers; - if(property.hasOwnProperty("excludedNormalizers")) { - for(const item in property.excludedNormalizers) { - newProperty.excludeNormalizer(item); - } - } - - newProperty.isOptional = property.isOptional; - newProperty.sourceProperty = property.sourceProperty; - newProperty.destinationProperty = property.destinationProperty; - - newProperty.end(); - } - } - - return this; - } - - public export(): string { - const properties: any = {}; - - for (const key in this.properties) { - if(this.properties.hasOwnProperty(key) === false) { - continue; - } - - const property = this.properties[key]; - - const excludedNormalizers: any = {} - - for(const element of property.excludedNormalizers.values()) { - excludedNormalizers[element] = true; - } - - properties[key] = { - "sourceProperty": property.sourceProperty, - "destinationProperty": property.destinationProperty, - "isOptional": property.isOptional, - "normalizers": property.normalizers, - "excludedNormalizers": excludedNormalizers, - }; - } - - return JSON.stringify({ - "normalizers": this.normalizers, - "beforeRowTransformInterceptors": this.beforeRowTransformInterceptors, - "afterRowTransformInterceptors": this.afterRowTransformInterceptors, - "properties": properties, - }); - } -} diff --git a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts b/packages/data-transformer/src/transformers/data-transformer.property.spec.ts deleted file mode 100644 index e28356aa7..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {DataTransformerProperty} from "./data-transformer.property"; -import {DataTransformerBuilder} from "./data-transformer.builder"; - -describe('Data Transformer Property', () => { - const dataTransformerBuilder: DataTransformerBuilder = new DataTransformerBuilder(); - let dataTransformerProperty: DataTransformerProperty; - - beforeEach(() => { - dataTransformerProperty = new DataTransformerProperty(dataTransformerBuilder); - }) - - it("should set the Source Property correctly", () => { - dataTransformerProperty.setSourceProperty("property"); - - expect(dataTransformerProperty.sourceProperty).toBe("property") - }) - - it("should set the Destination Property correctly", () => { - dataTransformerProperty.setDestinationProperty("property"); - - expect(dataTransformerProperty.destinationProperty).toBe("property") - }) - - it("should add a normalizer correctly", () => { - const options = { - "optionA": true, - }; - - dataTransformerProperty.addNormalizer("normalizer", options); - - expect(dataTransformerProperty.normalizers.length).toBe(1); - expect(dataTransformerProperty.normalizers[0].key).toBe("normalizer") - expect(dataTransformerProperty.normalizers[0].options).toBe(options) - }) - - it("should return correctly if it has a normalizer correctly", () => { - const options = { - "optionA": true, - }; - - dataTransformerProperty.addNormalizer("normalizer", options); - - expect(dataTransformerProperty.hasNormalizer("normalizer")).toBeTruthy(); - expect(dataTransformerProperty.hasNormalizer("dafds")).toBeFalsy(); - }) - - it("should add an excluded normalizer correctly", () => { - const options = { - "optionA": true, - }; - - dataTransformerProperty.excludeNormalizer("normalizer"); - - expect(dataTransformerProperty.excludedNormalizers.size).toBe(1); - expect(dataTransformerProperty.excludedNormalizers.has("normalizer")).toBeTruthy() - }) - - it("should set the IsOptional property", () => { - dataTransformerProperty.setIsOptional(true); - - expect(dataTransformerProperty.isOptional).toBeTruthy(); - }) - - it("should properly return the builder when calling end()", () => { - expect(dataTransformerProperty.end()).toBe(dataTransformerBuilder); - }) - - it("should properly accept a list of sourceProperties", () => {}) -}); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.property.ts b/packages/data-transformer/src/transformers/data-transformer.property.ts deleted file mode 100644 index ef54b3c4b..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.property.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerBuilder} from "./data-transformer.builder"; - -export class DataTransformerProperty { - public sourceProperty!: string; - public destinationProperty!: string; - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public excludedNormalizers: Set = new Set(); - public isOptional: boolean = false; - - public children: {[key in (string|number)]: DataTransformerProperty} = {}; - - public constructor(private readonly builder: DataTransformerBuilder) { - } - - public setSourceProperty(sourceProperty: string): DataTransformerProperty { - this.sourceProperty = sourceProperty; - return this; - } - public setDestinationProperty(destinationProperty: string): DataTransformerProperty { - this.destinationProperty = destinationProperty; - return this; - } - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerProperty { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) - } - - if(this.builder.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the builder and cannot be also added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public excludeNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): DataTransformerProperty { - if(this.excludedNormalizers.has(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The EXCLUDED data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey) - } - - this.excludedNormalizers.add(normalizerUniqueKey); - - return this; - } - - public setIsOptional(isOptional: boolean): DataTransformerProperty { - this.isOptional = isOptional; - - return this; - } - - public end(): DataTransformerBuilder { - this.builder.addNewProperty(this); - - return this.builder; - } - -} \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data.transformer.ts b/packages/data-transformer/src/transformers/data.transformer.ts deleted file mode 100644 index 3a9fe8d06..000000000 --- a/packages/data-transformer/src/transformers/data.transformer.ts +++ /dev/null @@ -1,104 +0,0 @@ -import {injectable, injectAll} from "tsyringe"; -import {moduleScoped, tag} from "@pristine-ts/common"; -import {DataTransformerModuleKeyname} from "../data-transformer.module.keyname"; -import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; -import {DataTransformerBuilder} from "./data-transformer.builder"; -import {DataTransformerProperty} from "./data-transformer.property"; -import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataTransformerRow} from "../types/data-transformer.row"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; -import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; -import {ClassMetadata} from "@pristine-ts/metadata"; -import {ClassConstructor, plainToInstance} from "class-transformer"; - -@moduleScoped(DataTransformerModuleKeyname) -@injectable() -export class DataTransformer { - private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface } = {} - private readonly dataTransformerInterceptorsMap: { [key in DataTransformerInterceptorUniqueKeyType]: DataTransformerInterceptorInterface } = {} - - public constructor(@injectAll("DataNormalizerInterface") private readonly dataNormalizers: DataNormalizerInterface[], - @injectAll("DataTransformerInterceptor") private readonly dataTransformerInterceptors: DataTransformerInterceptorInterface[],) { - dataNormalizers.forEach(dataNormalizer => { - this.dataNormalizersMap[dataNormalizer.getUniqueKey()] = dataNormalizer; - }) - - dataTransformerInterceptors.forEach(interceptor => { - this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; - }); - } - - public async transformRows(builder: DataTransformerBuilder, rows: DataTransformerRow[], destinationType?: ClassConstructor): Promise { - return rows.map(row => this.transform(builder, row, destinationType)); - } - - public async transform(builder: DataTransformerBuilder, source: DataTransformerRow, destinationType?: ClassConstructor): Promise { - const globalNormalizers = builder.normalizers; - - let row: DataTransformerRow = {}; - - if(destinationType) { - row = plainToInstance(destinationType, {}); - } - - let interceptedInputRow = source; - - // Execute the before row interceptors. - for (const element of builder.beforeRowTransformInterceptors) { - const interceptor = this.dataTransformerInterceptorsMap[element.key]; - - if (interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } - - // todo: Pass the options when we start using them. - interceptedInputRow = await interceptor.beforeRowTransform(interceptedInputRow); - } - - // Loop over the properties defined in the builder - for (const key in builder.properties) { - if (builder.properties.hasOwnProperty(key) === false) { - continue; - } - - const property: DataTransformerProperty = builder.properties[key]; - if (interceptedInputRow.hasOwnProperty(property.sourceProperty) === false) { - if (property.isOptional) { - continue; - } - - throw new DataTransformerSourcePropertyNotFoundError("The property '" + key + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", key) - } - - let value = interceptedInputRow[property.sourceProperty]; - - // Remove the normalizers part of the excludedNormalizers - const normalizers = globalNormalizers.filter(element => property.excludedNormalizers.has(element.key) === false); - normalizers.push(...property.normalizers); - - normalizers.forEach(element => { - const dataNormalizer = this.dataNormalizersMap[element.key]; - value = dataNormalizer.normalize(value, element.options); - }) - - // Assign the resulting value in the destination - row[property.destinationProperty] = value; - } - - // Execute the before row interceptors. - for (const element of builder.afterRowTransformInterceptors) { - const interceptor: DataTransformerInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; - - if (interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } - - // todo pass the options when we start using it. - row = await interceptor.afterRowTransform(row); - } - - return row; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/transformers.ts b/packages/data-transformer/src/transformers/transformers.ts deleted file mode 100644 index 3f6f77c90..000000000 --- a/packages/data-transformer/src/transformers/transformers.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./data.transformer"; -export * from "./data-transformer.builder"; -export * from "./data-transformer.property"; diff --git a/packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts b/packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts deleted file mode 100644 index ca2901216..000000000 --- a/packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts +++ /dev/null @@ -1 +0,0 @@ -export type DataTransformerInterceptorUniqueKeyType = string; \ No newline at end of file diff --git a/packages/data-transformer/src/types/data-transformer.row.ts b/packages/data-transformer/src/types/data-transformer.row.ts deleted file mode 100644 index bb6ffc7de..000000000 --- a/packages/data-transformer/src/types/data-transformer.row.ts +++ /dev/null @@ -1 +0,0 @@ -export type DataTransformerRow = {[key in string]: any} | any; \ No newline at end of file diff --git a/packages/data-transformer/src/types/types.ts b/packages/data-transformer/src/types/types.ts deleted file mode 100644 index ccceae527..000000000 --- a/packages/data-transformer/src/types/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./data-normalizer-unique-key.type"; -export * from "./data-transformer.row"; -export * from "./data-transformer-interceptor-unique-key.type"; \ No newline at end of file From 0bf454f6631e585f4f3ea10a7194dab6b2b5284a Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 14:36:20 -0800 Subject: [PATCH 13/20] - Continued improvements. --- .../src/builders/data-mapping.builder.ts | 4 +- .../src/enums/data-mapping-node-type.enum.ts | 3 +- ...node-invalid-source-property-type.error.ts | 19 +++ packages/data-mapping/src/errors/errors.ts | 1 + .../src/mappers/data.mapper.spec.ts | 3 - .../src/nodes/base-data-mapping.node.ts | 2 + .../src/nodes/data-mapping.leaf.spec.ts | 47 +++++++ .../src/nodes/data-mapping.leaf.ts | 28 ++++- .../src/nodes/data-mapping.node.ts | 115 +++++++++++++++++- 9 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts diff --git a/packages/data-mapping/src/builders/data-mapping.builder.ts b/packages/data-mapping/src/builders/data-mapping.builder.ts index e678ba8a4..f76813a20 100644 --- a/packages/data-mapping/src/builders/data-mapping.builder.ts +++ b/packages/data-mapping/src/builders/data-mapping.builder.ts @@ -122,7 +122,7 @@ export class DataMappingBuilder extends BaseDataMappingNode{ * */ public addArrayOfScalar(): DataMappingLeaf { - return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.Array); + return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.ScalarArray); } /** @@ -130,7 +130,7 @@ export class DataMappingBuilder extends BaseDataMappingNode{ * the array will be treated as being the same. */ public addArrayOfObjects(): DataMappingNode { - return new DataMappingNode(this, this, DataMappingNodeTypeEnum.Array); + return new DataMappingNode(this, this, DataMappingNodeTypeEnum.ObjectArray); } /** diff --git a/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts b/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts index 8a04000b3..abfe0423e 100644 --- a/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts +++ b/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts @@ -1,5 +1,6 @@ export enum DataMappingNodeTypeEnum { - Array = "DATA_MAPPING_NODE_ARRAY", + ScalarArray = "DATA_MAPPING_NODE_SCALAR_ARRAY", + ObjectArray = "DATA_MAPPING_NODE_OBJECT_ARRAY", Node = "DATA_MAPPING_NODE", Leaf = "DATA_MAPPING_LEAF", } \ No newline at end of file diff --git a/packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts b/packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts new file mode 100644 index 000000000..1fabdd18b --- /dev/null +++ b/packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts @@ -0,0 +1,19 @@ +import {LoggableError} from "@pristine-ts/common"; + +/** + * This Error is thrown when a node is of type array but the `source[sourceProperty]` doesn't actually contain an array. + */ +export class ArrayDataMappingNodeInvalidSourcePropertyTypeError extends LoggableError { + + public constructor(message: string, sourceProperty: string) { + super(message, { + sourceProperty, + }); + + + // Set the prototype explicitly. + // As specified in the documentation in TypeScript + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, ArrayDataMappingNodeInvalidSourcePropertyTypeError.prototype); + } +} diff --git a/packages/data-mapping/src/errors/errors.ts b/packages/data-mapping/src/errors/errors.ts index 742001c2d..02c2bbc99 100644 --- a/packages/data-mapping/src/errors/errors.ts +++ b/packages/data-mapping/src/errors/errors.ts @@ -1,3 +1,4 @@ +export * from "./array-data-mapping-node-invalid-source-property-type.error"; export * from "./data-after-row-transformer-interceptor-already-added.error"; export * from "./data-before-row-transformer-interceptor-already-added.error"; export * from "./data-normalizer-already-added.error"; diff --git a/packages/data-mapping/src/mappers/data.mapper.spec.ts b/packages/data-mapping/src/mappers/data.mapper.spec.ts index dda706709..d92488849 100644 --- a/packages/data-mapping/src/mappers/data.mapper.spec.ts +++ b/packages/data-mapping/src/mappers/data.mapper.spec.ts @@ -1,8 +1,5 @@ -import {DataTransformer} from "../transformers/data.transformer"; import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; -import {DataTransformerBuilder} from "../transformers/data-transformer.builder"; import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; -import {DataTransformerRow} from "../types/data-transformer.row"; import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; diff --git a/packages/data-mapping/src/nodes/base-data-mapping.node.ts b/packages/data-mapping/src/nodes/base-data-mapping.node.ts index 15551b632..7910183e0 100644 --- a/packages/data-mapping/src/nodes/base-data-mapping.node.ts +++ b/packages/data-mapping/src/nodes/base-data-mapping.node.ts @@ -19,4 +19,6 @@ export abstract class BaseDataMappingNode { this.nodes[node.sourceProperty] = node; } + + } \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts index 4154b8739..dd4f09d45 100644 --- a/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts @@ -3,6 +3,7 @@ import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; describe("Data Mapping Leaf", () => { it("should map the property corresponding to the leaf from the source to the destination while also applying the normalizers and excluding the specified normalizers", async () => { @@ -55,4 +56,50 @@ describe("Data Mapping Leaf", () => { expect(destination.name).toBeDefined() expect(destination.name).toBe("title__"); }) + + it("should properly map an array of scalars", async () => { + class Source { + array: string[] = [ + "-4", + "100", + "9350", + "5", + ] + } + + class Destination { + name: number[] = []; + } + + class ConvertToNumber implements DataNormalizerInterface { + getUniqueKey(): DataNormalizerUniqueKey { + return ConvertToNumber.name; + } + + normalize(source: any, options?: {}): number { + return parseInt(source); + } + } + + const dataBuilder = new DataMappingBuilder(); + dataBuilder.addNormalizer(ConvertToNumber.name); + + const leaf = new DataMappingLeaf(dataBuilder, dataBuilder, DataMappingNodeTypeEnum.ScalarArray); + leaf.setSourceProperty("array"); + leaf.setDestinationProperty("name"); + + const destination = new Destination(); + + await leaf.map(new Source(), destination, { + [ConvertToNumber.name]: new ConvertToNumber(), + }); + + expect(destination.name).toBeDefined() + expect(Array.isArray(destination.name)).toBeTruthy() + expect(destination.name.length).toBe(4); + expect(destination.name[0]).toBe(-4); + expect(destination.name[1]).toBe(100); + expect(destination.name[2]).toBe(9350); + expect(destination.name[3]).toBe(5); + }) }); \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.ts index b5411d9d2..a2eed3b7f 100644 --- a/packages/data-mapping/src/nodes/data-mapping.leaf.ts +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.ts @@ -5,6 +5,9 @@ import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {DataMappingNode} from "./data-mapping.node"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import { + ArrayDataMappingNodeInvalidSourcePropertyTypeError +} from "../errors/array-data-mapping-node-invalid-source-property-type.error"; export class DataMappingLeaf { /** @@ -126,6 +129,7 @@ export class DataMappingLeaf { /** * This method maps the `sourceProperty` from the `source` object and maps it to the `destinationProperty` of the * `destination` object while applying the normalizers. + * * @param source * @param destination * @param normalizersMap @@ -139,10 +143,32 @@ export class DataMappingLeaf { throw new DataTransformerSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) } - let value = source[this.sourceProperty]; const normalizers = this.root.normalizers.filter(element => this.excludedNormalizers.has(element.key) === false); normalizers.push(...this.normalizers); + if(this.type === DataMappingNodeTypeEnum.ScalarArray) { + // This means that the source[propertyKey] contains an array of objects and each object should be mapped + const array = source[this.sourceProperty]; + + if(Array.isArray(array) === false) { + throw new ArrayDataMappingNodeInvalidSourcePropertyTypeError(`According to your schema, the property '${this.sourceProperty}' in the source object must contain an Array of Scalar. Instead, it contains: '${typeof array}'.`, this.sourceProperty); + } + + destination[this.destinationProperty] = []; + + for (let value of array) { + normalizers.forEach(element => { + const normalizer = normalizersMap[element.key]; + value = normalizer.normalize(value, element.options); + }) + + destination[this.destinationProperty].push(value); + } + + return; + } + + let value = source[this.sourceProperty]; normalizers.forEach(element => { const normalizer = normalizersMap[element.key]; value = normalizer.normalize(value, element.options); diff --git a/packages/data-mapping/src/nodes/data-mapping.node.ts b/packages/data-mapping/src/nodes/data-mapping.node.ts index e683c24d1..8b4f19f34 100644 --- a/packages/data-mapping/src/nodes/data-mapping.node.ts +++ b/packages/data-mapping/src/nodes/data-mapping.node.ts @@ -5,6 +5,9 @@ import {BaseDataMappingNode} from "./base-data-mapping.node"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import { + ArrayDataMappingNodeInvalidSourcePropertyTypeError +} from "../errors/array-data-mapping-node-invalid-source-property-type.error"; export class DataMappingNode extends BaseDataMappingNode { /** @@ -80,7 +83,7 @@ export class DataMappingNode extends BaseDataMappingNode { * */ public addArrayOfScalar(): DataMappingLeaf { - return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.Array); + return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.ScalarArray); } /** @@ -88,7 +91,7 @@ export class DataMappingNode extends BaseDataMappingNode { * the array will be treated as being the same. */ public addArrayOfObjects(): DataMappingNode { - return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.Array); + return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.ObjectArray); } /** @@ -101,6 +104,14 @@ export class DataMappingNode extends BaseDataMappingNode { return this.parent; } + /** + * This method maps the `sourceProperty` from the `source` object and maps it to the `destinationProperty` of the + * `destination` object while applying the normalizers. + * + * @param source + * @param destination + * @param normalizersMap + */ public async map(source: any, destination: any, normalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface }) { if(source.hasOwnProperty(this.sourceProperty) === false) { if(this.isOptional) { @@ -113,12 +124,45 @@ export class DataMappingNode extends BaseDataMappingNode { let sourceElement = source[this.sourceProperty]; if(destination[this.destinationProperty] === undefined) { - // todo: we need to get the expected Type of the `destination[this.destinationProperty]` and actually instantiate it. - destination[this.destinationProperty] = {}; + if(this.type === DataMappingNodeTypeEnum.ObjectArray) { + destination[this.destinationProperty] = []; + } else { + // todo: we need to get the expected Type of the `destination[this.destinationProperty]` and actually instantiate it. + destination[this.destinationProperty] = {}; + } } let destinationElement = destination[this.destinationProperty]; + if(this.type === DataMappingNodeTypeEnum.ObjectArray) { + // This means that the source[propertyKey] contains an array of objects and each object should be mapped + const array = source[this.sourceProperty]; + + if(Array.isArray(array) === false) { + throw new ArrayDataMappingNodeInvalidSourcePropertyTypeError(`According to your schema, the property '${this.sourceProperty}' in the source object must contain an Array of objects. Instead, it contains: '${typeof array}'.`, this.sourceProperty); + } + + for (let element of array) { + // todo: we need to get the expected Type of the object in the array in the Destination object + let dest = {}; + + for (let key in this.nodes) { + if(this.nodes.hasOwnProperty(key) === false) { + continue; + } + + const node = this.nodes[key]; + + await node.map(element, dest, normalizersMap); + } + + destinationElement.push(dest); + } + + return; + } + + // When the current node is not an array, we simply iterate for (let key in this.nodes) { if(this.nodes.hasOwnProperty(key) === false) { continue; @@ -129,4 +173,67 @@ export class DataMappingNode extends BaseDataMappingNode { await node.map(sourceElement, destinationElement, normalizersMap); } } + + /** + * This method imports a schema. + * + * @param schema + */ + public import(schema: any) { + this.sourceProperty = schema.sourceProperty; + this.destinationProperty = schema.destinationProperty; + this.isOptional = schema.isOptional; + this.nodes = {}; + + const nodes = schema.nodes; + + for(let key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + const nodeInfo = nodes[key]; + + const type: DataMappingNodeTypeEnum = nodeInfo["_type"]; + + switch (type) { + case DataMappingNodeTypeEnum.Leaf: + const leaf = new DataMappingLeaf(this.root, this, type); + leaf.import(nodeInfo); + this.nodes[leaf.sourceProperty] = leaf; + continue; + + case DataMappingNodeTypeEnum.Node: + case DataMappingNodeTypeEnum.ScalarArray: + case DataMappingNodeTypeEnum.ObjectArray: + const node = new DataMappingNode(this.root, this, type); + node.import(nodeInfo); + this.nodes[node.sourceProperty] = node; + continue; + } + } + } + + /** + * This method exports this node. + */ + public export() { + const nodes = this.nodes; + + for (let key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + nodes[key] = nodes[key].export(); + } + + return { + "_type": this.type, + "sourceProperty": this.sourceProperty, + "destinationProperty": this.destinationProperty, + "isOptional": this.isOptional, + "nodes": nodes, + } + } } \ No newline at end of file From 199f8d671ee610883d7c18cdd600ede74cff0cce Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 15:55:57 -0800 Subject: [PATCH 14/20] - Testing the final validation. --- packages/data-mapping/package-lock.json | 136 +++++++++++++++++- packages/data-mapping/package.json | 1 + .../src/builders/data-mapping.builder.ts | 62 ++++++++ .../src/mappers/data.mapper.spec.ts | 136 +++++++++++++++++- .../src/nodes/data-mapping.leaf.ts | 8 +- .../src/nodes/data-mapping.node.ts | 2 +- 6 files changed, 338 insertions(+), 7 deletions(-) diff --git a/packages/data-mapping/package-lock.json b/packages/data-mapping/package-lock.json index 301677e39..0a77c046c 100644 --- a/packages/data-mapping/package-lock.json +++ b/packages/data-mapping/package-lock.json @@ -1,18 +1,20 @@ { - "name": "@pristine-ts/data-transformer", + "name": "@pristine-ts/data-mapper", "version": "0.0.276", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "@pristine-ts/data-transformer", + "name": "@pristine-ts/data-mapper", "version": "0.0.276", "license": "ISC", "dependencies": { + "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/common": "file:../common", "@pristine-ts/logging": "file:../logging", "@pristine-ts/metadata": "~1.0.2", - "class-transformer": "^0.5.1" + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" } }, "../common": { @@ -35,6 +37,29 @@ "date-fns": "^2.30.0" } }, + "node_modules/@babel/runtime": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", + "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@pristine-ts/class-validator": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@pristine-ts/class-validator/-/class-validator-1.0.22.tgz", + "integrity": "sha512-Fuvl2rq3wz/lO6gcoBHt3FZjr1diHBfz2Ug5wCqZceQZIHeUWwHM3T6GxazPDOjXPIH1yNGZo07e5o3Uma0ibw==", + "dependencies": { + "@pristine-ts/metadata": "^1.0.2", + "date-fns": "^2.28.0", + "libphonenumber-js": "^1.10.6", + "reflect-metadata": "^0.2.1", + "validator": "^13.7.0" + } + }, "node_modules/@pristine-ts/common": { "resolved": "../common", "link": true @@ -56,13 +81,82 @@ "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.10.53", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.53.tgz", + "integrity": "sha512-sDTnnqlWK4vH4AlDQuswz3n4Hx7bIQWTpIcScJX+Sp7St3LXHmfiax/ZFfyYxHmkdCvydOLSuvtAO/XpXiSySw==" + }, "node_modules/reflect-metadata": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tsyringe": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.8.0.tgz", + "integrity": "sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } } }, "dependencies": { + "@babel/runtime": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", + "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@pristine-ts/class-validator": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@pristine-ts/class-validator/-/class-validator-1.0.22.tgz", + "integrity": "sha512-Fuvl2rq3wz/lO6gcoBHt3FZjr1diHBfz2Ug5wCqZceQZIHeUWwHM3T6GxazPDOjXPIH1yNGZo07e5o3Uma0ibw==", + "requires": { + "@pristine-ts/metadata": "^1.0.2", + "date-fns": "^2.28.0", + "libphonenumber-js": "^1.10.6", + "reflect-metadata": "^0.2.1", + "validator": "^13.7.0" + } + }, "@pristine-ts/common": { "version": "file:../common", "requires": { @@ -92,10 +186,46 @@ "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, + "libphonenumber-js": { + "version": "1.10.53", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.53.tgz", + "integrity": "sha512-sDTnnqlWK4vH4AlDQuswz3n4Hx7bIQWTpIcScJX+Sp7St3LXHmfiax/ZFfyYxHmkdCvydOLSuvtAO/XpXiSySw==" + }, "reflect-metadata": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tsyringe": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.8.0.tgz", + "integrity": "sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==", + "requires": { + "tslib": "^1.9.3" + } + }, + "validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==" } } } diff --git a/packages/data-mapping/package.json b/packages/data-mapping/package.json index 54fa52cd5..3e94edc63 100644 --- a/packages/data-mapping/package.json +++ b/packages/data-mapping/package.json @@ -23,6 +23,7 @@ "@pristine-ts/common": "file:../common", "@pristine-ts/logging": "file:../logging", "@pristine-ts/metadata": "~1.0.2", + "@pristine-ts/class-validator": "^1.0.22", "class-transformer": "^0.5.1", "tsyringe": "^4.8.0" }, diff --git a/packages/data-mapping/src/builders/data-mapping.builder.ts b/packages/data-mapping/src/builders/data-mapping.builder.ts index f76813a20..f08deefd6 100644 --- a/packages/data-mapping/src/builders/data-mapping.builder.ts +++ b/packages/data-mapping/src/builders/data-mapping.builder.ts @@ -140,4 +140,66 @@ export class DataMappingBuilder extends BaseDataMappingNode{ public end(): DataMappingBuilder { return this; } + + /** + * This method imports a schema. + * + * @param schema + */ + public import(schema: any) { + this.normalizers = schema.normalizers; + this.beforeMappingInterceptors = schema.beforeMappingInterceptors; + this.afterMappingInterceptors = schema.afterMappingInterceptors; + + const nodes = schema.nodes; + + for(let key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + const nodeInfo = nodes[key]; + + const type: DataMappingNodeTypeEnum = nodeInfo["_type"]; + + switch (type) { + case DataMappingNodeTypeEnum.ScalarArray: + case DataMappingNodeTypeEnum.Leaf: + const leaf = new DataMappingLeaf(this, this, type); + leaf.import(nodeInfo); + this.nodes[leaf.sourceProperty] = leaf; + continue; + + case DataMappingNodeTypeEnum.Node: + case DataMappingNodeTypeEnum.ObjectArray: + const node = new DataMappingNode(this, this, type); + node.import(nodeInfo); + this.nodes[node.sourceProperty] = node; + continue; + } + } + } + + /** + * This method exports this node. + */ + public export() { + const nodes = this.nodes; + + for (let key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + nodes[key] = nodes[key].export(); + } + + return { + "nodes": nodes, + "normalizers":this.normalizers, + "beforeMappingInterceptors": this.beforeMappingInterceptors, + "afterMappingInterceptors": this.afterMappingInterceptors, + + } + } } \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/data.mapper.spec.ts b/packages/data-mapping/src/mappers/data.mapper.spec.ts index d92488849..1041e4966 100644 --- a/packages/data-mapping/src/mappers/data.mapper.spec.ts +++ b/packages/data-mapping/src/mappers/data.mapper.spec.ts @@ -4,13 +4,145 @@ import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interce import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; import {property} from "@pristine-ts/metadata"; +import "jest-extended" +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataMapper} from "./data.mapper"; +import {Type} from "class-transformer"; describe("Data Mapper", () =>{ it("should map a very complex object into another complex object. Then, it should export the builder, import the builder and make sure it still maps everything properly.", async () => { + class ArraySource { + rank: number; + } + + class NestedSource { + nestedTitle: string; + } + + class Source { + title: string; + + nested: NestedSource; + + array: ArraySource[] = []; + + children: string[] = []; + } + + class ArrayDestination { + position: number; + } + + class NestedDestination { + nestedName: string; + } + + class Destination { + name: string; + + @Type(() => NestedDestination) + child: NestedDestination; + + @Type(() => ArrayDestination) + list: ArrayDestination[]; + + infants: string[] = [] + } + + let dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .addNormalizer(LowercaseNormalizer.name) + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .addNestingLevel() + .setSourceProperty("nested") + .setDestinationProperty("child") + .add() + .setSourceProperty("nestedTitle") + .setDestinationProperty("nestedName") + .end() + .end() + .addArrayOfObjects() + .setSourceProperty("array") + .setDestinationProperty("list") + .add() + .setSourceProperty("rank") + .setDestinationProperty("position") + .end() + .end() + .addArrayOfScalar() + .setSourceProperty("children") + .setDestinationProperty("infants") + .end() + .end(); + const source = new Source(); + source.title = "TITLE"; + source.children = ["Etienne", "Antoine", "Olivier"]; + source.nested = new NestedSource(); + source.nested.nestedTitle = "NESTED_TITLE"; + source.array = []; + source.array[0] = new ArraySource() + source.array[0].rank = 1 + source.array[1] = new ArraySource() + source.array[1].rank = 2 + + + let dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const destination: Destination = await dataMapper.map(dataMappingBuilder, source, Destination); + + // Check that the mapping was properly done. + expect(destination instanceof Destination).toBeTruthy(); + + expect(destination.name).toBe("TITLE"); + expect(destination.infants.length).toBe(3); + expect(destination.infants[0]).toBe("etienne") + expect(destination.infants[1]).toBe("antoine") + expect(destination.infants[2]).toBe("olivier") + expect(destination.child).toBeDefined() + //expect(destination.child instanceof NestedDestination).toBeTruthy(); + expect(destination.child.nestedName).toBe("nested_title") + expect(destination.list).toBeDefined() + expect(destination.list.length).toBe(2) + //expect(destination.list[0] instanceof ArrayDestination).toBeTruthy() + expect(destination.list[0].position).toBe(1) + //expect(destination.list[1] instanceof ArrayDestination).toBeTruthy() + expect(destination.list[1].position).toBe(2) + + // Make sure that the import and export work and still map properly + const schema = dataMappingBuilder.export(); + + dataMapper = new DataMapper([new LowercaseNormalizer()], []); + dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder.import(schema); + + const destinationAfterExportAndReimport = await dataMapper.map(dataMappingBuilder, source, Destination); + + // Check AGAIN that the mapping was properly done. + expect(destinationAfterExportAndReimport instanceof Destination).toBeTruthy(); + + expect(destinationAfterExportAndReimport.name).toBe("TITLE"); + expect(destinationAfterExportAndReimport.infants.length).toBe(3); + expect(destinationAfterExportAndReimport.infants[0]).toBe("etienne") + expect(destinationAfterExportAndReimport.infants[1]).toBe("antoine") + expect(destinationAfterExportAndReimport.infants[2]).toBe("olivier") + expect(destinationAfterExportAndReimport.child).toBeDefined() + //expect(destinationAfterExportAndReimport.child instanceof NestedDestination).toBeTruthy(); + expect(destinationAfterExportAndReimport.child.nestedName).toBe("nested_title") + expect(destinationAfterExportAndReimport.list).toBeDefined() + expect(destinationAfterExportAndReimport.list.length).toBe(2) + //expect(destinationAfterExportAndReimport.list[0] instanceof ArrayDestination).toBeTruthy() + expect(destinationAfterExportAndReimport.list[0].position).toBe(1) + //expect(destinationAfterExportAndReimport.list[1] instanceof ArrayDestination).toBeTruthy() + expect(destinationAfterExportAndReimport.list[1].position).toBe(2) }) - it("should properly transform", async () => { + /*it("should properly transform", async () => { const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); const source = [{ @@ -249,5 +381,5 @@ describe("Data Mapper", () =>{ const destination = await dataTransformer.transform(dataTransformerBuilder, source, Destination); expect(destination.name).toBe("title"); - }) + })*/ }) \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.ts index a2eed3b7f..195ff107c 100644 --- a/packages/data-mapping/src/nodes/data-mapping.leaf.ts +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.ts @@ -203,13 +203,19 @@ export class DataMappingLeaf { * This method exports this node. */ public export(): any { + const excludedNormalizers: any = {} + + for(const element of this.excludedNormalizers.values()) { + excludedNormalizers[element] = true; + } + return { "_type": this.type, "sourceProperty": this.sourceProperty, "destinationProperty": this.destinationProperty, "isOptional": this.isOptional, "normalizers": this.normalizers, - "excludedNormalizers": this.excludedNormalizers, + "excludedNormalizers": excludedNormalizers, } } } \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.node.ts b/packages/data-mapping/src/nodes/data-mapping.node.ts index 8b4f19f34..f97202f83 100644 --- a/packages/data-mapping/src/nodes/data-mapping.node.ts +++ b/packages/data-mapping/src/nodes/data-mapping.node.ts @@ -197,6 +197,7 @@ export class DataMappingNode extends BaseDataMappingNode { const type: DataMappingNodeTypeEnum = nodeInfo["_type"]; switch (type) { + case DataMappingNodeTypeEnum.ScalarArray: case DataMappingNodeTypeEnum.Leaf: const leaf = new DataMappingLeaf(this.root, this, type); leaf.import(nodeInfo); @@ -204,7 +205,6 @@ export class DataMappingNode extends BaseDataMappingNode { continue; case DataMappingNodeTypeEnum.Node: - case DataMappingNodeTypeEnum.ScalarArray: case DataMappingNodeTypeEnum.ObjectArray: const node = new DataMappingNode(this.root, this, type); node.import(nodeInfo); From a1cc7471359318047a2e04a8b91f88da1c889715 Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 20:10:38 -0800 Subject: [PATCH 15/20] - Updated the missing tests. --- package-lock.json | 32 +++- .../src/mappers/data.mapper.spec.ts | 176 +++++++++--------- .../data-mapping/src/mappers/data.mapper.ts | 18 +- 3 files changed, 123 insertions(+), 103 deletions(-) diff --git a/package-lock.json b/package-lock.json index fdcef274d..ff9100f3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@pristine-ts/common": "file:packages/common", "@pristine-ts/configuration": "file:packages/configuration", "@pristine-ts/core": "file:packages/core", - "@pristine-ts/data-transformer": "file:packages/data-transformer", + "@pristine-ts/data-mapping": "file:packages/data-mapping", "@pristine-ts/e2e": "file:tests/e2e", "@pristine-ts/express": "file:packages/express", "@pristine-ts/file": "file:packages/file", @@ -4116,8 +4116,8 @@ "resolved": "packages/core", "link": true }, - "node_modules/@pristine-ts/data-transformer": { - "resolved": "packages/data-transformer", + "node_modules/@pristine-ts/data-mapping": { + "resolved": "packages/data-mapping", "link": true }, "node_modules/@pristine-ts/e2e": { @@ -15599,9 +15599,23 @@ "uuid": "dist/bin/uuid" } }, + "packages/data-mapping": { + "name": "@pristine-ts/data-mapper", + "version": "0.0.276", + "license": "ISC", + "dependencies": { + "@pristine-ts/class-validator": "^1.0.22", + "@pristine-ts/common": "file:../common", + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" + } + }, "packages/data-transformer": { "name": "@pristine-ts/data-transformer", "version": "0.0.276", + "extraneous": true, "license": "ISC", "dependencies": { "@pristine-ts/common": "file:../common", @@ -15804,7 +15818,6 @@ "version": "1.0.0", "dependencies": { "@pristine-ts/auth0": "file:../../packages/auth0", - "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/cli": "file:../../packages/cli", "@pristine-ts/common": "file:../../packages/common", "@pristine-ts/core": "file:../../packages/core", @@ -19233,18 +19246,21 @@ } } }, - "@pristine-ts/data-transformer": { - "version": "file:packages/data-transformer", + "@pristine-ts/data-mapping": { + "version": "file:packages/data-mapping", "requires": { + "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/common": "file:../common", - "@pristine-ts/logging": "file:../logging" + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" } }, "@pristine-ts/e2e": { "version": "file:tests/e2e", "requires": { "@pristine-ts/auth0": "file:../../packages/auth0", - "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/cli": "file:../../packages/cli", "@pristine-ts/common": "file:../../packages/common", "@pristine-ts/core": "file:../../packages/core", diff --git a/packages/data-mapping/src/mappers/data.mapper.spec.ts b/packages/data-mapping/src/mappers/data.mapper.spec.ts index 1041e4966..f34cfd406 100644 --- a/packages/data-mapping/src/mappers/data.mapper.spec.ts +++ b/packages/data-mapping/src/mappers/data.mapper.spec.ts @@ -105,13 +105,13 @@ describe("Data Mapper", () =>{ expect(destination.infants[1]).toBe("antoine") expect(destination.infants[2]).toBe("olivier") expect(destination.child).toBeDefined() - //expect(destination.child instanceof NestedDestination).toBeTruthy(); + expect(destination.child instanceof NestedDestination).toBeTruthy(); expect(destination.child.nestedName).toBe("nested_title") expect(destination.list).toBeDefined() expect(destination.list.length).toBe(2) - //expect(destination.list[0] instanceof ArrayDestination).toBeTruthy() + expect(destination.list[0] instanceof ArrayDestination).toBeTruthy() expect(destination.list[0].position).toBe(1) - //expect(destination.list[1] instanceof ArrayDestination).toBeTruthy() + expect(destination.list[1] instanceof ArrayDestination).toBeTruthy() expect(destination.list[1].position).toBe(2) // Make sure that the import and export work and still map properly @@ -132,18 +132,18 @@ describe("Data Mapper", () =>{ expect(destinationAfterExportAndReimport.infants[1]).toBe("antoine") expect(destinationAfterExportAndReimport.infants[2]).toBe("olivier") expect(destinationAfterExportAndReimport.child).toBeDefined() - //expect(destinationAfterExportAndReimport.child instanceof NestedDestination).toBeTruthy(); + expect(destinationAfterExportAndReimport.child instanceof NestedDestination).toBeTruthy(); expect(destinationAfterExportAndReimport.child.nestedName).toBe("nested_title") expect(destinationAfterExportAndReimport.list).toBeDefined() expect(destinationAfterExportAndReimport.list.length).toBe(2) - //expect(destinationAfterExportAndReimport.list[0] instanceof ArrayDestination).toBeTruthy() + expect(destinationAfterExportAndReimport.list[0] instanceof ArrayDestination).toBeTruthy() expect(destinationAfterExportAndReimport.list[0].position).toBe(1) - //expect(destinationAfterExportAndReimport.list[1] instanceof ArrayDestination).toBeTruthy() + expect(destinationAfterExportAndReimport.list[1] instanceof ArrayDestination).toBeTruthy() expect(destinationAfterExportAndReimport.list[1].position).toBe(2) }) - /*it("should properly transform", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + it("should properly transform", async () => { + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); const source = [{ NAME: "Etienne Noel", @@ -151,23 +151,23 @@ describe("Data Mapper", () =>{ TOTAL: 10, }]; - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder .add() - .setSourceProperty("NAME") - .setDestinationProperty("name") + .setSourceProperty("NAME") + .setDestinationProperty("name") .end() .add() - .setSourceProperty("province") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") + .setSourceProperty("province") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") .end() .add() - .setSourceProperty("TOTAL") - .setDestinationProperty("total") + .setSourceProperty("TOTAL") + .setDestinationProperty("total") .end(); - const destination = await dataTransformer.transform(dataTransformerBuilder, source); + const destination = await dataMapper.mapAll(dataMappingBuilder, source); expect(destination.length).toBe(1) @@ -178,29 +178,29 @@ describe("Data Mapper", () =>{ }) it("should properly transform an array with numerical indices", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); const source = [ ["Etienne Noel", "QUEBEC", 10], ]; - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder .add() - .setSourceProperty("0") - .setDestinationProperty("name") + .setSourceProperty("0") + .setDestinationProperty("name") .end() .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") + .setSourceProperty("1") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") .end() .add() - .setSourceProperty("2") - .setDestinationProperty("total") + .setSourceProperty("2") + .setDestinationProperty("total") .end(); - const destination = await dataTransformer.transform(dataTransformerBuilder, source); + const destination = await dataMapper.mapAll(dataMappingBuilder, source); expect(destination.length).toBe(1) @@ -212,10 +212,10 @@ describe("Data Mapper", () =>{ it("should properly call the before row transformers and respect the order of calls", async () => { const firstInterceptor: DataMappingInterceptorInterface = { - async beforeMapping(row: DataTransformerRow): Promise { + async beforeMapping(row: any): Promise { return row; }, - async afterMapping(row: DataTransformerRow): Promise { + async afterMapping(row: any): Promise { return { "after": row["name"] + row["province"] + row["total"] + row["added_property"], }; @@ -225,11 +225,11 @@ describe("Data Mapper", () =>{ }, } const secondInterceptor: DataMappingInterceptorInterface = { - async beforeMapping(row: DataTransformerRow): Promise { + async beforeMapping(row: any): Promise { row[3] = "Property added in the beforeRowTransform"; return row; }, - async afterMapping(row: DataTransformerRow): Promise { + async afterMapping(row: any): Promise { return row; }, getUniqueKey(): DataMappingInterceptorUniqueKeyType { @@ -242,7 +242,7 @@ describe("Data Mapper", () =>{ const beforeRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "beforeMapping") const afterRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "afterMapping") - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], [ + const dataTransformer = new DataMapper([new LowercaseNormalizer()], [ firstInterceptor, secondInterceptor, ]); @@ -251,78 +251,79 @@ describe("Data Mapper", () =>{ ["Etienne Noel", "QUEBEC", 10], ]; - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .addBeforeRowTransformInterceptor("first_interceptor") - .addBeforeRowTransformInterceptor("second_interceptor") - .addAfterRowTransformInterceptor("first_interceptor") - .addAfterRowTransformInterceptor("second_interceptor") + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .addBeforeMappingInterceptor("first_interceptor") + .addBeforeMappingInterceptor("second_interceptor") + .addAfterMappingInterceptor("first_interceptor") + .addAfterMappingInterceptor("second_interceptor") .add() - .setSourceProperty("0") - .setDestinationProperty("name") + .setSourceProperty("0") + .setDestinationProperty("name") .end() .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") + .setSourceProperty("1") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") .end() .add() - .setSourceProperty("2") - .setDestinationProperty("total") + .setSourceProperty("2") + .setDestinationProperty("total") .end() .add() - .setSourceProperty("3") - .setDestinationProperty("added_property") + .setSourceProperty("3") + .setDestinationProperty("added_property") .end(); - const transformedData: any[] = await dataTransformer.transform(dataTransformerBuilder, source); + const mappedData: any[] = await dataTransformer.mapAll(dataMappingBuilder, source); expect(beforeRowFirstInterceptorSpy).toHaveBeenCalledBefore(beforeRowSecondInterceptorSpy); expect(beforeRowSecondInterceptorSpy).toHaveBeenCalledBefore(afterRowFirstInterceptorSpy) expect(afterRowFirstInterceptorSpy).toHaveBeenCalledBefore(afterRowSecondInterceptorSpy); - expect(transformedData[0]["after"]).toBe("Etienne Noelquebec10Property added in the beforeRowTransform") + expect(mappedData[0]["after"]).toBe("Etienne Noelquebec10Property added in the beforeRowTransform") }) it("should throw properly when before row transformer cannot be found", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .addBeforeRowTransformInterceptor("first_interceptor") + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .addBeforeMappingInterceptor("first_interceptor") .add() - .setSourceProperty("0") - .setDestinationProperty("name") + .setSourceProperty("0") + .setDestinationProperty("name") .end() - await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); + await expect(dataMapper.mapAll(dataMappingBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); }) it("should throw properly when after row transformer cannot be found", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .addAfterRowTransformInterceptor("first_interceptor") + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .addAfterMappingInterceptor("first_interceptor") .add() .setSourceProperty("0") .setDestinationProperty("name") .end() - await expect(dataTransformer.transform(dataTransformerBuilder, [{"0": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); + await expect(dataMapper.mapAll(dataMappingBuilder, [{"0": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); }) + it("should throw properly when an element is not optional and not found in the source", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .add() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() - await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); - }) + await expect(dataMapper.mapAll(dataMappingBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); + }) it("should properly type the return object when a destinationType is passed", async () => { class Source { @@ -338,22 +339,21 @@ describe("Data Mapper", () =>{ const source = new Source(); source.title = "TITLE"; - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder .add() - .setSourceProperty("title") - .setDestinationProperty("name") - .addNormalizer(LowercaseNormalizer.name) + .setSourceProperty("title") + .setDestinationProperty("name") + .addNormalizer(LowercaseNormalizer.name) .end() - const destination = await dataTransformer.transform(dataTransformerBuilder, source, Destination); + const destination = await dataMapper.map(dataMappingBuilder, source, Destination); expect(destination.name).toBe("title"); }) - it("should properly type the nested objects", async () => { class Source { @property() @@ -368,18 +368,18 @@ describe("Data Mapper", () =>{ const source = new Source(); source.title = "TITLE"; - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder .add() - .setSourceProperty("title") - .setDestinationProperty("name") - .addNormalizer(LowercaseNormalizer.name) + .setSourceProperty("title") + .setDestinationProperty("name") + .addNormalizer(LowercaseNormalizer.name) .end() - const destination = await dataTransformer.transform(dataTransformerBuilder, source, Destination); + const destination = await dataMapper.map(dataMappingBuilder, source, Destination); expect(destination.name).toBe("title"); - })*/ + }) }) \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/data.mapper.ts b/packages/data-mapping/src/mappers/data.mapper.ts index ba9ee4f67..d4aa2dccb 100644 --- a/packages/data-mapping/src/mappers/data.mapper.ts +++ b/packages/data-mapping/src/mappers/data.mapper.ts @@ -34,7 +34,13 @@ export class DataMapper { * @param destinationType */ public async mapAll(builder: DataMappingBuilder, source: any[], destinationType?: ClassConstructor): Promise { - return source.map(element => this.map(builder, element, destinationType)); + const destination = []; + + for(const element of source) { + destination.push(await this.map(builder, element, destinationType)); + } + + return destination; } /** @@ -46,14 +52,8 @@ export class DataMapper { * @param destinationType */ public async map(builder: DataMappingBuilder, source: any, destinationType?: ClassConstructor): Promise { - const globalNormalizers = builder.normalizers; - let destination = {}; - if(destinationType) { - destination = plainToInstance(destinationType, {}); - } - let interceptedSource = source; // Execute the before interceptors. @@ -90,6 +90,10 @@ export class DataMapper { destination = await interceptor.afterMapping(destination); } + if(destinationType) { + destination = plainToInstance(destinationType, destination); + } + return destination; } } \ No newline at end of file From e0a7592e86f66a25b1881e87767d47051ba8b55c Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 20:15:21 -0800 Subject: [PATCH 16/20] - Renamed to mapping --- .../src/builders/data-mapping.builder.ts | 14 ++++++------- .../src/data-mapping.module.keyname.ts | 2 +- ...apping-interceptor-already-added.error.ts} | 4 ++-- ...apping-interceptor-already-added.error.ts} | 4 ++-- ...ta-mapping-interceptor-not-found.error.ts} | 4 ++-- ...apping-source-property-not-found.error.ts} | 4 ++-- packages/data-mapping/src/errors/errors.ts | 8 ++++---- .../src/mappers/data.mapper.spec.ts | 10 +++++----- .../data-mapping/src/mappers/data.mapper.ts | 6 +++--- .../src/nodes/data-mapping.leaf.ts | 4 ++-- .../src/nodes/data-mapping.node.ts | 20 +++++++++---------- 11 files changed, 40 insertions(+), 40 deletions(-) rename packages/data-mapping/src/errors/{data-after-row-transformer-interceptor-already-added.error.ts => data-after-mapping-interceptor-already-added.error.ts} (79%) rename packages/data-mapping/src/errors/{data-before-row-transformer-interceptor-already-added.error.ts => data-before-mapping-interceptor-already-added.error.ts} (79%) rename packages/data-mapping/src/errors/{data-transformer-interceptor-not-found.error.ts => data-mapping-interceptor-not-found.error.ts} (82%) rename packages/data-mapping/src/errors/{data-transformer-source-property-not-found.error.ts => data-mapping-source-property-not-found.error.ts} (77%) diff --git a/packages/data-mapping/src/builders/data-mapping.builder.ts b/packages/data-mapping/src/builders/data-mapping.builder.ts index f08deefd6..9a2d1bc1d 100644 --- a/packages/data-mapping/src/builders/data-mapping.builder.ts +++ b/packages/data-mapping/src/builders/data-mapping.builder.ts @@ -3,11 +3,11 @@ import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type" import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; import { - DataBeforeRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-before-row-transformer-interceptor-already-added.error"; + DataBeforeMappingInterceptorAlreadyAddedError +} from "../errors/data-before-mapping-interceptor-already-added.error"; import { - DataAfterRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-after-row-transformer-interceptor-already-added.error"; + DataAfterMappingInterceptorAlreadyAddedError +} from "../errors/data-after-mapping-interceptor-already-added.error"; import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; import {BaseDataMappingNode} from "../nodes/base-data-mapping.node"; import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; @@ -53,7 +53,7 @@ export class DataMappingBuilder extends BaseDataMappingNode{ */ public addBeforeMappingInterceptor(key: DataMappingInterceptorUniqueKeyType, options?: any): DataMappingBuilder { if(this.hasBeforeMappingInterceptor(key)) { - throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) + throw new DataBeforeMappingInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) } this.beforeMappingInterceptors.push({ @@ -80,7 +80,7 @@ export class DataMappingBuilder extends BaseDataMappingNode{ */ public addAfterMappingInterceptor(key: DataMappingInterceptorUniqueKeyType, options?: any): DataMappingBuilder { if(this.hasAfterMappingInterceptor(key)) { - throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) + throw new DataAfterMappingInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) } this.afterMappingInterceptors.push({ @@ -153,7 +153,7 @@ export class DataMappingBuilder extends BaseDataMappingNode{ const nodes = schema.nodes; - for(let key in nodes) { + for(const key in nodes) { if(nodes.hasOwnProperty(key) === false) { continue; } diff --git a/packages/data-mapping/src/data-mapping.module.keyname.ts b/packages/data-mapping/src/data-mapping.module.keyname.ts index 09ad80b0f..18a73cd8b 100644 --- a/packages/data-mapping/src/data-mapping.module.keyname.ts +++ b/packages/data-mapping/src/data-mapping.module.keyname.ts @@ -1 +1 @@ -export const DataMappingModuleKeyname: string = "pristine.data-transformer"; +export const DataMappingModuleKeyname: string = "pristine.data-mapping"; diff --git a/packages/data-mapping/src/errors/data-after-row-transformer-interceptor-already-added.error.ts b/packages/data-mapping/src/errors/data-after-mapping-interceptor-already-added.error.ts similarity index 79% rename from packages/data-mapping/src/errors/data-after-row-transformer-interceptor-already-added.error.ts rename to packages/data-mapping/src/errors/data-after-mapping-interceptor-already-added.error.ts index 1dab18212..b8101f77d 100644 --- a/packages/data-mapping/src/errors/data-after-row-transformer-interceptor-already-added.error.ts +++ b/packages/data-mapping/src/errors/data-after-mapping-interceptor-already-added.error.ts @@ -5,7 +5,7 @@ import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interce /** * This Error is thrown when the before row interceptor is added more than once to the builder. */ -export class DataAfterRowTransformerInterceptorAlreadyAddedError extends LoggableError { +export class DataAfterMappingInterceptorAlreadyAddedError extends LoggableError { public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { @@ -16,6 +16,6 @@ export class DataAfterRowTransformerInterceptorAlreadyAddedError extends Loggabl // Set the prototype explicitly. // As specified in the documentation in TypeScript // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, DataAfterRowTransformerInterceptorAlreadyAddedError.prototype); + Object.setPrototypeOf(this, DataAfterMappingInterceptorAlreadyAddedError.prototype); } } diff --git a/packages/data-mapping/src/errors/data-before-row-transformer-interceptor-already-added.error.ts b/packages/data-mapping/src/errors/data-before-mapping-interceptor-already-added.error.ts similarity index 79% rename from packages/data-mapping/src/errors/data-before-row-transformer-interceptor-already-added.error.ts rename to packages/data-mapping/src/errors/data-before-mapping-interceptor-already-added.error.ts index 716211e51..4b872e2cb 100644 --- a/packages/data-mapping/src/errors/data-before-row-transformer-interceptor-already-added.error.ts +++ b/packages/data-mapping/src/errors/data-before-mapping-interceptor-already-added.error.ts @@ -5,7 +5,7 @@ import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interce /** * This Error is thrown when the after row interceptor is added more than once to the builder. */ -export class DataBeforeRowTransformerInterceptorAlreadyAddedError extends LoggableError { +export class DataBeforeMappingInterceptorAlreadyAddedError extends LoggableError { public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { @@ -16,6 +16,6 @@ export class DataBeforeRowTransformerInterceptorAlreadyAddedError extends Loggab // Set the prototype explicitly. // As specified in the documentation in TypeScript // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, DataBeforeRowTransformerInterceptorAlreadyAddedError.prototype); + Object.setPrototypeOf(this, DataBeforeMappingInterceptorAlreadyAddedError.prototype); } } diff --git a/packages/data-mapping/src/errors/data-transformer-interceptor-not-found.error.ts b/packages/data-mapping/src/errors/data-mapping-interceptor-not-found.error.ts similarity index 82% rename from packages/data-mapping/src/errors/data-transformer-interceptor-not-found.error.ts rename to packages/data-mapping/src/errors/data-mapping-interceptor-not-found.error.ts index 0589fa6ce..cc36abd67 100644 --- a/packages/data-mapping/src/errors/data-transformer-interceptor-not-found.error.ts +++ b/packages/data-mapping/src/errors/data-mapping-interceptor-not-found.error.ts @@ -5,7 +5,7 @@ import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interce /** * This Error is thrown if the Data Transformer Class is not found in the list of available interceptors. It might be missing a tag. */ -export class DataTransformerInterceptorNotFoundError extends LoggableError { +export class DataMappingInterceptorNotFoundError extends LoggableError { public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { @@ -16,6 +16,6 @@ export class DataTransformerInterceptorNotFoundError extends LoggableError { // Set the prototype explicitly. // As specified in the documentation in TypeScript // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, DataTransformerInterceptorNotFoundError.prototype); + Object.setPrototypeOf(this, DataMappingInterceptorNotFoundError.prototype); } } diff --git a/packages/data-mapping/src/errors/data-transformer-source-property-not-found.error.ts b/packages/data-mapping/src/errors/data-mapping-source-property-not-found.error.ts similarity index 77% rename from packages/data-mapping/src/errors/data-transformer-source-property-not-found.error.ts rename to packages/data-mapping/src/errors/data-mapping-source-property-not-found.error.ts index 924fa6225..fab4868f7 100644 --- a/packages/data-mapping/src/errors/data-transformer-source-property-not-found.error.ts +++ b/packages/data-mapping/src/errors/data-mapping-source-property-not-found.error.ts @@ -4,7 +4,7 @@ import {Request} from "@pristine-ts/common"; /** * This Error is thrown when a property isn't optional and should be found in the source object. */ -export class DataTransformerSourcePropertyNotFoundError extends LoggableError { +export class DataMappingSourcePropertyNotFoundError extends LoggableError { public constructor(message: string, sourceProperty: string) { super(message, { @@ -15,6 +15,6 @@ export class DataTransformerSourcePropertyNotFoundError extends LoggableError { // Set the prototype explicitly. // As specified in the documentation in TypeScript // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, DataTransformerSourcePropertyNotFoundError.prototype); + Object.setPrototypeOf(this, DataMappingSourcePropertyNotFoundError.prototype); } } diff --git a/packages/data-mapping/src/errors/errors.ts b/packages/data-mapping/src/errors/errors.ts index 02c2bbc99..2f2b6d06d 100644 --- a/packages/data-mapping/src/errors/errors.ts +++ b/packages/data-mapping/src/errors/errors.ts @@ -1,8 +1,8 @@ export * from "./array-data-mapping-node-invalid-source-property-type.error"; -export * from "./data-after-row-transformer-interceptor-already-added.error"; -export * from "./data-before-row-transformer-interceptor-already-added.error"; +export * from "./data-after-mapping-interceptor-already-added.error"; +export * from "./data-before-mapping-interceptor-already-added.error"; export * from "./data-normalizer-already-added.error"; -export * from "./data-transformer-interceptor-not-found.error"; -export * from "./data-transformer-source-property-not-found.error"; +export * from "./data-mapping-interceptor-not-found.error"; +export * from "./data-mapping-source-property-not-found.error"; export * from "./normalizer-invalid-source-type.error"; export * from "./undefined-source-property.error"; \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/data.mapper.spec.ts b/packages/data-mapping/src/mappers/data.mapper.spec.ts index f34cfd406..123c13280 100644 --- a/packages/data-mapping/src/mappers/data.mapper.spec.ts +++ b/packages/data-mapping/src/mappers/data.mapper.spec.ts @@ -1,8 +1,8 @@ import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; -import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; -import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; +import {DataMappingInterceptorNotFoundError} from "../errors/data-mapping-interceptor-not-found.error"; +import {DataMappingSourcePropertyNotFoundError} from "../errors/data-mapping-source-property-not-found.error"; import {property} from "@pristine-ts/metadata"; import "jest-extended" import {DataMappingBuilder} from "../builders/data-mapping.builder"; @@ -295,7 +295,7 @@ describe("Data Mapper", () =>{ .setDestinationProperty("name") .end() - await expect(dataMapper.mapAll(dataMappingBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); + await expect(dataMapper.mapAll(dataMappingBuilder, [{"a": "a"}])).rejects.toThrowError(DataMappingInterceptorNotFoundError); }) it("should throw properly when after row transformer cannot be found", async () => { @@ -309,7 +309,7 @@ describe("Data Mapper", () =>{ .setDestinationProperty("name") .end() - await expect(dataMapper.mapAll(dataMappingBuilder, [{"0": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); + await expect(dataMapper.mapAll(dataMappingBuilder, [{"0": "a"}])).rejects.toThrowError(DataMappingInterceptorNotFoundError); }) it("should throw properly when an element is not optional and not found in the source", async () => { @@ -322,7 +322,7 @@ describe("Data Mapper", () =>{ .setDestinationProperty("name") .end() - await expect(dataMapper.mapAll(dataMappingBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); + await expect(dataMapper.mapAll(dataMappingBuilder, [{"a": "a"}])).rejects.toThrowError(DataMappingSourcePropertyNotFoundError); }) it("should properly type the return object when a destinationType is passed", async () => { diff --git a/packages/data-mapping/src/mappers/data.mapper.ts b/packages/data-mapping/src/mappers/data.mapper.ts index d4aa2dccb..e0fc2a131 100644 --- a/packages/data-mapping/src/mappers/data.mapper.ts +++ b/packages/data-mapping/src/mappers/data.mapper.ts @@ -7,7 +7,7 @@ import {DataMappingModuleKeyname} from "../data-mapping.module.keyname"; import {injectable, injectAll} from "tsyringe"; import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {ClassConstructor, plainToInstance} from "class-transformer"; -import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; +import {DataMappingInterceptorNotFoundError} from "../errors/data-mapping-interceptor-not-found.error"; @moduleScoped(DataMappingModuleKeyname) @injectable() @@ -61,7 +61,7 @@ export class DataMapper { const interceptor = this.dataTransformerInterceptorsMap[element.key]; if (interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); + throw new DataMappingInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); } // todo: Pass the options when we start using them. @@ -83,7 +83,7 @@ export class DataMapper { const interceptor: DataMappingInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; if (interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); + throw new DataMappingInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); } // todo pass the options when we start using it. diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.ts index 195ff107c..2d66e17bb 100644 --- a/packages/data-mapping/src/nodes/data-mapping.leaf.ts +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.ts @@ -3,7 +3,7 @@ import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type" import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {DataMappingNode} from "./data-mapping.node"; -import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; +import {DataMappingSourcePropertyNotFoundError} from "../errors/data-mapping-source-property-not-found.error"; import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; import { ArrayDataMappingNodeInvalidSourcePropertyTypeError @@ -140,7 +140,7 @@ export class DataMappingLeaf { return } - throw new DataTransformerSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) + throw new DataMappingSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) } const normalizers = this.root.normalizers.filter(element => this.excludedNormalizers.has(element.key) === false); diff --git a/packages/data-mapping/src/nodes/data-mapping.node.ts b/packages/data-mapping/src/nodes/data-mapping.node.ts index f97202f83..0597945ae 100644 --- a/packages/data-mapping/src/nodes/data-mapping.node.ts +++ b/packages/data-mapping/src/nodes/data-mapping.node.ts @@ -2,7 +2,7 @@ import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; import {DataMappingLeaf} from "./data-mapping.leaf"; import {DataMappingBuilder} from "../builders/data-mapping.builder"; import {BaseDataMappingNode} from "./base-data-mapping.node"; -import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; +import {DataMappingSourcePropertyNotFoundError} from "../errors/data-mapping-source-property-not-found.error"; import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; import { @@ -118,10 +118,10 @@ export class DataMappingNode extends BaseDataMappingNode { return } - throw new DataTransformerSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) + throw new DataMappingSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) } - let sourceElement = source[this.sourceProperty]; + const sourceElement = source[this.sourceProperty]; if(destination[this.destinationProperty] === undefined) { if(this.type === DataMappingNodeTypeEnum.ObjectArray) { @@ -132,7 +132,7 @@ export class DataMappingNode extends BaseDataMappingNode { } } - let destinationElement = destination[this.destinationProperty]; + const destinationElement = destination[this.destinationProperty]; if(this.type === DataMappingNodeTypeEnum.ObjectArray) { // This means that the source[propertyKey] contains an array of objects and each object should be mapped @@ -142,11 +142,11 @@ export class DataMappingNode extends BaseDataMappingNode { throw new ArrayDataMappingNodeInvalidSourcePropertyTypeError(`According to your schema, the property '${this.sourceProperty}' in the source object must contain an Array of objects. Instead, it contains: '${typeof array}'.`, this.sourceProperty); } - for (let element of array) { + for (const element of array) { // todo: we need to get the expected Type of the object in the array in the Destination object - let dest = {}; + const dest = {}; - for (let key in this.nodes) { + for (const key in this.nodes) { if(this.nodes.hasOwnProperty(key) === false) { continue; } @@ -163,7 +163,7 @@ export class DataMappingNode extends BaseDataMappingNode { } // When the current node is not an array, we simply iterate - for (let key in this.nodes) { + for (const key in this.nodes) { if(this.nodes.hasOwnProperty(key) === false) { continue; } @@ -187,7 +187,7 @@ export class DataMappingNode extends BaseDataMappingNode { const nodes = schema.nodes; - for(let key in nodes) { + for(const key in nodes) { if(nodes.hasOwnProperty(key) === false) { continue; } @@ -220,7 +220,7 @@ export class DataMappingNode extends BaseDataMappingNode { public export() { const nodes = this.nodes; - for (let key in nodes) { + for (const key in nodes) { if(nodes.hasOwnProperty(key) === false) { continue; } From 5b781965df46341653996ea7c99a80124f4f948e Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 20:17:46 -0800 Subject: [PATCH 17/20] - Update. --- packages/data-mapping/src/builders/data-mapping.builder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/data-mapping/src/builders/data-mapping.builder.ts b/packages/data-mapping/src/builders/data-mapping.builder.ts index 9a2d1bc1d..39d05215a 100644 --- a/packages/data-mapping/src/builders/data-mapping.builder.ts +++ b/packages/data-mapping/src/builders/data-mapping.builder.ts @@ -186,7 +186,7 @@ export class DataMappingBuilder extends BaseDataMappingNode{ public export() { const nodes = this.nodes; - for (let key in nodes) { + for (const key in nodes) { if(nodes.hasOwnProperty(key) === false) { continue; } From 03a8bd48e907d2ed2ed95d077d8a3ddaf922d384 Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Thu, 18 Jan 2024 20:21:27 -0800 Subject: [PATCH 18/20] - Update. --- .../data-mapping/src/builders/data-mapping.builder.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/data-mapping/src/builders/data-mapping.builder.spec.ts b/packages/data-mapping/src/builders/data-mapping.builder.spec.ts index 2588dafcd..5437b673a 100644 --- a/packages/data-mapping/src/builders/data-mapping.builder.spec.ts +++ b/packages/data-mapping/src/builders/data-mapping.builder.spec.ts @@ -144,13 +144,13 @@ describe("Data Mapping Builder", () => { expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nestedTitle.destinationProperty).toBe("nestedName") expect(dataMappingBuilder.nodes.array).toBeDefined() - expect(dataMappingBuilder.nodes.array.type).toBe(DataMappingNodeTypeEnum.Array) + expect(dataMappingBuilder.nodes.array.type).toBe(DataMappingNodeTypeEnum.ObjectArray) expect(dataMappingBuilder.nodes.array.destinationProperty).toBe("list") expect((dataMappingBuilder.nodes.array as DataMappingNode).nodes.rank.sourceProperty).toBe("rank") expect((dataMappingBuilder.nodes.array as DataMappingNode).nodes.rank.destinationProperty).toBe("position") expect(dataMappingBuilder.nodes.children).toBeDefined() - expect(dataMappingBuilder.nodes.children.type).toBe(DataMappingNodeTypeEnum.Array) + expect(dataMappingBuilder.nodes.children.type).toBe(DataMappingNodeTypeEnum.ScalarArray) expect(dataMappingBuilder.nodes.children.destinationProperty).toBe("infants") expect((dataMappingBuilder.nodes.children as DataMappingLeaf).sourceProperty).toBe("children") expect((dataMappingBuilder.nodes.children as DataMappingLeaf).destinationProperty).toBe("infants") From 0902c14f8ae7cb76ea275b20cd5072ec2d4c75ce Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Fri, 19 Jan 2024 14:33:31 -0800 Subject: [PATCH 19/20] Update. --- .github/workflows/build.yml | 12 ++++++------ package-lock.json | 32 ++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af6b8d36d..a1455fe35 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,9 +33,9 @@ jobs: - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - run: npm install - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +# - run: npm install +# env: +# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: npm run build - run: npx eslint --ext .ts packages/** - run: npm run test @@ -63,9 +63,9 @@ jobs: - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - run: npm install - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +# - run: npm install +# env: +# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: git config --global user.name 'ima-bot' - run: git config --global user.email 'ima-bot@ima-tech.ca' - run: npm run bump-patch diff --git a/package-lock.json b/package-lock.json index fdcef274d..ff9100f3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@pristine-ts/common": "file:packages/common", "@pristine-ts/configuration": "file:packages/configuration", "@pristine-ts/core": "file:packages/core", - "@pristine-ts/data-transformer": "file:packages/data-transformer", + "@pristine-ts/data-mapping": "file:packages/data-mapping", "@pristine-ts/e2e": "file:tests/e2e", "@pristine-ts/express": "file:packages/express", "@pristine-ts/file": "file:packages/file", @@ -4116,8 +4116,8 @@ "resolved": "packages/core", "link": true }, - "node_modules/@pristine-ts/data-transformer": { - "resolved": "packages/data-transformer", + "node_modules/@pristine-ts/data-mapping": { + "resolved": "packages/data-mapping", "link": true }, "node_modules/@pristine-ts/e2e": { @@ -15599,9 +15599,23 @@ "uuid": "dist/bin/uuid" } }, + "packages/data-mapping": { + "name": "@pristine-ts/data-mapper", + "version": "0.0.276", + "license": "ISC", + "dependencies": { + "@pristine-ts/class-validator": "^1.0.22", + "@pristine-ts/common": "file:../common", + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" + } + }, "packages/data-transformer": { "name": "@pristine-ts/data-transformer", "version": "0.0.276", + "extraneous": true, "license": "ISC", "dependencies": { "@pristine-ts/common": "file:../common", @@ -15804,7 +15818,6 @@ "version": "1.0.0", "dependencies": { "@pristine-ts/auth0": "file:../../packages/auth0", - "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/cli": "file:../../packages/cli", "@pristine-ts/common": "file:../../packages/common", "@pristine-ts/core": "file:../../packages/core", @@ -19233,18 +19246,21 @@ } } }, - "@pristine-ts/data-transformer": { - "version": "file:packages/data-transformer", + "@pristine-ts/data-mapping": { + "version": "file:packages/data-mapping", "requires": { + "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/common": "file:../common", - "@pristine-ts/logging": "file:../logging" + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" } }, "@pristine-ts/e2e": { "version": "file:tests/e2e", "requires": { "@pristine-ts/auth0": "file:../../packages/auth0", - "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/cli": "file:../../packages/cli", "@pristine-ts/common": "file:../../packages/common", "@pristine-ts/core": "file:../../packages/core", From e68e7f7425609e52080f9e4c885519ece07bccc3 Mon Sep 17 00:00:00 2001 From: Etienne Noel Date: Wed, 24 Jan 2024 11:59:33 -0800 Subject: [PATCH 20/20] - Update the name. --- packages/data-mapping/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/data-mapping/package.json b/packages/data-mapping/package.json index 3e94edc63..fd7be07f3 100644 --- a/packages/data-mapping/package.json +++ b/packages/data-mapping/package.json @@ -1,10 +1,10 @@ { - "name": "@pristine-ts/data-mapper", + "name": "@pristine-ts/data-mapping", "version": "0.0.276", "description": "", - "module": "dist/lib/esm/data-mapper.module.js", - "main": "dist/lib/cjs/data-mapper.module.js", - "types": "dist/types/data-mapper.module.d.ts", + "module": "dist/lib/esm/data-mapping.module.js", + "main": "dist/lib/cjs/data-mapping.module.js", + "types": "dist/types/data-mapping.module.d.ts", "scripts": { "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json", "prepublish": "npm run build",