diff --git a/generators/php/base/src/context/PhpTypeMapper.ts b/generators/php/base/src/context/PhpTypeMapper.ts index 44581b2a8f1..2716ac6f49a 100644 --- a/generators/php/base/src/context/PhpTypeMapper.ts +++ b/generators/php/base/src/context/PhpTypeMapper.ts @@ -132,9 +132,8 @@ export class PhpTypeMapper { case "enum": return preserveEnums ? php.Type.reference(classReference) : php.Type.enumString(classReference); case "object": - return php.Type.reference(classReference); case "union": - return php.Type.mixed(); + return php.Type.reference(classReference); case "undiscriminatedUnion": { return php.Type.union( // need to dedupe because lists and sets are both represented as array diff --git a/generators/php/sdk/versions.yml b/generators/php/sdk/versions.yml index fb815965b3c..1000409aed0 100644 --- a/generators/php/sdk/versions.yml +++ b/generators/php/sdk/versions.yml @@ -1,3 +1,10 @@ +- version: 0.13.1 + changelogEntry: + - type: fix + summary: >- + Render union references as their type references rather than mixed. + irVersion: 55 + - version: 0.13.0 changelogEntry: - type: feat diff --git a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/unions.json b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/unions.json index 2dfbc56afbd..020a3b47b30 100644 --- a/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/unions.json +++ b/packages/cli/generation/ir-generator-tests/src/dynamic-snippets/__test__/test-definitions/unions.json @@ -8743,6 +8743,94 @@ "type": "json" } }, + "endpoint_bigunion.update-many": { + "auth": null, + "declaration": { + "name": { + "originalName": "update-many", + "camelCase": { + "unsafeName": "updateMany", + "safeName": "updateMany" + }, + "snakeCase": { + "unsafeName": "update_many", + "safeName": "update_many" + }, + "screamingSnakeCase": { + "unsafeName": "UPDATE_MANY", + "safeName": "UPDATE_MANY" + }, + "pascalCase": { + "unsafeName": "UpdateMany", + "safeName": "UpdateMany" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "bigunion", + "camelCase": { + "unsafeName": "bigunion", + "safeName": "bigunion" + }, + "snakeCase": { + "unsafeName": "bigunion", + "safeName": "bigunion" + }, + "screamingSnakeCase": { + "unsafeName": "BIGUNION", + "safeName": "BIGUNION" + }, + "pascalCase": { + "unsafeName": "Bigunion", + "safeName": "Bigunion" + } + } + ], + "packagePath": [], + "file": { + "originalName": "bigunion", + "camelCase": { + "unsafeName": "bigunion", + "safeName": "bigunion" + }, + "snakeCase": { + "unsafeName": "bigunion", + "safeName": "bigunion" + }, + "screamingSnakeCase": { + "unsafeName": "BIGUNION", + "safeName": "BIGUNION" + }, + "pascalCase": { + "unsafeName": "Bigunion", + "safeName": "Bigunion" + } + } + } + }, + "location": { + "method": "PATCH", + "path": "/many" + }, + "request": { + "type": "body", + "pathParameters": [], + "body": { + "type": "typeReference", + "value": { + "type": "list", + "value": { + "type": "named", + "value": "type_bigunion:BigUnion" + } + } + } + }, + "response": { + "type": "json" + } + }, "endpoint_union.get": { "auth": null, "declaration": { diff --git a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/unions.json b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/unions.json index e3b2e948a27..01a67fe2fb2 100644 --- a/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/unions.json +++ b/packages/cli/register/src/ir-to-fdr-converter/__test__/__snapshots__/unions.json @@ -2409,6 +2409,115 @@ "codeSamples": [] } ] + }, + { + "auth": false, + "method": "PATCH", + "id": "update-many", + "originalEndpointId": "endpoint_bigunion.update-many", + "name": "Update Many", + "path": { + "pathParameters": [], + "parts": [ + { + "type": "literal", + "value": "/" + }, + { + "type": "literal", + "value": "/many" + } + ] + }, + "queryParameters": [], + "headers": [], + "request": { + "type": { + "type": "json", + "contentType": "application/json", + "shape": { + "type": "reference", + "value": { + "type": "list", + "itemType": { + "type": "id", + "value": "type_bigunion:BigUnion" + } + } + } + } + }, + "response": { + "type": { + "type": "reference", + "value": { + "type": "map", + "keyType": { + "type": "primitive", + "value": { + "type": "string" + } + }, + "valueType": { + "type": "primitive", + "value": { + "type": "boolean" + } + } + } + } + }, + "errorsV2": [], + "examples": [ + { + "path": "/many", + "pathParameters": {}, + "queryParameters": {}, + "headers": {}, + "requestBody": [ + { + "type": "normalSweet", + "value": "value", + "id": "id", + "created-at": "2024-01-15T09:30:00Z" + }, + { + "type": "normalSweet", + "value": "value", + "id": "id", + "created-at": "2024-01-15T09:30:00Z" + } + ], + "requestBodyV3": { + "type": "json", + "value": [ + { + "type": "normalSweet", + "value": "value", + "id": "id", + "created-at": "2024-01-15T09:30:00Z" + }, + { + "type": "normalSweet", + "value": "value", + "id": "id", + "created-at": "2024-01-15T09:30:00Z" + } + ] + }, + "responseStatusCode": 200, + "responseBody": { + "string": true + }, + "responseBodyV3": { + "type": "json", + "value": { + "string": true + } + }, + "codeSamples": [] + } + ] } ], "webhooks": [], diff --git a/seed/php-sdk/unions/.mock/definition/bigunion.yml b/seed/php-sdk/unions/.mock/definition/bigunion.yml index fdc5bde1dde..6302d0fc309 100644 --- a/seed/php-sdk/unions/.mock/definition/bigunion.yml +++ b/seed/php-sdk/unions/.mock/definition/bigunion.yml @@ -168,3 +168,9 @@ service: request: BigUnion response: boolean + update-many: + path: /many + method: PATCH + request: list + response: map + diff --git a/seed/php-sdk/unions/src/Bigunion/BigunionClient.php b/seed/php-sdk/unions/src/Bigunion/BigunionClient.php index 8661e59e222..39e28d6d47d 100644 --- a/seed/php-sdk/unions/src/Bigunion/BigunionClient.php +++ b/seed/php-sdk/unions/src/Bigunion/BigunionClient.php @@ -4,14 +4,16 @@ use GuzzleHttp\ClientInterface; use Seed\Core\Client\RawClient; +use Seed\Bigunion\Types\BigUnion; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; use Seed\Core\Json\JsonApiRequest; use Seed\Core\Client\HttpMethod; -use Seed\Core\Json\JsonDecoder; use JsonException; use GuzzleHttp\Exception\RequestException; use Psr\Http\Client\ClientExceptionInterface; +use Seed\Core\Json\JsonDecoder; +use Seed\Core\Json\JsonSerializer; class BigunionClient { @@ -59,11 +61,11 @@ public function __construct( * queryParameters?: array, * bodyProperties?: array, * } $options - * @return mixed + * @return BigUnion * @throws SeedException * @throws SeedApiException */ - public function get(string $id, ?array $options = null): mixed + public function get(string $id, ?array $options = null): BigUnion { $options = array_merge($this->options, $options ?? []); try { @@ -78,7 +80,7 @@ public function get(string $id, ?array $options = null): mixed $statusCode = $response->getStatusCode(); if ($statusCode >= 200 && $statusCode < 400) { $json = $response->getBody()->getContents(); - return JsonDecoder::decodeMixed($json); + return BigUnion::fromJson($json); } } catch (JsonException $e) { throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); @@ -103,7 +105,7 @@ public function get(string $id, ?array $options = null): mixed } /** - * @param mixed $request + * @param BigUnion $request * @param ?array{ * baseUrl?: string, * maxRetries?: int, @@ -116,7 +118,7 @@ public function get(string $id, ?array $options = null): mixed * @throws SeedException * @throws SeedApiException */ - public function update(mixed $request, ?array $options = null): bool + public function update(BigUnion $request, ?array $options = null): bool { $options = array_merge($this->options, $options ?? []); try { @@ -155,4 +157,58 @@ public function update(mixed $request, ?array $options = null): bool body: $response->getBody()->getContents(), ); } + + /** + * @param array $request + * @param ?array{ + * baseUrl?: string, + * maxRetries?: int, + * timeout?: float, + * headers?: array, + * queryParameters?: array, + * bodyProperties?: array, + * } $options + * @return array + * @throws SeedException + * @throws SeedApiException + */ + public function updateMany(array $request, ?array $options = null): array + { + $options = array_merge($this->options, $options ?? []); + try { + $response = $this->client->sendRequest( + new JsonApiRequest( + baseUrl: $options['baseUrl'] ?? $this->client->options['baseUrl'] ?? '', + path: "/many", + method: HttpMethod::PATCH, + body: JsonSerializer::serializeArray($request, [BigUnion::class]), + ), + $options, + ); + $statusCode = $response->getStatusCode(); + if ($statusCode >= 200 && $statusCode < 400) { + $json = $response->getBody()->getContents(); + return JsonDecoder::decodeArray($json, ['string' => 'bool']); // @phpstan-ignore-line + } + } catch (JsonException $e) { + throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); + } catch (RequestException $e) { + $response = $e->getResponse(); + if ($response === null) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: "API request failed", + statusCode: $response->getStatusCode(), + body: $response->getBody()->getContents(), + ); + } catch (ClientExceptionInterface $e) { + throw new SeedException(message: $e->getMessage(), previous: $e); + } + throw new SeedApiException( + message: 'API request failed', + statusCode: $statusCode, + body: $response->getBody()->getContents(), + ); + } } diff --git a/seed/php-sdk/unions/src/Union/UnionClient.php b/seed/php-sdk/unions/src/Union/UnionClient.php index f149eb0605a..a320cfba333 100644 --- a/seed/php-sdk/unions/src/Union/UnionClient.php +++ b/seed/php-sdk/unions/src/Union/UnionClient.php @@ -4,14 +4,15 @@ use GuzzleHttp\ClientInterface; use Seed\Core\Client\RawClient; +use Seed\Union\Types\Shape; use Seed\Exceptions\SeedException; use Seed\Exceptions\SeedApiException; use Seed\Core\Json\JsonApiRequest; use Seed\Core\Client\HttpMethod; -use Seed\Core\Json\JsonDecoder; use JsonException; use GuzzleHttp\Exception\RequestException; use Psr\Http\Client\ClientExceptionInterface; +use Seed\Core\Json\JsonDecoder; class UnionClient { @@ -59,11 +60,11 @@ public function __construct( * queryParameters?: array, * bodyProperties?: array, * } $options - * @return mixed + * @return Shape * @throws SeedException * @throws SeedApiException */ - public function get(string $id, ?array $options = null): mixed + public function get(string $id, ?array $options = null): Shape { $options = array_merge($this->options, $options ?? []); try { @@ -78,7 +79,7 @@ public function get(string $id, ?array $options = null): mixed $statusCode = $response->getStatusCode(); if ($statusCode >= 200 && $statusCode < 400) { $json = $response->getBody()->getContents(); - return JsonDecoder::decodeMixed($json); + return Shape::fromJson($json); } } catch (JsonException $e) { throw new SeedException(message: "Failed to deserialize response: {$e->getMessage()}", previous: $e); @@ -103,7 +104,7 @@ public function get(string $id, ?array $options = null): mixed } /** - * @param mixed $request + * @param Shape $request * @param ?array{ * baseUrl?: string, * maxRetries?: int, @@ -116,7 +117,7 @@ public function get(string $id, ?array $options = null): mixed * @throws SeedException * @throws SeedApiException */ - public function update(mixed $request, ?array $options = null): bool + public function update(Shape $request, ?array $options = null): bool { $options = array_merge($this->options, $options ?? []); try { diff --git a/seed/php-sdk/unions/src/dynamic-snippets/example2/snippet.php b/seed/php-sdk/unions/src/dynamic-snippets/example2/snippet.php index 7ac5d182c4d..af2b85d8f5d 100644 --- a/seed/php-sdk/unions/src/dynamic-snippets/example2/snippet.php +++ b/seed/php-sdk/unions/src/dynamic-snippets/example2/snippet.php @@ -3,12 +3,22 @@ namespace Example; use Seed\SeedClient; +use Seed\Bigunion\Types\BigUnion; +use DateTime; +use Seed\Bigunion\Types\NormalSweet; $client = new SeedClient( options: [ 'baseUrl' => 'https://api.fern.com', ], ); -$client->bigunion->get( - 'id', +$client->bigunion->updateMany( + [ + BigUnion::normalSweet('id', new DateTime('2024-01-15T09:30:00Z'), new DateTime('2024-01-15T09:30:00Z'), new NormalSweet([ + 'value' => 'value', + ])), + BigUnion::normalSweet('id', new DateTime('2024-01-15T09:30:00Z'), new DateTime('2024-01-15T09:30:00Z'), new NormalSweet([ + 'value' => 'value', + ])), + ], ); diff --git a/seed/php-sdk/unions/src/dynamic-snippets/example3/snippet.php b/seed/php-sdk/unions/src/dynamic-snippets/example3/snippet.php index f3d46c9e298..7ac5d182c4d 100644 --- a/seed/php-sdk/unions/src/dynamic-snippets/example3/snippet.php +++ b/seed/php-sdk/unions/src/dynamic-snippets/example3/snippet.php @@ -9,6 +9,6 @@ 'baseUrl' => 'https://api.fern.com', ], ); -$client->bigunion->update( - , +$client->bigunion->get( + 'id', ); diff --git a/seed/php-sdk/unions/src/dynamic-snippets/example4/snippet.php b/seed/php-sdk/unions/src/dynamic-snippets/example4/snippet.php new file mode 100644 index 00000000000..f3d46c9e298 --- /dev/null +++ b/seed/php-sdk/unions/src/dynamic-snippets/example4/snippet.php @@ -0,0 +1,14 @@ + 'https://api.fern.com', + ], +); +$client->bigunion->update( + , +); diff --git a/test-definitions/fern/apis/unions/definition/bigunion.yml b/test-definitions/fern/apis/unions/definition/bigunion.yml index fdc5bde1dde..6302d0fc309 100644 --- a/test-definitions/fern/apis/unions/definition/bigunion.yml +++ b/test-definitions/fern/apis/unions/definition/bigunion.yml @@ -168,3 +168,9 @@ service: request: BigUnion response: boolean + update-many: + path: /many + method: PATCH + request: list + response: map +