Skip to content

Commit 21550c5

Browse files
authored
feat: typesafety for new surface client options (#450)
1 parent 532d501 commit 21550c5

File tree

11 files changed

+1264
-53
lines changed

11 files changed

+1264
-53
lines changed

src/GapicClientTrait.php

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
use Google\ApiCore\Transport\GrpcTransport;
4444
use Google\ApiCore\Transport\RestTransport;
4545
use Google\ApiCore\Transport\TransportInterface;
46+
use Google\ApiCore\Options\CallOptions;
47+
use Google\ApiCore\Options\ClientOptions;
48+
use Google\ApiCore\Options\TransportOptions;
4649
use Google\Auth\CredentialsLoader;
4750
use Google\Auth\FetchAuthTokenInterface;
4851
use Google\LongRunning\Operation;
@@ -363,45 +366,50 @@ private function setClientOptions(array $options)
363366
'libName',
364367
'libVersion',
365368
]);
366-
367-
$clientConfig = $options['clientConfig'];
368-
if (is_string($clientConfig)) {
369-
$clientConfig = json_decode(file_get_contents($clientConfig), true);
369+
if ($this->isNewClientSurface()) {
370+
// cast to ClientOptions for new surfaces only
371+
$options = new ClientOptions($options);
372+
} elseif (is_string($options['clientConfig'])) {
373+
// perform validation for V1 surfaces which is done in the
374+
// ClientOptions class for v2 surfaces.
375+
$options['clientConfig'] = json_decode(
376+
file_get_contents($options['clientConfig']),
377+
true
378+
);
379+
self::validateFileExists($options['descriptorsConfigPath']);
370380
}
371381
$this->serviceName = $options['serviceName'];
372382
$this->retrySettings = RetrySettings::load(
373383
$this->serviceName,
374-
$clientConfig,
384+
$options['clientConfig'],
375385
$options['disableRetries']
376386
);
377387

388+
$headerInfo = [
389+
'libName' => $options['libName'],
390+
'libVersion' => $options['libVersion'],
391+
'gapicVersion' => $options['gapicVersion'],
392+
];
378393
// Edge case: If the client has the gRPC extension installed, but is
379394
// a REST-only library, then the grpcVersion header should not be set.
380395
if ($this->transport instanceof GrpcTransport) {
381-
$options['grpcVersion'] = phpversion('grpc');
382-
unset($options['restVersion']);
396+
$headerInfo['grpcVersion'] = phpversion('grpc');
383397
} elseif ($this->transport instanceof RestTransport
384398
|| $this->transport instanceof GrpcFallbackTransport) {
385-
unset($options['grpcVersion']);
386-
$options['restVersion'] = Version::getApiCoreVersion();
399+
$headerInfo['restVersion'] = Version::getApiCoreVersion();
387400
}
388-
401+
$this->agentHeader = AgentHeader::buildAgentHeader($headerInfo);
402+
389403
// Set "client_library_name" depending on client library surface being used
390404
$userAgentHeader = sprintf(
391405
'gcloud-php-%s/%s',
392406
$this->isNewClientSurface() ? 'new' : 'legacy',
393407
$options['gapicVersion']
394408
);
395-
$this->agentHeader = AgentHeader::buildAgentHeader(
396-
$this->pluckArray([
397-
'libName',
398-
'libVersion',
399-
'gapicVersion'
400-
], $options)
401-
);
402409
$this->agentHeader['User-Agent'] = [$userAgentHeader];
403410

404411
self::validateFileExists($options['descriptorsConfigPath']);
412+
405413
$descriptors = require($options['descriptorsConfigPath']);
406414
$this->descriptors = $descriptors['interfaces'][$this->serviceName];
407415

@@ -449,15 +457,15 @@ private function createCredentialsWrapper($credentials, array $credentialsConfig
449457
/**
450458
* @param string $apiEndpoint
451459
* @param string $transport
452-
* @param array $transportConfig
460+
* @param TransportOptions|array $transportConfig
453461
* @param callable $clientCertSource
454462
* @return TransportInterface
455463
* @throws ValidationException
456464
*/
457465
private function createTransport(
458466
string $apiEndpoint,
459467
$transport,
460-
array $transportConfig,
468+
$transportConfig,
461469
callable $clientCertSource = null
462470
) {
463471
if (!is_string($transport)) {
@@ -475,7 +483,12 @@ private function createTransport(
475483
));
476484
}
477485
$configForSpecifiedTransport = $transportConfig[$transport] ?? [];
478-
$configForSpecifiedTransport['clientCertSource'] = $clientCertSource;
486+
if (is_array($configForSpecifiedTransport)) {
487+
$configForSpecifiedTransport['clientCertSource'] = $clientCertSource;
488+
} else {
489+
$configForSpecifiedTransport->setClientCertSource($clientCertSource);
490+
$configForSpecifiedTransport = $configForSpecifiedTransport->toArray();
491+
}
479492
switch ($transport) {
480493
case 'grpc':
481494
// Setting the user agent for gRPC requires special handling
@@ -715,6 +728,7 @@ private function startCall(
715728
int $callType = Call::UNARY_CALL,
716729
string $interfaceName = null
717730
) {
731+
$optionalArgs = $this->configureCallOptions($optionalArgs);
718732
$callStack = $this->createCallStack(
719733
$this->configureCallConstructionOptions($methodName, $optionalArgs)
720734
);
@@ -811,6 +825,19 @@ private function configureCallConstructionOptions(string $methodName, array $opt
811825
];
812826
}
813827

828+
/**
829+
* @return array
830+
*/
831+
private function configureCallOptions(array $optionalArgs): array
832+
{
833+
if ($this->isNewClientSurface()) {
834+
// cast to CallOptions for new surfaces only
835+
return (new CallOptions($optionalArgs))->toArray();
836+
}
837+
838+
return $optionalArgs;
839+
}
840+
814841
/**
815842
* @param string $methodName
816843
* @param array $optionalArgs {
@@ -836,6 +863,7 @@ private function startOperationsCall(
836863
string $interfaceName = null,
837864
string $operationClass = null
838865
) {
866+
$optionalArgs = $this->configureCallOptions($optionalArgs);
839867
$callStack = $this->createCallStack(
840868
$this->configureCallConstructionOptions($methodName, $optionalArgs)
841869
);
@@ -915,6 +943,7 @@ private function getPagedListResponseAsync(
915943
Message $request,
916944
string $interfaceName = null
917945
) {
946+
$optionalArgs = $this->configureCallOptions($optionalArgs);
918947
$callStack = $this->createCallStack(
919948
$this->configureCallConstructionOptions($methodName, $optionalArgs)
920949
);

src/Options/CallOptions.php

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
/*
3+
* Copyright 2023 Google LLC
4+
* All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted provided that the following conditions are
8+
* met:
9+
*
10+
* * Redistributions of source code must retain the above copyright
11+
* notice, this list of conditions and the following disclaimer.
12+
* * Redistributions in binary form must reproduce the above
13+
* copyright notice, this list of conditions and the following disclaimer
14+
* in the documentation and/or other materials provided with the
15+
* distribution.
16+
* * Neither the name of Google Inc. nor the names of its
17+
* contributors may be used to endorse or promote products derived from
18+
* this software without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
33+
namespace Google\ApiCore\Options;
34+
35+
use ArrayAccess;
36+
use Google\ApiCore\CredentialsWrapper;
37+
use Google\ApiCore\RetrySettings;
38+
use Google\ApiCore\TransportInterface;
39+
40+
/**
41+
* The CallOptions class provides typing to the associative array of options
42+
* passed to transport RPC methods. See {@see TransportInterface::startUnaryCall()},
43+
* {@see TransportInterface::startBidiStreamingCall()},
44+
* {@see TransportInterface::startClientStreamingCall()}, and
45+
* {@see TransportInterface::startServerStreamingCall()}.
46+
*/
47+
class CallOptions implements ArrayAccess
48+
{
49+
use OptionsTrait;
50+
51+
private array $headers;
52+
private ?int $timeoutMillis;
53+
private array $transportSpecificOptions;
54+
55+
/** @var RetrySettings|array|null $retrySettings */
56+
private $retrySettings;
57+
58+
/**
59+
* @param array $options {
60+
* Call options
61+
*
62+
* @type array $headers
63+
* Key-value array containing headers
64+
* @type int $timeoutMillis
65+
* The timeout in milliseconds for the call.
66+
* @type array $transportOptions
67+
* Transport-specific call options. See {@see CallOptions::setTransportOptions}.
68+
* @type RetrySettings|array $retrySettings
69+
* A retry settings override for the call. If $retrySettings is an
70+
* array, the settings will be merged with the method's default
71+
* retry settings. If $retrySettings is a RetrySettings object,
72+
* that object will be used instead of the method defaults.
73+
* }
74+
*/
75+
public function __construct(array $options)
76+
{
77+
$this->fromArray($options);
78+
}
79+
80+
/**
81+
* Sets the array of options as class properites.
82+
*
83+
* @param array $arr See the constructor for the list of supported options.
84+
*/
85+
private function fromArray(array $arr): void
86+
{
87+
$this->setHeaders($arr['headers'] ?? []);
88+
$this->setTimeoutMillis($arr['timeoutMillis'] ?? null);
89+
$this->setTransportSpecificOptions($arr['transportOptions'] ?? []);
90+
$this->setRetrySettings($arr['retrySettings'] ?? null);
91+
}
92+
93+
/**
94+
* @param array $headers
95+
*/
96+
public function setHeaders(array $headers)
97+
{
98+
$this->headers = $headers;
99+
}
100+
101+
/**
102+
* @param int|null $timeoutMillis
103+
*/
104+
public function setTimeoutMillis(?int $timeoutMillis)
105+
{
106+
$this->timeoutMillis = $timeoutMillis;
107+
}
108+
109+
/**
110+
* @param array $transportSpecificOptions {
111+
* Transport-specific call-time options.
112+
*
113+
* @type array $grpcOptions
114+
* Key-value pairs for gRPC-specific options passed as the `$options` argument to {@see \Grpc\BaseStub}
115+
* request methods. Current options are `call_credentials_callback` and `timeout`.
116+
* **NOTE**: This library sets `call_credentials_callback` using {@see CredentialsWrapper}, and `timeout`
117+
* using the `timeoutMillis` call option, so these options are not very useful.
118+
* @type array $grpcFallbackOptions
119+
* Key-value pairs for gRPC fallback specific options passed as the `$options` argument to the
120+
* `$httpHandler` callable. By default these are passed to {@see \GuzzleHttp\Client} as request options.
121+
* See {@link https://docs.guzzlephp.org/en/stable/request-options.html}.
122+
* @type array $restOptions
123+
* Key-value pairs for REST-specific options passed as the `$options` argument to the `$httpHandler`
124+
* callable. By default these are passed to {@see \GuzzleHttp\Client} as request options.
125+
* See {@link https://docs.guzzlephp.org/en/stable/request-options.html}.
126+
* }
127+
*/
128+
public function setTransportSpecificOptions(array $transportSpecificOptions)
129+
{
130+
$this->transportSpecificOptions = $transportSpecificOptions;
131+
}
132+
133+
/**
134+
* @param RetrySettings|array|null $retrySettings
135+
*/
136+
public function setRetrySettings($retrySettings)
137+
{
138+
$this->retrySettings = $retrySettings;
139+
}
140+
}

0 commit comments

Comments
 (0)