Skip to content

Commit 25f0491

Browse files
committed
feat: adds fromArray key validation
1 parent cb985cc commit 25f0491

File tree

11 files changed

+82
-52
lines changed

11 files changed

+82
-52
lines changed

src/Common/AbstractDataValueObject.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace WordPress\AiClient\Common;
66

7+
use InvalidArgumentException;
78
use JsonSerializable;
89
use stdClass;
910
use WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface;
@@ -31,6 +32,36 @@ abstract class AbstractDataValueObject implements
3132
WithJsonSchemaInterface,
3233
JsonSerializable
3334
{
35+
/**
36+
* Validates that required keys exist in the array data.
37+
*
38+
* @since n.e.x.t
39+
*
40+
* @param TArrayShape $data The array data to validate.
41+
* @param string[] $requiredKeys The keys that must be present.
42+
* @throws InvalidArgumentException If any required key is missing.
43+
*/
44+
protected static function validateFromArrayData(array $data, array $requiredKeys): void
45+
{
46+
$missingKeys = [];
47+
48+
foreach ($requiredKeys as $key) {
49+
if (!isset($data[$key])) {
50+
$missingKeys[] = $key;
51+
}
52+
}
53+
54+
if (!empty($missingKeys)) {
55+
throw new InvalidArgumentException(
56+
sprintf(
57+
'%s::fromArray() missing required keys: %s',
58+
static::class,
59+
implode(', ', $missingKeys)
60+
)
61+
);
62+
}
63+
}
64+
3465
/**
3566
* Converts the object to a JSON-serializable format.
3667
*

src/Files/DTO/File.php

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -423,18 +423,17 @@ public function toArray(): array
423423
*/
424424
public static function fromArray(array $array): File
425425
{
426-
$fileType = FileTypeEnum::from($array['fileType']);
426+
static::validateFromArrayData($array, ['fileType']);
427427

428-
if ($fileType->isRemote()) {
429-
if (!isset($array['url']) || !isset($array['mimeType'])) {
430-
throw new \InvalidArgumentException('Remote file requires url and mimeType.');
431-
}
432-
return new self($array['url'], $array['mimeType']);
428+
// Check which properties are set to determine how to construct the File
429+
$mimeType = $array['mimeType'] ?? null;
430+
431+
if (isset($array['url'])) {
432+
return new self($array['url'], $mimeType);
433+
} elseif (isset($array['base64Data'])) {
434+
return new self($array['base64Data'], $mimeType);
433435
} else {
434-
if (!isset($array['mimeType']) || !isset($array['base64Data'])) {
435-
throw new \InvalidArgumentException('Inline file requires mimeType and base64Data.');
436-
}
437-
return new self($array['base64Data'], $array['mimeType']);
436+
throw new InvalidArgumentException('File requires either url or base64Data.');
438437
}
439438
}
440439
}

src/Messages/DTO/Message.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace WordPress\AiClient\Messages\DTO;
66

7+
use InvalidArgumentException;
78
use WordPress\AiClient\Common\AbstractDataValueObject;
89
use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
910

@@ -126,6 +127,8 @@ public function toArray(): array
126127
*/
127128
final public static function fromArray(array $array): Message
128129
{
130+
static::validateFromArrayData($array, ['role', 'parts']);
131+
129132
$role = MessageRoleEnum::from($array['role']);
130133
$partsData = $array['parts'];
131134
$parts = array_map(function (array $partData) {

src/Messages/DTO/MessagePart.php

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ final class MessagePart extends AbstractDataValueObject
6767
* @since n.e.x.t
6868
*
6969
* @param mixed $content The content of this message part.
70-
* @throws \InvalidArgumentException If an unsupported content type is provided.
70+
* @throws InvalidArgumentException If an unsupported content type is provided.
7171
*/
7272
public function __construct($content)
7373
{
@@ -85,7 +85,7 @@ public function __construct($content)
8585
$this->functionResponse = $content;
8686
} else {
8787
$type = is_object($content) ? get_class($content) : gettype($content);
88-
throw new \InvalidArgumentException(
88+
throw new InvalidArgumentException(
8989
sprintf(
9090
'Unsupported content type %s. Expected string, File, '
9191
. 'FunctionCall, or FunctionResponse.',
@@ -239,10 +239,7 @@ public function toArray(): array
239239
} elseif ($this->functionResponse !== null) {
240240
$data['functionResponse'] = $this->functionResponse->toArray();
241241
} else {
242-
throw new RuntimeException(
243-
'MessagePart requires one of: text, file, functionCall, or functionResponse. '
244-
. 'This should not be a possible condition.'
245-
);
242+
throw new RuntimeException('MessagePart requires one of: text, file, functionCall, or functionResponse. This should not be a possible condition.');
246243
}
247244

248245
return $data;
@@ -255,32 +252,21 @@ public function toArray(): array
255252
*/
256253
public static function fromArray(array $array): MessagePart
257254
{
258-
$type = MessagePartTypeEnum::from($array['type']);
255+
static::validateFromArrayData($array, ['type']);
259256

260-
if ($type->isText()) {
261-
if (!isset($array['text'])) {
262-
throw new \InvalidArgumentException('Text message part requires text field.');
263-
}
257+
// Check which properties are set to determine how to construct the MessagePart
258+
if (isset($array['text'])) {
264259
return new self($array['text']);
265-
} elseif ($type->isFile()) {
266-
if (!isset($array['file'])) {
267-
throw new \InvalidArgumentException('File message part requires file field.');
268-
}
269-
$fileData = $array['file'];
270-
return new self(File::fromArray($fileData));
271-
} elseif ($type->isFunctionCall()) {
272-
if (!isset($array['functionCall'])) {
273-
throw new \InvalidArgumentException('Function call message part requires functionCall field.');
274-
}
275-
$functionCallData = $array['functionCall'];
276-
return new self(FunctionCall::fromArray($functionCallData));
260+
} elseif (isset($array['file'])) {
261+
return new self(File::fromArray($array['file']));
262+
} elseif (isset($array['functionCall'])) {
263+
return new self(FunctionCall::fromArray($array['functionCall']));
264+
} elseif (isset($array['functionResponse'])) {
265+
return new self(FunctionResponse::fromArray($array['functionResponse']));
277266
} else {
278-
// Function response is the only remaining option
279-
if (!isset($array['functionResponse'])) {
280-
throw new \InvalidArgumentException('Function response message part requires functionResponse field.');
281-
}
282-
$functionResponseData = $array['functionResponse'];
283-
return new self(FunctionResponse::fromArray($functionResponseData));
267+
throw new InvalidArgumentException(
268+
'MessagePart requires one of: text, file, functionCall, or functionResponse.'
269+
);
284270
}
285271
}
286272
}

src/Operations/DTO/GenerativeAiOperation.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace WordPress\AiClient\Operations\DTO;
66

7+
use InvalidArgumentException;
78
use WordPress\AiClient\Common\AbstractDataValueObject;
89
use WordPress\AiClient\Operations\Contracts\OperationInterface;
910
use WordPress\AiClient\Operations\Enums\OperationStateEnum;
@@ -168,6 +169,8 @@ public function toArray(): array
168169
*/
169170
public static function fromArray(array $array): GenerativeAiOperation
170171
{
172+
static::validateFromArrayData($array, ['id', 'state']);
173+
171174
$state = OperationStateEnum::from($array['state']);
172175
$result = null;
173176
if (isset($array['result'])) {

src/Results/DTO/Candidate.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ public function toArray(): array
145145
*/
146146
public static function fromArray(array $array): Candidate
147147
{
148+
static::validateFromArrayData($array, ['message', 'finishReason', 'tokenCount']);
149+
148150
$messageData = $array['message'];
149151

150152
return new self(

src/Results/DTO/GenerativeAiResult.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ public function toArray(): array
415415
*/
416416
public static function fromArray(array $array): GenerativeAiResult
417417
{
418+
static::validateFromArrayData($array, ['id', 'candidates', 'tokenUsage']);
419+
418420
$candidates = array_map(fn(array $candidateData) => Candidate::fromArray($candidateData), $array['candidates']);
419421

420422
return new self(

src/Results/DTO/TokenUsage.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ public function toArray(): array
141141
*/
142142
public static function fromArray(array $array): TokenUsage
143143
{
144+
static::validateFromArrayData($array, ['promptTokens', 'completionTokens', 'totalTokens']);
145+
144146
return new self(
145147
$array['promptTokens'],
146148
$array['completionTokens'],

src/Tools/DTO/FunctionDeclaration.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace WordPress\AiClient\Tools\DTO;
66

7+
use InvalidArgumentException;
78
use WordPress\AiClient\Common\AbstractDataValueObject;
89

910
/**
@@ -142,6 +143,8 @@ public function toArray(): array
142143
*/
143144
public static function fromArray(array $array): FunctionDeclaration
144145
{
146+
static::validateFromArrayData($array, ['name', 'description']);
147+
145148
return new self(
146149
$array['name'],
147150
$array['description'],

src/Tools/DTO/FunctionResponse.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ public function toArray(): array
137137
*/
138138
public static function fromArray(array $array): FunctionResponse
139139
{
140+
static::validateFromArrayData($array, ['id', 'name', 'response']);
141+
140142
return new self(
141143
$array['id'],
142144
$array['name'],

0 commit comments

Comments
 (0)