Skip to content

Commit

Permalink
Refactor converting HTTPWG rulesets
Browse files Browse the repository at this point in the history
  • Loading branch information
gapple committed Feb 14, 2024
1 parent 6897d27 commit 3925c08
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 145 deletions.
157 changes: 157 additions & 0 deletions tests/Httpwg/HttpwgRuleExpectedConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

namespace gapple\Tests\StructuredFields\Httpwg;

use gapple\StructuredFields\Bytes;
use gapple\StructuredFields\Date;
use gapple\StructuredFields\Dictionary;
use gapple\StructuredFields\DisplayString;
use gapple\StructuredFields\InnerList;
use gapple\StructuredFields\Item;
use gapple\StructuredFields\OuterList;
use gapple\StructuredFields\Parameters;
use gapple\StructuredFields\Token;
use ParagonIE\ConstantTime\Base32;

/**
* @phpstan-type ExpectedItem array{ExpectedBareValue, ExpectedParameters}
* @phpstan-type ExpectedDictionary array<array{string, ExpectedTuple}>
* @phpstan-type ExpectedOuterList array<ExpectedTuple>
* @phpstan-type ExpectedInnerList array{array<ExpectedItem>, ExpectedParameters}
* @phpstan-type ExpectedTuple ExpectedItem|ExpectedInnerList
* @phpstan-type ExpectedBareValue bool|int|float|string|ExpectedTypedValue
* @phpstan-type ExpectedTypedValue object{__type: 'binary'|'date'|'displaystring'|'token', value: int|string}
* @phpstan-type ExpectedParameters array<array{string, ExpectedBareValue}>
*/
class HttpwgRuleExpectedConverter
{
/**
* Convert the expected value of an item tuple.
*
* @param ExpectedItem $item
* @return Item
*/
public static function item(array $item): Item
{
return new Item(self::value($item[0]), self::parameters($item[1]));
}

/**
* Convert the expected values of a dictionary.
*
* @param ExpectedDictionary $dictionary
* @return Dictionary
*/
public static function dictionary(array $dictionary): Dictionary
{
$output = new Dictionary();

foreach ($dictionary as $value) {
// Null byte is not supported as first character of property name.
if (strpos($value[0], "\0") === 0) {
throw new \UnexpectedValueException();
}

if (is_array($value[1][0])) {
$output->{$value[0]} = self::innerList($value[1]);
} else {
$output->{$value[0]} = self::item($value[1]);
}
}

return $output;
}

/**
* Convert the expected values of a list.
*
* @param ExpectedOuterList $list
* @return OuterList
*/
public static function list(array $list): OuterList
{
$output = new OuterList();

foreach ($list as $value) {
if (is_array($value[0])) {
$output[] = self::innerList($value);
} else {
$output[] = self::item($value);
}
}

return $output;
}

/**
* Convert the expected values of a parameters map.
*
* @param ExpectedParameters $parameters
* @return Parameters
*/
private static function parameters(array $parameters): Parameters
{
$output = new Parameters();

foreach ($parameters as $value) {
// Null byte is not supported as first character of property name.
if (strpos($value[0], "\0") === 0) {
throw new \UnexpectedValueException();
}

$output->{$value[0]} = self::value($value[1]);
}

return $output;
}

/**
* Convert the expected values of an inner list tuple.
*
* @param ExpectedInnerList $innerList
* @return InnerList
*/
private static function innerList(array $innerList): InnerList
{
$outputList = [];

foreach ($innerList[0] as $value) {
$outputList[] = new Item(self::value($value[0]), self::parameters($value[1]));
}

return new InnerList($outputList, self::parameters($innerList[1]));
}

/**
* Convert any encoded special values to typed objects.
*
* @param ExpectedBareValue $data
* The expected bare value.
* @return bool|int|float|string|Bytes|Date|DisplayString|Token
*/
private static function value($data)
{
if (!is_object($data)) {
return $data;
}

if (property_exists($data, '__type')) {
switch ($data->__type) {
case 'binary':
assert(is_string($data->value));
return new Bytes(Base32::decodeUpper($data->value));
case 'date':
assert(is_int($data->value));
return new Date($data->value);
case 'displaystring':
assert(is_string($data->value));
return new DisplayString($data->value);
case 'token':
assert(is_string($data->value));
return new Token($data->value);
}
}

throw new \UnexpectedValueException("Unknown value type");
}
}
162 changes: 17 additions & 145 deletions tests/Httpwg/HttpwgTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,9 @@

namespace gapple\Tests\StructuredFields\Httpwg;

use gapple\StructuredFields\Bytes;
use gapple\StructuredFields\Date;
use gapple\StructuredFields\Dictionary;
use gapple\StructuredFields\DisplayString;
use gapple\StructuredFields\InnerList;
use gapple\StructuredFields\Item;
use gapple\StructuredFields\OuterList;
use gapple\StructuredFields\Parameters;
use gapple\StructuredFields\Token;
use gapple\Tests\StructuredFields\Rule;
use gapple\Tests\StructuredFields\RulesetTest;
use ParagonIE\ConstantTime\Base32;

/**
* @phpstan-type ExpectedParameters array<array{string, mixed}>
* @phpstan-type ExpectedTuple array{mixed, ExpectedParameters}
* @phpstan-type ExpectedInnerList array{array<ExpectedTuple>, ExpectedParameters}
* @phpstan-type ExpectedOuterList array<ExpectedTuple>
* @phpstan-type ExpectedDictionary array<array{string, ExpectedTuple}>
*/
abstract class HttpwgTest extends RulesetTest
{
/**
Expand Down Expand Up @@ -51,16 +34,28 @@ protected function rulesetDataProvider(): array
}

$dataset = [];
foreach ($rules as $rule) {
if (isset($rule->expected)) {
foreach ($rules as $rawRule) {
if (isset($rawRule->expected)) {
try {
$rule->expected = self::{"convertExpected" . ucfirst($rule->header_type)}($rule->expected);
} catch (\UnexpectedValueException $e) {
switch ($rawRule->header_type) {
case 'item':
$rawRule->expected = HttpwgRuleExpectedConverter::item($rawRule->expected);
break;
case 'list':
$rawRule->expected = HttpwgRuleExpectedConverter::list($rawRule->expected);
break;
case 'dictionary':
$rawRule->expected = HttpwgRuleExpectedConverter::dictionary($rawRule->expected);
break;
default:
throw new \UnexpectedValueException('Unknown header type');
}
} catch (\UnexpectedValueException | \AssertionError $e) {
// Skip rules that cannot be parsed.
continue;
}
}
$rule = Rule::fromClass($rule);
$rule = Rule::fromClass($rawRule);

if (isset($dataset[$rule->name])) {
user_error(
Expand All @@ -74,127 +69,4 @@ protected function rulesetDataProvider(): array

return $dataset;
}

/**
* Convert the expected value of an item tuple.
*
* @param ExpectedTuple $item
* @return Item
*/
private static function convertExpectedItem(array $item): Item
{
return new Item(self::convertValue($item[0]), self::convertParameters($item[1]));
}

/**
* Convert the expected values of a parameters map.
*
* @param ExpectedParameters $parameters
* @return Parameters
*/
private static function convertParameters(array $parameters): Parameters
{
$output = new Parameters();

foreach ($parameters as $value) {
// Null byte is not supported as first character of property name.
if (strpos($value[0], "\0") === 0) {
throw new \UnexpectedValueException();
}

$output->{$value[0]} = self::convertValue($value[1]);
}

return $output;
}

/**
* Convert the expected values of an inner list tuple.
*
* @param ExpectedInnerList $innerList
* @return InnerList
*/
private static function convertInnerList(array $innerList): InnerList
{
$outputList = [];

foreach ($innerList[0] as $value) {
$outputList[] = new Item(self::convertValue($value[0]), self::convertParameters($value[1]));
}

return new InnerList($outputList, self::convertParameters($innerList[1]));
}

/**
* Convert the expected values of a list.
*
* @param ExpectedOuterList $list
* @return OuterList
*/
private static function convertExpectedList(array $list): OuterList
{
$output = new OuterList();

foreach ($list as $value) {
if (is_array($value[0])) {
$output[] = self::convertInnerList($value);
} else {
$output[] = self::convertExpectedItem($value);
}
}

return $output;
}

/**
* Convert the expected values of a dictionary.
*
* @param ExpectedDictionary $dictionary
* @return Dictionary
*/
private static function convertExpectedDictionary(array $dictionary): Dictionary
{
$output = new Dictionary();

foreach ($dictionary as $value) {
// Null byte is not supported as first character of property name.
if (strpos($value[0], "\0") === 0) {
throw new \UnexpectedValueException();
}

if (is_array($value[1][0])) {
$output->{$value[0]} = self::convertInnerList($value[1]);
} else {
$output->{$value[0]} = self::convertExpectedItem($value[1]);
}
}

return $output;
}

/**
* Convert any encoded special values to typed objects.
*
* @param mixed $data
* The expected bare value.
* @return mixed
*/
private static function convertValue($data)
{
if (is_object($data) && property_exists($data, '__type')) {
/** @var \stdClass $data */
switch ($data->__type) {
case 'token':
return new Token($data->value);
case 'binary':
return new Bytes(Base32::decodeUpper($data->value));
case 'date':
return new Date($data->value);
case 'displaystring':
return new DisplayString($data->value);
}
}

return $data;
}
}

0 comments on commit 3925c08

Please sign in to comment.