Skip to content

Commit 54ffea3

Browse files
feat: Validate options no serializer (#8625)
* add validateOptions helper method * add test * style fix * remove Message classname from supported validateOptions types * add support without a serializer * Remove the serializer usage inside the validateOptions method * Added the JSON_FORCE_OBJECT flag to json_encode --------- Co-authored-by: Brent Shaffer <betterbrent@google.com>
1 parent 5776416 commit 54ffea3

3 files changed

Lines changed: 176 additions & 2 deletions

File tree

Core/src/ApiHelperTrait.php

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
*/
1818
namespace Google\Cloud\Core;
1919

20+
use Exception;
2021
use Google\ApiCore\ArrayTrait;
2122
use Google\ApiCore\Options\CallOptions;
23+
use Google\Protobuf\Internal\Message;
2224
use Google\Protobuf\NullValue;
25+
use LogicException;
2326

2427
/**
2528
* @internal
@@ -260,8 +263,53 @@ private function splitOptionalArgs(array $input, array $extraAllowedKeys = []):
260263
$callOptionFields = array_keys((new CallOptions([]))->toArray());
261264
$keys = array_merge($callOptionFields, $extraAllowedKeys);
262265

263-
$optionalArgs = $this->pluckArray($keys, $input);
266+
$callOptions = $this->pluckArray($keys, $input);
264267

265-
return [$input, $optionalArgs];
268+
return [$input, $callOptions];
269+
}
270+
271+
/**
272+
* Helper method used to validate optons based on the supplied $optionTypes
273+
* $optionTypes can be an array of string keys, a protobuf Message classname, or a
274+
* the CallOptions classname. Parameters are split and returned in the order
275+
* that the options types are provided.
276+
*/
277+
private function validateOptions(array $options, array|Message|string ...$optionTypes): array
278+
{
279+
$splitOptions = [];
280+
foreach ($optionTypes as $optionType) {
281+
if (is_array($optionType)) {
282+
$splitOptions[] = $this->pluckArray($optionType, $options);
283+
} elseif ($optionType === CallOptions::class) {
284+
$callOptionKeys = array_keys((new CallOptions([]))->toArray());
285+
$splitOptions[] = $this->pluckArray($callOptionKeys, $options);
286+
} elseif ($optionType instanceof Message) {
287+
$messageKeys = array_map(
288+
fn ($method) => lcfirst(substr($method, 3)),
289+
array_filter(
290+
get_class_methods($optionType),
291+
fn ($m) => 0 === strpos($m, 'get')
292+
)
293+
);
294+
$messageOptions = $this->pluckArray($messageKeys, $options);
295+
if ($optionType instanceof Message) {
296+
$optionType->mergeFromJsonString(json_encode($messageOptions, JSON_FORCE_OBJECT));
297+
$validatedOptionGroup = $optionType;
298+
} else {
299+
$validatedOptionGroup = $messageOptions;
300+
}
301+
$splitOptions[] = $validatedOptionGroup;
302+
} else {
303+
throw new LogicException(sprintf('Invalid option type: %s', $optionType));
304+
}
305+
}
306+
307+
if (!empty($options)) {
308+
throw new Exception(
309+
'Unexpected option(s) provided: ' . implode(', ', array_keys($options))
310+
);
311+
}
312+
313+
return $splitOptions;
266314
}
267315
}

Core/tests/Unit/ApiHelperTraitTest.php

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717

1818
namespace Google\Cloud\Core\Tests\Unit;
1919

20+
use Google\ApiCore\Options\CallOptions;
21+
use Google\ApiCore\Serializer;
22+
use Google\ApiCore\Testing\MockRequest;
2023
use Google\Cloud\Core\Duration;
2124
use Google\Cloud\Core\Testing\GrpcTestTrait;
2225
use Google\Cloud\Core\Tests\Unit\Stubs\ApiHelpersTraitImpl;
26+
use LogicException;
2327
use PHPUnit\Framework\TestCase;
2428
use Prophecy\PhpUnit\ProphecyTrait;
2529

@@ -36,6 +40,7 @@ class ApiHelperTraitTest extends TestCase
3640
public function setUp(): void
3741
{
3842
$this->implementation = new ApiHelpersTraitImpl();
43+
$this->implementation->serializer = new Serializer();
3944
}
4045

4146
public function testFormatsTimestamp()
@@ -258,4 +263,122 @@ public function unpackValueProvider()
258263
]
259264
];
260265
}
266+
267+
/**
268+
* @dataProvider validateOptionsProvider
269+
*/
270+
public function testValidateOptions($options, $optionTypes, $expected)
271+
{
272+
$this->assertEquals(
273+
$expected,
274+
$this->implementation->validateOptions($options, ...$optionTypes)
275+
);
276+
// test using an implementation without a serializer
277+
$this->assertEquals(
278+
$expected,
279+
(new ApiHelpersTraitImpl)->validateOptions($options, ...$optionTypes)
280+
);
281+
}
282+
283+
public function validateOptionsProvider()
284+
{
285+
return [
286+
[
287+
[
288+
'foo' => 'bar',
289+
'baz' => 'bat',
290+
'qux' => 'quux',
291+
],
292+
[
293+
['foo', 'baz', 'qux'],
294+
],
295+
[
296+
[
297+
'foo' => 'bar',
298+
'baz' => 'bat',
299+
'qux' => 'quux',
300+
],
301+
]
302+
],
303+
[
304+
[
305+
'pageToken' => 'bat',
306+
'qux' => 'quux',
307+
'timeoutMillis' => 123,
308+
],
309+
[
310+
CallOptions::class,
311+
new MockRequest(),
312+
['qux'],
313+
],
314+
[
315+
['timeoutMillis' => 123],
316+
(new MockRequest())->setPageToken('bat'),
317+
['qux' => 'quux'],
318+
]
319+
],
320+
[
321+
[
322+
'baz' => 'bat',
323+
],
324+
[
325+
['baz'],
326+
new MockRequest(),
327+
CallOptions::class,
328+
],
329+
[
330+
['baz' => 'bat'],
331+
new MockRequest(),
332+
[],
333+
]
334+
],
335+
[
336+
[
337+
'baz' => 'bat',
338+
'pageToken' => 'foo1',
339+
],
340+
[
341+
['baz'],
342+
new MockRequest(),
343+
],
344+
[
345+
['baz' => 'bat'],
346+
(new MockRequest())->setPageToken('foo1'),
347+
]
348+
],
349+
];
350+
}
351+
352+
public function testValidateOptionsThrowsException()
353+
{
354+
$this->expectException(\Exception::class);
355+
$this->expectExceptionMessage('Unexpected option(s) provided: bar');
356+
357+
$options = [
358+
'foo' => 'bar',
359+
'bar' => 'baz',
360+
];
361+
362+
$this->implementation->validateOptions($options, ['foo']);
363+
}
364+
365+
public function testValidateOptionsWithClassnameThrowsException()
366+
{
367+
$this->expectException(LogicException::class);
368+
$this->expectExceptionMessage('Invalid option type: ' . Blob::class);
369+
370+
$options = [
371+
'foo' => 'bar',
372+
'bar' => 'baz',
373+
];
374+
375+
[$blob, $validated] = $this->implementation->validateOptions(
376+
$options,
377+
Blob::class,
378+
['foo', 'bar']
379+
);
380+
381+
$this->assertEquals([], $blob);
382+
$this->assertEquals($options, $validated);
383+
}
261384
}

Core/tests/Unit/Stubs/ApiHelpersTraitImpl.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@ class ApiHelpersTraitImpl
3131
formatDurationForApi as public;
3232
formatValueForApi as public;
3333
unpackValue as public;
34+
validateOptions as public;
3435
}
36+
37+
public $serializer;
3538
}

0 commit comments

Comments
 (0)