Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Core): add RequestHandler and supporting classes #6871

Merged
merged 25 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ef252d7
feat: Add request handler and a GapicRequestWrapper (#6807)
saranshdhingra Dec 13, 2023
f1cec73
Added tests for ApiHelpersTrait
saranshdhingra Dec 13, 2023
7a09351
Marked the classes as internal
saranshdhingra Dec 13, 2023
510b562
Added missed use statements
saranshdhingra Dec 13, 2023
739ac5c
Update RequestHandler to accept only GAPIC classes and not objects
saranshdhingra Dec 18, 2023
4e2fcb7
Fix lint issues
saranshdhingra Dec 19, 2023
3597b40
Addressed PR comments
saranshdhingra Jan 2, 2024
7b639be
Fixed Unit tests for the latest changes
saranshdhingra Jan 2, 2024
ec21e82
Fixed return type for constructGapic
saranshdhingra Jan 2, 2024
be3a5b0
Removed setSerialzer from RequestHandler
saranshdhingra Jan 2, 2024
8eef4dd
Removed mixed type declerations
saranshdhingra Jan 2, 2024
33fd1ec
Removed mixed types from argument declerations
saranshdhingra Jan 2, 2024
7e32839
Merged RequestHandler and RequestCallerTrait
saranshdhingra Jan 3, 2024
4a9c090
Removed incompatible changes in ApiHelpersTrait
saranshdhingra Jan 8, 2024
4a9d03c
Removed getSerializer from RequestHandler
saranshdhingra Jan 8, 2024
d7a1c85
Removed trailing comas
saranshdhingra Jan 8, 2024
04ed254
Merged RequestHandler and GapicRequestWrapper
saranshdhingra Jan 16, 2024
a046bb6
Fixed tests
saranshdhingra Jan 16, 2024
85a088a
Fixed segmentation faults in tests with protobuf enabled
saranshdhingra Jan 22, 2024
c72ae30
Removed calls to reveal() in RequestHandler
saranshdhingra Jan 22, 2024
c394634
Merge branch 'main' into add-request-handler
saranshdhingra Jan 22, 2024
4147a9d
Added client related changes
saranshdhingra Jan 24, 2024
265fbc0
Updated version for gax in Core
saranshdhingra Jan 24, 2024
4806e22
Addressed PR comments
saranshdhingra Jan 26, 2024
8aaa1da
Marked the RequestProcessorTrait as internal
saranshdhingra Jan 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Core/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"guzzlehttp/promises": "^1.4||^2.0",
"guzzlehttp/psr7": "^2.6",
"monolog/monolog": "^2.9|^3.0",
"psr/http-message": "^1.0|^2.0"
"psr/http-message": "^1.0|^2.0",
"google/gax": "^1.26.3"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
Expand All @@ -20,7 +21,6 @@
"phpdocumentor/reflection": "^5.3.3",
"phpdocumentor/reflection-docblock": "^5.3",
"erusev/parsedown": "^1.6",
"google/gax": "^1.26.0",
"opis/closure": "^3",
"google/cloud-common-protos": "~0.5"
},
Expand Down
267 changes: 267 additions & 0 deletions Core/src/ApiHelperTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<?php

/**
* Copyright 2023 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Google\Cloud\Core;

use Google\ApiCore\Options\CallOptions;
use Google\Protobuf\NullValue;
use Google\Cloud\Core\Duration;

/**
* @internal
* Supplies helper methods to interact with the APIs.
*/
trait ApiHelperTrait
{
use ArrayTrait;
use TimeTrait;

/**
* Format a struct for the API.
*
* @param array $fields
* @return array
*/
private function formatStructForApi(array $fields)
{
$fFields = [];

foreach ($fields as $key => $value) {
$fFields[$key] = $this->formatValueForApi($value);
}

return ['fields' => $fFields];
}

private function unpackStructFromApi(array $struct)
{
$vals = [];
foreach ($struct['fields'] as $key => $val) {
$vals[$key] = $this->unpackValue($val);
}
return $vals;
}

private function unpackValue($value)
{
if (count($value) > 1) {
throw new \RuntimeException("Unexpected fields in struct: $value");
}

foreach ($value as $setField => $setValue) {
switch ($setField) {
case 'listValue':
$valueList = [];
foreach ($setValue['values'] as $innerValue) {
$valueList[] = $this->unpackValue($innerValue);
}
return $valueList;
case 'structValue':
return $this->unpackStructFromApi($value['structValue']);
default:
return $setValue;
}
}
}

private function flattenStruct(array $struct)
{
return $struct['fields'];
}

private function flattenValue(array $value)
{
if (count($value) > 1) {
throw new \RuntimeException("Unexpected fields in struct: $value");
}

if (isset($value['nullValue'])) {
return null;
}

return array_pop($value);
}

private function flattenListValue(array $value)
{
return $value['values'];
}

/**
* Format a list for the API.
*
* @param array $list
* @return array
*/
private function formatListForApi(array $list)
{
$values = [];

foreach ($list as $value) {
$values[] = $this->formatValueForApi($value);
}

return ['values' => $values];
}

/**
* Format a value for the API.
*
* @param mixed $value
* @return array
*/
private function formatValueForApi($value)
{
$type = gettype($value);

switch ($type) {
case 'string':
return ['string_value' => $value];
case 'double':
case 'integer':
return ['number_value' => $value];
case 'boolean':
return ['bool_value' => $value];
case 'NULL':
return ['null_value' => NullValue::NULL_VALUE];
case 'array':
if (!empty($value) && $this->isAssoc($value)) {
return ['struct_value' => $this->formatStructForApi($value)];
}

return ['list_value' => $this->formatListForApi($value)];
}

return [];
}

/**
* Format a gRPC timestamp to match the format returned by the REST API.
*
* @param array $timestamp
* @return string
*/
private function formatTimestampFromApi(array $timestamp)
{
$timestamp += [
'seconds' => 0,
'nanos' => 0
];

$dt = $this->createDateTimeFromSeconds($timestamp['seconds']);

return $this->formatTimeAsString($dt, $timestamp['nanos']);
}

/**
* Format a timestamp for the API with nanosecond precision.
*
* @param string $value
* @return array
*/
private function formatTimestampForApi($value)
{
list ($dt, $nanos) = $this->parseTimeString($value);

return [
'seconds' => (int) $dt->format('U'),
'nanos' => (int) $nanos
];
}

/**
* Format a duration for the API.
*
* @param string|mixed $value
* @return array
*/
private function formatDurationForApi($value)
{
if (is_string($value)) {
$d = explode('.', trim($value, 's'));
if (count($d) < 2) {
$seconds = $d[0];
$nanos = 0;
} else {
$seconds = (int) $d[0];
$nanos = $this->convertFractionToNanoSeconds($d[1]);
}
} elseif ($value instanceof Duration) {
$d = $value->get();
$seconds = $d['seconds'];
$nanos = $d['nanos'];
}

return [
'seconds' => $seconds,
'nanos' => $nanos
];
}

/**
* Construct a gapic client. Allows for tests to intercept.
*
* @param string $gapicName
* @param array $config
* @return mixed
*/
protected function constructGapic($gapicName, array $config)
{
return new $gapicName($config);
}

/**
* Helper function to convert selective elements into protos out of a given input array.
*
* Example:
* ```
* $output = $topic->convertDataToProtos(['schema' =>[], 'other vals'], ['schema' => Schema::class]);
* $output['schema']; // This will be of the Schema type.
* ```
*
* @param array $input The input array.
* @param array $map The key,value pairs specifying the elements and the proto classes.
*
* @return array The modified array
*/
private function convertDataToProtos(array $input, array $map) : array
{
foreach ($map as $key => $className) {
if (isset($input[$key])) {
$input[$key] = $this->serializer->decodeMessage(new $className, $input[$key]);
}
}

return $input;
}

/**
* Helper method used to split a supplied set of options into parameters that are passed into
* a proto message and optional args.
* We strictly treat the parameters allowed by `CallOptions` in GAX as the optional params
* and everything else that is passed is passed to the Proto message constructor.
*/
private function splitOptionalArgs(array $input, array $extraAllowedKeys = []) : array
{
$callOptionFields = array_keys((new CallOptions([]))->toArray());
$keys = array_merge($callOptionFields, $extraAllowedKeys);

$optionalArgs = $this->pluckArray($keys, $input);

return [$input, $optionalArgs];
}
}
Loading
Loading