Skip to content

Commit

Permalink
feat: typesafety for new surface client options (#450)
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer authored Sep 14, 2023
1 parent 532d501 commit 21550c5
Show file tree
Hide file tree
Showing 11 changed files with 1,264 additions and 53 deletions.
69 changes: 49 additions & 20 deletions src/GapicClientTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
use Google\ApiCore\Transport\GrpcTransport;
use Google\ApiCore\Transport\RestTransport;
use Google\ApiCore\Transport\TransportInterface;
use Google\ApiCore\Options\CallOptions;
use Google\ApiCore\Options\ClientOptions;
use Google\ApiCore\Options\TransportOptions;
use Google\Auth\CredentialsLoader;
use Google\Auth\FetchAuthTokenInterface;
use Google\LongRunning\Operation;
Expand Down Expand Up @@ -363,45 +366,50 @@ private function setClientOptions(array $options)
'libName',
'libVersion',
]);

$clientConfig = $options['clientConfig'];
if (is_string($clientConfig)) {
$clientConfig = json_decode(file_get_contents($clientConfig), true);
if ($this->isNewClientSurface()) {
// cast to ClientOptions for new surfaces only
$options = new ClientOptions($options);
} elseif (is_string($options['clientConfig'])) {
// perform validation for V1 surfaces which is done in the
// ClientOptions class for v2 surfaces.
$options['clientConfig'] = json_decode(
file_get_contents($options['clientConfig']),
true
);
self::validateFileExists($options['descriptorsConfigPath']);
}
$this->serviceName = $options['serviceName'];
$this->retrySettings = RetrySettings::load(
$this->serviceName,
$clientConfig,
$options['clientConfig'],
$options['disableRetries']
);

$headerInfo = [
'libName' => $options['libName'],
'libVersion' => $options['libVersion'],
'gapicVersion' => $options['gapicVersion'],
];
// Edge case: If the client has the gRPC extension installed, but is
// a REST-only library, then the grpcVersion header should not be set.
if ($this->transport instanceof GrpcTransport) {
$options['grpcVersion'] = phpversion('grpc');
unset($options['restVersion']);
$headerInfo['grpcVersion'] = phpversion('grpc');
} elseif ($this->transport instanceof RestTransport
|| $this->transport instanceof GrpcFallbackTransport) {
unset($options['grpcVersion']);
$options['restVersion'] = Version::getApiCoreVersion();
$headerInfo['restVersion'] = Version::getApiCoreVersion();
}

$this->agentHeader = AgentHeader::buildAgentHeader($headerInfo);

// Set "client_library_name" depending on client library surface being used
$userAgentHeader = sprintf(
'gcloud-php-%s/%s',
$this->isNewClientSurface() ? 'new' : 'legacy',
$options['gapicVersion']
);
$this->agentHeader = AgentHeader::buildAgentHeader(
$this->pluckArray([
'libName',
'libVersion',
'gapicVersion'
], $options)
);
$this->agentHeader['User-Agent'] = [$userAgentHeader];

self::validateFileExists($options['descriptorsConfigPath']);

$descriptors = require($options['descriptorsConfigPath']);
$this->descriptors = $descriptors['interfaces'][$this->serviceName];

Expand Down Expand Up @@ -449,15 +457,15 @@ private function createCredentialsWrapper($credentials, array $credentialsConfig
/**
* @param string $apiEndpoint
* @param string $transport
* @param array $transportConfig
* @param TransportOptions|array $transportConfig
* @param callable $clientCertSource
* @return TransportInterface
* @throws ValidationException
*/
private function createTransport(
string $apiEndpoint,
$transport,
array $transportConfig,
$transportConfig,
callable $clientCertSource = null
) {
if (!is_string($transport)) {
Expand All @@ -475,7 +483,12 @@ private function createTransport(
));
}
$configForSpecifiedTransport = $transportConfig[$transport] ?? [];
$configForSpecifiedTransport['clientCertSource'] = $clientCertSource;
if (is_array($configForSpecifiedTransport)) {
$configForSpecifiedTransport['clientCertSource'] = $clientCertSource;
} else {
$configForSpecifiedTransport->setClientCertSource($clientCertSource);
$configForSpecifiedTransport = $configForSpecifiedTransport->toArray();
}
switch ($transport) {
case 'grpc':
// Setting the user agent for gRPC requires special handling
Expand Down Expand Up @@ -715,6 +728,7 @@ private function startCall(
int $callType = Call::UNARY_CALL,
string $interfaceName = null
) {
$optionalArgs = $this->configureCallOptions($optionalArgs);
$callStack = $this->createCallStack(
$this->configureCallConstructionOptions($methodName, $optionalArgs)
);
Expand Down Expand Up @@ -811,6 +825,19 @@ private function configureCallConstructionOptions(string $methodName, array $opt
];
}

/**
* @return array
*/
private function configureCallOptions(array $optionalArgs): array
{
if ($this->isNewClientSurface()) {
// cast to CallOptions for new surfaces only
return (new CallOptions($optionalArgs))->toArray();
}

return $optionalArgs;
}

/**
* @param string $methodName
* @param array $optionalArgs {
Expand All @@ -836,6 +863,7 @@ private function startOperationsCall(
string $interfaceName = null,
string $operationClass = null
) {
$optionalArgs = $this->configureCallOptions($optionalArgs);
$callStack = $this->createCallStack(
$this->configureCallConstructionOptions($methodName, $optionalArgs)
);
Expand Down Expand Up @@ -915,6 +943,7 @@ private function getPagedListResponseAsync(
Message $request,
string $interfaceName = null
) {
$optionalArgs = $this->configureCallOptions($optionalArgs);
$callStack = $this->createCallStack(
$this->configureCallConstructionOptions($methodName, $optionalArgs)
);
Expand Down
140 changes: 140 additions & 0 deletions src/Options/CallOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php
/*
* Copyright 2023 Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

namespace Google\ApiCore\Options;

use ArrayAccess;
use Google\ApiCore\CredentialsWrapper;
use Google\ApiCore\RetrySettings;
use Google\ApiCore\TransportInterface;

/**
* The CallOptions class provides typing to the associative array of options
* passed to transport RPC methods. See {@see TransportInterface::startUnaryCall()},
* {@see TransportInterface::startBidiStreamingCall()},
* {@see TransportInterface::startClientStreamingCall()}, and
* {@see TransportInterface::startServerStreamingCall()}.
*/
class CallOptions implements ArrayAccess
{
use OptionsTrait;

private array $headers;
private ?int $timeoutMillis;
private array $transportSpecificOptions;

/** @var RetrySettings|array|null $retrySettings */
private $retrySettings;

/**
* @param array $options {
* Call options
*
* @type array $headers
* Key-value array containing headers
* @type int $timeoutMillis
* The timeout in milliseconds for the call.
* @type array $transportOptions
* Transport-specific call options. See {@see CallOptions::setTransportOptions}.
* @type RetrySettings|array $retrySettings
* A retry settings override for the call. If $retrySettings is an
* array, the settings will be merged with the method's default
* retry settings. If $retrySettings is a RetrySettings object,
* that object will be used instead of the method defaults.
* }
*/
public function __construct(array $options)
{
$this->fromArray($options);
}

/**
* Sets the array of options as class properites.
*
* @param array $arr See the constructor for the list of supported options.
*/
private function fromArray(array $arr): void
{
$this->setHeaders($arr['headers'] ?? []);
$this->setTimeoutMillis($arr['timeoutMillis'] ?? null);
$this->setTransportSpecificOptions($arr['transportOptions'] ?? []);
$this->setRetrySettings($arr['retrySettings'] ?? null);
}

/**
* @param array $headers
*/
public function setHeaders(array $headers)
{
$this->headers = $headers;
}

/**
* @param int|null $timeoutMillis
*/
public function setTimeoutMillis(?int $timeoutMillis)
{
$this->timeoutMillis = $timeoutMillis;
}

/**
* @param array $transportSpecificOptions {
* Transport-specific call-time options.
*
* @type array $grpcOptions
* Key-value pairs for gRPC-specific options passed as the `$options` argument to {@see \Grpc\BaseStub}
* request methods. Current options are `call_credentials_callback` and `timeout`.
* **NOTE**: This library sets `call_credentials_callback` using {@see CredentialsWrapper}, and `timeout`
* using the `timeoutMillis` call option, so these options are not very useful.
* @type array $grpcFallbackOptions
* Key-value pairs for gRPC fallback specific options passed as the `$options` argument to the
* `$httpHandler` callable. By default these are passed to {@see \GuzzleHttp\Client} as request options.
* See {@link https://docs.guzzlephp.org/en/stable/request-options.html}.
* @type array $restOptions
* Key-value pairs for REST-specific options passed as the `$options` argument to the `$httpHandler`
* callable. By default these are passed to {@see \GuzzleHttp\Client} as request options.
* See {@link https://docs.guzzlephp.org/en/stable/request-options.html}.
* }
*/
public function setTransportSpecificOptions(array $transportSpecificOptions)
{
$this->transportSpecificOptions = $transportSpecificOptions;
}

/**
* @param RetrySettings|array|null $retrySettings
*/
public function setRetrySettings($retrySettings)
{
$this->retrySettings = $retrySettings;
}
}
Loading

0 comments on commit 21550c5

Please sign in to comment.