From 613d5db5a35c680c4061811b816d53cba59728b7 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:23:07 +0100 Subject: [PATCH 01/15] fix psalm error --- src/Exception/InvalidTopLevelType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exception/InvalidTopLevelType.php b/src/Exception/InvalidTopLevelType.php index b07ebee..a7948ce 100644 --- a/src/Exception/InvalidTopLevelType.php +++ b/src/Exception/InvalidTopLevelType.php @@ -3,6 +3,6 @@ namespace Innmind\MediaType\Exception; -class InvalidTopLevelType extends DomainException +final class InvalidTopLevelType extends DomainException { } From a1fa411f7d2d8b2d18301800cb3065e381d082c0 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:29:30 +0100 Subject: [PATCH 02/15] replace phpunit by blackbox --- .github/workflows/ci.yml | 97 +++----------------------------- .gitignore | 1 - blackbox.php | 27 +++++++++ composer.json | 9 ++- phpunit.xml.dist | 22 -------- tests/Fixtures/MediaTypeTest.php | 10 ++-- tests/MediaTypeTest.php | 38 +++++++------ tests/ParameterTest.php | 48 ++++++++-------- 8 files changed, 90 insertions(+), 162 deletions(-) create mode 100644 blackbox.php delete mode 100644 phpunit.xml.dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d650d88..189105d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,95 +1,16 @@ name: CI -on: [push] +on: [push, pull_request] jobs: - phpunit: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macOS-latest] - php-version: ['8.2', '8.3'] - dependencies: ['lowest', 'highest'] - name: 'PHPUnit' - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - coverage: none - - name: Composer - uses: "ramsey/composer-install@v2" - with: - dependency-versions: ${{ matrix.dependencies }} - - name: PHPUnit - run: vendor/bin/phpunit + blackbox: + uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main coverage: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macOS-latest] - php-version: ['8.2', '8.3'] - dependencies: ['lowest', 'highest'] - name: 'Coverage' - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - coverage: xdebug - - name: Composer - uses: "ramsey/composer-install@v2" - with: - dependency-versions: ${{ matrix.dependencies }} - - name: PHPUnit - run: vendor/bin/phpunit --coverage-clover=coverage.clover - env: - BLACKBOX_SET_SIZE: 1 - - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} + uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main + secrets: inherit psalm: - runs-on: ubuntu-latest - strategy: - matrix: - php-version: ['8.2', '8.3'] - dependencies: ['lowest', 'highest'] - name: 'Psalm' - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - - name: Composer - uses: "ramsey/composer-install@v2" - with: - dependency-versions: ${{ matrix.dependencies }} - - name: Psalm - run: vendor/bin/psalm --shepherd + uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main cs: - runs-on: ubuntu-latest - strategy: - matrix: - php-version: ['8.2'] - name: 'CS' - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - - name: Composer - uses: "ramsey/composer-install@v2" - - name: CS - run: vendor/bin/php-cs-fixer fix --diff --dry-run + uses: innmind/github-workflows/.github/workflows/cs.yml@main + with: + php-version: '8.2' diff --git a/.gitignore b/.gitignore index e96516b..987e2a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ composer.lock vendor -.phpunit.result.cache diff --git a/blackbox.php b/blackbox.php new file mode 100644 index 0000000..0eb49cf --- /dev/null +++ b/blackbox.php @@ -0,0 +1,27 @@ +when( + \getenv('ENABLE_COVERAGE') !== false, + static fn(Application $app) => $app + ->scenariiPerProof(1) + ->codeCoverage( + CodeCoverage::of( + __DIR__.'/src/', + __DIR__.'/tests/', + ) + ->dumpTo('coverage.clover') + ->enableWhen(true), + ), + ) + ->tryToProve(Load::directory(__DIR__.'/tests/')) + ->exit(); diff --git a/composer.json b/composer.json index ae41167..03e1352 100644 --- a/composer.json +++ b/composer.json @@ -30,18 +30,17 @@ } }, "require-dev": { - "phpunit/phpunit": "~10.2", - "innmind/black-box": "~5.0", - "vimeo/psalm": "~5.6", + "innmind/black-box": "~6.5", + "vimeo/psalm": "~5.6|dev-master", "innmind/coding-standard": "~2.0" }, "conflict": { - "innmind/black-box": "<5.0|~6.0" + "innmind/black-box": "<6.0|~7.0" }, "suggest": { "innmind/black-box": "For property based testing" }, "provide": { - "innmind/black-box-sets": "5.0" + "innmind/black-box-sets": "6.0" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index dc064fb..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - ./tests - - - - - . - - - ./tests - ./vendor - - - diff --git a/tests/Fixtures/MediaTypeTest.php b/tests/Fixtures/MediaTypeTest.php index 3d6aeb0..04ebe9f 100644 --- a/tests/Fixtures/MediaTypeTest.php +++ b/tests/Fixtures/MediaTypeTest.php @@ -5,8 +5,8 @@ use Fixtures\Innmind\MediaType\MediaType; use Innmind\MediaType\MediaType as Model; -use PHPUnit\Framework\TestCase; use Innmind\BlackBox\{ + PHPUnit\Framework\TestCase, PHPUnit\BlackBox, Set, Random, @@ -24,16 +24,16 @@ public function testInterface() foreach ($set->values(Random::default) as $value) { $this->assertInstanceOf(Set\Value::class, $value); - $this->assertTrue($value->isImmutable()); + $this->assertTrue($value->immutable()); $this->assertInstanceOf(Model::class, $value->unwrap()); } } - public function testAllGeneratedMediaTypesAreParseable() + public function testAllGeneratedMediaTypesAreParseable(): BlackBox\Proof { - $this + return $this ->forAll(MediaType::any()) - ->then(function($mediaType) { + ->prove(function($mediaType) { $this->assertSame( $mediaType->toString(), Model::of($mediaType->toString())->toString(), diff --git a/tests/MediaTypeTest.php b/tests/MediaTypeTest.php index f34442a..6d25c76 100644 --- a/tests/MediaTypeTest.php +++ b/tests/MediaTypeTest.php @@ -10,8 +10,8 @@ Exception\DomainException, }; use Innmind\Immutable\Sequence; -use PHPUnit\Framework\TestCase; use Innmind\BlackBox\{ + PHPUnit\Framework\TestCase, PHPUnit\BlackBox, Set, }; @@ -53,14 +53,14 @@ public function testStringCast() ); } - public function testThrowWhenTheTopLevelIsInvalid() + public function testThrowWhenTheTopLevelIsInvalid(): BlackBox\Proof { - $this + return $this ->forAll( - Set\Strings::any()->filter(static fn($string) => !MediaType::topLevels()->contains($string)), - Set\Strings::any(), + Set::strings()->exclude(static fn($string) => MediaType::topLevels()->contains($string)), + Set::strings(), ) - ->then(function($topLevel, $subType) { + ->prove(function($topLevel, $subType) { $this->expectException(InvalidTopLevelType::class); $this->expectExceptionMessage($topLevel); @@ -113,11 +113,11 @@ public function testMaybeParametersInDoubleQuotes() $this->assertSame('UTF-8', $parameters[0]->value()); } - public function testReturnNothingWhenInvalidMediaTypeString() + public function testReturnNothingWhenInvalidMediaTypeString(): BlackBox\Proof { - $this - ->forAll(Set\Strings::any()) - ->then(function($string) { + return $this + ->forAll(Set::strings()) + ->prove(function($string) { // this may optimistically generate a valid media type string at // some point but generally any random string is invalid $this->assertNull( @@ -139,13 +139,13 @@ public function testReturnNothingWhenTopLevelInvalid() ); } - public function testThrowWhenSubTypeInvalid() + public function testThrowWhenSubTypeInvalid(): BlackBox\Proof { - $this + return $this ->forAll( - Set\Strings::any()->filter(static fn($type) => !(bool) \preg_match('~^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$~', $type)), + Set::strings()->exclude(static fn($type) => (bool) \preg_match('~^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$~', $type)), ) - ->then(function($type) { + ->prove(function($type) { $this->expectException(DomainException::class); $this->expectExceptionMessage($type); @@ -153,13 +153,15 @@ public function testThrowWhenSubTypeInvalid() }); } - public function testThrowWhenSuffixInvalid() + public function testThrowWhenSuffixInvalid(): BlackBox\Proof { - $this + return $this ->forAll( - Set\Strings::atLeast(1)->filter(static fn($suffix) => !(bool) \preg_match('~^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$~', $suffix)), + Set::strings() + ->atLeast(1) + ->exclude(static fn($suffix) => (bool) \preg_match('~^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$~', $suffix)), ) - ->then(function($suffix) { + ->prove(function($suffix) { try { new MediaType('application', 'json', $suffix); $this->fail('it should throw'); diff --git a/tests/ParameterTest.php b/tests/ParameterTest.php index 054184e..c58365e 100644 --- a/tests/ParameterTest.php +++ b/tests/ParameterTest.php @@ -7,8 +7,8 @@ Parameter, Exception\DomainException, }; -use PHPUnit\Framework\TestCase; use Innmind\BlackBox\{ + PHPUnit\Framework\TestCase, PHPUnit\BlackBox, Set, }; @@ -17,14 +17,14 @@ class ParameterTest extends TestCase { use BlackBox; - public function testInterface() + public function testInterface(): BlackBox\Proof { - $this + return $this ->forAll( - Set\Strings::any()->filter(static fn($name) => (bool) \preg_match('~^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$~', $name)), - Set\Strings::any(), + Set::strings()->filter(static fn($name) => (bool) \preg_match('~^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$~', $name)), + Set::strings(), ) - ->then(function($name, $value) { + ->prove(function($name, $value) { $parameter = new Parameter($name, $value); $this->assertSame($name, $parameter->name()); @@ -33,14 +33,14 @@ public function testInterface() }); } - public function testThrowWhenNameInvalid() + public function testThrowWhenNameInvalid(): BlackBox\Proof { - $this + return $this ->forAll( - Set\Strings::any()->filter(static fn($name) => !(bool) \preg_match('~^[\w\-.]+$~', $name)), - Set\Strings::any(), + Set::strings()->exclude(static fn($name) => (bool) \preg_match('~^[\w\-.]+$~', $name)), + Set::strings(), ) - ->then(function($name, $value) { + ->prove(function($name, $value) { $this->expectException(DomainException::class); $this->expectExceptionMessage($name); @@ -48,24 +48,26 @@ public function testThrowWhenNameInvalid() }); } - public function testAcceptValueContainedInDoubleQuotes() + public function testAcceptValueContainedInDoubleQuotes(): BlackBox\Proof { - $this + return $this ->forAll( - Set\Composite::immutable( + Set::compose( static fn($first, $rest) => $first.$rest, - Set\Chars::alphanumerical(), - Set\Strings::madeOf( - Set\Chars::alphanumerical(), - Set\Elements::of('!', '#', '$', '&', '^', '_', '.', '-'), - )->between(0, 125), + Set::strings()->chars()->alphanumerical(), + Set::strings() + ->madeOf( + Set\Chars::alphanumerical(), + Set::of('!', '#', '$', '&', '^', '_', '.', '-'), + ) + ->between(0, 125), ), - Set\Strings::madeOf( - Set\Chars::alphanumerical(), - Set\Elements::of('!', '#', '$', '&', '^', '_', '.', '-', "'", '*', '+', '`', '|', '~'), + Set::strings()->madeOf( + Set::strings()->chars()->alphanumerical(), + Set::of('!', '#', '$', '&', '^', '_', '.', '-', "'", '*', '+', '`', '|', '~'), ), ) - ->then(function($name, $value) { + ->prove(function($name, $value) { $parameter = Parameter::of(\sprintf( '%s="%s"', $name, From 17d886275628c3b02e4ad4d1e1c6f4219c7127f1 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:29:35 +0100 Subject: [PATCH 03/15] add workflow to create releases --- .github/workflows/release.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b25ad8a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,11 @@ +name: Create release + +on: + push: + tags: + - '*' + +jobs: + release: + uses: innmind/github-workflows/.github/workflows/release.yml@main + secrets: inherit From ba3dbe1cbfd9808f82775a2689e664bd9d94022d Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:30:30 +0100 Subject: [PATCH 04/15] use innmind/static-analysis --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 03e1352..e43d35b 100644 --- a/composer.json +++ b/composer.json @@ -30,8 +30,8 @@ } }, "require-dev": { + "innmind/static-analysis": "^1.2.1", "innmind/black-box": "~6.5", - "vimeo/psalm": "~5.6|dev-master", "innmind/coding-standard": "~2.0" }, "conflict": { From d882ef82cba0b4471278dda45169ced66a571a21 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:32:55 +0100 Subject: [PATCH 05/15] remove use of deprecated code --- fixtures/MediaType.php | 38 +++++++++++++++----------------- tests/Fixtures/MediaTypeTest.php | 4 ++-- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/fixtures/MediaType.php b/fixtures/MediaType.php index 9e3faae..c1b19a9 100644 --- a/fixtures/MediaType.php +++ b/fixtures/MediaType.php @@ -12,21 +12,20 @@ final class MediaType { /** - * @return Set + * @return Set\Provider */ - public static function any(): Set + public static function any(): Set\Provider { $alphaNumerical = [...\range('A', 'Z'), ...\range('a', 'z'), ...\range(0, 9)]; - $validChars = Set\Composite::immutable( + $validChars = Set::compose( static fn($first, array $rest): string => \implode('', [$first, ...$rest]), - Set\Elements::of(...$alphaNumerical), - Set\Sequence::of( - Set\Elements::of('!', '#', '$', '&', '^', '_', '.', '-', ...$alphaNumerical), - Set\Integers::between(0, 126), - ), + Set::of(...$alphaNumerical), + Set::sequence( + Set::of('!', '#', '$', '&', '^', '_', '.', '-', ...$alphaNumerical), + )->between(0, 126), ); - return Set\Composite::immutable( + return Set::compose( static function($topLevel, $subType, $suffix, $parameterName, $parameterValue): Model { if ($parameterName) { return new Model( @@ -46,20 +45,19 @@ static function($topLevel, $subType, $suffix, $parameterName, $parameterValue): $suffix, ); }, - Set\Elements::of(...Model::topLevels()->toList()), + Set::of(...Model::topLevels()->toList()), $validChars, - Set\Either::any( - Set\Elements::of(''), - $validChars, - ), - Set\Either::any( + Set::either( + Set::of(''), $validChars, - Set\Elements::of(null), // to generate a type without a parameter ), - Set\Strings::madeOf( - Set\Chars::alphanumerical(), - Set\Elements::of('-', '.'), - )->between(1, 100), + $validChars->nullable(), // to generate a type without a parameter + Set::strings() + ->madeOf( + Set::strings()->chars()->alphanumerical(), + Set::of('-', '.'), + ) + ->between(1, 100), ); } } diff --git a/tests/Fixtures/MediaTypeTest.php b/tests/Fixtures/MediaTypeTest.php index 04ebe9f..090aa9a 100644 --- a/tests/Fixtures/MediaTypeTest.php +++ b/tests/Fixtures/MediaTypeTest.php @@ -20,9 +20,9 @@ public function testInterface() { $set = MediaType::any(); - $this->assertInstanceOf(Set::class, $set); + $this->assertInstanceOf(Set\Provider::class, $set); - foreach ($set->values(Random::default) as $value) { + foreach ($set->toSet()->values(Random::default) as $value) { $this->assertInstanceOf(Set\Value::class, $value); $this->assertTrue($value->immutable()); $this->assertInstanceOf(Model::class, $value->unwrap()); From 0be711d02ecfea501d093b64442280936bc26772 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:37:15 +0100 Subject: [PATCH 06/15] make MediaType constructor private --- CHANGELOG.md | 6 ++++++ fixtures/MediaType.php | 4 ++-- src/MediaType.php | 15 ++++++++++++++- tests/MediaTypeTest.php | 12 ++++++------ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5da1219..a1c1825 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +### Changed + +- `Innmind\MediaType\MediaType` constructor is now private, use `::from()` instead + ## 2.2.0 - 2023-09-16 ### Added diff --git a/fixtures/MediaType.php b/fixtures/MediaType.php index c1b19a9..d647b04 100644 --- a/fixtures/MediaType.php +++ b/fixtures/MediaType.php @@ -28,7 +28,7 @@ public static function any(): Set\Provider return Set::compose( static function($topLevel, $subType, $suffix, $parameterName, $parameterValue): Model { if ($parameterName) { - return new Model( + return Model::from( $topLevel, $subType, $suffix, @@ -39,7 +39,7 @@ static function($topLevel, $subType, $suffix, $parameterName, $parameterValue): ); } - return new Model( + return Model::from( $topLevel, $subType, $suffix, diff --git a/src/MediaType.php b/src/MediaType.php index 815f9d4..235080b 100644 --- a/src/MediaType.php +++ b/src/MediaType.php @@ -31,7 +31,7 @@ final class MediaType /** * @no-named-arguments */ - public function __construct( + private function __construct( string $topLevel, string $subType, string $suffix = '', @@ -58,6 +58,19 @@ public function __construct( $this->parameters = Sequence::of(...$parameters); } + /** + * @psalm-pure + * @no-named-arguments + */ + public static function from( + string $topLevel, + string $subType, + string $suffix = '', + Parameter ...$parameters, + ): self { + return new self($topLevel, $subType, $suffix, ...$parameters); + } + /** * @psalm-pure * @throws DomainException diff --git a/tests/MediaTypeTest.php b/tests/MediaTypeTest.php index 6d25c76..95d4afd 100644 --- a/tests/MediaTypeTest.php +++ b/tests/MediaTypeTest.php @@ -22,7 +22,7 @@ class MediaTypeTest extends TestCase public function testInterface() { - $mediaType = new MediaType( + $mediaType = MediaType::from( 'application', 'json', 'whatever', @@ -46,10 +46,10 @@ public function testStringCast() { $this->assertSame( 'application/json', - (new MediaType( + MediaType::from( 'application', 'json', - ))->toString(), + )->toString(), ); } @@ -64,7 +64,7 @@ public function testThrowWhenTheTopLevelIsInvalid(): BlackBox\Proof $this->expectException(InvalidTopLevelType::class); $this->expectExceptionMessage($topLevel); - new MediaType($topLevel, $subType); + MediaType::from($topLevel, $subType); }); } @@ -149,7 +149,7 @@ public function testThrowWhenSubTypeInvalid(): BlackBox\Proof $this->expectException(DomainException::class); $this->expectExceptionMessage($type); - new MediaType('application', $type); + MediaType::from('application', $type); }); } @@ -163,7 +163,7 @@ public function testThrowWhenSuffixInvalid(): BlackBox\Proof ) ->prove(function($suffix) { try { - new MediaType('application', 'json', $suffix); + MediaType::from('application', 'json', $suffix); $this->fail('it should throw'); } catch (DomainException $e) { $this->assertSame($suffix, $e->getMessage()); From be8991ff109daf52fda8c2eb176927b549ebc64e Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:39:06 +0100 Subject: [PATCH 07/15] make Parameter constructor private --- CHANGELOG.md | 1 + fixtures/MediaType.php | 2 +- src/Parameter.php | 10 +++++++++- tests/MediaTypeTest.php | 2 +- tests/ParameterTest.php | 4 ++-- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1c1825..b5766a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changed - `Innmind\MediaType\MediaType` constructor is now private, use `::from()` instead +- `Innmind\MediaType\Parameter` constructor is now private, use `::from()` instead ## 2.2.0 - 2023-09-16 diff --git a/fixtures/MediaType.php b/fixtures/MediaType.php index d647b04..73b3e6f 100644 --- a/fixtures/MediaType.php +++ b/fixtures/MediaType.php @@ -32,7 +32,7 @@ static function($topLevel, $subType, $suffix, $parameterName, $parameterValue): $topLevel, $subType, $suffix, - new Parameter( + Parameter::from( $parameterName, $parameterValue, ), diff --git a/src/Parameter.php b/src/Parameter.php index f67a379..07c1d08 100644 --- a/src/Parameter.php +++ b/src/Parameter.php @@ -22,7 +22,7 @@ final class Parameter private string $name; private string $value; - public function __construct(string $name, string $value) + private function __construct(string $name, string $value) { $format = self::NAME; @@ -34,6 +34,14 @@ public function __construct(string $name, string $value) $this->value = $value; } + /** + * @psalm-pure + */ + public static function from(string $name, string $value): self + { + return new self($name, $value); + } + /** * @psalm-pure * diff --git a/tests/MediaTypeTest.php b/tests/MediaTypeTest.php index 95d4afd..67df3db 100644 --- a/tests/MediaTypeTest.php +++ b/tests/MediaTypeTest.php @@ -26,7 +26,7 @@ public function testInterface() 'application', 'json', 'whatever', - $parameter = new Parameter('charset', 'UTF-8'), + $parameter = Parameter::from('charset', 'UTF-8'), ); $this->assertTrue($mediaType->parameters()->equals(Sequence::of($parameter))); diff --git a/tests/ParameterTest.php b/tests/ParameterTest.php index c58365e..3baf3c2 100644 --- a/tests/ParameterTest.php +++ b/tests/ParameterTest.php @@ -25,7 +25,7 @@ public function testInterface(): BlackBox\Proof Set::strings(), ) ->prove(function($name, $value) { - $parameter = new Parameter($name, $value); + $parameter = Parameter::from($name, $value); $this->assertSame($name, $parameter->name()); $this->assertSame($value, $parameter->value()); @@ -44,7 +44,7 @@ public function testThrowWhenNameInvalid(): BlackBox\Proof $this->expectException(DomainException::class); $this->expectExceptionMessage($name); - new Parameter($name, $value); + Parameter::from($name, $value); }); } From bd8ff3a1426c4556dedca860715ce926cdfdc58d Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:39:44 +0100 Subject: [PATCH 08/15] require php 8.4 --- .github/workflows/ci.yml | 10 ++++------ CHANGELOG.md | 1 + composer.json | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 189105d..779f162 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,13 +4,11 @@ on: [push, pull_request] jobs: blackbox: - uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main + uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@next coverage: - uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main + uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@next secrets: inherit psalm: - uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main + uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@next cs: - uses: innmind/github-workflows/.github/workflows/cs.yml@main - with: - php-version: '8.2' + uses: innmind/github-workflows/.github/workflows/cs.yml@next diff --git a/CHANGELOG.md b/CHANGELOG.md index b5766a8..afc942d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changed +- Require PHP `8.4` - `Innmind\MediaType\MediaType` constructor is now private, use `::from()` instead - `Innmind\MediaType\Parameter` constructor is now private, use `::from()` instead diff --git a/composer.json b/composer.json index e43d35b..fb55169 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "issues": "http://github.com/Innmind/MediaType/issues" }, "require": { - "php": "~8.2", + "php": "~8.4", "innmind/immutable": "~4.15|~5.0" }, "autoload": { From 8c27449914e00918d08142d8e10e6d38974e1517 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:40:19 +0100 Subject: [PATCH 09/15] update dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fb55169..264310d 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ }, "require": { "php": "~8.4", - "innmind/immutable": "~4.15|~5.0" + "innmind/immutable": "dev-next" }, "autoload": { "psr-4": { From a7f40289cbf173716c8fabdb659047f3495337f9 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:42:25 +0100 Subject: [PATCH 10/15] add MediaType::attempt() --- CHANGELOG.md | 4 ++++ src/MediaType.php | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afc942d..3fd84a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- `Innmind\MediaType\MediaType::attempt()` + ### Changed - Require PHP `8.4` diff --git a/src/MediaType.php b/src/MediaType.php index 235080b..9335584 100644 --- a/src/MediaType.php +++ b/src/MediaType.php @@ -8,6 +8,7 @@ DomainException, }; use Innmind\Immutable\{ + Attempt, Sequence, Set, Str, @@ -77,10 +78,7 @@ public static function from( */ public static function of(string $string): self { - return self::maybe($string)->match( - static fn($self) => $self, - static fn() => throw new DomainException($string), - ); + return self::attempt($string)->unwrap(); } /** @@ -105,6 +103,18 @@ public static function maybe(string $string): Maybe ); } + /** + * @psalm-pure + * + * @return Attempt + */ + public static function attempt(string $string): Attempt + { + return self::maybe($string)->attempt( + static fn() => throw new DomainException($string), + ); + } + /** * @psalm-pure */ From 6be784200a120c3f2035dadfbcc04cbe8a47cf55 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:55:28 +0100 Subject: [PATCH 11/15] use an enum to represent the top level --- CHANGELOG.md | 5 +++ fixtures/MediaType.php | 3 +- src/Exception/DomainException.php | 2 +- src/Exception/InvalidTopLevelType.php | 8 ---- src/MediaType.php | 64 +++++++++++---------------- src/TopLevel.php | 45 +++++++++++++++++++ tests/MediaTypeTest.php | 31 ++++--------- 7 files changed, 86 insertions(+), 72 deletions(-) delete mode 100644 src/Exception/InvalidTopLevelType.php create mode 100644 src/TopLevel.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fd84a0..71727fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ - Require PHP `8.4` - `Innmind\MediaType\MediaType` constructor is now private, use `::from()` instead - `Innmind\MediaType\Parameter` constructor is now private, use `::from()` instead +- `Innmind\MediaType\MediaType` top level is now represented by `Innmind\MediaType\TopLevel` + +### Removed + +- `Innmind\MediaType\Exception\InvalidTopLevelType` ## 2.2.0 - 2023-09-16 diff --git a/fixtures/MediaType.php b/fixtures/MediaType.php index 73b3e6f..f383316 100644 --- a/fixtures/MediaType.php +++ b/fixtures/MediaType.php @@ -6,6 +6,7 @@ use Innmind\MediaType\{ MediaType as Model, Parameter, + TopLevel, }; use Innmind\BlackBox\Set; @@ -45,7 +46,7 @@ static function($topLevel, $subType, $suffix, $parameterName, $parameterValue): $suffix, ); }, - Set::of(...Model::topLevels()->toList()), + Set::of(...TopLevel::cases()), $validChars, Set::either( Set::of(''), diff --git a/src/Exception/DomainException.php b/src/Exception/DomainException.php index fe19573..8c80e65 100644 --- a/src/Exception/DomainException.php +++ b/src/Exception/DomainException.php @@ -3,6 +3,6 @@ namespace Innmind\MediaType\Exception; -class DomainException extends \DomainException implements Exception +final class DomainException extends \DomainException implements Exception { } diff --git a/src/Exception/InvalidTopLevelType.php b/src/Exception/InvalidTopLevelType.php deleted file mode 100644 index a7948ce..0000000 --- a/src/Exception/InvalidTopLevelType.php +++ /dev/null @@ -1,8 +0,0 @@ - */ @@ -33,15 +31,11 @@ final class MediaType * @no-named-arguments */ private function __construct( - string $topLevel, + TopLevel $topLevel, string $subType, string $suffix = '', Parameter ...$parameters, ) { - if (!self::topLevels()->contains($topLevel)) { - throw new InvalidTopLevelType($topLevel); - } - $format = self::FORMAT; $regex = "~^$format$~"; @@ -64,7 +58,7 @@ private function __construct( * @no-named-arguments */ public static function from( - string $topLevel, + TopLevel $topLevel, string $subType, string $suffix = '', Parameter ...$parameters, @@ -93,8 +87,8 @@ public static function maybe(string $string): Maybe ->map(static fn($string) => $string->pregSplit('~[;,] ?~')) ->flatMap( static fn($splits) => self::capture($splits->first())->flatMap( - static fn(Str $topLevel, Str $subType, Str $suffix) => self::build( - $topLevel->toString(), + static fn(TopLevel $topLevel, Str $subType, Str $suffix) => self::build( + $topLevel, $subType->toString(), $suffix->toString(), $splits->drop(1), @@ -120,10 +114,10 @@ public static function attempt(string $string): Attempt */ public static function null(): self { - return new self('application', 'octet-stream'); + return new self(TopLevel::application, 'octet-stream'); } - public function topLevel(): string + public function topLevel(): TopLevel { return $this->topLevel; } @@ -155,41 +149,25 @@ public function toString(): string return \sprintf( '%s/%s%s%s', - $this->topLevel, + $this->topLevel->name, $this->subType, $this->suffix !== '' ? '+'.$this->suffix : '', !$parameters->empty() ? '; '.$parameters->toString() : '', ); } - /** - * List of allowed top levels - * - * @return Set - */ - public static function topLevels(): Set - { - return Set::strings( - 'application', - 'audio', - 'font', - 'example', - 'image', - 'message', - 'model', - 'multipart', - 'text', - 'video', - ); - } - private static function pattern(): string { $format = self::FORMAT; return \sprintf( "~%s/$format(\+$format)?([;,] $format=[\w\-.]+)?~", - Str::of('|')->join(self::topLevels())->toString(), + Str::of('|') + ->join( + Sequence::of(...TopLevel::cases()) + ->map(static fn($level) => $level->name), + ) + ->toString(), ); } @@ -199,7 +177,7 @@ private static function pattern(): string * @return Maybe */ private static function build( - string $topLevel, + TopLevel $topLevel, string $subType, string $suffix, Sequence $parameters, @@ -229,11 +207,19 @@ private static function capture(Maybe $string): Maybe\Comprehension return $string ->map(static fn($string) => $string->capture(\sprintf( "~^(?%s)/(?$format)(\+(?$format))?$~", - Str::of('|')->join(self::topLevels())->toString(), + Str::of('|') + ->join( + Sequence::of(...TopLevel::cases()) + ->map(static fn($level) => $level->name), + ) + ->toString(), ))) ->match( static fn($matches) => Maybe::all( - $matches->get('topLevel'), + $matches + ->get('topLevel') + ->map(static fn($level) => $level->toString()) + ->flatMap(TopLevel::maybe(...)), $matches->get('subType'), $matches->get('suffix')->otherwise(static fn() => Maybe::just(Str::of(''))), ), diff --git a/src/TopLevel.php b/src/TopLevel.php new file mode 100644 index 0000000..ab5abb3 --- /dev/null +++ b/src/TopLevel.php @@ -0,0 +1,45 @@ + + */ + public static function maybe(string $value): Maybe + { + return Maybe::of(match ($value) { + 'application' => self::application, + 'audio' => self::audio, + 'font' => self::font, + 'example' => self::example, + 'image' => self::image, + 'message' => self::message, + 'model' => self::model, + 'multipart' => self::multipart, + 'text' => self::text, + 'video' => self::video, + default => null, + }); + } +} diff --git a/tests/MediaTypeTest.php b/tests/MediaTypeTest.php index 67df3db..fd184bc 100644 --- a/tests/MediaTypeTest.php +++ b/tests/MediaTypeTest.php @@ -6,7 +6,7 @@ use Innmind\MediaType\{ MediaType, Parameter, - Exception\InvalidTopLevelType, + TopLevel, Exception\DomainException, }; use Innmind\Immutable\Sequence; @@ -23,14 +23,14 @@ class MediaTypeTest extends TestCase public function testInterface() { $mediaType = MediaType::from( - 'application', + TopLevel::application, 'json', 'whatever', $parameter = Parameter::from('charset', 'UTF-8'), ); $this->assertTrue($mediaType->parameters()->equals(Sequence::of($parameter))); - $this->assertSame('application', $mediaType->topLevel()); + $this->assertSame(TopLevel::application, $mediaType->topLevel()); $this->assertSame('json', $mediaType->subType()); $this->assertSame('whatever', $mediaType->suffix()); $this->assertSame('application/json+whatever; charset=UTF-8', $mediaType->toString()); @@ -47,27 +47,12 @@ public function testStringCast() $this->assertSame( 'application/json', MediaType::from( - 'application', + TopLevel::application, 'json', )->toString(), ); } - public function testThrowWhenTheTopLevelIsInvalid(): BlackBox\Proof - { - return $this - ->forAll( - Set::strings()->exclude(static fn($string) => MediaType::topLevels()->contains($string)), - Set::strings(), - ) - ->prove(function($topLevel, $subType) { - $this->expectException(InvalidTopLevelType::class); - $this->expectExceptionMessage($topLevel); - - MediaType::from($topLevel, $subType); - }); - } - public function testMaybe() { $mediaType = MediaType::maybe( @@ -78,7 +63,7 @@ public function testMaybe() ); $this->assertInstanceOf(MediaType::class, $mediaType); - $this->assertSame('application', $mediaType->topLevel()); + $this->assertSame(TopLevel::application, $mediaType->topLevel()); $this->assertSame('tree.octet-stream', $mediaType->subType()); $this->assertSame('suffix', $mediaType->suffix()); $this->assertSame(3, $mediaType->parameters()->size()); @@ -105,7 +90,7 @@ public function testMaybeParametersInDoubleQuotes() ); $this->assertInstanceOf(MediaType::class, $mediaType); - $this->assertSame('application', $mediaType->topLevel()); + $this->assertSame(TopLevel::application, $mediaType->topLevel()); $this->assertSame('octet-stream', $mediaType->subType()); $this->assertSame(1, $mediaType->parameters()->size()); $parameters = $mediaType->parameters()->toList(); @@ -149,7 +134,7 @@ public function testThrowWhenSubTypeInvalid(): BlackBox\Proof $this->expectException(DomainException::class); $this->expectExceptionMessage($type); - MediaType::from('application', $type); + MediaType::from(TopLevel::application, $type); }); } @@ -163,7 +148,7 @@ public function testThrowWhenSuffixInvalid(): BlackBox\Proof ) ->prove(function($suffix) { try { - MediaType::from('application', 'json', $suffix); + MediaType::from(TopLevel::application, 'json', $suffix); $this->fail('it should throw'); } catch (DomainException $e) { $this->assertSame($suffix, $e->getMessage()); From 57431d40ce499b3a867e19d3d65e65502ceac5d5 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 16:58:15 +0100 Subject: [PATCH 12/15] remove custom exceptions --- CHANGELOG.md | 2 ++ src/Exception/DomainException.php | 8 -------- src/Exception/Exception.php | 8 -------- src/MediaType.php | 12 +++++------- src/Parameter.php | 3 +-- tests/MediaTypeTest.php | 5 ++--- tests/ParameterTest.php | 7 ++----- 7 files changed, 12 insertions(+), 33 deletions(-) delete mode 100644 src/Exception/DomainException.php delete mode 100644 src/Exception/Exception.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 71727fe..b7f4183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ ### Removed - `Innmind\MediaType\Exception\InvalidTopLevelType` +- `Innmind\MediaType\Exception\Exception` +- `Innmind\MediaType\Exception\DomainException` ## 2.2.0 - 2023-09-16 diff --git a/src/Exception/DomainException.php b/src/Exception/DomainException.php deleted file mode 100644 index 8c80e65..0000000 --- a/src/Exception/DomainException.php +++ /dev/null @@ -1,8 +0,0 @@ -matches($regex)) { - throw new DomainException($subType); + throw new \DomainException($subType); } if ($suffix !== '' && !Str::of($suffix)->matches($regex)) { - throw new DomainException($suffix); + throw new \DomainException($suffix); } $this->topLevel = $topLevel; @@ -68,7 +65,8 @@ public static function from( /** * @psalm-pure - * @throws DomainException + * + * @throws \DomainException */ public static function of(string $string): self { @@ -105,7 +103,7 @@ public static function maybe(string $string): Maybe public static function attempt(string $string): Attempt { return self::maybe($string)->attempt( - static fn() => throw new DomainException($string), + static fn() => throw new \DomainException($string), ); } diff --git a/src/Parameter.php b/src/Parameter.php index 07c1d08..6316dc0 100644 --- a/src/Parameter.php +++ b/src/Parameter.php @@ -3,7 +3,6 @@ namespace Innmind\MediaType; -use Innmind\MediaType\Exception\DomainException; use Innmind\Immutable\{ Str, Maybe, @@ -27,7 +26,7 @@ private function __construct(string $name, string $value) $format = self::NAME; if (!Str::of($name)->matches("~^$format$~")) { - throw new DomainException($name); + throw new \DomainException($name); } $this->name = $name; diff --git a/tests/MediaTypeTest.php b/tests/MediaTypeTest.php index fd184bc..191f12d 100644 --- a/tests/MediaTypeTest.php +++ b/tests/MediaTypeTest.php @@ -7,7 +7,6 @@ MediaType, Parameter, TopLevel, - Exception\DomainException, }; use Innmind\Immutable\Sequence; use Innmind\BlackBox\{ @@ -131,7 +130,7 @@ public function testThrowWhenSubTypeInvalid(): BlackBox\Proof Set::strings()->exclude(static fn($type) => (bool) \preg_match('~^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$~', $type)), ) ->prove(function($type) { - $this->expectException(DomainException::class); + $this->expectException(\DomainException::class); $this->expectExceptionMessage($type); MediaType::from(TopLevel::application, $type); @@ -150,7 +149,7 @@ public function testThrowWhenSuffixInvalid(): BlackBox\Proof try { MediaType::from(TopLevel::application, 'json', $suffix); $this->fail('it should throw'); - } catch (DomainException $e) { + } catch (\DomainException $e) { $this->assertSame($suffix, $e->getMessage()); } }); diff --git a/tests/ParameterTest.php b/tests/ParameterTest.php index 3baf3c2..382a62c 100644 --- a/tests/ParameterTest.php +++ b/tests/ParameterTest.php @@ -3,10 +3,7 @@ namespace Tests\Innmind\MediaType; -use Innmind\MediaType\{ - Parameter, - Exception\DomainException, -}; +use Innmind\MediaType\Parameter; use Innmind\BlackBox\{ PHPUnit\Framework\TestCase, PHPUnit\BlackBox, @@ -41,7 +38,7 @@ public function testThrowWhenNameInvalid(): BlackBox\Proof Set::strings(), ) ->prove(function($name, $value) { - $this->expectException(DomainException::class); + $this->expectException(\DomainException::class); $this->expectExceptionMessage($name); Parameter::from($name, $value); From d4be19cae5c360a6b9c75d1acb0922906f108e13 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 1 Nov 2025 17:16:03 +0100 Subject: [PATCH 13/15] avoid checking the subtype and suffix conformity twice when parsing --- src/MediaType.php | 163 ++++++++++++++++++++-------------------------- 1 file changed, 69 insertions(+), 94 deletions(-) diff --git a/src/MediaType.php b/src/MediaType.php index 380dd3d..d2e7f92 100644 --- a/src/MediaType.php +++ b/src/MediaType.php @@ -18,21 +18,25 @@ final class MediaType /** @see https://tools.ietf.org/html/rfc6838#section-4.2 */ private const FORMAT = '[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}'; - private TopLevel $topLevel; - private string $subType; - private string $suffix; - /** @var Sequence */ - private Sequence $parameters; + private function __construct( + private TopLevel $topLevel, + private string $subType, + private string $suffix, + /** @var Sequence */ + private Sequence $parameters, + ) { + } /** + * @psalm-pure * @no-named-arguments */ - private function __construct( + public static function from( TopLevel $topLevel, string $subType, string $suffix = '', Parameter ...$parameters, - ) { + ): self { $format = self::FORMAT; $regex = "~^$format$~"; @@ -44,23 +48,12 @@ private function __construct( throw new \DomainException($suffix); } - $this->topLevel = $topLevel; - $this->subType = $subType; - $this->suffix = $suffix; - $this->parameters = Sequence::of(...$parameters); - } - - /** - * @psalm-pure - * @no-named-arguments - */ - public static function from( - TopLevel $topLevel, - string $subType, - string $suffix = '', - Parameter ...$parameters, - ): self { - return new self($topLevel, $subType, $suffix, ...$parameters); + return new self( + $topLevel, + $subType, + $suffix, + Sequence::of(...$parameters), + ); } /** @@ -84,14 +77,19 @@ public static function maybe(string $string): Maybe ->filter(static fn($string) => $string->matches(self::pattern())) ->map(static fn($string) => $string->pregSplit('~[;,] ?~')) ->flatMap( - static fn($splits) => self::capture($splits->first())->flatMap( - static fn(TopLevel $topLevel, Str $subType, Str $suffix) => self::build( - $topLevel, - $subType->toString(), - $suffix->toString(), - $splits->drop(1), + static fn($splits) => $splits + ->first() + ->flatMap(self::capture(...)) + ->flatMap( + static fn($self) => $splits + ->drop(1) + ->map(static fn($parameter) => $parameter->toString()) + ->map(Parameter::of(...)) + ->sink($self) + ->maybe(static fn($self, $parameter) => $parameter->map( + $self->withParameter(...), + )), ), - ), ); } @@ -112,7 +110,12 @@ public static function attempt(string $string): Attempt */ public static function null(): self { - return new self(TopLevel::application, 'octet-stream'); + return new self( + TopLevel::application, + 'octet-stream', + '', + Sequence::of(), + ); } public function topLevel(): TopLevel @@ -154,6 +157,16 @@ public function toString(): string ); } + private function withParameter(Parameter $parameter): self + { + return new self( + $this->topLevel, + $this->subType, + $this->suffix, + ($this->parameters)($parameter), + ); + } + private static function pattern(): string { $format = self::FORMAT; @@ -170,71 +183,33 @@ private static function pattern(): string } /** - * @param Sequence $parameters - * * @return Maybe */ - private static function build( - TopLevel $topLevel, - string $subType, - string $suffix, - Sequence $parameters, - ): Maybe { - if ($parameters->empty()) { - return Maybe::just(new self($topLevel, $subType, $suffix)); - } - - /** @psalm-suppress NamedArgumentNotAllowed */ - return self::captureParameters($parameters)->map( - static fn(Parameter ...$parameters) => new self( - $topLevel, - $subType, - $suffix, - ...$parameters, - ), - ); - } - - /** - * @param Maybe $string - */ - private static function capture(Maybe $string): Maybe\Comprehension + private static function capture(Str $string): Maybe { $format = self::FORMAT; - - return $string - ->map(static fn($string) => $string->capture(\sprintf( - "~^(?%s)/(?$format)(\+(?$format))?$~", - Str::of('|') - ->join( - Sequence::of(...TopLevel::cases()) - ->map(static fn($level) => $level->name), - ) - ->toString(), - ))) - ->match( - static fn($matches) => Maybe::all( - $matches - ->get('topLevel') - ->map(static fn($level) => $level->toString()) - ->flatMap(TopLevel::maybe(...)), - $matches->get('subType'), - $matches->get('suffix')->otherwise(static fn() => Maybe::just(Str::of(''))), - ), - static fn() => Maybe::all(Maybe::nothing()), - ); - } - - /** - * @param Sequence $parameters - */ - private static function captureParameters(Sequence $parameters): Maybe\Comprehension - { - return $parameters - ->map(static fn($parameter) => Parameter::of($parameter->toString())) - ->match( - static fn($first, $rest) => Maybe::all($first, ...$rest->toList()), - static fn() => Maybe::all(Maybe::nothing()), - ); + $matches = $string->capture(\sprintf( + "~^(?%s)/(?$format)(\+(?$format))?$~", + Str::of('|') + ->join( + Sequence::of(...TopLevel::cases()) + ->map(static fn($level) => $level->name), + ) + ->toString(), + )); + + return Maybe::all( + $matches + ->get('topLevel') + ->map(static fn($level) => $level->toString()) + ->flatMap(TopLevel::maybe(...)), + $matches->get('subType'), + $matches->get('suffix')->otherwise(static fn() => Maybe::just(Str::of(''))), + )->map(static fn(TopLevel $topLevel, Str $subType, Str $suffix) => new self( + $topLevel, + $subType->toString(), + $suffix->toString(), + Sequence::of(), + )); } } From d3c78f9a6c48a8fd8754eaf3dfda8733756ffee0 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 25 Jan 2026 15:57:17 +0100 Subject: [PATCH 14/15] tag dependencies --- .github/workflows/ci.yml | 8 ++++---- composer.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 779f162..2f3eecb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,11 +4,11 @@ on: [push, pull_request] jobs: blackbox: - uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main coverage: - uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main secrets: inherit psalm: - uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main cs: - uses: innmind/github-workflows/.github/workflows/cs.yml@next + uses: innmind/github-workflows/.github/workflows/cs.yml@main diff --git a/composer.json b/composer.json index 264310d..2c2f74a 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ }, "require": { "php": "~8.4", - "innmind/immutable": "dev-next" + "innmind/immutable": "~6.0" }, "autoload": { "psr-4": { @@ -30,7 +30,7 @@ } }, "require-dev": { - "innmind/static-analysis": "^1.2.1", + "innmind/static-analysis": "~1.3", "innmind/black-box": "~6.5", "innmind/coding-standard": "~2.0" }, From 3b723c0ebd7355ba3d42d082b729518476d9b92f Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 25 Jan 2026 15:59:45 +0100 Subject: [PATCH 15/15] add extensive CI --- .github/workflows/extensive.yml | 12 ++++++++++++ blackbox.php | 4 ++++ 2 files changed, 16 insertions(+) create mode 100644 .github/workflows/extensive.yml diff --git a/.github/workflows/extensive.yml b/.github/workflows/extensive.yml new file mode 100644 index 0000000..257f139 --- /dev/null +++ b/.github/workflows/extensive.yml @@ -0,0 +1,12 @@ +name: Extensive CI + +on: + push: + tags: + - '*' + paths: + - '.github/workflows/extensive.yml' + +jobs: + blackbox: + uses: innmind/github-workflows/.github/workflows/extensive.yml@main diff --git a/blackbox.php b/blackbox.php index 0eb49cf..502a180 100644 --- a/blackbox.php +++ b/blackbox.php @@ -10,6 +10,10 @@ }; Application::new($argv) + ->when( + \getenv('BLACKBOX_SET_SIZE') !== false, + static fn(Application $app) => $app->scenariiPerProof((int) \getenv('BLACKBOX_SET_SIZE')), + ) ->when( \getenv('ENABLE_COVERAGE') !== false, static fn(Application $app) => $app