Skip to content

Commit

Permalink
feat: add ResourceHelperTrait (#428)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahdietz authored Dec 5, 2022
1 parent 6e01fd2 commit 0439efa
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/ResourceHelperTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php
/*
* Copyright 2022 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;

use Google\ApiCore\ValidationException;

/**
* Provides functionality for loading a resource name template map from a descriptor config,
* retrieving a PathTemplate, and parsing values using registered templates.
*
* @internal
*/
trait ResourceHelperTrait
{
/** @var array|null */
private static $templateMap;

// Must be implemented by extendees to call loadPathTemplates.
private static function registerPathTemplates()
{
// TODO: Add void return type hint.
self::$templateMap = [];
}

private static function loadPathTemplates(string $configPath, string $serviceName)
{
// TODO: Add void return type hint.
if (!is_null(self::$templateMap)) {
return;
}

$descriptors = require($configPath);
$templates = $descriptors['interfaces'][$serviceName]['templateMap'] ?? [];
self::$templateMap = [];
foreach ($templates as $name => $template) {
self::$templateMap[$name] = new PathTemplate($template);
}
}

private static function getPathTemplate(string $key)
{
// TODO: Add nullable return type reference once PHP 7.1 is minimum.
if (is_null(self::$templateMap)) {
self::registerPathTemplates();
}
return self::$templateMap[$key] ?? null;
}

private static function parseFormattedName(string $formattedName, string $template = null): array
{
if (is_null(self::$templateMap)) {
self::registerPathTemplates();
}
if ($template) {
if (!isset(self::$templateMap[$template])) {
throw new ValidationException("Template name $template does not exist");
}

return self::$templateMap[$template]->match($formattedName);
}

foreach (self::$templateMap as $templateName => $pathTemplate) {
try {
return $pathTemplate->match($formattedName);
} catch (ValidationException $ex) {
// Swallow the exception to continue trying other path templates
}
}

throw new ValidationException("Input did not match any known format. Input: $formattedName");
}
}
116 changes: 116 additions & 0 deletions tests/Tests/Unit/ResourceHelperTraitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php
/*
* Copyright 2022 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\Tests\Unit;

use Google\ApiCore\PathTemplate;
use Google\ApiCore\ResourceHelperTrait;
use Google\ApiCore\ValidationException;
use PHPUnit\Framework\TestCase;
use Yoast\PHPUnitPolyfills\Polyfills\ExpectException;

class ResourceHelperTraitTest extends TestCase
{
use ExpectException;

public function testLoadPathTemplates()
{
$got = ResourceHelperTraitStub::testLoadPathTemplates();
$this->assertEquals(count($got), 4);
$this->assertTrue($got['project'] instanceof PathTemplate);
}

public function testGetPathTemplate()
{
$got = ResourceHelperTraitStub::testGetPathTemplate('project');
$this->assertNotNull($got);
$this->assertTrue($got instanceof PathTemplate);
}

public function testGetPathTemplateNull()
{
$got = ResourceHelperTraitStub::testGetPathTemplate('does_not_exist');
$this->assertNull($got);
}

public function testParseName()
{
$got = ResourceHelperTraitStub::parseName('projects/abc123', 'project');
$this->assertEquals(count($got), 1);
$this->assertEquals($got['project'], 'abc123');
}

public function testParseNameInvalidTemplate()
{
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('Template name does_not_exist does not exist');

ResourceHelperTraitStub::parseName('projects/abc123', 'does_not_exist');
}

public function testParseNameNoMatchingPattern()
{
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('Input did not match any known format. Input: no/matching/pattern');

ResourceHelperTraitStub::parseName('no/matching/pattern');
}

}

class ResourceHelperTraitStub
{
use ResourceHelperTrait;

const CONFIG_PATH = __DIR__ . '/testdata/test_service_descriptor_config.php';
const SERVICE_NAME = 'test.interface.v1.api';

private static function registerPathTemplates()
{
self::loadPathTemplates(self::CONFIG_PATH, self::SERVICE_NAME);
}

public static function parseName($formattedName, $template = null)
{
return self::parseFormattedName($formattedName, $template);
}

public static function testLoadPathTemplates()
{
self::loadPathTemplates(self::CONFIG_PATH, self::SERVICE_NAME);
return self::$templateMap;
}

public static function testGetPathTemplate($key)
{
return self::getPathTemplate($key);
}
}
6 changes: 6 additions & 0 deletions tests/Tests/Unit/testdata/test_service_descriptor_config.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
'responsePageTokenGetMethod' => 'getNextPageToken',
],
],
'templateMap' => [
'project' => 'projects/{project}',
'location' => 'projects/{project}/locations/{location}',
'archive' => 'archives/{archive}',
'book' => 'archives/{archive}/books/{book}',
],
],
],
];

0 comments on commit 0439efa

Please sign in to comment.