Skip to content

Commit 7a9f0f1

Browse files
authored
Add static generation strategy (#122)
1 parent f3b4cec commit 7a9f0f1

File tree

107 files changed

+3388
-183
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+3388
-183
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ composer require --dev canvural/php-openapi-faker
2222

2323
## Usage
2424

25-
First you need to create an instance of `OpenAPIFaker` with your schema that you want to fake data from.
25+
First you need to create an instance of `OpenAPIFaker` with your schema that you want to fake data from. You can use `createFromJson`, `createFromYaml` or `createFromSchema` to create an instance of `OpenAPIFaker`.
2626
```php
2727
$faker = \Vural\OpenAPIFaker\OpenAPIFaker::createFromJson($yourSchemaAsJson);
2828
```
2929

30-
Then you can use `mockResponse`, `mockRequest` and `mockSchema` methods on it to generate fake data for your requests, responses and schemas. Like so:
30+
Then you can use `mockResponse`, `mockResponseForExample`, `mockRequest`, `mockRequestForExample` and `mockComponentSchema` methods on it to generate fake data for your requests, responses and schemas. Like so:
3131

3232
```php
3333
$fakeData = $faker->mockResponse('/todos','GET');
@@ -54,6 +54,10 @@ Override `maxItems` if it's greater than this value.
5454

5555
If enabled, every property or item will be generated regardless if they are required or not. **Default**: `false`
5656

57+
### `strategy`
58+
By default, `OpenAPIFaker` uses a dynamic generation strategy. You can enable the static examples generation by using the `static` strategy.
59+
**Default**: `dynamic`
60+
5761
## Changelog
5862

5963
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

composer.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,23 @@
5151
"sort-packages": true
5252
},
5353
"scripts": {
54+
"check": [
55+
"@codestyle",
56+
"@phpstan",
57+
"@test"
58+
],
59+
"ci:check": [
60+
"@codestyle",
61+
"@phpstan",
62+
"@test-coverage",
63+
"@test-mutation"
64+
],
5465
"codestyle": "vendor/bin/phpcs",
5566
"phpstan": "vendor/bin/phpstan analyse --ansi",
5667
"test": "vendor/bin/phpunit",
57-
"test-coverage": "vendor/bin/phpunit --coverage-clover=.build/phpunit/clover.xml",
68+
"test-coverage": "vendor/bin/phpunit --coverage-clover=.build/phpunit/clover.xml --coverage-html=.build/phpunit",
5869
"test-fast": "vendor/bin/phpunit --color=always --exclude Integration",
70+
"test-fast-coverage": "vendor/bin/phpunit --exclude Integration --coverage-clover=.build/phpunit/clover.xml --coverage-html=.build/phpunit",
5971
"test-mutation": "vendor/bin/infection --ignore-msi-with-no-mutations --min-covered-msi=89 --min-msi=89"
6072
}
6173
}

infection.json.dist

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
"mutators": {
1111
"@default": true,
1212
"global-ignore": [
13-
"Vural\\OpenAPIFaker\\SchemaFaker\\BooleanFaker"
13+
"Vural\\OpenAPIFaker\\SchemaFaker\\BooleanFaker",
14+
"Vural\\OpenAPIFaker\\Utils\\RegexUtils::generateSample",
15+
"Vural\\OpenAPIFaker\\Utils\\StringUtils::convertToBinary",
16+
"Vural\\OpenAPIFaker\\Utils\\StringUtils::ensureLength",
17+
"Vural\\OpenAPIFaker\\Utils\\NumberUtils::ensureRange"
1418
],
1519
"LessThan": {
1620
"ignore": [
@@ -24,9 +28,34 @@
2428
},
2529
"CastInt": {
2630
"ignore": [
27-
"Vural\\OpenAPIFaker\\SchemaFaker\\NumberFaker::generate"
31+
"Vural\\OpenAPIFaker\\SchemaFaker\\NumberFaker::generateDynamic",
32+
"Vural\\OpenAPIFaker\\SchemaFaker\\NumberFaker::generateStatic",
33+
"Vural\\OpenAPIFaker\\SchemaFaker\\NumberFaker::generateStaticFromFormat"
34+
]
35+
},
36+
"CastFloat": {
37+
"ignore": [
38+
"Vural\\OpenAPIFaker\\SchemaFaker\\NumberFaker::generateDynamic",
39+
"Vural\\OpenAPIFaker\\SchemaFaker\\NumberFaker::generateStatic",
40+
"Vural\\OpenAPIFaker\\SchemaFaker\\NumberFaker::generateStaticFromFormat"
41+
]
42+
},
43+
"IncrementInteger": {
44+
"ignore": [
45+
"Vural\\OpenAPIFaker\\SchemaFaker\\StringFaker::generateStatic",
46+
"Vural\\OpenAPIFaker\\SchemaFaker\\StringFaker::generateDynamic",
47+
"Vural\\OpenAPIFaker\\SchemaFaker\\StringFaker::generateStaticFromFormat",
48+
"Vural\\OpenAPIFaker\\SchemaFaker\\StringFaker::generateDynamicFromFormat"
49+
]
50+
},
51+
"DecrementInteger": {
52+
"ignore": [
53+
"Vural\\OpenAPIFaker\\SchemaFaker\\StringFaker::generateStatic",
54+
"Vural\\OpenAPIFaker\\SchemaFaker\\StringFaker::generateDynamic",
55+
"Vural\\OpenAPIFaker\\SchemaFaker\\StringFaker::generateStaticFromFormat",
56+
"Vural\\OpenAPIFaker\\SchemaFaker\\StringFaker::generateDynamicFromFormat"
2857
]
2958
}
3059
},
31-
"timeout": 20
60+
"timeout": 5
3261
}

phpstan-baseline.neon

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@ parameters:
22
ignoreErrors:
33
-
44
message: "#^Parameter \\#1 \\$schema of class Vural\\\\OpenAPIFaker\\\\SchemaFaker\\\\SchemaFaker constructor expects cebe\\\\openapi\\\\spec\\\\Schema, cebe\\\\openapi\\\\spec\\\\Reference\\|cebe\\\\openapi\\\\spec\\\\Schema\\|null given\\.$#"
5-
count: 2
6-
path: src/OpenAPIFaker.php
7-
5+
count: 1
6+
path: src/SchemaFaker/RequestFaker.php
7+
88
-
99
message: "#^Parameter \\#1 \\$schema of class Vural\\\\OpenAPIFaker\\\\SchemaFaker\\\\SchemaFaker constructor expects cebe\\\\openapi\\\\spec\\\\Schema, cebe\\\\openapi\\\\spec\\\\Reference\\|cebe\\\\openapi\\\\spec\\\\Schema\\|null given\\.$#"
1010
count: 1
11-
path: src/SchemaFaker/ArrayFaker.php
11+
path: src/SchemaFaker/ResponseFaker.php
1212

1313
-
14-
message: "#^Parameter \\#1 \\$schema of class Vural\\\\OpenAPIFaker\\\\SchemaFaker\\\\SchemaFaker constructor expects cebe\\\\openapi\\\\spec\\\\Schema, cebe\\\\openapi\\\\spec\\\\Reference\\|cebe\\\\openapi\\\\spec\\\\Schema given\\.$#"
14+
message: "#^Parameter \\#1 \\$schema of class Vural\\\\OpenAPIFaker\\\\SchemaFaker\\\\SchemaFaker constructor expects cebe\\\\openapi\\\\spec\\\\Schema, cebe\\\\openapi\\\\spec\\\\Reference\\|cebe\\\\openapi\\\\spec\\\\Schema\\|null given\\.$#"
1515
count: 1
16-
path: src/SchemaFaker/ObjectFaker.php
17-
16+
path: src/SchemaFaker/ArrayFaker.php

src/Exception/NoExample.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Vural\OpenAPIFaker\Exception;
6+
7+
use Exception;
8+
9+
use function Safe\sprintf;
10+
11+
class NoExample extends Exception
12+
{
13+
protected string $example;
14+
15+
public static function forRequest(string $example): self
16+
{
17+
$e = new self(sprintf('OpenAPI spec does not have a example "%s" request', $example));
18+
$e->example = $example;
19+
20+
return $e;
21+
}
22+
23+
public static function forResponse(string $example): self
24+
{
25+
$e = new self(sprintf('OpenAPI spec does not have a example "%s" response', $example));
26+
$e->example = $example;
27+
28+
return $e;
29+
}
30+
}

src/OpenAPIFaker.php

Lines changed: 108 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
use cebe\openapi\spec\Schema;
1515
use Exception;
1616
use League\OpenAPIValidation\PSR7 as LeagueOpenAPI;
17+
use Vural\OpenAPIFaker\Exception\NoExample;
1718
use Vural\OpenAPIFaker\Exception\NoPath;
1819
use Vural\OpenAPIFaker\Exception\NoRequest;
1920
use Vural\OpenAPIFaker\Exception\NoResponse;
2021
use Vural\OpenAPIFaker\Exception\NoSchema;
22+
use Vural\OpenAPIFaker\SchemaFaker\RequestFaker;
23+
use Vural\OpenAPIFaker\SchemaFaker\ResponseFaker;
2124
use Vural\OpenAPIFaker\SchemaFaker\SchemaFaker;
2225

2326
use function array_key_exists;
@@ -59,6 +62,14 @@ public static function createFromYaml(string $yaml): self
5962
return $instance;
6063
}
6164

65+
public static function createFromSchema(OpenApi $schema): self
66+
{
67+
$instance = new static();
68+
$instance->openAPISchema = $schema;
69+
70+
return $instance;
71+
}
72+
6273
/**
6374
* @throws NoPath
6475
* @throws NoRequest
@@ -68,21 +79,25 @@ public function mockRequest(
6879
string $method,
6980
string $contentType = 'application/json',
7081
): mixed {
71-
$operation = $this->findOperation($path, $method);
82+
$content = $this->findContentForRequest($path, $method, $contentType);
7283

73-
if ($operation->requestBody === null) {
74-
throw NoRequest::forPathAndMethod($path, $method);
75-
}
76-
77-
/** @var RequestBody $requestBody */
78-
$requestBody = $operation->requestBody;
79-
$contents = $requestBody->content;
84+
return (new RequestFaker($content, $this->options))->generate();
85+
}
8086

81-
if (! array_key_exists($contentType, $contents)) {
82-
throw NoRequest::forPathAndMethodAndContentType($path, $method, $contentType);
83-
}
87+
/**
88+
* @throws NoPath
89+
* @throws NoRequest
90+
* @throws NoExample
91+
*/
92+
public function mockRequestForExample(
93+
string $path,
94+
string $method,
95+
string $exampleName,
96+
string $contentType = 'application/json',
97+
): mixed {
98+
$content = $this->findContentForRequest($path, $method, $contentType);
8499

85-
return (new SchemaFaker($contents[$contentType]->schema, $this->options, true))->generate();
100+
return (new RequestFaker($content, $this->options))->generate($exampleName);
86101
}
87102

88103
/**
@@ -95,28 +110,26 @@ public function mockResponse(
95110
string $statusCode = '200',
96111
string $contentType = 'application/json',
97112
): mixed {
98-
$operation = $this->findOperation($path, $method);
99-
100-
if ($operation->responses === null) {
101-
throw NoResponse::forPathAndMethod($path, $method);
102-
}
103-
104-
if (! $operation->responses->hasResponse($statusCode)) {
105-
throw NoResponse::forPathAndMethodAndStatusCode($path, $method, $statusCode);
106-
}
113+
$content = $this->findContentForResponse($path, $method, $statusCode, $contentType);
107114

108-
/** @var Response $response */
109-
$response = $operation->responses->getResponse($statusCode);
110-
$contents = $response->content;
111-
112-
if (! array_key_exists($contentType, $contents)) {
113-
throw NoResponse::forPathAndMethodAndStatusCode($path, $method, $statusCode);
114-
}
115+
return (new ResponseFaker($content, $this->options))->generate();
116+
}
115117

116-
/** @var MediaType $content */
117-
$content = $contents[$contentType];
118+
/**
119+
* @throws NoPath
120+
* @throws NoResponse
121+
* @throws NoExample
122+
*/
123+
public function mockResponseForExample(
124+
string $path,
125+
string $method,
126+
string $exampleName,
127+
string $statusCode = '200',
128+
string $contentType = 'application/json',
129+
): mixed {
130+
$content = $this->findContentForResponse($path, $method, $statusCode, $contentType);
118131

119-
return (new SchemaFaker($content->schema, $this->options))->generate();
132+
return (new ResponseFaker($content, $this->options))->generate($exampleName);
120133
}
121134

122135
/** @throws Exception */
@@ -136,7 +149,7 @@ public function mockComponentSchema(string $schemaName): mixed
136149
return (new SchemaFaker($schema, $this->options))->generate();
137150
}
138151

139-
/** @param array{minItems?:?int, maxItems?:?int, alwaysFakeOptionals?:bool} $options */
152+
/** @param array{minItems?:?int, maxItems?:?int, alwaysFakeOptionals?:bool, strategy?:string} $options */
140153
public function setOptions(array $options): self
141154
{
142155
foreach ($options as $key => $value) {
@@ -162,4 +175,67 @@ private function findOperation(string $path, string $method): Operation
162175

163176
return $operation;
164177
}
178+
179+
/**
180+
* @throws NoPath
181+
* @throws NoRequest
182+
*/
183+
private function findContentForRequest(
184+
string $path,
185+
string $method,
186+
string $contentType = 'application/json',
187+
): MediaType {
188+
$operation = $this->findOperation($path, $method);
189+
190+
if ($operation->requestBody === null) {
191+
throw NoRequest::forPathAndMethod($path, $method);
192+
}
193+
194+
/** @var RequestBody $requestBody */
195+
$requestBody = $operation->requestBody;
196+
$contents = $requestBody->content;
197+
198+
if (! array_key_exists($contentType, $contents)) {
199+
throw NoRequest::forPathAndMethodAndContentType($path, $method, $contentType);
200+
}
201+
202+
/** @var MediaType $content */
203+
$content = $contents[$contentType];
204+
205+
return $content;
206+
}
207+
208+
/**
209+
* @throws NoPath
210+
* @throws NoResponse
211+
*/
212+
private function findContentForResponse(
213+
string $path,
214+
string $method,
215+
string $statusCode = '200',
216+
string $contentType = 'application/json',
217+
): MediaType {
218+
$operation = $this->findOperation($path, $method);
219+
220+
if ($operation->responses === null) {
221+
throw NoResponse::forPathAndMethod($path, $method);
222+
}
223+
224+
if (! $operation->responses->hasResponse($statusCode)) {
225+
throw NoResponse::forPathAndMethodAndStatusCode($path, $method, $statusCode);
226+
}
227+
228+
/** @var Response $response */
229+
$response = $operation->responses->getResponse($statusCode);
230+
$contents = $response->content;
231+
232+
if (! array_key_exists($contentType, $contents)) {
233+
throw NoResponse::forPathAndMethodAndStatusCode($path, $method, $statusCode);
234+
}
235+
236+
/** @var MediaType $content */
237+
$content = $contents[$contentType];
238+
239+
return $content;
240+
}
165241
}

0 commit comments

Comments
 (0)