Skip to content

Commit

Permalink
Makes UriTemplate compliant against the latest tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Jun 6, 2023
1 parent 7759b59 commit 959690d
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 17 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ composer.lock
.php_cs.cache
.php-cs-fixer.cache
.phpunit.result.cache
.phpunit.cache
17 changes: 16 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,23 @@
"phpstan/phpstan-strict-rules": "^1.5.1",
"phpunit/phpunit": "^9.6.8",
"psr/http-factory": "^1.0.2",
"psr/http-message": "^2.0"
"psr/http-message": "^2.0",
"uri-templates/uritemplate-test": "*@dev"
},
"repositories": [
{
"type": "package",
"package": {
"name": "uri-templates/uritemplate-test",
"version": "dev-master",
"source": {
"url": "https://github.com/uri-templates/uritemplate-test.git",
"type": "git",
"reference": "master"
}
}
}
],
"autoload": {
"psr-4": {
"League\\Uri\\": ["uri", "components", "interfaces"]
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<exclude>
<directory suffix="Bench.php">uri</directory>
<directory suffix="Test.php">uri</directory>
<directory suffix="TestCase.php">uri</directory>
<directory suffix="Bench.php">components</directory>
<directory suffix="Test.php">components</directory>
<directory suffix="Bench.php">interfaces</directory>
Expand Down
50 changes: 38 additions & 12 deletions uri/UriTemplate/Operator.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use function preg_match;
use function rawurlencode;
use function str_contains;
use function str_replace;
use function substr;

/**
Expand All @@ -33,6 +32,24 @@
*/
enum Operator: string
{
/**
* RFC3986 Sub delimiter characters regular expression pattern.
*
* @link https://tools.ietf.org/html/rfc3986#section-2.2
*
* @var string
*/
private const REGEXP_CHARS_SUBDELIM = "\!\$&'\(\)\*\+,;\=%";

/**
* RFC3986 unreserved characters regular expression pattern.
*
* @link https://tools.ietf.org/html/rfc3986#section-2.3
*
* @var string
*/
private const REGEXP_CHARS_UNRESERVED = 'A-Za-z\d_\-\.~';

/**
* Expression regular expression pattern.
*
Expand Down Expand Up @@ -86,18 +103,8 @@ public function isNamed(): bool
*/
public function decode(string $var): string
{
static $delimiters = [
':', '/', '?', '#', '[', ']', '@', '!', '$',
'&', '\'', '(', ')', '*', '+', ',', ';', '=',
];

static $delimitersEncoded = [
'%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24',
'%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D',
];

return match ($this) {
Operator::ReservedChars, Operator::Fragment => str_replace($delimitersEncoded, $delimiters, rawurlencode($var)),
Operator::ReservedChars, Operator::Fragment => self::encodeQueryOrFragment($var),
default => rawurlencode($var),
};
}
Expand Down Expand Up @@ -230,4 +237,23 @@ private function replaceList(array $value, VarSpecifier $varSpec): array

return [implode(',', $pairs), $useQuery];
}

/**
* Returns the RFC3986 encoded string matched.
*/
private static function urlEncodeMatch(array $matches): string
{
return rawurlencode($matches[0]);
}

private static function encodeQueryOrFragment(string $uriPart): string
{
if ('' === $uriPart) {
return $uriPart;
}

static $pattern = '/[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.':@\/?]++|%(?![A-Fa-f\d]{2})/';

return (string) preg_replace_callback($pattern, self::urlEncodeMatch(...), $uriPart);
}
}
10 changes: 8 additions & 2 deletions uri/UriTemplate/TemplateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@
namespace League\Uri\UriTemplate;

use League\Uri\Exceptions\SyntaxError;
use PHPUnit\Framework\TestCase;
use League\Uri\UriTemplateSpecificationTestCase;

/**
* @coversDefaultClass \League\Uri\UriTemplate\Template
*/
final class TemplateTest extends TestCase
final class TemplateTest extends UriTemplateSpecificationTestCase
{
protected static array $testFilenames = [
'spec-examples.json',
'negative-tests.json',
'extended-tests.json',
];

/**
* @covers ::createFromString
* @covers ::__construct
Expand Down
4 changes: 2 additions & 2 deletions uri/UriTemplate/VariableBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ final class VariableBag implements ArrayAccess, Countable
private array $variables = [];

/**
* @param iterable<string,string|bool|int|float|array<string|bool|int|float>> $variables
* @param iterable<string|int, string|bool|int|float|array<string|bool|int|float>> $variables
*/
public function __construct(iterable $variables = [])
{
foreach ($variables as $name => $value) {
$this->assign($name, $value);
$this->assign((string) $name, $value);
}
}

Expand Down
85 changes: 85 additions & 0 deletions uri/UriTemplateSpecificationTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace League\Uri;

use JsonException;
use League\Uri\UriTemplate\Template;
use League\Uri\UriTemplate\VariableBag;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Throwable;
use const JSON_THROW_ON_ERROR;

abstract class UriTemplateSpecificationTestCase extends TestCase
{
protected static string $rootPath = __DIR__.'/../vendor/uri-templates/uritemplate-test';

/** @var array<string> */
protected static array $testFilenames;

/**
* @test
* @dataProvider uriTemplateSpecificationDataProvider
*/
public function it_can_pass_http_tests(string $title, int|null $level, array $variables, string $input, string|array|false $expected): void
{
if (false === $expected) {
$this->expectException(Throwable::class);
}

$result = Template::createFromString($input)->expand(new VariableBag($variables));

if (is_array($expected)) {
self::assertContains($result, $expected);
} else {
self::assertSame($expected, $result);
}
}

/**
* @throws JsonException
* @return iterable<string, array{
* title:string,
* level:int|null,
* variables:array{string, string|int},
* input:string,
* expected:string|array<string>|false
* }>
*/
public static function uriTemplateSpecificationDataProvider(): iterable
{
foreach (static::$testFilenames as $path) {
$path = static::$rootPath.'/'.ltrim($path, '/');
if (false === $content = file_get_contents($path)) {
throw new RuntimeException("unable to connect to the path `$path`.");
}

/** @var array $records */
$records = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
foreach ($records as $title => $testSuite) {
$level = $testSuite['level'] ?? null;
$variables = $testSuite['variables'];
foreach ($testSuite['testcases'] as $offset => [$input, $expected]) {
yield $title.' - '.$level.' # '.($offset + 1) => [
'title' => $title,
'level' => $level,
'variables' => $variables,
'input' => $input,
'expected' => $expected,
];
}
}
}
}
}

0 comments on commit 959690d

Please sign in to comment.