• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 13203378522

07 Feb 2025 03:56PM UTC coverage: 8.501% (+0.7%) from 7.837%
13203378522

push

github

soyuka
Merge 4.1

111 of 490 new or added lines in 51 files covered. (22.65%)

5590 existing lines in 163 files now uncovered.

13345 of 156987 relevant lines covered (8.5%)

22.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Metadata\Tests\Extractor;
15

16
use ApiPlatform\Metadata\ApiResource;
17
use ApiPlatform\Metadata\Delete;
18
use ApiPlatform\Metadata\Extractor\XmlResourceExtractor;
19
use ApiPlatform\Metadata\Extractor\YamlResourceExtractor;
20
use ApiPlatform\Metadata\Get;
21
use ApiPlatform\Metadata\GetCollection;
22
use ApiPlatform\Metadata\GraphQl\DeleteMutation;
23
use ApiPlatform\Metadata\GraphQl\Mutation;
24
use ApiPlatform\Metadata\GraphQl\Query;
25
use ApiPlatform\Metadata\GraphQl\QueryCollection;
26
use ApiPlatform\Metadata\GraphQl\Subscription;
27
use ApiPlatform\Metadata\HttpOperation;
28
use ApiPlatform\Metadata\Operations;
29
use ApiPlatform\Metadata\Patch;
30
use ApiPlatform\Metadata\Post;
31
use ApiPlatform\Metadata\QueryParameter;
32
use ApiPlatform\Metadata\Resource\Factory\ExtractorResourceMetadataCollectionFactory;
33
use ApiPlatform\Metadata\Resource\Factory\OperationDefaultsTrait;
34
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
35
use ApiPlatform\Metadata\Tests\Extractor\Adapter\ResourceAdapterInterface;
36
use ApiPlatform\Metadata\Tests\Extractor\Adapter\XmlResourceAdapter;
37
use ApiPlatform\Metadata\Tests\Extractor\Adapter\YamlResourceAdapter;
38
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\Comment;
39
use ApiPlatform\Metadata\Util\CamelCaseToSnakeCaseNameConverter;
40
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
41
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
42
use ApiPlatform\OpenApi\Model\RequestBody;
43
use PHPUnit\Framework\AssertionFailedError;
44
use PHPUnit\Framework\TestCase;
45
use Symfony\Component\WebLink\Link;
46

47
/**
48
 * Ensures XML and YAML mappings are fully compatible with ApiResource.
49
 *
50
 * @author Vincent Chalamon <vincentchalamon@gmail.com>
51
 */
52
final class ResourceMetadataCompatibilityTest extends TestCase
53
{
54
    use OperationDefaultsTrait;
55
    private const RESOURCE_CLASS = Comment::class;
56
    private const SHORT_NAME = 'Comment';
57
    private const DEFAULTS = [
58
        'route_prefix' => '/v1',
59
    ];
60
    private const FIXTURES = [
61
        null,
62
        [
63
            'uriTemplate' => '/users/{userId}/comments',
64
            'shortName' => self::SHORT_NAME,
65
            'description' => 'A list of Comments from User',
66
            'routePrefix' => '/api',
67
            'stateless' => true,
68
            'sunset' => '2021-01-01',
69
            'acceptPatch' => 'application/merge-patch+json',
70
            'status' => 200,
71
            'host' => 'example.com',
72
            'condition' => 'request.headers.get(\'User-Agent\') matches \{/firefox/i\'',
73
            'controller' => 'App\Controller\CommentController',
74
            'urlGenerationStrategy' => 1,
75
            'deprecationReason' => 'This resource is deprecated',
76
            'elasticsearch' => true,
77
            'messenger' => true,
78
            'input' => 'App\Dto\CommentInput',
79
            'output' => 'App\Dto\CommentOutut',
80
            'fetchPartial' => true,
81
            'forceEager' => true,
82
            'paginationClientEnabled' => true,
83
            'paginationClientItemsPerPage' => true,
84
            'paginationClientPartial' => true,
85
            'paginationEnabled' => true,
86
            'paginationFetchJoinCollection' => true,
87
            'paginationUseOutputWalkers' => true,
88
            'paginationItemsPerPage' => 42,
89
            'paginationMaximumItemsPerPage' => 200,
90
            'paginationPartial' => true,
91
            'paginationType' => 'page',
92
            'security' => 'is_granted(\'ROLE_USER\')',
93
            'securityMessage' => 'Sorry, you can\'t access this resource.',
94
            'securityPostDenormalize' => 'is_granted(\'ROLE_ADMIN\')',
95
            'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this resource.',
96
            'securityPostValidation' => 'is_granted(\'ROLE_OWNER\')',
97
            'securityPostValidationMessage' => 'Sorry, you must the owner of this resource to access it.',
98
            'queryParameterValidationEnabled' => true,
99
            'strictQueryParameterValidation' => false,
100
            'hideHydraOperation' => false,
101
            'types' => ['someirischema', 'anotheririschema'],
102
            'formats' => [
103
                'json' => null,
104
                'jsonld' => null,
105
                'xls' => 'application/vnd.ms-excel',
106
            ],
107
            'inputFormats' => [
108
                'json' => 'application/merge-patch+json',
109
            ],
110
            'outputFormats' => [
111
                'json' => 'application/merge-patch+json',
112
            ],
113
            'uriVariables' => [
114
                'userId' => [
115
                    'fromClass' => Comment::class,
116
                    'fromProperty' => 'author',
117
                    'compositeIdentifier' => true,
118
                ],
119
            ],
120
            'defaults' => [
121
                'prout' => 'pouet',
122
            ],
123
            'requirements' => [
124
                'id' => '\d+',
125
            ],
126
            'options' => [
127
                'foo' => 'bar',
128
            ],
129
            'schemes' => ['http', 'https'],
130
            'cacheHeaders' => [
131
                'max_age' => 60,
132
                'shared_max_age' => 120,
133
                'vary' => ['Authorization', 'Accept-Language'],
134
            ],
135
            'normalizationContext' => [
136
                'groups' => 'comment:read',
137
            ],
138
            'denormalizationContext' => [
139
                'groups' => ['comment:write', 'comment:custom'],
140
            ],
141
            'collectDenormalizationErrors' => true,
142
            'hydraContext' => [
143
                'foo' => ['bar' => 'baz'],
144
            ],
145
            'openapi' => [
146
                'extensionProperties' => [
147
                    'bar' => 'baz',
148
                ],
149
            ],
150
            'validationContext' => [
151
                'foo' => 'bar',
152
            ],
153
            'filters' => ['comment.custom_filter'],
154
            'order' => ['foo', 'bar'],
155
            'paginationViaCursor' => [
156
                'id' => 'DESC',
157
            ],
158
            'exceptionToStatus' => [
159
                'Symfony\Component\Serializer\Exception\ExceptionInterface' => 400,
160
            ],
161
            'extraProperties' => [
162
                'custom_property' => 'Lorem ipsum dolor sit amet',
163
                'another_custom_property' => [
164
                    'Lorem ipsum' => 'Dolor sit amet',
165
                ],
166
            ],
167
            'mercure' => true,
168
            'stateOptions' => [
169
                'elasticsearchOptions' => [
170
                    'index' => 'foo_index',
171
                    'type' => 'foo_type',
172
                ],
173
            ],
174
            'graphQlOperations' => [
175
                [
176
                    'args' => [
177
                        'foo' => [
178
                            'type' => 'custom',
179
                            'bar' => 'baz',
180
                        ],
181
                    ],
182
                    'extraArgs' => [
183
                        'bar' => [
184
                            'type' => 'custom',
185
                            'baz' => 'qux',
186
                        ],
187
                    ],
188
                    'queryParameterValidationEnabled' => true,
189
                    'shortName' => self::SHORT_NAME,
190
                    'description' => 'Creates a Comment.',
191
                    'class' => Mutation::class,
192
                    'name' => 'create',
193
                    'urlGenerationStrategy' => 0,
194
                    'deprecationReason' => 'I don\'t know',
195
                    'normalizationContext' => [
196
                        'groups' => 'comment:read_collection',
197
                    ],
198
                    'denormalizationContext' => [
199
                        'groups' => ['comment:write'],
200
                    ],
201
                    'validationContext' => [
202
                        'foo' => 'bar',
203
                    ],
204
                    'filters' => ['comment.another_custom_filter'],
205
                    'elasticsearch' => false,
206
                    'mercure' => [
207
                        'private' => true,
208
                    ],
209
                    'messenger' => 'input',
210
                    'input' => 'App\Dto\CreateCommentInput',
211
                    'output' => 'App\Dto\CommentCollectionOutut',
212
                    'order' => ['userId'],
213
                    'fetchPartial' => false,
214
                    'forceEager' => false,
215
                    'paginationClientEnabled' => false,
216
                    'paginationClientItemsPerPage' => false,
217
                    'paginationClientPartial' => false,
218
                    'paginationEnabled' => false,
219
                    'paginationFetchJoinCollection' => false,
220
                    'paginationUseOutputWalkers' => false,
221
                    'paginationItemsPerPage' => 54,
222
                    'paginationMaximumItemsPerPage' => 200,
223
                    'paginationPartial' => false,
224
                    'paginationType' => 'page',
225
                    'security' => 'is_granted(\'IS_AUTHENTICATED_ANONYMOUSLY\')',
226
                    'securityMessage' => 'Sorry, you can\'t access this collection.',
227
                    'securityPostDenormalize' => 'is_granted(\'ROLE_CUSTOM_ADMIN\')',
228
                    'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this collection.',
229
                    'read' => true,
230
                    'deserialize' => false,
231
                    'validate' => false,
232
                    'write' => false,
233
                    'serialize' => true,
234
                    'priority' => 200,
235
                    'extraProperties' => [
236
                        'custom_property' => 'Lorem ipsum dolor sit amet',
237
                        'another_custom_property' => [
238
                            'Lorem ipsum' => 'Dolor sit amet',
239
                        ],
240
                        'foo' => 'bar',
241
                        'route_prefix' => '/v1', // from defaults
242
                    ],
243
                    'stateOptions' => [
244
                        'elasticsearchOptions' => [
245
                            'index' => 'foo_index',
246
                            'type' => 'foo_type',
247
                        ],
248
                    ],
249
                ],
250
                [
251
                    'class' => Query::class,
252
                    'queryParameterValidationEnabled' => true,
253
                    'extraProperties' => [
254
                        'route_prefix' => '/v1',
255
                        'custom_property' => 'Lorem ipsum dolor sit amet',
256
                        'another_custom_property' => [
257
                            'Lorem ipsum' => 'Dolor sit amet',
258
                        ],
259
                    ],
260
                    'stateOptions' => [
261
                        'elasticsearchOptions' => [
262
                            'index' => 'foo_index',
263
                            'type' => 'foo_type',
264
                        ],
265
                    ],
266
                ],
267
                [
268
                    'class' => QueryCollection::class,
269
                    'queryParameterValidationEnabled' => true,
270
                    'extraProperties' => [
271
                        'route_prefix' => '/v1',
272
                        'custom_property' => 'Lorem ipsum dolor sit amet',
273
                        'another_custom_property' => [
274
                            'Lorem ipsum' => 'Dolor sit amet',
275
                        ],
276
                    ],
277
                    'stateOptions' => [
278
                        'elasticsearchOptions' => [
279
                            'index' => 'foo_index',
280
                            'type' => 'foo_type',
281
                        ],
282
                    ],
283
                ],
284
                [
285
                    'class' => Subscription::class,
286
                    'queryParameterValidationEnabled' => true,
287
                    'extraProperties' => [
288
                        'route_prefix' => '/v1',
289
                        'custom_property' => 'Lorem ipsum dolor sit amet',
290
                        'another_custom_property' => [
291
                            'Lorem ipsum' => 'Dolor sit amet',
292
                        ],
293
                    ],
294
                    'stateOptions' => [
295
                        'elasticsearchOptions' => [
296
                            'index' => 'foo_index',
297
                            'type' => 'foo_type',
298
                        ],
299
                    ],
300
                ],
301
            ],
302
            'operations' => [
303
                [
304
                    'name' => 'custom_operation_name',
305
                    'method' => 'GET',
306
                    'uriTemplate' => '/users/{userId}/comments{._format}',
307
                    'shortName' => self::SHORT_NAME,
308
                    'description' => 'A list of Comments',
309
                    'types' => ['Comment'],
310
                    'formats' => [
311
                        'json' => null,
312
                        'jsonld' => null,
313
                        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
314
                    ],
315
                    'inputFormats' => [
316
                        'jsonld' => 'application/merge-patch+json+ld',
317
                    ],
318
                    'outputFormats' => [
319
                        'jsonld' => 'application/merge-patch+json+ld',
320
                    ],
321
                    'uriVariables' => [
322
                        'userId' => [
323
                            'fromClass' => Comment::class,
324
                            'fromProperty' => 'author',
325
                            'compositeIdentifier' => true,
326
                        ],
327
                    ],
328
                    'routePrefix' => '/foo/api',
329
                    'defaults' => [
330
                        '_bar' => '_foo',
331
                    ],
332
                    'requirements' => [
333
                        'userId' => '\d+',
334
                    ],
335
                    'options' => [
336
                        'bar' => 'baz',
337
                    ],
338
                    'stateless' => false,
339
                    'sunset' => '2021-12-01',
340
                    'acceptPatch' => 'text/example;charset=utf-8',
341
                    'status' => 204,
342
                    'host' => 'api-platform.com',
343
                    'schemes' => ['https'],
344
                    'headers' => ['key' => 'value'],
345
                    'condition' => 'request.headers.has(\'Accept\')',
346
                    'controller' => 'App\Controller\CustomController',
347
                    'class' => GetCollection::class,
348
                    'urlGenerationStrategy' => 0,
349
                    'deprecationReason' => 'I don\'t know',
350
                    'cacheHeaders' => [
351
                        'max_age' => 60,
352
                        'shared_max_age' => 120,
353
                        'vary' => ['Authorization', 'Accept-Language', 'Accept'],
354
                    ],
355
                    'normalizationContext' => [
356
                        'groups' => 'comment:read_collection',
357
                    ],
358
                    'denormalizationContext' => [
359
                        'groups' => ['comment:write'],
360
                    ],
361
                    'hydraContext' => [
362
                        'foo' => ['bar' => 'baz'],
363
                    ],
364
                    'openapi' => [
365
                        'extensionProperties' => [
366
                            'bar' => 'baz',
367
                        ],
368
                    ],
369
                    'validationContext' => [
370
                        'foo' => 'bar',
371
                    ],
372
                    'filters' => ['comment.another_custom_filter'],
373
                    'elasticsearch' => false,
374
                    'mercure' => [
375
                        'private' => true,
376
                    ],
377
                    'messenger' => 'input',
378
                    'input' => 'App\Dto\CreateCommentInput',
379
                    'output' => 'App\Dto\CommentCollectionOutut',
380
                    'order' => ['userId'],
381
                    'fetchPartial' => false,
382
                    'forceEager' => false,
383
                    'paginationClientEnabled' => false,
384
                    'paginationClientItemsPerPage' => false,
385
                    'paginationClientPartial' => false,
386
                    'paginationViaCursor' => [
387
                        'userId' => 'DESC',
388
                    ],
389
                    'paginationEnabled' => false,
390
                    'paginationFetchJoinCollection' => false,
391
                    'paginationUseOutputWalkers' => false,
392
                    'paginationItemsPerPage' => 54,
393
                    'paginationMaximumItemsPerPage' => 200,
394
                    'paginationPartial' => false,
395
                    'paginationType' => 'page',
396
                    'security' => 'is_granted(\'IS_AUTHENTICATED_ANONYMOUSLY\')',
397
                    'securityMessage' => 'Sorry, you can\'t access this collection.',
398
                    'securityPostDenormalize' => 'is_granted(\'ROLE_CUSTOM_ADMIN\')',
399
                    'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this collection.',
400
                    'exceptionToStatus' => [
401
                        'Symfony\Component\Serializer\Exception\ExceptionInterface' => 404,
402
                    ],
403
                    'queryParameterValidationEnabled' => false,
404
                    'strictQueryParameterValidation' => false,
405
                    'hideHydraOperation' => false,
406
                    'read' => true,
407
                    'deserialize' => false,
408
                    'validate' => false,
409
                    'write' => false,
410
                    'serialize' => true,
411
                    'priority' => 200,
412
                    'extraProperties' => [
413
                        'custom_property' => 'Lorem ipsum dolor sit amet',
414
                        'another_custom_property' => [
415
                            'Lorem ipsum' => 'Dolor sit amet',
416
                        ],
417
                        'foo' => 'bar',
418
                    ],
419
                    'links' => [
420
                        ['rel' => 'http://www.w3.org/ns/json-ld#error', 'href' => 'http://www.w3.org/ns/hydra/error'],
421
                    ],
422
                    'parameters' => [
423
                        'author' => ['key' => 'author', 'required' => true, 'schema' => ['type' => 'string']],
424
                    ],
425
                ],
426
                [
427
                    'uriTemplate' => '/users/{userId}/comments/{commentId}{._format}',
428
                    'class' => Get::class,
429
                    'uriVariables' => [
430
                        'userId' => [
431
                            'fromClass' => Comment::class,
432
                            'fromProperty' => 'author',
433
                            'compositeIdentifier' => true,
434
                        ],
435
                        'commentId' => [Comment::class, 'id'],
436
                    ],
437
                    'links' => [
438
                        ['rel' => 'http://www.w3.org/ns/json-ld#error', 'href' => 'http://www.w3.org/ns/hydra/error'],
439
                    ],
440
                    'parameters' => [
441
                        'date' => ['key' => 'date'],
442
                    ],
443
                ],
444
            ],
445
        ],
446
    ];
447
    private const BASE = [
448
        'shortName',
449
        'description',
450
        'urlGenerationStrategy',
451
        'deprecationReason',
452
        'elasticsearch',
453
        'messenger',
454
        'mercure',
455
        'input',
456
        'output',
457
        'fetchPartial',
458
        'forceEager',
459
        'paginationClientEnabled',
460
        'paginationClientItemsPerPage',
461
        'paginationClientPartial',
462
        'paginationEnabled',
463
        'paginationFetchJoinCollection',
464
        'paginationUseOutputWalkers',
465
        'paginationItemsPerPage',
466
        'paginationMaximumItemsPerPage',
467
        'paginationPartial',
468
        'paginationType',
469
        'processor',
470
        'provider',
471
        'security',
472
        'securityMessage',
473
        'securityPostDenormalize',
474
        'securityPostDenormalizeMessage',
475
        'securityPostValidation',
476
        'securityPostValidationMessage',
477
        'normalizationContext',
478
        'denormalizationContext',
479
        'collectDenormalizationErrors',
480
        'validationContext',
481
        'filters',
482
        'order',
483
        'extraProperties',
484
    ];
485
    private const EXTENDED_BASE = [
486
        'uriTemplate',
487
        'routePrefix',
488
        'stateless',
489
        'sunset',
490
        'acceptPatch',
491
        'status',
492
        'host',
493
        'condition',
494
        'controller',
495
        'queryParameterValidationEnabled',
496
        'strictQueryParameterValidation',
497
        'hideHydraOperation',
498
        'exceptionToStatus',
499
        'types',
500
        'formats',
501
        'inputFormats',
502
        'outputFormats',
503
        'uriVariables',
504
        'defaults',
505
        'requirements',
506
        'options',
507
        'schemes',
508
        'cacheHeaders',
509
        'hydraContext',
510
        'openapi',
511
        'paginationViaCursor',
512
        'stateOptions',
513
        'links',
514
        'rules',
515
        'headers',
516
        'parameters',
517
    ];
518

519
    #[\PHPUnit\Framework\Attributes\DataProvider('getExtractors')]
520
    public function testValidMetadata(string $extractorClass, ResourceAdapterInterface $adapter): void
521
    {
522
        $reflClass = new \ReflectionClass(ApiResource::class);
×
523
        $parameters = $reflClass->getConstructor()->getParameters();
×
UNCOV
524
        $this->defaults = self::DEFAULTS;
×
UNCOV
525
        $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter();
×
526

527
        try {
UNCOV
528
            $extractor = new $extractorClass($adapter(self::RESOURCE_CLASS, $parameters, self::FIXTURES));
×
UNCOV
529
            $factory = new ExtractorResourceMetadataCollectionFactory($extractor, null, self::DEFAULTS, null, true);
×
UNCOV
530
            $collection = $factory->create(self::RESOURCE_CLASS);
×
UNCOV
531
        } catch (\Exception $exception) {
×
532
            throw new AssertionFailedError('Failed asserting that the schema is valid according to '.ApiResource::class, 0, $exception);
×
533
        }
534

535
        $resources = $this->buildApiResources();
×
UNCOV
536
        $this->assertEquals(new ResourceMetadataCollection(self::RESOURCE_CLASS, $resources), $collection);
×
537
    }
538

539
    public static function getExtractors(): array
540
    {
UNCOV
541
        return [
×
UNCOV
542
            [XmlResourceExtractor::class, new XmlResourceAdapter()],
×
543
            [YamlResourceExtractor::class, new YamlResourceAdapter()],
×
UNCOV
544
        ];
×
545
    }
546

547
    /**
548
     * @return ApiResource[]
549
     */
550
    private function buildApiResources(): array
551
    {
552
        $resources = [];
×
553

UNCOV
554
        foreach (self::FIXTURES as $fixtures) {
×
UNCOV
555
            $resource = (new ApiResource())->withClass(self::RESOURCE_CLASS)->withShortName(self::SHORT_NAME);
×
556

UNCOV
557
            if (null === $fixtures) {
×
558
                // Build default operations
559
                $operations = [];
×
560
                foreach ([new Get(), new GetCollection(), new Post(), new Patch(), new Delete()] as $operation) {
×
561
                    [$name, $operation] = $this->getOperationWithDefaults($resource, $operation);
×
562
                    $operations[$name] = $operation;
×
563
                }
564

UNCOV
565
                $resource = $resource->withOperations(new Operations($operations));
×
566

567
                // Build default GraphQL operations
568
                $graphQlOperations = [];
×
UNCOV
569
                foreach ([new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')] as $graphQlOperation) {
×
UNCOV
570
                    $description = $graphQlOperation instanceof Mutation ? ucfirst("{$graphQlOperation->getName()}s a {$resource->getShortName()}.") : null;
×
571
                    [$name, $operation] = $this->getOperationWithDefaults($resource, $graphQlOperation);
×
572
                    $graphQlOperations[$name] = $operation->withDescription($description);
×
573
                }
574

UNCOV
575
                $resources[] = $resource->withGraphQlOperations($graphQlOperations);
×
576

577
                continue;
×
578
            }
579

UNCOV
580
            foreach ($fixtures as $parameter => $value) {
×
581
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
UNCOV
582
                    $value = $this->{'with'.ucfirst($parameter)}($value, $fixtures);
×
583
                }
584

UNCOV
585
                if (method_exists($resource, 'with'.ucfirst($parameter))) {
×
UNCOV
586
                    $resource = $resource->{'with'.ucfirst($parameter)}($value, $fixtures);
×
587
                    continue;
×
588
                }
589

UNCOV
590
                throw new \RuntimeException(\sprintf('Unknown ApiResource parameter "%s".', $parameter));
×
591
            }
592

593
            $resources[] = $resource;
×
594
        }
595

596
        return $resources;
×
597
    }
598

599
    private function withOpenapi(array|bool $values): bool|OpenApiOperation
600
    {
601
        if (\is_bool($values)) {
×
602
            return $values;
×
603
        }
604

605
        $allowedProperties = array_map(fn (\ReflectionProperty $reflProperty): string => $reflProperty->getName(), (new \ReflectionClass(OpenApiOperation::class))->getProperties());
×
606
        foreach ($values as $key => $value) {
×
UNCOV
607
            $values[$key] = match ($key) {
×
UNCOV
608
                'externalDocs' => new ExternalDocumentation(description: $value['description'] ?? '', url: $value['url'] ?? ''),
×
609
                'requestBody' => new RequestBody(description: $value['description'] ?? '', content: isset($value['content']) ? new \ArrayObject($value['content']) : null, required: $value['required'] ?? false),
×
610
                'callbacks' => new \ArrayObject($value),
×
UNCOV
611
                default => $value,
×
UNCOV
612
            };
×
613

UNCOV
614
            if (\in_array($key, $allowedProperties, true)) {
×
UNCOV
615
                continue;
×
616
            }
617

618
            $values['extensionProperties'][$key] = $value;
×
619
            unset($values[$key]);
×
620
        }
621

622
        return new OpenApiOperation(...$values);
×
623
    }
624

625
    private function withUriVariables(array $values): array
626
    {
UNCOV
627
        $uriVariables = [];
×
628
        foreach ($values as $parameterName => $value) {
×
629
            if (\is_string($value)) {
×
UNCOV
630
                $uriVariables[$value] = $value;
×
631
                continue;
×
632
            }
633

634
            if (isset($value['fromClass']) || isset($value[0])) {
×
635
                $uriVariables[$parameterName]['from_class'] = $value['fromClass'] ?? $value[0];
×
636
            }
637
            if (isset($value['fromProperty']) || isset($value[1])) {
×
638
                $uriVariables[$parameterName]['from_property'] = $value['fromProperty'] ?? $value[1];
×
639
            }
640
            if (isset($value['toClass'])) {
×
641
                $uriVariables[$parameterName]['to_class'] = $value['toClass'];
×
642
            }
UNCOV
643
            if (isset($value['toProperty'])) {
×
UNCOV
644
                $uriVariables[$parameterName]['to_property'] = $value['toProperty'];
×
645
            }
UNCOV
646
            if (isset($value['identifiers'])) {
×
UNCOV
647
                $uriVariables[$parameterName]['identifiers'] = $value['identifiers'];
×
648
            }
UNCOV
649
            if (isset($value['compositeIdentifier'])) {
×
650
                $uriVariables[$parameterName]['composite_identifier'] = $value['compositeIdentifier'];
×
651
            }
652
        }
653

654
        return $uriVariables;
×
655
    }
656

657
    private function withOperations(array $values, ?array $fixtures): Operations
658
    {
UNCOV
659
        $operations = [];
×
UNCOV
660
        foreach ($values as $value) {
×
UNCOV
661
            $class = $value['class'] ?? HttpOperation::class;
×
662
            unset($value['class']);
×
663
            $operation = (new $class())->withClass(self::RESOURCE_CLASS);
×
664

UNCOV
665
            foreach (array_merge(self::BASE, self::EXTENDED_BASE) as $parameter) {
×
UNCOV
666
                if ((!\array_key_exists($parameter, $value) || null === $value[$parameter]) && isset($fixtures[$parameter])) {
×
667
                    $value[$parameter] = $fixtures[$parameter];
×
668
                }
669
            }
670

UNCOV
671
            foreach ($value as $parameter => $parameterValue) {
×
672
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
UNCOV
673
                    $parameterValue = $this->{'with'.ucfirst($parameter)}($parameterValue);
×
674
                }
675

676
                if (method_exists($operation, 'with'.ucfirst($parameter))) {
×
UNCOV
677
                    $operation = $operation->{'with'.ucfirst($parameter)}($parameterValue);
×
UNCOV
678
                    continue;
×
679
                }
680

UNCOV
681
                throw new \RuntimeException(\sprintf('Unknown Operation parameter "%s".', $parameter));
×
682
            }
683

684
            $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, self::RESOURCE_CLASS);
×
685
            $operations[$operationName] = $operation;
×
686
        }
687

688
        return new Operations($operations);
×
689
    }
690

691
    private function withGraphQlOperations(array $values, ?array $fixtures): array
692
    {
UNCOV
693
        $operations = [];
×
UNCOV
694
        foreach ($values as $value) {
×
UNCOV
695
            $class = $value['class'];
×
696
            unset($value['class']);
×
697
            $operation = (new $class())->withClass(self::RESOURCE_CLASS);
×
698

UNCOV
699
            foreach (self::BASE as $parameter) {
×
UNCOV
700
                if ((!\array_key_exists($parameter, $value) || null === $value[$parameter]) && isset($fixtures[$parameter])) {
×
701
                    $value[$parameter] = $fixtures[$parameter];
×
702
                }
703
            }
704

UNCOV
705
            foreach ($value as $parameter => $parameterValue) {
×
706
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
UNCOV
707
                    $parameterValue = $this->{'with'.ucfirst($parameter)}($parameterValue);
×
708
                }
709

710
                if (method_exists($operation, 'with'.ucfirst($parameter))) {
×
UNCOV
711
                    $operation = $operation->{'with'.ucfirst($parameter)}($parameterValue);
×
UNCOV
712
                    continue;
×
713
                }
714

UNCOV
715
                throw new \RuntimeException(\sprintf('Unknown GraphQlOperation parameter "%s".', $parameter));
×
716
            }
717

718
            $operationName = $operation->getName();
×
719
            $operations[$operationName] = $operation;
×
720
        }
721

722
        return $operations;
×
723
    }
724

725
    private function withStateOptions(array $values)
726
    {
727
        if (!$values) {
×
728
            return null;
×
729
        }
730

UNCOV
731
        if (1 !== \count($values)) {
×
732
            throw new \InvalidArgumentException('Only one options can be configured at a time.');
×
733
        }
734

UNCOV
735
        $configuration = reset($values);
×
UNCOV
736
        switch (key($values)) {
×
737
            case 'elasticsearchOptions':
×
738
                return null;
×
739
        }
740

741
        throw new \LogicException(\sprintf('Unsupported "%s" state options.', key($values)));
×
742
    }
743

744
    private function withLinks(array $values): ?array
745
    {
746
        if (!$values) {
×
747
            return null;
×
748
        }
749

750
        return [new Link($values[0]['rel'] ?? null, $values[0]['href'] ?? null)];
×
751
    }
752

753
    private function withParameters(array $values): ?array
754
    {
755
        if (!$values) {
×
UNCOV
756
            return null;
×
757
        }
758

UNCOV
759
        $parameters = [];
×
UNCOV
760
        foreach ($values as $k => $value) {
×
UNCOV
761
            $parameters[$k] = new QueryParameter(key: $value['key'] ?? $k, required: $value['required'] ?? null, schema: $value['schema'] ?? null);
×
762
        }
763

UNCOV
764
        return $parameters;
×
765
    }
766
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc