Skip to content

Commit ce2a649

Browse files
committed
Add union type support, better example data, and schema generation
1 parent edba553 commit ce2a649

29 files changed

+1203
-547
lines changed

example/openapi-client-miele.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,23 @@ state:
44
- composer.json
55
- composer.lock
66
spec: https://www.miele.com/developer/swagger-ui/m3rdapi.yaml
7+
#spec: m3rdapi.yaml
78
namespace:
89
source: ApiClients\Client\Miele
910
test: ApiClients\Tests\Client\Miele
1011
entryPoints:
1112
call: true
1213
operations: true
13-
webHooks: true
14+
webHooks: false
1415
destination:
1516
root: generated-miele
1617
source: src
1718
test: tests
1819
templates:
19-
dir: ../templates
20+
dir: templates
21+
variables:
22+
fullname: Miele
23+
packageName: miele
2024
schemas:
2125
allowDuplication: true
2226
useAliasesForDuplication: true

example/openapi-client-one.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ state:
44
- composer.json
55
- composer.lock
66
spec: https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.yaml
7-
#spec: file:///home/wyrihaximus/Projects/PHPAPIClients/openapi-client-generator/example/api.github.com.yaml
7+
#spec: api.github.com.yaml
88
entryPoints:
99
call: true
1010
operations: true

example/openapi-client-subsplit.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ state:
44
- composer.json
55
- composer.lock
66
spec: https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.yaml
7-
#spec: file:///home/wyrihaximus/Projects/PHPAPIClients/openapi-client-generator/example/api.github.com.yaml
7+
#spec: api.github.com.yaml
88
entryPoints:
99
call: true
1010
operations: true

src/ContentType/Raw.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public static function contentType(): iterable
1717
yield 'text/plain';
1818
yield 'text/x-markdown';
1919
yield 'text/html';
20+
yield 'application/pdf';
2021
}
2122

2223
public static function parse(Expr $expr): Expr

src/Gatherer/ExampleData.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,14 @@ public static function gather(mixed $exampleData, PropertyType $type, string $pr
4646
}
4747

4848
if ($exampleData === null && $type->type === 'scalar' && is_string($type->payload)) {
49-
return self::scalarData($type->payload, $type->format, $type->pattern);
49+
return self::scalarData(strlen($propertyName), $type->payload, $type->format, $type->pattern);
5050
}
5151

52+
return self::determiteType($exampleData);
53+
}
54+
55+
public static function determiteType(mixed $exampleData): Representation\ExampleData
56+
{
5257
return match (gettype($exampleData)) {
5358
'boolean' => new Representation\ExampleData(
5459
$exampleData,
@@ -83,14 +88,14 @@ public static function gather(mixed $exampleData, PropertyType $type, string $pr
8388
}
8489

8590
/** @phpstan-ignore-next-line */
86-
public static function scalarData(string $type, ?string $format, ?string $pattern = null): Representation\ExampleData
91+
public static function scalarData(int $seed, string $type, ?string $format, ?string $pattern = null): Representation\ExampleData
8792
{
8893
if ($type === 'int' || $type === '?int') {
89-
return new Representation\ExampleData(13, new Node\Scalar\LNumber(13));
94+
return new Representation\ExampleData($seed, new Node\Scalar\LNumber($seed));
9095
}
9196

9297
if ($type === 'float' || $type === '?float' || $type === 'int|float' || $type === 'null|int|float') {
93-
return new Representation\ExampleData(13.13, new Node\Scalar\DNumber(13.13));
98+
return new Representation\ExampleData($seed / 10, new Node\Scalar\DNumber($seed / 10));
9499
}
95100

96101
if ($type === 'bool' || $type === '?bool') {

src/Gatherer/HydratorUtils.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ public static function listSchemas(Schema $schema): iterable
1717
yield $schema;
1818

1919
foreach ($schema->properties as $property) {
20-
foreach ($property->type as $propertyType) {
21-
yield from self::listSchemasFromPropertyType($propertyType);
22-
}
20+
yield from self::listSchemasFromPropertyType($property->type);
2321
}
2422
}
2523

src/Gatherer/Operation.php

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use ApiClients\Tools\OpenApiClientGenerator\Registry\Schema as SchemaRegistry;
1010
use ApiClients\Tools\OpenApiClientGenerator\Registry\ThrowableSchema;
1111
use ApiClients\Tools\OpenApiClientGenerator\Representation\Header;
12-
use ApiClients\Tools\OpenApiClientGenerator\Representation\OperationRedirect;
12+
use ApiClients\Tools\OpenApiClientGenerator\Representation\OperationEmptyResponse;
1313
use ApiClients\Tools\OpenApiClientGenerator\Representation\OperationRequestBody;
1414
use ApiClients\Tools\OpenApiClientGenerator\Representation\OperationResponse;
1515
use ApiClients\Tools\OpenApiClientGenerator\Representation\Parameter;
@@ -27,6 +27,7 @@
2727
use function lcfirst;
2828
use function Safe\preg_replace;
2929
use function str_replace;
30+
use function strlen;
3031
use function strtoupper;
3132
use function trim;
3233
use function ucfirst;
@@ -49,15 +50,15 @@ public static function gather(
4950
): \ApiClients\Tools\OpenApiClientGenerator\Representation\Operation {
5051
$returnType = [];
5152
$parameters = [];
52-
$redirects = [];
53+
$empties = [];
5354
foreach ($operation->parameters as $parameter) {
5455
$parameterType = str_replace([
5556
'integer',
5657
'any',
5758
'boolean',
5859
], [
5960
'int',
60-
'mixed',
61+
'string|object',
6162
'bool',
6263
], implode('|', is_array($parameter->schema->type) ? $parameter->schema->type : [$parameter->schema->type]));
6364

@@ -69,7 +70,7 @@ public static function gather(
6970
$parameter->schema->format,
7071
$parameter->in,
7172
$parameter->schema->default,
72-
ExampleData::scalarData($parameterType, $parameter->schema->format),
73+
ExampleData::scalarData(strlen($parameter->name),$parameterType, $parameter->schema->format),
7374
);
7475
}
7576

@@ -91,7 +92,9 @@ public static function gather(
9192
$response = [];
9293
foreach ($operation->responses ?? [] as $code => $spec) {
9394
$isError = $code >= 400;
95+
$contentCount = 0;
9496
foreach ($spec->content as $contentType => $contentTypeMediaType) {
97+
$contentCount++;
9598
$responseClassname = $schemaRegistry->get(
9699
$contentTypeMediaType->schema,
97100
'Operations\\' . $classNameSanitized . '\\Response\\' . Utils::className(
@@ -106,10 +109,12 @@ public static function gather(
106109
$code,
107110
$contentType,
108111
$spec->description,
109-
Schema::gather(
112+
Type::gather(
110113
$baseNamespace,
111114
$responseClassname,
115+
$contentType,
112116
$contentTypeMediaType->schema,
117+
true,
113118
$schemaRegistry,
114119
),
115120
);
@@ -121,28 +126,22 @@ public static function gather(
121126
$returnType[] = $responseClassname;
122127
}
123128

124-
if ($code < 300 || $code >= 400) {
125-
continue;
126-
}
127-
128-
$headers = [];
129-
foreach ($spec->headers as $headerName => $headerSpec) {
130-
$headers[$headerName] = new Header($headerName, Schema::gather(
131-
$baseNamespace,
132-
$schemaRegistry->get(
129+
if ($contentCount === 0) {
130+
$headers = [];
131+
foreach ($spec->headers as $headerName => $headerSpec) {
132+
$headers[$headerName] = new Header($headerName, Schema::gather(
133+
$baseNamespace,
134+
$schemaRegistry->get(
135+
$headerSpec->schema,
136+
'WebHookHeader\\' . ucfirst(preg_replace('/\PL/u', '', $headerName)),
137+
),
133138
$headerSpec->schema,
134-
'WebHookHeader\\' . ucfirst(preg_replace('/\PL/u', '', $headerName)),
135-
),
136-
$headerSpec->schema,
137-
$schemaRegistry
138-
));
139-
}
139+
$schemaRegistry
140+
), ExampleData::determiteType($headerSpec->example));
141+
}
140142

141-
if (count($headers) <= 0) {
142-
continue;
143+
$empties[] = new OperationEmptyResponse($code, $spec->description, $headers);
143144
}
144-
145-
$redirects[] = new OperationRedirect($code, $spec->description, $headers);
146145
}
147146

148147
if (count($returnType) === 0) {
@@ -154,7 +153,7 @@ public static function gather(
154153
ClassString::factory($baseNamespace, $classNameSanitized),
155154
ClassString::factory($baseNamespace, 'Operator\\' . Utils::fixKeyword($className)),
156155
lcfirst(trim(Utils::basename($className), '\\')),
157-
trim(Utils::dirname($className), '\\'),
156+
strlen(trim(trim(Utils::dirname($className), '\\'), '.')) > 0 ? trim(Utils::dirname($className), '\\') : 'Fallback',
158157
$operation->operationId,
159158
strtoupper($matchMethod),
160159
strtoupper($method),
@@ -167,7 +166,7 @@ public static function gather(
167166
],
168167
$requestBody,
169168
$response,
170-
$redirects,
169+
$empties,
171170
);
172171
}
173172
}

src/Gatherer/OperationHydrator.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ public static function gather(
1818

1919
foreach ($operations as $operation) {
2020
foreach ($operation->response as $response) {
21-
foreach (HydratorUtils::listSchemas($response->schema) as $schema) {
22-
$schemaClasses[] = $schema;
21+
if ($response->content->payload instanceof \ApiClients\Tools\OpenApiClientGenerator\Representation\Schema) {
22+
foreach (HydratorUtils::listSchemas($response->content->payload) as $schema) {
23+
$schemaClasses[] = $schema;
24+
}
2325
}
2426
}
2527
}

src/Gatherer/Property.php

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66

77
use ApiClients\Tools\OpenApiClientGenerator\Configuration\Namespace_;
88
use ApiClients\Tools\OpenApiClientGenerator\Registry\Schema as SchemaRegistry;
9-
use ApiClients\Tools\OpenApiClientGenerator\Representation\PropertyType;
10-
use ApiClients\Tools\OpenApiClientGenerator\Representation\Schema;
119
use cebe\openapi\spec\Schema as baseSchema;
1210
use Jawira\CaseConverter\Convert;
11+
use ApiClients\Tools\OpenApiClientGenerator\Representation;
12+
use PhpParser\Node;
13+
use NumberToWords\NumberToWords;
1314

15+
use function array_filter;
16+
use function array_values;
1417
use function count;
18+
use function preg_replace_callback;
1519
use function str_replace;
1620
use function strlen;
1721

@@ -20,28 +24,32 @@ final class Property
2024
public static function gather(
2125
Namespace_ $baseNamespace,
2226
string $className,
23-
string $propertyName,
27+
string $sourcePropertyName,
2428
bool $required,
2529
baseSchema $property,
2630
SchemaRegistry $schemaRegistry,
27-
): \ApiClients\Tools\OpenApiClientGenerator\Representation\Property {
31+
): Representation\Property {
32+
$enum = [];
2833
$exampleData = null;
2934

3035
/** @phpstan-ignore-next-line */
3136
if (count($property->examples ?? []) > 0) {
37+
$examples = array_values(array_filter($property->examples, static fn (mixed $value): bool => $value !== null));
3238
// Main reason we're doing this is so we cause more variety in the example data when a list of examples is provided, but also consistently pick the same item so we do don't cause code churn
3339
/** @phpstan-ignore-next-line */
34-
$exampleData = $property->examples[strlen($propertyName) % 2 ? 0 : count($property->examples) - 1];
40+
$exampleData = $examples[strlen($sourcePropertyName) % 2 ? 0 : count($examples) - 1];
3541
}
3642

3743
if ($exampleData === null && $property->example !== null) {
3844
$exampleData = $property->example;
3945
}
4046

4147
if ($exampleData === null && count($property->enum ?? []) > 0) {
48+
$enum = $property->enum;
49+
$enums = array_values(array_filter($property->enum, static fn (mixed $value): bool => $value !== null));
4250
// Main reason we're doing this is so we cause more variety in the enum based example data, but also consistently pick the same item so we do don't cause code churn
4351
/** @phpstan-ignore-next-line */
44-
$exampleData = $property->enum[strlen($propertyName) % 2 ? 0 : count($property->enum) - 1];
52+
$exampleData = $enums[strlen($sourcePropertyName) % 2 ? 0 : count($enums) - 1];
4553
}
4654

4755
$propertyName = str_replace([
@@ -51,10 +59,17 @@ public static function gather(
5159
'$',
5260
], [
5361
'_AT_',
54-
'_PLUSES_',
55-
'_MINUS_',
56-
'',
57-
], $propertyName);
62+
'_PLUS_',
63+
'_MIN_',
64+
'_DOLLAR_',
65+
], $sourcePropertyName);
66+
$propertyName = preg_replace_callback(
67+
'/[0-9]+/',
68+
static function ($matches) {
69+
return '_' . str_replace(['-', ' '], '_', NumberToWords::transformNumber('en', (int) $matches[0])) . '_';
70+
},
71+
$propertyName,
72+
);
5873

5974
$type = Type::gather(
6075
$baseNamespace,
@@ -64,25 +79,39 @@ public static function gather(
6479
$required,
6580
$schemaRegistry,
6681
);
67-
if ($type->payload instanceof Schema) {
82+
if ($type->payload instanceof Representation\Schema) {
6883
if (count($type->payload->properties) === 0) {
69-
$type = new PropertyType('scalar', null, null, 'mixed', false);
84+
$type = new Representation\PropertyType('scalar', null, null, 'string', false);
7085
}
71-
} elseif ($type->payload instanceof PropertyType && $type->payload->payload instanceof Schema) {
86+
} elseif ($type->payload instanceof Representation\PropertyType && $type->payload->payload instanceof Representation\Schema) {
7287
if (count($type->payload->payload->properties) === 0) {
73-
$type = new PropertyType('scalar', null, null, 'mixed', false);
88+
$type = new Representation\PropertyType('scalar', null, null, 'string', false);
7489
}
7590
}
7691

77-
$exampleData = ExampleData::gather($exampleData, $type, $propertyName);
92+
if ($property->type === 'array' && is_array($type->payload)) {
93+
$arrayItemsRaw = [];
94+
$arrayItemsNode = [];
95+
96+
foreach ($type->payload as $index => $arrayItem) {
97+
$arrayItemExampleData = ExampleData::gather($exampleData, $arrayItem, $propertyName . str_pad('', $index + 1, '_'));
98+
$arrayItemsRaw[] = $arrayItemExampleData->raw;
99+
$arrayItemsNode[] = new Node\Expr\ArrayItem($arrayItemExampleData->node);
100+
}
78101

79-
return new \ApiClients\Tools\OpenApiClientGenerator\Representation\Property(
102+
$exampleData = new Representation\ExampleData($arrayItemsRaw, new Node\Expr\Array_($arrayItemsNode));
103+
} else {
104+
$exampleData = ExampleData::gather($exampleData, $type, $propertyName);
105+
}
106+
107+
return new Representation\Property(
80108
(new Convert($propertyName))->toCamel(),
81-
$propertyName,
109+
$sourcePropertyName,
82110
$property->description ?? '',
83111
$exampleData,
84-
[$type],
85-
$type->nullable
112+
$type,
113+
$type->nullable,
114+
$enum,
86115
);
87116
}
88117
}

src/Gatherer/Schema.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public static function gather(
7272
$properties,
7373
$schema,
7474
$isArray,
75+
($schema->type === null ? ['object'] : (is_array($schema->type) ? $schema->type : [$schema->type]))
7576
);
7677
}
7778
}

0 commit comments

Comments
 (0)