Skip to content

Commit

Permalink
Address initial code review feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
garethgeorge committed May 18, 2023
1 parent d54252b commit 76cf596
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 148 deletions.
29 changes: 29 additions & 0 deletions src/BadRequestError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/**
* Copyright 2023 Google LLC.
*
* 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\CloudFunctions;

use Exception;

/**
* BadRequestError indicates a malformatted request.
* @internal
*/
class BadRequestError extends Exception
{
}
38 changes: 32 additions & 6 deletions src/CloudEventFunctionWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@

use CloudEvents\V1\CloudEventInterface;
use GuzzleHttp\Psr7\Response;
use LogicException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use ReflectionParameter;

class CloudEventFunctionWrapper extends ValidatingFunctionWrapper
class CloudEventFunctionWrapper extends FunctionWrapper
{
use FunctionValidationTrait;

private const TYPE_LEGACY = 1;
private const TYPE_BINARY = 2;
private const TYPE_STRUCTURED = 3;
Expand All @@ -50,6 +54,33 @@ public function __construct(callable $function, bool $marshalToCloudEventInterfa
{
$this->marshalToCloudEventInterface = $marshalToCloudEventInterface;
parent::__construct($function);

$this->validateFunctionSignature(
$this->getFunctionReflection($function)
);
}

private function throwInvalidFirstParameterException(): void
{
$class = $this->getFunctionParameterClassName();
throw new LogicException(sprintf(
'Your function must have "%s" as the typehint for the first argument',
$class
));
}

private function validateFirstParameter(ReflectionParameter $param): void
{
$type = $param->getType();
$class = $this->getFunctionParameterClassName();
if (!$type || $type->getName() !== $class) {
$this->throwInvalidFirstParameterException();
}
}

private function getFunctionParameterClassName(): string
{
return $this->marshalToCloudEventInterface ? CloudEventInterface::class : CloudEvent::class;
}

public function execute(ServerRequestInterface $request): ResponseInterface
Expand Down Expand Up @@ -145,9 +176,4 @@ private function fromBinaryRequest(

return CloudEvent::fromArray($content);
}

protected function getFunctionParameterClassName(): string
{
return $this->marshalToCloudEventInterface ? CloudEventInterface::class : CloudEvent::class;
}
}
54 changes: 54 additions & 0 deletions src/FunctionValidationTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
* Copyright 2023 Google LLC.
*
* 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\CloudFunctions;

use LogicException;
use ReflectionFunctionAbstract;
use ReflectionParameter;

trait FunctionValidationTrait
{
abstract public function validateFirstParameter(ReflectionParameter $param): void;

/**
* @throws LogicException
*/
abstract public function throwInvalidFirstParameterException(): void;

private function validateFunctionSignature(
ReflectionFunctionAbstract $reflection
) {
$parameters = $reflection->getParameters();
$parametersCount = count($parameters);

if ($parametersCount === 0) {
$this->throwInvalidFirstParameterException();
}

$this->validateFirstParameter($parameters[0]);

for ($i = 1; $i < $parametersCount; $i++) {
if (!$parameters[$i]->isOptional()) {
throw new LogicException(
'If your function accepts more than one parameter the '
. 'additional parameters must be optional'
);
}
}
}
}
35 changes: 29 additions & 6 deletions src/HttpFunctionWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,37 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use GuzzleHttp\Psr7\Response;
use ReflectionParameter;

class HttpFunctionWrapper extends ValidatingFunctionWrapper
class HttpFunctionWrapper extends FunctionWrapper
{
use FunctionValidationTrait;

public function __construct(callable $function)
{
parent::__construct($function);

$this->validateFunctionSignature(
$this->getFunctionReflection($function)
);
}

private function throwInvalidFirstParameterException(): void
{
throw new LogicException(sprintf(
'Your function must have "%s" as the typehint for the first argument',
ServerRequestInterface::class
));
}

private function validateFirstParameter(ReflectionParameter $param): void
{
$type = $param->getType();
if (!$type || $type->getName() !== ServerRequestInterface::class) {
$this->throwInvalidFirstParameterException();
}
}

public function execute(ServerRequestInterface $request): ResponseInterface
{
$path = $request->getUri()->getPath();
Expand All @@ -43,9 +71,4 @@ public function execute(ServerRequestInterface $request): ResponseInterface
'Function response must be string or ' . ResponseInterface::class
);
}

protected function getFunctionParameterClassName(): string
{
return ServerRequestInterface::class;
}
}
7 changes: 6 additions & 1 deletion src/Invoker.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ public function __construct($target, ?string $signatureType = null)
};
}

public function setErrorLogger(callable $logFunc)
{
$this->errorLogFunc = $logFunc;
}

public function handle(
ServerRequestInterface $request = null
): ResponseInterface {
Expand All @@ -84,7 +89,7 @@ public function handle(

try {
return $this->function->execute($request);
} catch (ParseError $e) {
} catch (BadRequestError $e) {
// Log the full error and stack trace
($this->errorLogFunc)((string) $e);
return new Response(400, [], 'Bad Request');
Expand Down
76 changes: 27 additions & 49 deletions src/TypedFunctionWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@
use LogicException;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use ReflectionParameter;

class TypedFunctionWrapper extends FunctionWrapper
{
use FunctionValidationTrait;

public const FUNCTION_STATUS_HEADER = 'X-Google-Status';

/** @var callable */
Expand All @@ -48,6 +49,29 @@ public function __construct(callable $function)
);
}

private function validateFirstParameter(ReflectionParameter $param): void
{
$type = $param->getType();
if ($type == null) {
$this->throwInvalidFirstParameterException();
}

try {
$this->functionArgClass = new ReflectionClass($type->getName());
} catch (ReflectionException $e) {
$name = $type->getName();
$message = $e->getMessage();
throw new LogicException("Could not find function parameter type $name, error: $message");
}
}

private function throwInvalidFirstParameterException(): void
{
throw new LogicException(
'Your function must declare exactly one required parameter that has a valid type hint'
);
}

public function execute(ServerRequestInterface $request): ResponseInterface
{
try {
Expand All @@ -60,7 +84,7 @@ public function execute(ServerRequestInterface $request): ResponseInterface
try {
$argInst->mergeFromJsonString($body);
} catch (Exception $e) {
throw new ParseError($e->getMessage(), 0, $e);
throw new BadRequestError($e->getMessage(), 0, $e);
}

$funcResult = call_user_func($this->function, $argInst);
Expand All @@ -72,50 +96,4 @@ public function execute(ServerRequestInterface $request): ResponseInterface

return new Response(200, ['content-type' => 'application/cloudevents+json'], $resultJson);
}

private function validateFunctionSignature(
ReflectionFunctionAbstract $reflection
) {
$parameters = $reflection->getParameters();
$parametersCount = count($parameters);

// Check there is at least one parameter
if ($parametersCount === 0) {
$this->throwInvalidFunctionException();
}

// Check the first parameter has the proper typehint
$type = $parameters[0]->getType();
if ($type == null) {
$this->throwInvalidFunctionException();
}

for ($i = 1; $i < $parametersCount; $i++) {
if (!$parameters[$i]->isOptional()) {
throw new LogicException(
'If your function accepts more than one parameter the '
. 'additional parameters must be optional'
);
}
}

try {
$this->functionArgClass = new ReflectionClass($type->getName());
} catch (ReflectionException $e) {
$name = $type->getName();
$message = $e->getMessage();
throw new LogicException("Could not find function parameter type $name, error: $message");
}
}

private function throwInvalidFunctionException()
{
throw new LogicException(
'Your function must declare exactly one required parameter that has a valid type hint'
);
}
}

class ParseError extends Exception
{
}
72 changes: 0 additions & 72 deletions src/ValidatingFunctionWrapper.php

This file was deleted.

Loading

0 comments on commit 76cf596

Please sign in to comment.