Passed
Push — trunk ( 2d6001...da76dd )
by Christian
15:37 queued 13s
created

EntityValidationService.getRequiredErrors   B

Complexity

Conditions 6

Size

Total Lines 46
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

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