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

api-platform / core / 13175309672

06 Feb 2025 09:04AM UTC coverage: 7.663% (-0.2%) from 7.841%
13175309672

push

github

web-flow
fix: ensure template files have a tpl file extension (#6826) (#6829)

2 of 5 new or added lines in 4 files covered. (40.0%)

3676 existing lines in 122 files now uncovered.

13073 of 170593 relevant lines covered (7.66%)

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

513
    #[\PHPUnit\Framework\Attributes\DataProvider('getExtractors')]
514
    public function testValidMetadata(string $extractorClass, ResourceAdapterInterface $adapter): void
515
    {
516
        $reflClass = new \ReflectionClass(ApiResource::class);
×
UNCOV
517
        $parameters = $reflClass->getConstructor()->getParameters();
×
UNCOV
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);
×
UNCOV
524
            $collection = $factory->create(self::RESOURCE_CLASS);
×
UNCOV
525
        } catch (\Exception $exception) {
×
526
            throw new AssertionFailedError('Failed asserting that the schema is valid according to '.ApiResource::class, 0, $exception);
×
527
        }
528

UNCOV
529
        $resources = $this->buildApiResources();
×
UNCOV
530
        $this->assertEquals(new ResourceMetadataCollection(self::RESOURCE_CLASS, $resources), $collection);
×
531
    }
532

533
    public static function getExtractors(): array
534
    {
535
        return [
×
UNCOV
536
            [XmlResourceExtractor::class, new XmlResourceAdapter()],
×
UNCOV
537
            [YamlResourceExtractor::class, new YamlResourceAdapter()],
×
UNCOV
538
        ];
×
539
    }
540

541
    /**
542
     * @return ApiResource[]
543
     */
544
    private function buildApiResources(): array
545
    {
546
        $resources = [];
×
547

548
        foreach (self::FIXTURES as $fixtures) {
×
UNCOV
549
            $resource = (new ApiResource())->withClass(self::RESOURCE_CLASS)->withShortName(self::SHORT_NAME);
×
550

551
            if (null === $fixtures) {
×
552
                // Build default operations
553
                $operations = [];
×
UNCOV
554
                foreach ([new Get(), new GetCollection(), new Post(), new Patch(), new Delete()] as $operation) {
×
UNCOV
555
                    [$name, $operation] = $this->getOperationWithDefaults($resource, $operation);
×
556
                    $operations[$name] = $operation;
×
557
                }
558

559
                $resource = $resource->withOperations(new Operations($operations));
×
560

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

UNCOV
569
                $resources[] = $resource->withGraphQlOperations($graphQlOperations);
×
570

571
                continue;
×
572
            }
573

UNCOV
574
            foreach ($fixtures as $parameter => $value) {
×
UNCOV
575
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
576
                    $value = $this->{'with'.ucfirst($parameter)}($value, $fixtures);
×
577
                }
578

UNCOV
579
                if (method_exists($resource, 'with'.ucfirst($parameter))) {
×
UNCOV
580
                    $resource = $resource->{'with'.ucfirst($parameter)}($value, $fixtures);
×
581
                    continue;
×
582
                }
583

584
                throw new \RuntimeException(\sprintf('Unknown ApiResource parameter "%s".', $parameter));
×
585
            }
586

587
            $resources[] = $resource;
×
588
        }
589

UNCOV
590
        return $resources;
×
591
    }
592

593
    private function withOpenapi(array|bool $values): bool|OpenApiOperation
594
    {
UNCOV
595
        if (\is_bool($values)) {
×
596
            return $values;
×
597
        }
598

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

UNCOV
608
            if (\in_array($key, $allowedProperties, true)) {
×
609
                continue;
×
610
            }
611

UNCOV
612
            $values['extensionProperties'][$key] = $value;
×
613
            unset($values[$key]);
×
614
        }
615

UNCOV
616
        return new OpenApiOperation(...$values);
×
617
    }
618

619
    private function withUriVariables(array $values): array
620
    {
621
        $uriVariables = [];
×
622
        foreach ($values as $parameterName => $value) {
×
UNCOV
623
            if (\is_string($value)) {
×
UNCOV
624
                $uriVariables[$value] = $value;
×
625
                continue;
×
626
            }
627

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

UNCOV
648
        return $uriVariables;
×
649
    }
650

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

UNCOV
659
            foreach (array_merge(self::BASE, self::EXTENDED_BASE) as $parameter) {
×
UNCOV
660
                if ((!\array_key_exists($parameter, $value) || null === $value[$parameter]) && isset($fixtures[$parameter])) {
×
UNCOV
661
                    $value[$parameter] = $fixtures[$parameter];
×
662
                }
663
            }
664

UNCOV
665
            foreach ($value as $parameter => $parameterValue) {
×
UNCOV
666
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
667
                    $parameterValue = $this->{'with'.ucfirst($parameter)}($parameterValue);
×
668
                }
669

UNCOV
670
                if (method_exists($operation, 'with'.ucfirst($parameter))) {
×
UNCOV
671
                    $operation = $operation->{'with'.ucfirst($parameter)}($parameterValue);
×
672
                    continue;
×
673
                }
674

675
                throw new \RuntimeException(\sprintf('Unknown Operation parameter "%s".', $parameter));
×
676
            }
677

UNCOV
678
            $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, self::RESOURCE_CLASS);
×
679
            $operations[$operationName] = $operation;
×
680
        }
681

UNCOV
682
        return new Operations($operations);
×
683
    }
684

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

UNCOV
693
            foreach (self::BASE as $parameter) {
×
UNCOV
694
                if ((!\array_key_exists($parameter, $value) || null === $value[$parameter]) && isset($fixtures[$parameter])) {
×
UNCOV
695
                    $value[$parameter] = $fixtures[$parameter];
×
696
                }
697
            }
698

UNCOV
699
            foreach ($value as $parameter => $parameterValue) {
×
UNCOV
700
                if (method_exists($this, 'with'.ucfirst($parameter))) {
×
701
                    $parameterValue = $this->{'with'.ucfirst($parameter)}($parameterValue);
×
702
                }
703

UNCOV
704
                if (method_exists($operation, 'with'.ucfirst($parameter))) {
×
UNCOV
705
                    $operation = $operation->{'with'.ucfirst($parameter)}($parameterValue);
×
706
                    continue;
×
707
                }
708

709
                throw new \RuntimeException(\sprintf('Unknown GraphQlOperation parameter "%s".', $parameter));
×
710
            }
711

UNCOV
712
            $operationName = $operation->getName();
×
713
            $operations[$operationName] = $operation;
×
714
        }
715

UNCOV
716
        return $operations;
×
717
    }
718

719
    private function withStateOptions(array $values)
720
    {
UNCOV
721
        if (!$values) {
×
722
            return null;
×
723
        }
724

UNCOV
725
        if (1 !== \count($values)) {
×
726
            throw new \InvalidArgumentException('Only one options can be configured at a time.');
×
727
        }
728

729
        $configuration = reset($values);
×
UNCOV
730
        switch (key($values)) {
×
UNCOV
731
            case 'elasticsearchOptions':
×
732
                return null;
×
733
        }
734

UNCOV
735
        throw new \LogicException(\sprintf('Unsupported "%s" state options.', key($values)));
×
736
    }
737

738
    private function withLinks(array $values): ?array
739
    {
UNCOV
740
        if (!$values) {
×
741
            return null;
×
742
        }
743

UNCOV
744
        return [new Link($values[0]['rel'] ?? null, $values[0]['href'] ?? null)];
×
745
    }
746

747
    private function withParameters(array $values): ?array
748
    {
UNCOV
749
        if (!$values) {
×
750
            return null;
×
751
        }
752

UNCOV
753
        $parameters = [];
×
UNCOV
754
        foreach ($values as $k => $value) {
×
755
            $parameters[$k] = new QueryParameter(key: $value['key'] ?? $k, required: $value['required'] ?? null, schema: $value['schema'] ?? null);
×
756
        }
757

UNCOV
758
        return $parameters;
×
759
    }
760
}
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