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

api-platform / core / 6067528200

04 Sep 2023 12:12AM UTC coverage: 36.875% (-21.9%) from 58.794%
6067528200

Pull #5791

github

web-flow
Merge 64157e578 into d09cfc9d2
Pull Request #5791: fix: strip down any sql function name

3096 of 3096 new or added lines in 205 files covered. (100.0%)

9926 of 26918 relevant lines covered (36.87%)

6.5 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\Put;
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\Tests\Fixtures\StateOptions;
40
use ApiPlatform\Metadata\Util\CamelCaseToSnakeCaseNameConverter;
41
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
42
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
43
use ApiPlatform\OpenApi\Model\RequestBody;
44
use ApiPlatform\State\OptionsInterface;
45
use PHPUnit\Framework\AssertionFailedError;
46
use PHPUnit\Framework\TestCase;
47
use Symfony\Component\WebLink\Link;
48

49
/**
50
 * Ensures XML and YAML mappings are fully compatible with ApiResource.
51
 *
52
 * @author Vincent Chalamon <vincentchalamon@gmail.com>
53
 */
54
final class ResourceMetadataCompatibilityTest extends TestCase
55
{
56
    use OperationDefaultsTrait;
57
    private const RESOURCE_CLASS = Comment::class;
58
    private const SHORT_NAME = 'Comment';
59
    private const DEFAULTS = [
60
        'route_prefix' => '/v1',
61
    ];
62
    private const FIXTURES = [
63
        null,
64
        [
65
            'uriTemplate' => '/users/{userId}/comments',
66
            'shortName' => self::SHORT_NAME,
67
            'description' => 'A list of Comments from User',
68
            'routePrefix' => '/api',
69
            'stateless' => true,
70
            'sunset' => '2021-01-01',
71
            'acceptPatch' => 'application/merge-patch+json',
72
            'status' => 200,
73
            'host' => 'example.com',
74
            'condition' => 'request.headers.get(\'User-Agent\') matches \{/firefox/i\'',
75
            'controller' => 'App\Controller\CommentController',
76
            'urlGenerationStrategy' => 1,
77
            'deprecationReason' => 'This resource is deprecated',
78
            'elasticsearch' => true,
79
            'messenger' => true,
80
            'input' => 'App\Dto\CommentInput',
81
            'output' => 'App\Dto\CommentOutut',
82
            'fetchPartial' => true,
83
            'forceEager' => true,
84
            'paginationClientEnabled' => true,
85
            'paginationClientItemsPerPage' => true,
86
            'paginationClientPartial' => true,
87
            'paginationEnabled' => true,
88
            'paginationFetchJoinCollection' => true,
89
            'paginationUseOutputWalkers' => true,
90
            'paginationItemsPerPage' => 42,
91
            'paginationMaximumItemsPerPage' => 200,
92
            'paginationPartial' => true,
93
            'paginationType' => 'page',
94
            'security' => 'is_granted(\'ROLE_USER\')',
95
            'securityMessage' => 'Sorry, you can\'t access this resource.',
96
            'securityPostDenormalize' => 'is_granted(\'ROLE_ADMIN\')',
97
            'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this resource.',
98
            'securityPostValidation' => 'is_granted(\'ROLE_OWNER\')',
99
            'securityPostValidationMessage' => 'Sorry, you must the owner of this resource to access it.',
100
            'queryParameterValidationEnabled' => true,
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
            // TODO Remove in 4.0
146
            'openapiContext' => [
147
                'bar' => 'baz',
148
            ],
149
            'openapi' => [
150
                'extensionProperties' => [
151
                    'bar' => 'baz',
152
                ],
153
            ],
154
            'validationContext' => [
155
                'foo' => 'bar',
156
            ],
157
            'filters' => ['comment.custom_filter'],
158
            'order' => ['foo', 'bar'],
159
            'paginationViaCursor' => [
160
                'id' => 'DESC',
161
            ],
162
            'exceptionToStatus' => [
163
                'Symfony\Component\Serializer\Exception\ExceptionInterface' => 400,
164
            ],
165
            'extraProperties' => [
166
                'custom_property' => 'Lorem ipsum dolor sit amet',
167
                'another_custom_property' => [
168
                    'Lorem ipsum' => 'Dolor sit amet',
169
                ],
170
            ],
171
            'mercure' => true,
172
            'stateOptions' => [
173
                'elasticsearchOptions' => [
174
                    'index' => 'foo_index',
175
                    'type' => 'foo_type',
176
                ],
177
            ],
178
            'graphQlOperations' => [
179
                [
180
                    'args' => [
181
                        'foo' => [
182
                            'type' => 'custom',
183
                            'bar' => 'baz',
184
                        ],
185
                    ],
186
                    'extraArgs' => [
187
                        'bar' => [
188
                            'type' => 'custom',
189
                            'baz' => 'qux',
190
                        ],
191
                    ],
192
                    'shortName' => self::SHORT_NAME,
193
                    'description' => 'Creates a Comment.',
194
                    'class' => Mutation::class,
195
                    'name' => 'create',
196
                    'urlGenerationStrategy' => 0,
197
                    'deprecationReason' => 'I don\'t know',
198
                    'normalizationContext' => [
199
                        'groups' => 'comment:read_collection',
200
                    ],
201
                    'denormalizationContext' => [
202
                        'groups' => ['comment:write'],
203
                    ],
204
                    'validationContext' => [
205
                        'foo' => 'bar',
206
                    ],
207
                    'filters' => ['comment.another_custom_filter'],
208
                    'elasticsearch' => false,
209
                    'mercure' => [
210
                        'private' => true,
211
                    ],
212
                    'messenger' => 'input',
213
                    'input' => 'App\Dto\CreateCommentInput',
214
                    'output' => 'App\Dto\CommentCollectionOutut',
215
                    'order' => ['userId'],
216
                    'fetchPartial' => false,
217
                    'forceEager' => false,
218
                    'paginationClientEnabled' => false,
219
                    'paginationClientItemsPerPage' => false,
220
                    'paginationClientPartial' => false,
221
                    'paginationEnabled' => false,
222
                    'paginationFetchJoinCollection' => false,
223
                    'paginationUseOutputWalkers' => false,
224
                    'paginationItemsPerPage' => 54,
225
                    'paginationMaximumItemsPerPage' => 200,
226
                    'paginationPartial' => false,
227
                    'paginationType' => 'page',
228
                    'security' => 'is_granted(\'IS_AUTHENTICATED_ANONYMOUSLY\')',
229
                    'securityMessage' => 'Sorry, you can\'t access this collection.',
230
                    'securityPostDenormalize' => 'is_granted(\'ROLE_CUSTOM_ADMIN\')',
231
                    'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this collection.',
232
                    'read' => true,
233
                    'deserialize' => false,
234
                    'validate' => false,
235
                    'write' => false,
236
                    'serialize' => true,
237
                    'priority' => 200,
238
                    'extraProperties' => [
239
                        'custom_property' => 'Lorem ipsum dolor sit amet',
240
                        'another_custom_property' => [
241
                            'Lorem ipsum' => 'Dolor sit amet',
242
                        ],
243
                        'foo' => 'bar',
244
                        'route_prefix' => '/v1', // from defaults
245
                    ],
246
                    'stateOptions' => [
247
                        'elasticsearchOptions' => [
248
                            'index' => 'foo_index',
249
                            'type' => 'foo_type',
250
                        ],
251
                    ],
252
                ],
253
                [
254
                    'class' => Query::class,
255
                    'extraProperties' => [
256
                        'route_prefix' => '/v1',
257
                        'custom_property' => 'Lorem ipsum dolor sit amet',
258
                        'another_custom_property' => [
259
                            'Lorem ipsum' => 'Dolor sit amet',
260
                        ],
261
                    ],
262
                    'stateOptions' => [
263
                        'elasticsearchOptions' => [
264
                            'index' => 'foo_index',
265
                            'type' => 'foo_type',
266
                        ],
267
                    ],
268
                ],
269
                [
270
                    'class' => QueryCollection::class,
271
                    'extraProperties' => [
272
                        'route_prefix' => '/v1',
273
                        'custom_property' => 'Lorem ipsum dolor sit amet',
274
                        'another_custom_property' => [
275
                            'Lorem ipsum' => 'Dolor sit amet',
276
                        ],
277
                    ],
278
                    'stateOptions' => [
279
                        'elasticsearchOptions' => [
280
                            'index' => 'foo_index',
281
                            'type' => 'foo_type',
282
                        ],
283
                    ],
284
                ],
285
                [
286
                    'class' => Subscription::class,
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
                    'condition' => 'request.headers.has(\'Accept\')',
345
                    'controller' => 'App\Controller\CustomController',
346
                    'class' => GetCollection::class,
347
                    'urlGenerationStrategy' => 0,
348
                    'deprecationReason' => 'I don\'t know',
349
                    'cacheHeaders' => [
350
                        'max_age' => 60,
351
                        'shared_max_age' => 120,
352
                        'vary' => ['Authorization', 'Accept-Language', 'Accept'],
353
                    ],
354
                    'normalizationContext' => [
355
                        'groups' => 'comment:read_collection',
356
                    ],
357
                    'denormalizationContext' => [
358
                        'groups' => ['comment:write'],
359
                    ],
360
                    'hydraContext' => [
361
                        'foo' => ['bar' => 'baz'],
362
                    ],
363
                    // TODO Remove in 4.0
364
                    'openapiContext' => [
365
                        'bar' => 'baz',
366
                    ],
367
                    'openapi' => [
368
                        'extensionProperties' => [
369
                            'bar' => 'baz',
370
                        ],
371
                    ],
372
                    'validationContext' => [
373
                        'foo' => 'bar',
374
                    ],
375
                    'filters' => ['comment.another_custom_filter'],
376
                    'elasticsearch' => false,
377
                    'mercure' => [
378
                        'private' => true,
379
                    ],
380
                    'messenger' => 'input',
381
                    'input' => 'App\Dto\CreateCommentInput',
382
                    'output' => 'App\Dto\CommentCollectionOutut',
383
                    'order' => ['userId'],
384
                    'fetchPartial' => false,
385
                    'forceEager' => false,
386
                    'paginationClientEnabled' => false,
387
                    'paginationClientItemsPerPage' => false,
388
                    'paginationClientPartial' => false,
389
                    'paginationViaCursor' => [
390
                        'userId' => 'DESC',
391
                    ],
392
                    'paginationEnabled' => false,
393
                    'paginationFetchJoinCollection' => false,
394
                    'paginationUseOutputWalkers' => false,
395
                    'paginationItemsPerPage' => 54,
396
                    'paginationMaximumItemsPerPage' => 200,
397
                    'paginationPartial' => false,
398
                    'paginationType' => 'page',
399
                    'security' => 'is_granted(\'IS_AUTHENTICATED_ANONYMOUSLY\')',
400
                    'securityMessage' => 'Sorry, you can\'t access this collection.',
401
                    'securityPostDenormalize' => 'is_granted(\'ROLE_CUSTOM_ADMIN\')',
402
                    'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this collection.',
403
                    'exceptionToStatus' => [
404
                        'Symfony\Component\Serializer\Exception\ExceptionInterface' => 404,
405
                    ],
406
                    'queryParameterValidationEnabled' => false,
407
                    'read' => true,
408
                    'deserialize' => false,
409
                    'validate' => false,
410
                    'write' => false,
411
                    'serialize' => true,
412
                    'priority' => 200,
413
                    'extraProperties' => [
414
                        'custom_property' => 'Lorem ipsum dolor sit amet',
415
                        'another_custom_property' => [
416
                            'Lorem ipsum' => 'Dolor sit amet',
417
                        ],
418
                        'foo' => 'bar',
419
                    ],
420
                    'links' => [
421
                        ['rel' => 'http://www.w3.org/ns/json-ld#error', 'href' => 'http://www.w3.org/ns/hydra/error'],
422
                    ],
423
                ],
424
                [
425
                    'uriTemplate' => '/users/{userId}/comments/{commentId}{._format}',
426
                    'class' => Get::class,
427
                    'uriVariables' => [
428
                        'userId' => [
429
                            'fromClass' => Comment::class,
430
                            'fromProperty' => 'author',
431
                            'compositeIdentifier' => true,
432
                        ],
433
                        'commentId' => [Comment::class, 'id'],
434
                    ],
435
                    'links' => [
436
                        ['rel' => 'http://www.w3.org/ns/json-ld#error', 'href' => 'http://www.w3.org/ns/hydra/error'],
437
                    ],
438
                ],
439
            ],
440
        ],
441
    ];
442
    private const BASE = [
443
        'shortName',
444
        'description',
445
        'urlGenerationStrategy',
446
        'deprecationReason',
447
        'elasticsearch',
448
        'messenger',
449
        'mercure',
450
        'input',
451
        'output',
452
        'fetchPartial',
453
        'forceEager',
454
        'paginationClientEnabled',
455
        'paginationClientItemsPerPage',
456
        'paginationClientPartial',
457
        'paginationEnabled',
458
        'paginationFetchJoinCollection',
459
        'paginationUseOutputWalkers',
460
        'paginationItemsPerPage',
461
        'paginationMaximumItemsPerPage',
462
        'paginationPartial',
463
        'paginationType',
464
        'processor',
465
        'provider',
466
        'security',
467
        'securityMessage',
468
        'securityPostDenormalize',
469
        'securityPostDenormalizeMessage',
470
        'securityPostValidation',
471
        'securityPostValidationMessage',
472
        'normalizationContext',
473
        'denormalizationContext',
474
        'collectDenormalizationErrors',
475
        'validationContext',
476
        'filters',
477
        'order',
478
        'extraProperties',
479
    ];
480
    private const EXTENDED_BASE = [
481
        'uriTemplate',
482
        'routePrefix',
483
        'stateless',
484
        'sunset',
485
        'acceptPatch',
486
        'status',
487
        'host',
488
        'condition',
489
        'controller',
490
        'queryParameterValidationEnabled',
491
        'exceptionToStatus',
492
        'types',
493
        'formats',
494
        'inputFormats',
495
        'outputFormats',
496
        'uriVariables',
497
        'defaults',
498
        'requirements',
499
        'options',
500
        'schemes',
501
        'cacheHeaders',
502
        'hydraContext',
503
        // TODO Remove in 4.0
504
        'openapiContext',
505
        'openapi',
506
        'paginationViaCursor',
507
        'stateOptions',
508
        'links',
509
    ];
510

511
    /**
512
     * @dataProvider getExtractors
513
     */
514
    public function testValidMetadata(string $extractorClass, ResourceAdapterInterface $adapter): void
515
    {
516
        $reflClass = new \ReflectionClass(ApiResource::class);
×
517
        $parameters = $reflClass->getConstructor()->getParameters();
×
518
        $this->defaults = self::DEFAULTS;
×
519
        $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter();
×
520

521
        try {
522
            $extractor = new $extractorClass($adapter(self::RESOURCE_CLASS, $parameters, self::FIXTURES));
×
523
            $factory = new ExtractorResourceMetadataCollectionFactory($extractor, null, self::DEFAULTS, null, true);
×
524
            $collection = $factory->create(self::RESOURCE_CLASS);
×
525
        } catch (\Exception $exception) {
×
526
            throw new AssertionFailedError('Failed asserting that the schema is valid according to '.ApiResource::class, 0, $exception);
×
527
        }
528

529
        $a = new ResourceMetadataCollection(self::RESOURCE_CLASS, $this->buildApiResources());
×
530
        $b = $collection;
×
531

532
        $this->assertEquals($a[0], $b[0]);
×
533

534
        $this->assertEquals(new ResourceMetadataCollection(self::RESOURCE_CLASS, $this->buildApiResources()), $collection);
×
535
    }
536

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

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

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

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

563
                $resource = $resource->withOperations(new Operations($operations));
×
564

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

573
                $resources[] = $resource->withGraphQlOperations($graphQlOperations);
×
574

575
                continue;
×
576
            }
577

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

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

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

591
            $resources[] = $resource;
×
592
        }
593

594
        return $resources;
×
595
    }
596

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

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

612
            if (\in_array($key, $allowedProperties, true)) {
×
613
                continue;
×
614
            }
615

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

620
        return new OpenApiOperation(...$values);
×
621
    }
622

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

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

652
        return $uriVariables;
×
653
    }
654

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

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

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

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

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

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

686
        return new Operations($operations);
×
687
    }
688

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

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

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

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

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

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

720
        return $operations;
×
721
    }
722

723
    private function withStateOptions(array $values): ?OptionsInterface
724
    {
725
        if (!$values) {
×
726
            return null;
×
727
        }
728

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

733
        $configuration = reset($values);
×
734
        switch (key($values)) {
×
735
            case 'elasticsearchOptions':
×
736
                return new StateOptions($configuration['index'] ?? null, $configuration['type'] ?? null);
×
737
        }
738

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

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

748
        return [new Link($values[0]['rel'] ?? null, $values[0]['href'] ?? null)];
×
749
    }
750
}
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

© 2026 Coveralls, Inc