AbstractPathMethodBuilder::formatPath()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
declare(strict_types = 1);
9
10
namespace Spryker\Glue\DynamicEntityBackendApi\Formatter\Builder;
11
12
use ArrayObject;
13
use Generated\Shared\Transfer\DynamicEntityConfigurationTransfer;
14
use Generated\Shared\Transfer\DynamicEntityDefinitionTransfer;
15
use Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer;
16
use Generated\Shared\Transfer\DynamicEntityFieldValidationTransfer;
17
use Spryker\Glue\DynamicEntityBackendApi\DynamicEntityBackendApiConfig;
18
use Spryker\Glue\DynamicEntityBackendApi\Exception\MissingFieldDefinitionException;
19
use Spryker\Glue\DynamicEntityBackendApi\Formatter\TreeBuilder\DynamicEntityConfigurationTreeBuilderInterface;
20
use Symfony\Component\HttpFoundation\Response;
21
22
abstract class AbstractPathMethodBuilder implements PathMethodBuilderInterface
23
{
24
    /**
25
     * @var string
26
     */
27
    protected const HEADER = 'header';
28
29
    /**
30
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
31
     *
32
     * @return array<string, mixed>
33
     */
34
    abstract public function buildPathData(DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer): array;
35
36
    /**
37
     * @param \Spryker\Glue\DynamicEntityBackendApi\DynamicEntityBackendApiConfig $config
38
     * @param \Spryker\Glue\DynamicEntityBackendApi\Formatter\TreeBuilder\DynamicEntityConfigurationTreeBuilderInterface $treeBuilder
39
     * @param \Spryker\Glue\DynamicEntityBackendApi\Formatter\Builder\SchemaBuilderInterface $schemaBuilder
40
     */
41
    public function __construct(
42
        protected DynamicEntityBackendApiConfig $config,
43
        protected DynamicEntityConfigurationTreeBuilderInterface $treeBuilder,
44
        protected SchemaBuilderInterface $schemaBuilder
45
    ) {
46
    }
47
48
    /**
49
     * @param \Generated\Shared\Transfer\DynamicEntityDefinitionTransfer $dynamicEntityDefinitionTransfer
50
     * @param bool $skipIdentifier
51
     * @param bool $filterIsCreatable
52
     * @param bool $filterIsEditable
53
     *
54
     * @throws \Spryker\Glue\DynamicEntityBackendApi\Exception\MissingFieldDefinitionException
55
     *
56
     * @return array<mixed>
57
     */
58
    protected function prepareFieldsArray(
59
        DynamicEntityDefinitionTransfer $dynamicEntityDefinitionTransfer,
60
        bool $skipIdentifier = false,
61
        bool $filterIsCreatable = false,
62
        bool $filterIsEditable = false
63
    ): array {
64
        $result = [];
65
66
        $dynamicEntityFieldDefinitionTransfers = $dynamicEntityDefinitionTransfer->getFieldDefinitions();
67
68
        if ($dynamicEntityFieldDefinitionTransfers->count() === 0) {
69
            throw new MissingFieldDefinitionException('No fields defined for dynamic entity.');
70
        }
71
72
        /** @var \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer */
73
        foreach ($dynamicEntityFieldDefinitionTransfers as $dynamicEntityFieldDefinitionTransfer) {
74
            $isFieldIdentifier = $dynamicEntityDefinitionTransfer->getIdentifierOrFail() === $dynamicEntityFieldDefinitionTransfer->getFieldNameOrFail();
75
76
            if ($skipIdentifier === true && $isFieldIdentifier === true) {
77
                continue;
78
            }
79
80
            if (
81
                !$isFieldIdentifier &&
82
                ($filterIsCreatable && !$dynamicEntityFieldDefinitionTransfer->getIsCreatable() || $filterIsEditable && !$dynamicEntityFieldDefinitionTransfer->getIsEditable())
83
            ) {
84
                continue;
85
            }
86
87
            $result[$dynamicEntityFieldDefinitionTransfer->getFieldVisibleNameOrFail()] = $this->createEnhancedPropertyDefinition($dynamicEntityFieldDefinitionTransfer, $dynamicEntityDefinitionTransfer);
88
        }
89
90
        return $result;
91
    }
92
93
    /**
94
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
95
     * @param bool $skipIdentifier
96
     * @param bool $filterIsCreatable
97
     * @param bool $filterIsEditable
98
     *
99
     * @return array<mixed>
100
     */
101
    protected function buildOneOfCombinationArrayRecursively(
102
        DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer,
103
        bool $skipIdentifier = false,
104
        bool $filterIsCreatable = false,
105
        bool $filterIsEditable = false
106
    ): array {
107
        return [
108
            $this->schemaBuilder->buildRootOneOfItem(
109
                $this->prepareFieldsArrayWithChildren(
110
                    $dynamicEntityConfigurationTransfer,
111
                    $skipIdentifier,
112
                    $filterIsCreatable,
113
                    $filterIsEditable,
114
                ),
115
            ),
116
        ];
117
    }
118
119
    /**
120
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
121
     * @param bool $skipIdentifier
122
     * @param bool $filterIsCreatable
123
     * @param bool $filterIsEditable
124
     *
125
     * @return array<string, mixed>
126
     */
127
    protected function prepareFieldsArrayWithChildren(
128
        DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer,
129
        bool $skipIdentifier = false,
130
        bool $filterIsCreatable = false,
131
        bool $filterIsEditable = false
132
    ): array {
133
        $fields = $this->prepareFieldsArray(
134
            $dynamicEntityConfigurationTransfer->getDynamicEntityDefinitionOrFail(),
135
            $skipIdentifier,
136
            $filterIsCreatable,
137
            $filterIsEditable,
138
        );
139
140
        /** @var \Generated\Shared\Transfer\DynamicEntityConfigurationRelationTransfer $childRelation */
141
        foreach ($dynamicEntityConfigurationTransfer->getChildRelations() as $childRelation) {
142
            $fields[$childRelation->getNameOrFail()] = [
143
                'type' => static::SCHEMA_TYPE_ARRAY,
144
                static::KEY_SCHEMA_ITEMS => [
145
                    'type' => static::SCHEMA_TYPE_OBJECT,
146
                    static::KEY_SCHEMA_PROPERTIES => $this->prepareFieldsArray(
147
                        $childRelation->getChildDynamicEntityConfigurationOrFail()->getDynamicEntityDefinitionOrFail(),
148
                        $skipIdentifier,
149
                        $filterIsCreatable,
150
                        $filterIsEditable,
151
                    ),
152
                ],
153
            ];
154
        }
155
156
        return $fields;
157
    }
158
159
    /**
160
     * @param \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer
161
     *
162
     * @return array<string, mixed>
163
     */
164
    protected function buildEnrichedPropertyDefinition(DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer): array
165
    {
166
        $propertyDefinition = $this->buildPropertyDefinition($dynamicEntityFieldDefinitionTransfer);
167
168
        $dynamicEntityFieldValidationTransfer = $dynamicEntityFieldDefinitionTransfer->getValidation();
169
170
        if ($dynamicEntityFieldValidationTransfer === null) {
171
            return $this->enrichPropertyDefinitionWithMetadata($propertyDefinition, $dynamicEntityFieldDefinitionTransfer);
172
        }
173
174
        $propertyDefinition = $this->addValidationToPropertyDefinition($propertyDefinition, $dynamicEntityFieldValidationTransfer);
175
176
        return $this->enrichPropertyDefinitionWithMetadata($propertyDefinition, $dynamicEntityFieldDefinitionTransfer);
177
    }
178
179
    /**
180
     * @param array<string, mixed> $propertyDefinition
181
     * @param \Generated\Shared\Transfer\DynamicEntityFieldValidationTransfer $dynamicEntityFieldValidationTransfer
182
     *
183
     * @return array<string, mixed>
184
     */
185
    protected function addValidationToPropertyDefinition(
186
        array $propertyDefinition,
187
        DynamicEntityFieldValidationTransfer $dynamicEntityFieldValidationTransfer
188
    ): array {
189
        if ($dynamicEntityFieldValidationTransfer->getMin() !== null) {
190
            $propertyDefinition['minimum'] = $dynamicEntityFieldValidationTransfer->getMin();
191
        }
192
193
        if ($dynamicEntityFieldValidationTransfer->getMax() !== null) {
194
            $propertyDefinition['maximum'] = $dynamicEntityFieldValidationTransfer->getMax();
195
        }
196
197
        if ($dynamicEntityFieldValidationTransfer->getMinLength() !== null) {
198
            $propertyDefinition['minLength'] = $dynamicEntityFieldValidationTransfer->getMinLength();
199
        }
200
201
        if ($dynamicEntityFieldValidationTransfer->getMaxLength() !== null) {
202
            $propertyDefinition['maxLength'] = $dynamicEntityFieldValidationTransfer->getMaxLength();
203
        }
204
205
        if ($dynamicEntityFieldValidationTransfer->getPrecision() !== null) {
206
            $propertyDefinition['minLength'] = 1;
207
            $propertyDefinition['maxLength'] = $dynamicEntityFieldValidationTransfer->getPrecision() + 1;
208
        }
209
210
        return $this->addAdvancedValidationRules($propertyDefinition, $dynamicEntityFieldValidationTransfer);
211
    }
212
213
    /**
214
     * @param array<string, mixed> $propertyDefinition
215
     * @param \Generated\Shared\Transfer\DynamicEntityFieldValidationTransfer $dynamicEntityFieldValidationTransfer
216
     *
217
     * @return array<string, mixed>
218
     */
219
    protected function addAdvancedValidationRules(
220
        array $propertyDefinition,
221
        DynamicEntityFieldValidationTransfer $dynamicEntityFieldValidationTransfer
222
    ): array {
223
        // Add scale information for numeric fields
224
        if ($dynamicEntityFieldValidationTransfer->getScale() !== null) {
225
            $propertyDefinition['multipleOf'] = pow(10, -$dynamicEntityFieldValidationTransfer->getScale());
226
        }
227
228
        // Add constraint information if available
229
        if ($dynamicEntityFieldValidationTransfer->getConstraints() !== null && $dynamicEntityFieldValidationTransfer->getConstraints()->count() > 0) {
230
            $constraints = [];
231
232
            /** @var \Generated\Shared\Transfer\DynamicEntityFieldValidationConstraintTransfer $constraint */
233
            foreach ($dynamicEntityFieldValidationTransfer->getConstraints() as $constraint) {
234
                $constraints[] = $constraint->getNameOrFail();
235
            }
236
237
            $propertyDefinition['constraints'] = $constraints;
238
        }
239
240
        return $propertyDefinition;
241
    }
242
243
    /**
244
     * @param \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer
245
     *
246
     * @return array<string, mixed>
247
     */
248
    protected function buildPropertyDefinition(DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer): array
249
    {
250
        $type = $dynamicEntityFieldDefinitionTransfer->getTypeOrFail();
251
252
        $propertyDefinition = ['type' => $type === 'float' ? 'number' : $type];
253
254
        if ($type === 'float') {
255
            $propertyDefinition['format'] = 'float';
256
        }
257
258
        // Add format for other common types
259
        if ($type === 'datetime') {
260
            $propertyDefinition['type'] = 'string';
261
            $propertyDefinition['format'] = 'date-time';
262
        }
263
264
        if ($type === 'date') {
265
            $propertyDefinition['type'] = 'string';
266
            $propertyDefinition['format'] = 'date';
267
        }
268
269
        if ($type === 'email') {
270
            $propertyDefinition['type'] = 'string';
271
            $propertyDefinition['format'] = 'email';
272
        }
273
274
        if ($type === 'uuid' || str_contains($type, 'uuid')) {
275
            $propertyDefinition['type'] = 'string';
276
            $propertyDefinition['format'] = 'uuid';
277
        }
278
279
        return $propertyDefinition;
280
    }
281
282
    /**
283
     * @param string $placeholder
284
     * @param string $resourceName
285
     *
286
     * @return string
287
     */
288
    protected function formatPath(string $placeholder, string $resourceName): string
289
    {
290
        return str_replace('//', '/', sprintf($placeholder, $this->config->getRoutePrefix(), $resourceName));
291
    }
292
293
    /**
294
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
295
     *
296
     * @return string
297
     */
298
    protected function getTag(DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer): string
299
    {
300
        return sprintf('dynamic-entity-%s', $dynamicEntityConfigurationTransfer->getTableAliasOrFail());
301
    }
302
303
    /**
304
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
305
     *
306
     * @return array<string, mixed>
307
     */
308
    protected function buildIdParameter(DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer): array
309
    {
310
        return $this->schemaBuilder->buildParameter(
311
            'id',
312
            'path',
313
            sprintf('ID of entity %s', $dynamicEntityConfigurationTransfer->getTableAliasOrFail()),
314
            static::SCHEMA_TYPE_INTEGER,
315
        );
316
    }
317
318
    /**
319
     * @return array<string, mixed>
320
     */
321
    protected function buildHeaderContentTypeParameter(): array
322
    {
323
        return $this->schemaBuilder->buildParameter(
324
            'Content-Type',
325
            'header',
326
            'Content type of request body.',
327
            'string',
328
            'application/json',
329
        );
330
    }
331
332
    /**
333
     * @return array<string, mixed>
334
     */
335
    protected function buildHeaderAcceptParameter(): array
336
    {
337
        return $this->schemaBuilder->buildParameter(
338
            'Accept',
339
            'header',
340
            'The Accept request HTTP header indicates which content types, expressed as MIME types, the client is able to understand.',
341
            'string',
342
            'application/json',
343
        );
344
    }
345
346
    /**
347
     * @return array<string, mixed>
348
     */
349
    protected function buildResponseDefault(): array
350
    {
351
        return [
352
            static::KEY_RESPONSE_DEFAULT => $this->schemaBuilder->buildResponse('An error occurred.', ['$ref' => static::SCHEMA_REF_COMPONENT_REST_ERROR]),
353
        ];
354
    }
355
356
    /**
357
     * @return array<mixed>
358
     */
359
    protected function buildResponseNotFound(): array
360
    {
361
        return [
362
            (string)Response::HTTP_NOT_FOUND => $this->schemaBuilder->buildResponse('Not Found.', ['$ref' => static::SCHEMA_REF_COMPONENT_REST_ERROR]),
363
        ];
364
    }
365
366
    /**
367
     * @return array<mixed>
368
     */
369
    protected function buildResponseNoContent(): array
370
    {
371
        return [
372
            (string)Response::HTTP_NO_CONTENT => $this->schemaBuilder->buildResponse('No content.'),
373
        ];
374
    }
375
376
    /**
377
     * @return array<mixed>
378
     */
379
    protected function buildResponseMethodNotAllowed(): array
380
    {
381
        return [
382
            (string)Response::HTTP_METHOD_NOT_ALLOWED => $this->schemaBuilder->buildResponse('Method not allowed.', ['$ref' => static::SCHEMA_REF_COMPONENT_REST_ERROR]),
383
        ];
384
    }
385
386
    /**
387
     * @return array<mixed>
388
     */
389
    protected function buildResponseUnauthorizedRequest(): array
390
    {
391
        return [
392
            (string)Response::HTTP_FORBIDDEN => $this->schemaBuilder->buildResponse('Unauthorized request.', ['$ref' => static::SCHEMA_REF_COMPONENT_REST_ERROR]),
393
        ];
394
    }
395
396
    /**
397
     * @param string $responseDescriptionValue
398
     * @param array<string, mixed> $fieldsArray
399
     * @param \ArrayObject<int, \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer> $dynamicEntityFieldDefinitionTransfers
400
     * @param string $code
401
     * @param bool $isCollection
402
     * @param bool $isOneOf
403
     *
404
     * @return array<string, array<string, mixed>>
405
     */
406
    protected function buildSuccessResponse(
407
        string $responseDescriptionValue,
408
        array $fieldsArray,
409
        ArrayObject $dynamicEntityFieldDefinitionTransfers,
410
        string $code,
411
        bool $isCollection = false,
412
        bool $isOneOf = false
413
    ): array {
414
        $schemaStructure = $this->buildSchemaStructure($fieldsArray, $dynamicEntityFieldDefinitionTransfers, $isCollection, $isOneOf);
415
416
        return $this->schemaBuilder->buildResponseArray($responseDescriptionValue, $code, $schemaStructure);
417
    }
418
419
    /**
420
     * @param string $description
421
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
422
     * @param bool $skipIdentifier
423
     * @param bool $filterIsCreatable
424
     * @param bool $filterIsEditable
425
     *
426
     * @return array<string, mixed>
427
     */
428
    protected function buildRequest(
429
        string $description,
430
        DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer,
431
        bool $skipIdentifier = false,
432
        bool $filterIsCreatable = false,
433
        bool $filterIsEditable = false
434
    ): array {
435
        if (!$this->haveChildRelations($dynamicEntityConfigurationTransfer)) {
436
            return $this->buildRequestBody(
437
                $description,
438
                $this->prepareFieldsArrayWithChildren(
439
                    $dynamicEntityConfigurationTransfer,
440
                    $skipIdentifier,
441
                    $filterIsCreatable,
442
                    $filterIsEditable,
443
                ),
444
                $dynamicEntityConfigurationTransfer->getDynamicEntityDefinitionOrFail()->getFieldDefinitions(),
445
            );
446
        }
447
448
        return $this->buildRequestBody(
449
            $description,
450
            $this->buildOneOfRequestItems(
451
                [$dynamicEntityConfigurationTransfer],
452
            ),
453
            $dynamicEntityConfigurationTransfer->getDynamicEntityDefinitionOrFail()->getFieldDefinitions(),
454
            false,
455
            true,
456
        );
457
    }
458
459
    /**
460
     * @param string $descriptionValue
461
     * @param array<string, mixed> $fieldsArray
462
     * @param \ArrayObject<int, \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer> $dynamicEntityDefinitionFieldTransfers
463
     * @param bool $isCollection
464
     * @param bool $isOneOf
465
     *
466
     * @return array<string, mixed>
467
     */
468
    protected function buildRequestBody(
469
        string $descriptionValue,
470
        array $fieldsArray,
471
        ArrayObject $dynamicEntityDefinitionFieldTransfers,
472
        bool $isCollection = false,
473
        bool $isOneOf = false
474
    ): array {
475
        $schemaStructure = $this->buildSchemaStructure($fieldsArray, $dynamicEntityDefinitionFieldTransfers, $isCollection, $isOneOf);
476
477
        return [
478
            static::KEY_REQUEST_BODY => $this->schemaBuilder->buildResponse($descriptionValue, $schemaStructure, true),
479
        ];
480
    }
481
482
    /**
483
     * @param array<string, mixed> $fieldsArray
484
     * @param \ArrayObject<int, \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer> $dynamicEntityDefinitionFieldTransfers
485
     * @param bool $isCollection
486
     * @param bool $isOneOf
487
     *
488
     * @return array<string, mixed>
489
     */
490
    protected function buildSchemaStructure(
491
        array $fieldsArray,
492
        ArrayObject $dynamicEntityDefinitionFieldTransfers,
493
        bool $isCollection = false,
494
        bool $isOneOf = false
495
    ): array {
496
        if ($isOneOf === true) {
497
            return $this->schemaBuilder->generateSchemaStructureOneOf($fieldsArray, $dynamicEntityDefinitionFieldTransfers, $isCollection);
498
        }
499
500
        return $this->schemaBuilder->generateSchemaStructure($fieldsArray, $dynamicEntityDefinitionFieldTransfers, $isCollection);
501
    }
502
503
    /**
504
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
505
     * @param string $operationId
506
     * @param string $summary
507
     *
508
     * @return array<string, mixed>
509
     */
510
    protected function expandPathData(
511
        DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer,
512
        string $operationId,
513
        string $summary
514
    ): array {
515
        return [
516
            static::KEY_TAGS => [$this->getTag($dynamicEntityConfigurationTransfer)],
517
            static::KEY_OPERATION_ID => sprintf(
518
                $operationId,
519
                $dynamicEntityConfigurationTransfer->getTableAliasOrFail(),
520
            ), static::KEY_SUMMARY => sprintf($summary, $dynamicEntityConfigurationTransfer->getTableAliasOrFail()),
521
        ];
522
    }
523
524
    /**
525
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer|null $dynamicEntityConfigurationTransfer
526
     *
527
     * @return array<string, mixed>
528
     */
529
    protected function buildKeyParameters(?DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer = null): array
530
    {
531
        if ($dynamicEntityConfigurationTransfer !== null) {
532
            return [
533
                static::KEY_PARAMETERS => [
534
                    $this->buildIdParameter($dynamicEntityConfigurationTransfer),
535
                    $this->buildHeaderContentTypeParameter(),
536
                    $this->buildHeaderAcceptParameter(),
537
                ],
538
            ];
539
        }
540
541
        return [
542
            static::KEY_PARAMETERS => [
543
                $this->buildHeaderContentTypeParameter(),
544
                $this->buildHeaderAcceptParameter(),
545
            ],
546
        ];
547
    }
548
549
    /**
550
     * @param array<mixed> $responses
551
     *
552
     * @return array<string, mixed>
553
     */
554
    protected function buildResponses(array $responses): array
555
    {
556
        return [
557
            static::KEY_RESPONSES => $responses,
558
        ];
559
    }
560
561
    /**
562
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
563
     *
564
     * @return bool
565
     */
566
    protected function haveChildRelations(DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer): bool
567
    {
568
        return $dynamicEntityConfigurationTransfer->getChildRelations()->count() > 0;
569
    }
570
571
    /**
572
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
573
     *
574
     * @return string
575
     */
576
    protected function getRequestDescription(DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer): string
577
    {
578
        if (!$this->haveChildRelations($dynamicEntityConfigurationTransfer)) {
579
            return '';
580
        }
581
582
        return sprintf(
583
            ' Request data can contain also child relation, for example: `{ ...fields, %s: { ...childFields } }`.',
584
            $dynamicEntityConfigurationTransfer->getChildRelations()->offsetGet(0)->getNameOrFail(),
585
        );
586
    }
587
588
    /**
589
     * @param array<\Generated\Shared\Transfer\DynamicEntityConfigurationTransfer> $dynamicEntityConfigurationTransfers
590
     * @param bool $skipIdentifier
591
     * @param bool $filterIsCreatable
592
     * @param bool $filterIsEditable
593
     *
594
     * @return array<mixed>
595
     */
596
    protected function buildOneOfRequestItems(
597
        array $dynamicEntityConfigurationTransfers,
598
        bool $skipIdentifier = false,
599
        bool $filterIsCreatable = false,
600
        bool $filterIsEditable = false
601
    ): array {
602
        $items = [];
603
604
        foreach ($dynamicEntityConfigurationTransfers as $dynamicEntityConfigurationTransfer) {
605
            $items[] = $this->schemaBuilder->buildRequestRootOneOfItem(
606
                $this->prepareFieldsArrayWithChildren(
607
                    $dynamicEntityConfigurationTransfer,
608
                    $skipIdentifier,
609
                    $filterIsCreatable,
610
                    $filterIsEditable,
611
                ),
612
                $dynamicEntityConfigurationTransfer->getDynamicEntityDefinitionOrFail()->getFieldDefinitions(),
613
            );
614
        }
615
616
        return $items;
617
    }
618
619
    /**
620
     * @param \Generated\Shared\Transfer\DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer
621
     *
622
     * @return array<mixed>
623
     */
624
    protected function buildFilterParameter(DynamicEntityConfigurationTransfer $dynamicEntityConfigurationTransfer): array
625
    {
626
        $resourceName = $dynamicEntityConfigurationTransfer->getTableAliasOrFail();
627
628
        $properties = [];
629
630
        /** @var \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer $fieldDefinition */
631
        foreach ($dynamicEntityConfigurationTransfer->getDynamicEntityDefinitionOrFail()->getFieldDefinitions() as $fieldDefinition) {
632
            $propertyKey = sprintf('%s.%s', $resourceName, $fieldDefinition->getFieldVisibleNameOrFail());
633
            $properties[$propertyKey] = $this->buildPropertyDefinition($fieldDefinition);
634
        }
635
636
        return [
637
            static::KEY_NAME => 'filter',
638
            static::KEY_IN => 'query',
639
            static::KEY_DESCRIPTION => 'Parameter is used to filter items by specified values.',
640
            static::KEY_REQUIRED => false,
641
            static::KEY_STYLE => 'deepObject',
642
            static::KEY_EXPLODE => true,
643
            static::KEY_SCHEMA => [
644
                'type' => static::SCHEMA_TYPE_OBJECT,
645
                static::KEY_SCHEMA_PROPERTIES => $properties,
646
            ],
647
        ];
648
    }
649
650
    /**
651
     * @param array<string, mixed> $propertyDefinition
652
     * @param \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer
653
     *
654
     * @return array<string, mixed>
655
     */
656
    protected function enrichPropertyDefinitionWithMetadata(
657
        array $propertyDefinition,
658
        DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer
659
    ): array {
660
        // Add description if available
661
        if ($dynamicEntityFieldDefinitionTransfer->getDescription() !== null) {
662
            $propertyDefinition['description'] = $dynamicEntityFieldDefinitionTransfer->getDescription();
663
        }
664
665
        $example = $this->generatePropertyExample($dynamicEntityFieldDefinitionTransfer);
666
667
        // Add examples if available, otherwise use contextual example
668
        if ($dynamicEntityFieldDefinitionTransfer->getExamples() !== null) {
669
            $example = $dynamicEntityFieldDefinitionTransfer->getExamples();
670
        }
671
672
        if ($example) {
673
            $examples = explode(', ', $example);
674
675
            if (count($examples) === 1) {
676
                $propertyDefinition['example'] = current($examples);
677
            }
678
679
            if (count($examples) > 1) {
680
                $propertyDefinition['example'] = $examples;
681
            }
682
        }
683
684
        if ($dynamicEntityFieldDefinitionTransfer->getEnumValues()) {
685
            $propertyDefinition['enum'] = explode(', ', $dynamicEntityFieldDefinitionTransfer->getEnumValues());
686
        }
687
688
        // Add title using field visible name if available
689
        if ($dynamicEntityFieldDefinitionTransfer->getFieldVisibleName() !== null) {
690
            $propertyDefinition['title'] = $dynamicEntityFieldDefinitionTransfer->getFieldVisibleName();
691
        }
692
693
        return $propertyDefinition;
694
    }
695
696
    /**
697
     * @param \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer
698
     * @param \Generated\Shared\Transfer\DynamicEntityDefinitionTransfer $dynamicEntityDefinitionTransfer
699
     *
700
     * @return array<mixed>
701
     */
702
    protected function createEnhancedPropertyDefinition(
703
        DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer,
704
        DynamicEntityDefinitionTransfer $dynamicEntityDefinitionTransfer
705
    ): array {
706
        $fieldSchema = $this->buildEnrichedPropertyDefinition($dynamicEntityFieldDefinitionTransfer);
707
708
        // Add enhanced metadata for better OpenAPI documentation
709
        $fieldName = $dynamicEntityFieldDefinitionTransfer->getFieldNameOrFail();
710
        $isIdentifier = $dynamicEntityDefinitionTransfer->getIdentifierOrFail() === $fieldName;
711
712
        if ($isIdentifier) {
713
            $fieldSchema['description'] = $fieldSchema['description'] ?? 'Unique identifier for the entity';
714
            $fieldSchema['readOnly'] = true;
715
        }
716
717
        // Add field-specific descriptions if not already present
718
        if (!isset($fieldSchema['description'])) {
719
            $fieldSchema['description'] = $this->generatePropertyDescription($dynamicEntityFieldDefinitionTransfer);
720
        }
721
722
        return $fieldSchema;
723
    }
724
725
    /**
726
     * @param \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer
727
     *
728
     * @return string
729
     */
730
    protected function generatePropertyDescription(DynamicEntityFieldDefinitionTransfer $dynamicEntityFieldDefinitionTransfer): string
731
    {
732
        $fieldName = $dynamicEntityFieldDefinitionTransfer->getFieldVisibleNameOrFail();
733
        $type = $dynamicEntityFieldDefinitionTransfer->getTypeOrFail();
734
735
        $description = ucfirst($fieldName);
736
737
        // Add type-specific information
738
        if ($type === 'datetime') {
739
            $description .= ' (ISO 8601 format)';
740
        }
741
742
        if ($type === 'email') {
743
            $description .= ' (valid email address)';
744
        }
745
746
        if ($type === 'uuid') {
747
            $description .= ' (UUID format)';
748
        }
749
750
        // Add editability information
751
        if (!$dynamicEntityFieldDefinitionTransfer->getIsEditable()) {
752
            $description .= ' (read-only)';
753
        }
754
755
        return $description;
756
    }
757
758
    /**
759
     * @param \Generated\Shared\Transfer\DynamicEntityFieldDefinitionTransfer $fieldDefinition
760
     *
761
     * @return string|null
762
     */
763
    protected function generatePropertyExample(DynamicEntityFieldDefinitionTransfer $fieldDefinition): ?string
764
    {
765
        $type = $fieldDefinition->getTypeOrFail();
766
        $fieldName = $fieldDefinition->getFieldNameOrFail();
767
768
        switch ($type) {
769
            case 'string':
770
                return $this->generateStringExample($fieldName);
771
            case 'integer':
772
                return '123';
773
            case 'float':
774
                return '123.45';
775
            case 'boolean':
776
                return 'true';
777
            case 'datetime':
778
                return '2023-01-01T12:00:00Z';
779
            case 'date':
780
                return '2023-01-01';
781
            case 'email':
782
                return '[email protected]';
783
            case 'uuid':
784
                return '550e8400-e29b-41d4-a716-446655440000';
785
            default:
786
                return null;
787
        }
788
    }
789
790
    /**
791
     * @param string $fieldName
792
     *
793
     * @return string|null
794
     */
795
    protected function generateStringExample(string $fieldName): ?string
796
    {
797
        $lowerFieldName = strtolower($fieldName);
798
799
        if (str_contains($lowerFieldName, 'email')) {
800
            return '[email protected]';
801
        }
802
803
        if (str_contains($lowerFieldName, 'name')) {
804
            return 'John Doe';
805
        }
806
807
        if (str_contains($lowerFieldName, 'phone')) {
808
            return '+1-555-123-4567';
809
        }
810
811
        if (str_contains($lowerFieldName, 'address1')) {
812
            return '123 Main St';
813
        }
814
815
        if (str_contains($lowerFieldName, 'address2')) {
816
            return 'New York';
817
        }
818
819
        if (str_contains($lowerFieldName, 'address3')) {
820
            return 'NY 10001';
821
        }
822
823
        return null;
824
    }
825
}
826