Passed
Push — 6.4 ( 969936...be76f2 )
by Christian
44:06 queued 29:02
created

EntityValidationService.getRequiredErrors   B

Complexity

Conditions 6

Size

Total Lines 44
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 28
dl 0
loc 44
rs 8.2746
c 0
b 0
f 0
1
import type { Entity } from '@shopware-ag/admin-extension-sdk/es/data/_internals/Entity';
2
import type ChangesetGenerator from 'src/core/data/changeset-generator.data';
3
import type EntityDefinition from 'src/core/data/entity-definition.data';
4
import type ErrorResolver from 'src/core/data/error-resolver.data';
5
import type EntityDefinitionFactory from 'src/core/factory/entity-definition.factory';
6
7
/**
8
 * @module app/entity-validation-service
9
 */
10
11
/**
12
 * @private
13
 */
14
export type ValidationError = {
15
    code: string,
16
    source: {
17
        pointer: string
18
    }
19
}
20
21
/**
22
 * @private
23
 */
24
export type CustomValidator = (errors: ValidationError[], entity: Entity, definition: EntityDefinition) => ValidationError[];
25
26
27
/**
28
 * A service for client side validation of entities
29
 * @private
30
 */
31
export default class EntityValidationService {
32
    private entityDefinitionFactory: typeof EntityDefinitionFactory;
33
34
    private changesetGenerator: ChangesetGenerator;
35
36
    private errorResolver: ErrorResolver;
37
38
    public static readonly ERROR_CODE_REQUIRED = 'c1051bb4-d103-4f74-8988-acbcafc7fdc3';
39
40
    constructor(
41
        entityDefinitionFactory: typeof EntityDefinitionFactory,
42
        changesetGenerator: ChangesetGenerator,
43
        errorResolver: ErrorResolver,
44
    ) {
45
        this.entityDefinitionFactory = entityDefinitionFactory;
46
        this.changesetGenerator = changesetGenerator;
47
        this.errorResolver = errorResolver;
48
    }
49
50
    public static createRequiredError(fieldPointer: string): ValidationError {
51
        return {
52
            code: EntityValidationService.ERROR_CODE_REQUIRED,
53
            source: {
54
                pointer: fieldPointer,
55
            },
56
        };
57
    }
58
59
    /**
60
     * Validates an entity and returns true if it is valid.
61
     * Found errors are reported to the internal error resolver and
62
     * displayed by looking into the error Store (is done automatically for most sw-fields).
63
     *
64
     * A CustomValidator callback can be provided to modify or add to the found errors.
65
     */
66
    public validate(entity: Entity, customValidator: CustomValidator | undefined): boolean {
67
        const entityName = entity.getEntityName();
68
        const definition = this.entityDefinitionFactory.get(entityName);
69
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
70
        const { changes } = this.changesetGenerator.generate(entity);
71
        let errors: ValidationError[] = [];
72
73
        // check for required fields
74
        const requiredFields = definition.getRequiredFields() as Record<string, never>;
75
        errors.push(...this.getRequiredErrors(entity, requiredFields));
76
77
        // run custom validator
78
        if (customValidator !== undefined) {
79
            errors = customValidator(errors, entity, definition);
80
        }
81
82
        // report errors
83
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
84
        this.errorResolver.handleWriteErrors({ errors }, [{ entity, changes }]);
85
        return errors.length < 1;
86
    }
87
88
    /**
89
     * Tries to find all the required fields which are not set in the given entity.
90
     * TODO: This implementation may only find required fields on the top level and may needs further improvement
91
     * for other use cases.
92
     *
93
     * @private
94
     */
95
    private getRequiredErrors(entity: Entity, requiredFields: Record<string, never>): ValidationError[] {
96
        const errors: ValidationError[] = [];
97
98
        Object.keys(requiredFields).forEach((field) => {
99
            // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment
100
            const fieldDefinition = requiredFields[field] as any;
101
            // @ts-expect-error
102
            // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment
103
            const value = entity[field] as any;
104
            const fieldFilterRegex = new RegExp('version|createdAt|translations', 'i');
105
106
            if (fieldFilterRegex.test(field)) {
107
                return;
108
            }
109
110
            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
111
            if (field.includes('price') && fieldDefinition.type === 'json_object' && Array.isArray(value)) {
112
                // detected price field -> custom handling of price fields
113
                value.forEach((price, index) => {
114
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
115
                    if (price.net === undefined || price.net === null) {
116
                        errors.push(EntityValidationService.createRequiredError(`/0/${field}/${index}/net`));
117
                    }
118
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
119
                    if (price.gross === undefined || price.gross === null) {
120
                        errors.push(EntityValidationService.createRequiredError(`/0/${field}/${index}/gross`));
121
                    }
122
                });
123
                // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
124
            } else if (value === undefined || (fieldDefinition.type === 'string' && value === '')) {
125
                // any other field
126
                errors.push(EntityValidationService.createRequiredError(`/0/${field}`));
127
            }
128
        });
129
130
        return errors;
131
    }
132
}
133