Skip to content

Commit

Permalink
feat(Core): add RequestHandler and supporting classes (#6871)
Browse files Browse the repository at this point in the history
  • Loading branch information
saranshdhingra authored Jan 26, 2024
1 parent 6eff9a3 commit 9df29db
Show file tree
Hide file tree
Showing 17 changed files with 1,615 additions and 559 deletions.
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

0 comments on commit 9df29db

Please sign in to comment.