Skip to content

Commit

Permalink
Add missing tests for DisplayString type
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Dec 22, 2023
1 parent f310ec5 commit d9c9f2c
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .php-cs-fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
'import_constants' => true,
'import_functions' => true,
],
'new_with_braces' => true,
'new_with_parentheses' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_phpdoc' => true,
'no_empty_comment' => true,
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ All Notable changes to `bakame/http-strucured-fields` will be documented in this
- Support for the `DisplayString` type
- `ByteSequence::tryFromEncoded`
- `Token::tryFromString`
- Adding functional API via `Bakame\Http\StructuredFields\parse` and `Bakame\Http\StructuredFields\build`
- Adding functional API via `http_parse_sf` and `http_build_sf`

### Fixed

Expand Down
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,26 @@ use Bakame\Http\StructuredFields\Token;

//1 - parsing an Accept Header
$headerValue = 'text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8';
$field = parse($headerValue, 'list');
$field = http_parse_structured_field('list', $headerValue);
$field[2]->value()->toString(); // returns 'application/xml'
$field[2]->parameter('q'); // returns (float) 0.9
$field[0]->value()->toString(); // returns 'text/html'
$field[0]->parameter('q'); // returns null

//2 - building a Retrofit Cookie Header
echo build(OuterList::new(
InnerList::fromAssociative(['foo', 'bar'], [
echo http_build_structured_field('list', [
[
['foo', 'bar'],
[
'expire' => $expire,
'path' => '/',
'max-age' => 2500,
'secure' => true,
'httponly' => true,
'samesite' => Token::fromString('lax'),
])
));
]
]
]);
// returns ("foo" "bar");expire=@1681504328;path="/";max-age=2500;secure;httponly=?0;samesite=lax
```

Expand Down Expand Up @@ -88,10 +91,10 @@ compliant HTTP field string value. To ease integration, the `__toString` method
implemented as an alias to the `toHttpValue` method.

````php
use function Bakame\Http\StructuredFields\parse;
use function Bakame\Http\StructuredFields\build;
use function Bakame\Http\StructuredFields\http_sf_parse;
use function Bakame\Http\StructuredFields\http_sf_build;

$field = parse('bar; baz=42; secure=?1', 'item');
$field = http_sf_parse('bar; baz=42; secure=?1', 'item');
echo $field->toHttpValue(); // return 'bar;baz=42;secure'
// on serialization the field has been normalized

Expand All @@ -102,7 +105,7 @@ header('foo: '. $field->toHttpValue());
//or
header('foo: '. $field);
//or
header('foo: '. build($field));
header('foo: '. http_sf_build($field));
````

All five (5) structured data type as defined in the RFC are provided inside the
Expand Down
21 changes: 0 additions & 21 deletions src/DataType.php

This file was deleted.

3 changes: 1 addition & 2 deletions src/DisplayString.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Bakame\Http\StructuredFields;

use Stringable;

use Throwable;

use function preg_match;
Expand All @@ -14,7 +13,7 @@
use function rawurlencode;

/**
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-3.3.5
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-sfbis#section-4.2.10
*/
final class DisplayString
{
Expand Down
20 changes: 20 additions & 0 deletions src/Item.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ public static function fromDecodedByteSequence(Stringable|string $value): self
return self::fromValue(Value::fromDecodedByteSequence($value));
}

/**
* Returns a new instance from an encoded byte sequence and an iterable of key-value parameters.
*
* @throws SyntaxError if the sequence is invalid
*/
public static function fromEncodedDisplayString(Stringable|string $value): self
{
return self::fromValue(Value::fromEncodedDisplayString($value));
}

/**
* Returns a new instance from a decoded byte sequence and an iterable of key-value parameters.
*
* @throws SyntaxError if the sequence is invalid
*/
public static function fromDecodedDisplayString(Stringable|string $value): self
{
return self::fromValue(Value::fromDecodedDisplayString($value));
}

/**
* Returns a new instance from a Token and an iterable of key-value parameters.
*
Expand Down
7 changes: 1 addition & 6 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ private static function extractString(string $httpValue): array
/**
* Returns a string from an HTTP textual representation and the consumed offset in a tuple.
*
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.5
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-sfbis#section-4.2.10
*
* @return array{0:DisplayString, 1:int}
*/
Expand Down Expand Up @@ -448,11 +448,6 @@ private static function extractDisplayString(string $httpValue): array
throw new SyntaxError("The HTTP textual representation '$httpValue' for a DisplayString contains uppercased percent encoding sequence.");
}

$intOctet = hexdec($octet);
if ($intOctet < 31 && $intOctet > 127) {
throw new SyntaxError("The HTTP textual representation '$httpValue' for a DisplayString contains invalid encoded sequence. $octet - $intOctet");
}

$output .= $char.$octet;
}

Expand Down
34 changes: 18 additions & 16 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,29 @@

declare(strict_types=1);

namespace Bakame\Http\StructuredFields;
use Bakame\Http\StructuredFields\Dictionary;
use Bakame\Http\StructuredFields\InnerList;
use Bakame\Http\StructuredFields\Item;
use Bakame\Http\StructuredFields\OuterList;
use Bakame\Http\StructuredFields\Parameters;
use Bakame\Http\StructuredFields\StructuredField;

if (!function_exists('parse')) {
if (!function_exists('http_parse_structured_field')) {
/**
* Parse a header conform to the HTTP Structured Field RFCs.
*
* @param 'dictionary'|'list'|'item' $type
*
*/
function parse(string $httpValue, string $type): StructuredField
{
return DataType::from($type)->newStructuredField($httpValue);
}
}

if (!function_exists('build')) {
/**
* Build an HTTP header value from a HTTP Structured Field instance.
* @param 'dictionary'|'parameters'|'list'|'innerlist'|'item' $type
* @throws OutOfRangeException If the value is unknown or undefined
*/
function build(StructuredField $structuredField): string
function http_parse_structured_field(string $type, string $httpValue): StructuredField
{
return $structuredField->toHttpValue();
return match ($type) {
'dictionary' => Dictionary::fromHttpValue($httpValue),
'parameters' => Parameters::fromHttpValue($httpValue),
'list' => OuterList::fromHttpValue($httpValue),
'innerlist' => InnerList::fromHttpValue($httpValue),
'item' => Item::fromHttpValue($httpValue), /* @phpstan-ignore-line */
default => throw new ValueError('The submitted type "'.$type.'" is unknown or not supported,'), /* @phpstan-ignore-line */
};
}
}
10 changes: 9 additions & 1 deletion tests/DisplayStringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ public function it_will_fail_on_invalid_decoded_string(): void
DisplayString::fromEncoded('%c3%28"');
}

#[Test]
public function it_will_fail_on_invalid_encoded_string_with_utf8_char(): void
{
$this->expectException(SyntaxError::class);

DisplayString::fromEncoded('%c3é"');
}

#[Test]
public function it_will_return_null_on_invalid_encoded_string(): void
{
Expand All @@ -37,7 +45,7 @@ public function it_will_return_null_on_invalid_encoded_string(): void
}

#[Test]
public function it_can_decode_base64_field(): void
public function it_can_decode_an_encoded_field(): void
{
$encoded = 'foo %22bar%22 \ baz';
$value = DisplayString::fromEncoded($encoded);
Expand Down
20 changes: 18 additions & 2 deletions tests/ItemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ public static function provideInvalidArguments(): iterable

#[Test]
#[DataProvider('provideFrom1stArgument')]
public function it_instantiate_many_types(ByteSequence|Token|DateTimeInterface|string|int|float|bool $value, string $expected): void
public function it_instantiate_many_types(ByteSequence|Token|DisplayString|DateTimeInterface|string|int|float|bool $value, string $expected): void
{
self::assertSame($expected, Item::new($value)->toHttpValue());
}

#[Test]
#[DataProvider('provideFrom1stArgument')]
public function it_updates_item(ByteSequence|Token|DateTimeInterface|string|int|float|bool $value, string $expected): void
public function it_updates_item(ByteSequence|Token|DisplayString|DateTimeInterface|string|int|float|bool $value, string $expected): void
{
$parameters = Parameters::fromAssociative(['foo' => 'bar']);

Expand All @@ -100,6 +100,7 @@ public static function provideFrom1stArgument(): iterable
return [
'decimal' => ['value' => 42.0, 'expected' => '42.0'],
'string' => ['value' => 'forty-two', 'expected' => '"forty-two"'],
'detail string' => ['value' => DisplayString::fromDecoded('😊'), 'expected' => '%"%f0%9f%98%8a"'],
'integer' => ['value' => 42, 'expected' => '42'],
'boolean true' => ['value' => true, 'expected' => '?1'],
'boolean false' => ['value' => false, 'expected' => '?0'],
Expand Down Expand Up @@ -241,6 +242,16 @@ public function it_instantiates_a_binary(): void
self::assertEquals($byteSequence, Item::fromEncodedByteSequence('Zm9vYmFy')->value());
}

#[Test]
public function it_instantiates_a_display_string(): void
{
$displayString = DisplayString::fromDecoded('😊');

self::assertEquals($displayString, Item::new(DisplayString::fromDecoded('😊'))->value());
self::assertEquals($displayString, Item::fromDecodedDisplayString('😊')->value());
self::assertEquals($displayString, Item::fromEncodedDisplayString('%f0%9f%98%8a')->value());
}

#[Test]
public function it_instantiates_a_string(): void
{
Expand Down Expand Up @@ -276,6 +287,10 @@ public static function itemTypeProvider(): iterable
'item' => Item::new('42'),
'expectedType' => Type::ByteSequence,
],
'display string' => [
'item' => Item::new(DisplayString::fromDecoded('😊')),
'expectedType' => Type::DisplayString,
],
'token' => [
'item' => Item::new(Token::fromString('forty-two')),
'expectedType' => Type::Token,
Expand All @@ -300,6 +315,7 @@ public function in_can_be_instantiated_using_bare_items(): void
{
$parameters = [
'string' => '42',
'displaystring' => DisplayString::fromDecoded('😊'),
'integer' => 42,
'float' => 4.2,
'boolean' => true,
Expand Down
16 changes: 16 additions & 0 deletions tests/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ public function it_will_fail_with_wrong_string_utf8(): void
$this->parser->parseDictionary('a="foébar"');
}

#[Test]
public function it_will_fail_with_wrong_string_encoded_char(): void
{
$this->expectException(SyntaxError::class);

$this->parser->parseDictionary('a=%"foobar'.rawurlencode(chr(10)).'"');
}

#[Test]
public function it_will_fail_with_wrong_detail_string_utf8(): void
{
$this->expectException(SyntaxError::class);

$this->parser->parseDictionary('a=%"foébar"');
}

#[Test]
public function it_fails_to_parse_invalid_string_1(): void
{
Expand Down
4 changes: 2 additions & 2 deletions tests/StructuredFieldTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public function it_can_pass_http_wg_tests(Record $test): void
$this->expectException(SyntaxError::class);
}

$structuredField = parse(implode(',', $test->raw), $test->type);
$structuredField = http_parse_structured_field($test->type, implode(',', $test->raw));

if (!$test->mustFail) {
self::assertSame(implode(',', $test->canonical), build($structuredField));
self::assertSame(implode(',', $test->canonical), $structuredField->toHttpValue());
}
}

Expand Down

0 comments on commit d9c9f2c

Please sign in to comment.