diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fa3b98..17789c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,5 +14,3 @@ jobs: uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main cs: uses: innmind/github-workflows/.github/workflows/cs.yml@main - with: - php-version: '8.2' 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/CHANGELOG.md b/CHANGELOG.md index c4449a0..636b32a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## [Unreleased] + +### Added + +- `Innmind\HttpTransport\Config` +- `Innmind\HttpTransport\Transport::map()` + +### Changed + +- Requires PHP `8.4` +- `Innmind\HttpTransport\Transport` is now a final class, all previous implementations are now flagged as internal +- Requires `innmind/http:~9.0` +- Requires `innmind/time:~1.0` + +### Removed + +- `Innmind\HttpTransport\Curl::maxConcurrency()` use `::map()` instead +- `Innmind\HttpTransport\Curl::disableSSLVerification()` use `::map()` instead +- `Innmind\HttpTransport\Curl::proxy()` use `::map()` instead +- `Innmind\HttpTransport\Curl::heartbeat()` (as it was intended for internal use) + +### Fixed + +- PHP `8.5` deprecation + ## 8.1.0 - 2025-09-18 ### Added diff --git a/blackbox.php b/blackbox.php index 50022e2..67a2468 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 diff --git a/composer.json b/composer.json index 633bdf7..5efad05 100644 --- a/composer.json +++ b/composer.json @@ -15,17 +15,16 @@ "issues": "http://github.com/Innmind/HttpTransport/issues" }, "require": { - "php": "~8.2", + "php": "~8.4", "ext-curl": "*", - "innmind/http": "~8.0", - "innmind/filesystem": "~8.1", + "innmind/http": "~9.0", + "innmind/filesystem": "~9.0", "psr/log": "~3.0", "ramsey/uuid": "^4.7", - "innmind/time-warp": "~4.0", - "innmind/time-continuum": "^4.1.1", - "innmind/immutable": "~5.14", - "innmind/url": "~4.0", - "innmind/io": "~3.2" + "innmind/time": "~1.0", + "innmind/immutable": "~6.0", + "innmind/url": "~5.0", + "innmind/io": "~4.0" }, "autoload": { "psr-4": { @@ -38,8 +37,8 @@ } }, "require-dev": { - "innmind/static-analysis": "^1.2.1", + "innmind/static-analysis": "~1.3", "innmind/coding-standard": "~2.0", - "innmind/black-box": "~6.2" + "innmind/black-box": "~6.5" } } diff --git a/src/CircuitBreaker.php b/src/CircuitBreaker.php index 2ae5bae..35aa594 100644 --- a/src/CircuitBreaker.php +++ b/src/CircuitBreaker.php @@ -13,10 +13,10 @@ Header\Value, }; use Innmind\Url\Url; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, - PointInTime, + Point, }; use Innmind\Immutable\{ Map, @@ -24,15 +24,16 @@ }; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ -final class CircuitBreaker implements Transport +final class CircuitBreaker implements Implementation { /** - * @param Map $openedCircuits + * @param Map $openedCircuits */ private function __construct( - private Transport $fulfill, + private Implementation $fulfill, private Clock $clock, private Period $delayBeforeRetry, private Map $openedCircuits, @@ -54,7 +55,7 @@ public function __invoke(Request $request): Either } public static function of( - Transport $fulfill, + Implementation $fulfill, Clock $clock, Period $delayBeforeRetry, ): self { @@ -66,6 +67,20 @@ public static function of( ); } + /** + * @psalm-mutation-free + */ + #[\Override] + public function map(callable $map): self + { + return new self( + $this->fulfill->map($map), + $this->clock, + $this->delayBeforeRetry, + Map::of(), + ); + } + private function open( Request $request, ServerError|ConnectionFailed $error, diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000..2b3b4c0 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,106 @@ +> $maxConcurrency + * @param Maybe $proxy + */ + private function __construct( + private Maybe $maxConcurrency, + private bool $verifySSL, + private Maybe $proxy, + ) { + } + + /** + * @psalm-pure + */ + public static function new(): self + { + /** @var Maybe> */ + $maxConcurrency = Maybe::nothing(); + /** @var Maybe */ + $proxy = Maybe::nothing(); + + return new self( + $maxConcurrency, + true, + $proxy, + ); + } + + /** + * @param int<1, max> $max + */ + #[\NoDiscard] + public function limitConcurrencyTo(int $max): self + { + return new self( + Maybe::just($max), + $this->verifySSL, + $this->proxy, + ); + } + + /** + * You should use this method only when trying to call a server you own that + * uses a self signed certificate that will fail the verification. + */ + #[\NoDiscard] + public function disableSSLVerification(): self + { + return new self( + $this->maxConcurrency, + false, + $this->proxy, + ); + } + + #[\NoDiscard] + public function throughProxy(Url $proxy): self + { + return new self( + $this->maxConcurrency, + $this->verifySSL, + Maybe::just($proxy), + ); + } + + /** + * @internal + * + * @return Maybe> + */ + public function maxConcurrency(): Maybe + { + return $this->maxConcurrency; + } + + /** + * @internal + */ + public function verifySSL(): bool + { + return $this->verifySSL; + } + + /** + * @internal + * + * @return Maybe + */ + public function proxy(): Maybe + { + return $this->proxy; + } +} diff --git a/src/Curl.php b/src/Curl.php index c4eda29..9226ce5 100644 --- a/src/Curl.php +++ b/src/Curl.php @@ -12,7 +12,7 @@ Factory\Header\Factory, }; use Innmind\Url\Url; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, }; @@ -20,14 +20,16 @@ use Innmind\Immutable\Either; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ -final class Curl implements Transport +final class Curl implements Implementation { /** * @param \Closure(): void $heartbeat */ private function __construct( + private Config $config, private Factory $headerFactory, private IO $io, private Concurrency $concurrency, @@ -64,6 +66,7 @@ public static function of( $io ??= IO::fromAmbientAuthority(); return new self( + Config::new(), Factory::new($clock), $io, Concurrency::new(), @@ -75,81 +78,53 @@ public static function of( } /** - * @psalm-mutation-free - * - * @param positive-int $max - */ - #[\NoDiscard] - public function maxConcurrency(int $max): self - { - return new self( - $this->headerFactory, - $this->io, - Concurrency::new($max), - $this->timeout, - $this->heartbeat, - $this->disableSSLVerification, - $this->proxy, - ); - } - - /** - * @psalm-mutation-free + * @internal * * @param Period $timeout Only seconds are allowed * @param callable(): void $heartbeat */ - #[\NoDiscard] - public function heartbeat(Period $timeout, ?callable $heartbeat = null): self - { + public static function async( + Clock $clock, + IO $io, + Period $timeout, + callable $heartbeat, + ): self { return new self( - $this->headerFactory, - $this->io, - $this->concurrency, + Config::new(), + Factory::new($clock), + $io, + Concurrency::new(), $timeout, - match ($heartbeat) { - null => static fn() => null, - default => \Closure::fromCallable($heartbeat), - }, - $this->disableSSLVerification, - $this->proxy, + \Closure::fromCallable($heartbeat), + false, + null, ); } /** - * You should use this method only when trying to call a server you own that - * uses a self signed certificate that will fail the verification. - * * @psalm-mutation-free */ - #[\NoDiscard] - public function disableSSLVerification(): self + #[\Override] + public function map(callable $map): self { - return new self( - $this->headerFactory, - $this->io, - $this->concurrency, - $this->timeout, - $this->heartbeat, - true, - $this->proxy, - ); - } + /** @psalm-suppress ImpureFunctionCall */ + $config = $map($this->config); - /** - * @psalm-mutation-free - */ - #[\NoDiscard] - public function proxy(Url $proxy): self - { return new self( + $config, $this->headerFactory, $this->io, - $this->concurrency, + Concurrency::new($config->maxConcurrency()->match( + static fn($max) => $max, + static fn() => null, + )), $this->timeout, $this->heartbeat, - $this->disableSSLVerification, - $proxy, + !$config->verifySSL(), + $config->proxy()->match( + static fn($proxy) => $proxy, + static fn() => null, + ), ); } } diff --git a/src/Curl/Concurrency.php b/src/Curl/Concurrency.php index 30102ee..0b7db21 100644 --- a/src/Curl/Concurrency.php +++ b/src/Curl/Concurrency.php @@ -8,7 +8,7 @@ Failure, Success, }; -use Innmind\TimeContinuum\Period; +use Innmind\Time\Period; use Innmind\Immutable\{ Sequence, Map, diff --git a/src/Curl/Ready.php b/src/Curl/Ready.php index 03f7317..7f76227 100644 --- a/src/Curl/Ready.php +++ b/src/Curl/Ready.php @@ -167,7 +167,6 @@ private function finalize(int $errorCode): Either ->toEncoding(Str\Encoding::ascii) ->length(), ); - \curl_close($this->handle); } } diff --git a/src/ExponentialBackoff.php b/src/ExponentialBackoff.php index c1a4f20..304b46a 100644 --- a/src/ExponentialBackoff.php +++ b/src/ExponentialBackoff.php @@ -3,25 +3,32 @@ namespace Innmind\HttpTransport; -use Innmind\Http\Request; -use Innmind\Http\Response\StatusCode; -use Innmind\TimeWarp\Halt; -use Innmind\TimeContinuum\Period; +use Innmind\Http\{ + Request, + Response\StatusCode, +}; +use Innmind\Time\{ + Halt, + Period, +}; use Innmind\Immutable\{ Sequence, Either, }; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ -final class ExponentialBackoff implements Transport +final class ExponentialBackoff implements Implementation { /** + * @psalm-mutation-free + * * @param Sequence $retries */ private function __construct( - private Transport $fulfill, + private Implementation $fulfill, private Halt $halt, private Sequence $retries, ) { @@ -33,7 +40,10 @@ public function __invoke(Request $request): Either return $this->fulfill($request, $this->retries); } - public static function of(Transport $fulfill, Halt $halt): self + /** + * @psalm-pure + */ + public static function of(Implementation $fulfill, Halt $halt): self { /** @psalm-suppress ArgumentTypeCoercion Periods are necessarily positive */ return new self( @@ -49,6 +59,18 @@ public static function of(Transport $fulfill, Halt $halt): self ); } + /** + * @psalm-mutation-free + */ + #[\Override] + public function map(callable $map): self + { + return self::of( + $this->fulfill->map($map), + $this->halt, + ); + } + /** * @param Sequence $retries * diff --git a/src/FollowRedirections.php b/src/FollowRedirections.php index f83ca17..75860b9 100644 --- a/src/FollowRedirections.php +++ b/src/FollowRedirections.php @@ -16,15 +16,18 @@ use Innmind\Immutable\Either; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ -final class FollowRedirections implements Transport +final class FollowRedirections implements Implementation { /** + * @psalm-mutation-free + * * @param int<1, max> $hops */ private function __construct( - private Transport $fulfill, + private Implementation $fulfill, private int $hops, ) { } @@ -35,11 +38,23 @@ public function __invoke(Request $request): Either return $this->fulfill($request, $this->hops); } - public static function of(Transport $fulfill): self + /** + * @psalm-pure + */ + public static function of(Implementation $fulfill): self { return new self($fulfill, 5); } + /** + * @psalm-mutation-free + */ + #[\Override] + public function map(callable $map): self + { + return self::of($this->fulfill->map($map)); + } + /** * @param int<0, max> $hops * diff --git a/src/Implementation.php b/src/Implementation.php new file mode 100644 index 0000000..04d5980 --- /dev/null +++ b/src/Implementation.php @@ -0,0 +1,28 @@ + + */ + #[\NoDiscard] + public function __invoke(Request $request): Either; + + /** + * @psalm-mutation-free + * + * @param callable(Config): Config $map + */ + #[\NoDiscard] + public function map(callable $map): self; +} diff --git a/src/Logger.php b/src/Logger.php index 063a798..7f06ce8 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -17,12 +17,13 @@ use Ramsey\Uuid\Uuid; /** - * @psalm-import-type Errors from Transport + * @internal + * @psalm-import-type Errors from Implementation */ -final class Logger implements Transport +final class Logger implements Implementation { private function __construct( - private Transport $fulfill, + private Implementation $fulfill, private LoggerInterface $logger, ) { } @@ -37,11 +38,23 @@ public function __invoke(Request $request): Either ->leftMap(fn($error) => $this->logError($error, $reference)); } - public static function psr(Transport $fulfill, LoggerInterface $logger): self + public static function psr(Implementation $fulfill, LoggerInterface $logger): self { return new self($fulfill, $logger); } + /** + * @psalm-mutation-free + */ + #[\Override] + public function map(callable $map): self + { + return new self( + $this->fulfill->map($map), + $this->logger, + ); + } + private function logRequest(Request $request): string { $this->logger->debug( diff --git a/src/Transport.php b/src/Transport.php index 5e482fa..4ed65b0 100644 --- a/src/Transport.php +++ b/src/Transport.php @@ -4,16 +4,115 @@ namespace Innmind\HttpTransport; use Innmind\Http\Request; +use Innmind\IO\IO; +use Innmind\Time\{ + Clock, + Period, + Halt, +}; use Innmind\Immutable\Either; +use Psr\Log\LoggerInterface; /** * @psalm-type Errors = Failure|ConnectionFailed|MalformedResponse|Information|Redirection|ClientError|ServerError */ -interface Transport +final class Transport { + private function __construct( + private Implementation $implementation, + ) { + } + /** * @return Either */ #[\NoDiscard] - public function __invoke(Request $request): Either; + public function __invoke(Request $request): Either + { + return ($this->implementation)($request); + } + + public static function curl( + Clock $clock, + ?IO $io = null, + ): self { + return new self(Curl::of($clock, $io)); + } + + public static function circuitBreaker( + self $transport, + Clock $clock, + Period $delayBeforeRetry, + ): self { + return new self(CircuitBreaker::of( + $transport->implementation, + $clock, + $delayBeforeRetry, + )); + } + + public static function exponentialBackoff( + self $transport, + Halt $halt, + ): self { + return new self(ExponentialBackoff::of( + $transport->implementation, + $halt, + )); + } + + public static function followRedirections(self $transport): self + { + return new self(FollowRedirections::of($transport->implementation)); + } + + public static function logger( + self $transport, + LoggerInterface $logger, + ): self { + return new self(Logger::psr( + $transport->implementation, + $logger, + )); + } + + /** + * @internal + * + * @param callable(): void $heartbeat + */ + public static function async( + Clock $clock, + IO $io, + Period $timeout, + callable $heartbeat, + ): self { + return new self(Curl::async( + $clock, + $io, + $timeout, + $heartbeat, + )); + } + + /** + * @internal + * + * @param callable(Request): Either $via + */ + public static function via(callable $via): self + { + return new self(Via::of($via)); + } + + /** + * @psalm-mutation-free + * + * @param callable(Config): Config $map + */ + #[\NoDiscard] + public function map(callable $map): self + { + return new self($this->implementation->map($map)); + } } diff --git a/src/Via.php b/src/Via.php new file mode 100644 index 0000000..f4f9a10 --- /dev/null +++ b/src/Via.php @@ -0,0 +1,46 @@ + $fulfill + */ + private function __construct( + private \Closure $fulfill, + ) { + } + + #[\Override] + public function __invoke(Request $request): Either + { + return ($this->fulfill)($request); + } + + /** + * @param callable(Request): Either $fulfill + */ + public static function of(callable $fulfill): self + { + // todo support exposing a Config to the callable ? + return new self(\Closure::fromCallable($fulfill)); + } + + /** + * @psalm-mutation-free + */ + #[\Override] + public function map(callable $map): self + { + return $this; + } +} diff --git a/tests/CircuitBreakerTest.php b/tests/CircuitBreakerTest.php index 51e38f8..7283b1a 100644 --- a/tests/CircuitBreakerTest.php +++ b/tests/CircuitBreakerTest.php @@ -4,8 +4,6 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - CircuitBreaker, - Curl, Transport, Success, ServerError, @@ -21,7 +19,7 @@ ProtocolVersion, }; use Innmind\Url\Url; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, }; @@ -30,18 +28,6 @@ class CircuitBreakerTest extends TestCase { - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - CircuitBreaker::of( - Curl::of(Clock::live()), - Clock::live(), - Period::millisecond(1), - ), - ); - } - public function testDoesntOpenCircuitOnSuccessfulResponse() { $request = Request::of( @@ -55,18 +41,8 @@ public function testDoesntOpenCircuitOnSuccessfulResponse() ); $expected = Either::right(new Success($request, $response)); - $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -88,18 +64,8 @@ public function testDoesntOpenCircuitOnRedirectionResponse() ); $expected = Either::left(new Redirection($request, $response)); - $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -121,18 +87,8 @@ public function testDoesntOpenCircuitOnClientErrorResponse() ); $expected = Either::left(new ClientError($request, $response)); - $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -154,18 +110,8 @@ public function testOpenCircuitOnServerError() ); $expected = Either::left(new ServerError($request, $response)); - $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -188,18 +134,8 @@ public function testOpenCircuitOnConnectionFailure() ); $expected = Either::left(new ConnectionFailed($request, '')); - $fulfill = CircuitBreaker::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return $this->expected; - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static fn() => $expected), Clock::live(), Period::hour(1), ); @@ -235,19 +171,12 @@ public function testOpenCircuitOnlyForTheDomainThatFailed() ); $expected1 = Either::left(new ServerError($request1, $response1)); $expected2 = Either::right(new Success($request2, $response2)); + $expected = [$expected1, $expected2]; - $fulfill = CircuitBreaker::of( - new class([$expected1, $expected2]) implements Transport { - public function __construct( - private array $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return \array_shift($this->expected); - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static function() use (&$expected) { + return \array_shift($expected); + }), Clock::live(), Period::hour(1), ); @@ -273,19 +202,12 @@ public function testRecloseTheCircuitAfterTheSpecifiedDelay() ); $expected1 = Either::left(new ServerError($request, $response1)); $expected2 = Either::right(new Success($request, $response2)); + $expected = [$expected1, $expected2]; - $fulfill = CircuitBreaker::of( - new class([$expected1, $expected2]) implements Transport { - public function __construct( - private array $expected, - ) { - } - - public function __invoke(Request $_): Either - { - return \array_shift($this->expected); - } - }, + $fulfill = Transport::circuitBreaker( + Transport::via(static function() use (&$expected) { + return \array_shift($expected); + }), Clock::live(), Period::millisecond(1), ); diff --git a/tests/ClientErrorTest.php b/tests/ClientErrorTest.php index 14a4f7a..45edd53 100644 --- a/tests/ClientErrorTest.php +++ b/tests/ClientErrorTest.php @@ -19,7 +19,7 @@ class ClientErrorTest extends TestCase { public function testAcceptClientErrorfulResponses() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() === StatusCode\Range::clientError) ->foreach(function($code) { $request = Request::of( @@ -40,7 +40,7 @@ public function testAcceptClientErrorfulResponses() public function testRejectOtherKindOfResponse() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() !== StatusCode\Range::clientError) ->foreach(function($code) { $request = Request::of( diff --git a/tests/CurlTest.php b/tests/CurlTest.php index 8f9bc42..a5d8a66 100644 --- a/tests/CurlTest.php +++ b/tests/CurlTest.php @@ -4,7 +4,6 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - Curl, Transport, Success, Redirection, @@ -24,11 +23,12 @@ Header\Location, }; use Innmind\Filesystem\{ - Adapter\Filesystem, + Adapter, File\Content, Name, }; -use Innmind\TimeContinuum\{ +use Innmind\IO\IO; +use Innmind\Time\{ Clock, Period, }; @@ -55,15 +55,7 @@ class CurlTest extends TestCase public function setUp(): void { - $this->curl = Curl::of(Clock::live()); - } - - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - $this->curl, - ); + $this->curl = Transport::curl(Clock::live()); } public function testOkResponse() @@ -238,7 +230,7 @@ public function testHead() public function testPost(): BlackBox\Proof { return $this - ->forAll(Set\Unicode::strings()) + ->forAll(Set::strings()->unicode()) ->prove(function($body) { $success = ($this->curl)(Request::of( Url::of('https://httpbin.org/post'), @@ -285,7 +277,7 @@ public function testPostLargeContent() $this ->assert() ->memory(function() { - $data = Filesystem::mount(Path::of(__DIR__.'/../data/')); + $data = Adapter::mount(Path::of(__DIR__.'/../data/'))->unwrap(); $memory = \memory_get_peak_usage(); $success = ($this->curl)(Request::of( @@ -365,7 +357,7 @@ public function testConcurrency() public function testMaxConcurrency() { - $curl = $this->curl->maxConcurrency(1); + $curl = $this->curl->map(static fn($config) => $config->limitConcurrencyTo(1)); $request = Request::of( Url::of('https://github.com'), Method::get, @@ -406,7 +398,9 @@ public function testMaxConcurrency() public function testHeartbeat() { $heartbeat = 0; - $curl = $this->curl->heartbeat( + $curl = Transport::async( + Clock::live(), + IO::fromAmbientAuthority(), Period::second(1), static function() use (&$heartbeat) { ++$heartbeat; @@ -427,7 +421,7 @@ static function() use (&$heartbeat) { public function testOutOfOrderUnwrapWithMaxConcurrency() { - $curl = $this->curl->maxConcurrency(2); + $curl = $this->curl->map(static fn($config) => $config->limitConcurrencyTo(2)); $request = Request::of( Url::of('https://github.com'), Method::get, @@ -452,7 +446,7 @@ public function testOutOfOrderUnwrapWithMaxConcurrency() public function testSubsequentRequestsAreCalledCorrectlyInsideFlatMaps() { - $curl = $this->curl->maxConcurrency(2); + $curl = $this->curl->map(static fn($config) => $config->limitConcurrencyTo(2)); $request = Request::of( Url::of('https://github.com'), Method::get, diff --git a/tests/ExponentialBackoffTest.php b/tests/ExponentialBackoffTest.php index 363ab98..f881f83 100644 --- a/tests/ExponentialBackoffTest.php +++ b/tests/ExponentialBackoffTest.php @@ -4,8 +4,6 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - ExponentialBackoff, - Curl, Transport, ServerError, Success, @@ -23,9 +21,8 @@ ProtocolVersion, Response\StatusCode, }; -use Innmind\TimeWarp\Halt; -use Innmind\TimeContinuum\{ - Clock, +use Innmind\Time\{ + Halt, Period, }; use Innmind\Url\Url; @@ -38,17 +35,6 @@ class ExponentialBackoffTest extends TestCase { - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - ExponentialBackoff::of( - Curl::of(Clock::live()), - Halt\Usleep::new(), - ), - ); - } - public function testDoesntRetryWhenInformationResponseOnFirstCall() { $request = Request::of( @@ -62,24 +48,9 @@ public function testDoesntRetryWhenInformationResponseOnFirstCall() ); $expected = Either::left(new Information($request, $response)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - }, - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -98,24 +69,9 @@ public function testDoesntRetryWhenSuccessfulResponseOnFirstCall() ); $expected = Either::right(new Success($request, $response)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - }, - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -134,24 +90,9 @@ public function testDoesntRetryWhenRedirectionResponseOnFirstCall() ); $expected = Either::left(new Redirection($request, $response)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - }, - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -170,24 +111,9 @@ public function testDoesntRetryWhenClientErrorResponseOnFirstCall() ); $expected = Either::left(new ClientError($request, $response)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - }, - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -202,24 +128,9 @@ public function testDoesntRetryWhenMalformedResponseOnFirstCall() ); $expected = Either::left(new MalformedResponse($request)); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - }, - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -234,24 +145,9 @@ public function testDoesntRetryWhenFailureOnFirstCall() ); $expected = Either::left(new Failure($request, 'whatever')); - $fulfill = ExponentialBackoff::of( - new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - }, - new class implements Halt { - public function __invoke(Period $period): Attempt - { - return Attempt::error(new \Exception); - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static fn() => $expected), + Halt::via(static fn() => Attempt::error(new \Exception)), ); $this->assertEquals($expected, $fulfill($request)); @@ -269,50 +165,34 @@ public function testRetryWhileThereIsStillATooManyRequestsError() $request->protocolVersion(), ); $expected = Either::left(new ClientError($request, $response)); + $calls = 0; + + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, $expected) { + ++$calls; - $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Transport { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - return $this->expected; - } - }, - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1, 6 => $this->test->assertEquals(Period::millisecond(100), $period), - 2, 7 => $this->test->assertEquals(Period::millisecond(271), $period), - 3, 8 => $this->test->assertEquals(Period::millisecond(738), $period), - 4, 9 => $this->test->assertEquals(Period::millisecond(2008), $period), - 5, 10 => $this->test->assertEquals(Period::millisecond(5459), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + return $expected; + }), + Halt::via(function($period) { + static $calls = 0; + ++$calls; + + match ($calls) { + 1, 6 => $this->assertEquals(Period::millisecond(100), $period), + 2, 7 => $this->assertEquals(Period::millisecond(271), $period), + 3, 8 => $this->assertEquals(Period::millisecond(738), $period), + 4, 9 => $this->assertEquals(Period::millisecond(2008), $period), + 5, 10 => $this->assertEquals(Period::millisecond(5459), $period), + }; + + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); // to make sure halt periods are kept between requests $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(12, $inner->calls); + $this->assertSame(12, $calls); } public function testRetryWhileThereIsStillAServerError() @@ -327,50 +207,34 @@ public function testRetryWhileThereIsStillAServerError() $request->protocolVersion(), ); $expected = Either::left(new ServerError($request, $response)); + $calls = 0; + + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, $expected) { + ++$calls; + + return $expected; + }), + Halt::via(function($period) { + static $calls = 0; + ++$calls; + + match ($calls) { + 1, 6 => $this->assertEquals(Period::millisecond(100), $period), + 2, 7 => $this->assertEquals(Period::millisecond(271), $period), + 3, 8 => $this->assertEquals(Period::millisecond(738), $period), + 4, 9 => $this->assertEquals(Period::millisecond(2008), $period), + 5, 10 => $this->assertEquals(Period::millisecond(5459), $period), + }; - $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Transport { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - return $this->expected; - } - }, - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1, 6 => $this->test->assertEquals(Period::millisecond(100), $period), - 2, 7 => $this->test->assertEquals(Period::millisecond(271), $period), - 3, 8 => $this->test->assertEquals(Period::millisecond(738), $period), - 4, 9 => $this->test->assertEquals(Period::millisecond(2008), $period), - 5, 10 => $this->test->assertEquals(Period::millisecond(5459), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); // to make sure halt periods are kept between requests $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(12, $inner->calls); + $this->assertSame(12, $calls); } public function testRetryWhileThereIsStillAConnectionFailure() @@ -381,50 +245,34 @@ public function testRetryWhileThereIsStillAConnectionFailure() ProtocolVersion::v11, ); $expected = Either::left(new ConnectionFailed($request, '')); + $calls = 0; - $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Transport { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - return $this->expected; - } - }, - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1, 6 => $this->test->assertEquals(Period::millisecond(100), $period), - 2, 7 => $this->test->assertEquals(Period::millisecond(271), $period), - 3, 8 => $this->test->assertEquals(Period::millisecond(738), $period), - 4, 9 => $this->test->assertEquals(Period::millisecond(2008), $period), - 5, 10 => $this->test->assertEquals(Period::millisecond(5459), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, $expected) { + ++$calls; + + return $expected; + }), + Halt::via(function($period) { + static $calls = 0; + ++$calls; + + match ($calls) { + 1, 6 => $this->assertEquals(Period::millisecond(100), $period), + 2, 7 => $this->assertEquals(Period::millisecond(271), $period), + 3, 8 => $this->assertEquals(Period::millisecond(738), $period), + 4, 9 => $this->assertEquals(Period::millisecond(2008), $period), + 5, 10 => $this->assertEquals(Period::millisecond(5459), $period), + }; + + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); // to make sure halt periods are kept between requests $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(12, $inner->calls); + $this->assertSame(12, $calls); } public function testStopRetryingWhenNoLongerReceivingAServerError() @@ -444,44 +292,29 @@ public function testStopRetryingWhenNoLongerReceivingAServerError() ); $error = Either::left(new ServerError($request, $response1)); $expected = Either::right(new Success($request, $response2)); + $all = [$error, $expected]; + $calls = 0; + + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, &$all) { + ++$calls; + + return \array_shift($all); + }), + Halt::via(function($period) { + static $calls = 0; + ++$calls; - $fulfill = ExponentialBackoff::of( - $inner = new class([$error, $expected]) implements Transport { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - return \array_shift($this->expected); - } - }, - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1 => $this->test->assertEquals(Period::millisecond(100), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + match ($calls) { + 1 => $this->assertEquals(Period::millisecond(100), $period), + }; + + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(2, $inner->calls); + $this->assertSame(2, $calls); } public function testByDefaultRetriesFiveTimesByUsingAPowerOfE() @@ -496,47 +329,31 @@ public function testByDefaultRetriesFiveTimesByUsingAPowerOfE() $request->protocolVersion(), ); $expected = Either::left(new ServerError($request, $response)); + $calls = 0; + + $fulfill = Transport::exponentialBackoff( + Transport::via(static function() use (&$calls, $expected) { + ++$calls; + + return $expected; + }), + Halt::via(function($period) { + static $calls = 0; + ++$calls; + + match ($calls) { + 1=> $this->assertEquals(Period::millisecond(100), $period), + 2=> $this->assertEquals(Period::millisecond(271), $period), + 3=> $this->assertEquals(Period::millisecond(738), $period), + 4=> $this->assertEquals(Period::millisecond(2008), $period), + 5=> $this->assertEquals(Period::millisecond(5459), $period), + }; - $fulfill = ExponentialBackoff::of( - $inner = new class($expected) implements Transport { - public function __construct( - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - return $this->expected; - } - }, - new class($this) implements Halt { - public function __construct( - private $test, - private int $calls = 0, - ) { - } - - public function __invoke(Period $period): Attempt - { - ++$this->calls; - - match ($this->calls) { - 1 => $this->test->assertEquals(Period::millisecond(100), $period), - 2 => $this->test->assertEquals(Period::millisecond(271), $period), - 3 => $this->test->assertEquals(Period::millisecond(738), $period), - 4 => $this->test->assertEquals(Period::millisecond(2008), $period), - 5 => $this->test->assertEquals(Period::millisecond(5459), $period), - }; - - return Attempt::result(SideEffect::identity()); - } - }, + return Attempt::result(SideEffect::identity); + }), ); $this->assertEquals($expected, $fulfill($request)); - $this->assertSame(6, $inner->calls); + $this->assertSame(6, $calls); } } diff --git a/tests/FollowRedirectionsTest.php b/tests/FollowRedirectionsTest.php index d010cef..6bb8afa 100644 --- a/tests/FollowRedirectionsTest.php +++ b/tests/FollowRedirectionsTest.php @@ -4,8 +4,6 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - FollowRedirections, - Curl, Transport, Information, Success, @@ -16,7 +14,6 @@ ConnectionFailed, Failure, }; -use Innmind\TimeContinuum\Clock; use Innmind\Http\{ Request, Response, @@ -43,14 +40,6 @@ class FollowRedirectionsTest extends TestCase { use BlackBox; - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - FollowRedirections::of(Curl::of(Clock::live())), - ); - } - public function testDoesntModifyNonRedirectionResults(): BlackBox\Proof { $request = Request::of( @@ -60,7 +49,7 @@ public function testDoesntModifyNonRedirectionResults(): BlackBox\Proof ); return $this - ->forAll(Set\Elements::of( + ->forAll(Set::of( Either::right(new Success( $request, Response::of( @@ -102,18 +91,8 @@ public function testDoesntModifyNonRedirectionResults(): BlackBox\Proof )), )) ->prove(function($result) use ($request) { - $inner = new class($result) implements Transport { - public function __construct( - private $result, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->result; - } - }; - $fulfill = FollowRedirections::of($inner); + $inner = Transport::via(static fn() => $result); + $fulfill = Transport::followRedirections($inner); $this->assertEquals($result, $fulfill($request)); }); @@ -125,15 +104,15 @@ public function testRedirectMaximum5Times(): BlackBox\Proof ->forAll( FUrl::any(), FUrl::any(), - Set\Elements::of(Method::get, Method::head), // unsafe methods are not redirected - Set\Elements::of( + Set::of(Method::get, Method::head), // unsafe methods are not redirected + Set::of( StatusCode::movedPermanently, StatusCode::found, StatusCode::seeOther, StatusCode::temporaryRedirect, StatusCode::permanentlyRedirect, ), - Set\Elements::of( + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, @@ -155,35 +134,24 @@ public function testRedirectMaximum5Times(): BlackBox\Proof ), ), )); - $inner = new class($this, $firstUrl, $expected) implements Transport { - public function __construct( - private $test, - private $firstUrl, - private $expected, - public int $calls = 0, - ) { + $calls = 0; + $inner = Transport::via(function($request) use (&$calls, $firstUrl, $expected) { + ++$calls; + + if ($calls === 1) { + $this->assertSame($firstUrl, $request->url()); + } else { + $this->assertNotSame($firstUrl, $request->url()); } - public function __invoke(Request $request): Either - { - ++$this->calls; - - if ($this->firstUrl) { - $this->test->assertSame($this->firstUrl, $request->url()); - $this->firstUrl = null; - } else { - $this->test->assertNotSame($this->firstUrl, $request->url()); - } - - return $this->expected; - } - }; - $fulfill = FollowRedirections::of($inner); + return $expected; + }); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); $this->assertEquals($expected, $result); - $this->assertSame(6, $inner->calls); + $this->assertSame(6, $calls); }); } @@ -192,15 +160,15 @@ public function testDoesntRedirectWhenNoLocationHeader(): BlackBox\Proof return $this ->forAll( FUrl::any(), - Set\Elements::of(...Method::cases()), - Set\Elements::of( + Set::of(...Method::cases()), + Set::of( StatusCode::movedPermanently, StatusCode::found, StatusCode::seeOther, StatusCode::temporaryRedirect, StatusCode::permanentlyRedirect, ), - Set\Elements::of( + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, @@ -219,18 +187,8 @@ public function testDoesntRedirectWhenNoLocationHeader(): BlackBox\Proof $protocol, ), )); - $inner = new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - }; - $fulfill = FollowRedirections::of($inner); + $inner = Transport::via(static fn() => $expected); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); @@ -246,13 +204,13 @@ public function testRedirectSeeOther(): BlackBox\Proof ->filter(static fn($url) => !$url->authority()->equals(Authority::none())) ->filter(static fn($url) => $url->path()->absolute()), FUrl::any(), - Set\Elements::of(...Method::cases()), - Set\Elements::of( + Set::of(...Method::cases()), + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, ), - Set\Unicode::strings(), + Set::strings()->unicode(), ) ->prove(function($firstUrl, $newUrl, $method, $protocol, $body) { $start = Request::of( @@ -269,58 +227,46 @@ public function testRedirectSeeOther(): BlackBox\Proof $protocol, ), )); - $inner = new class($this, $start, $newUrl, $protocol, $expected) implements Transport { - public function __construct( - private $test, - private $start, - private $newUrl, - private $protocol, - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - if ($this->calls === 1) { - $this->test->assertSame($this->start, $request); - - return Either::left(new Redirection( - $this->start, - Response::of( - StatusCode::seeOther, - $this->protocol, - Headers::of( - Location::of($this->newUrl), - ), + $calls = 0; + $inner = Transport::via(function($request) use (&$calls, $start, $newUrl, $protocol, $expected) { + ++$calls; + + if ($calls === 1) { + $this->assertSame($start, $request); + + return Either::left(new Redirection( + $start, + Response::of( + StatusCode::seeOther, + $protocol, + Headers::of( + Location::of($newUrl), ), - )); - } - - $this->test->assertSame(Method::get, $request->method()); - $this->test->assertFalse($request->url()->authority()->equals(Authority::none())); - $this->test->assertTrue($request->url()->path()->absolute()); - // not a direct comparison as new url might be a relative path - $this->test->assertStringEndsWith( - $this->newUrl->path()->toString(), - $request->url()->path()->toString(), - ); - $this->test->assertSame($this->newUrl->query(), $request->url()->query()); - $this->test->assertSame($this->newUrl->fragment(), $request->url()->fragment()); - $this->test->assertSame($this->start->headers(), $request->headers()); - $this->test->assertSame('', $request->body()->toString()); - - return $this->expected; + ), + )); } - }; - $fulfill = FollowRedirections::of($inner); + + $this->assertSame(Method::get, $request->method()); + $this->assertFalse($request->url()->authority()->equals(Authority::none())); + $this->assertTrue($request->url()->path()->absolute()); + // not a direct comparison as new url might be a relative path + $this->assertStringEndsWith( + $newUrl->path()->toString(), + $request->url()->path()->toString(), + ); + $this->assertSame($newUrl->query(), $request->url()->query()); + $this->assertSame($newUrl->fragment(), $request->url()->fragment()); + $this->assertSame($start->headers(), $request->headers()); + $this->assertSame('', $request->body()->toString()); + + return $expected; + }); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); $this->assertEquals($expected, $result); - $this->assertSame(2, $inner->calls); + $this->assertSame(2, $calls); }); } @@ -332,19 +278,19 @@ public function testRedirect(): BlackBox\Proof ->filter(static fn($url) => !$url->authority()->equals(Authority::none())) ->filter(static fn($url) => $url->path()->absolute()), FUrl::any(), - Set\Elements::of(Method::get, Method::head), // unsafe methods are not redirected - Set\Elements::of( + Set::of(Method::get, Method::head), // unsafe methods are not redirected + Set::of( StatusCode::movedPermanently, StatusCode::found, StatusCode::temporaryRedirect, StatusCode::permanentlyRedirect, ), - Set\Elements::of( + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, ), - Set\Unicode::strings(), + Set::strings()->unicode(), ) ->prove(function($firstUrl, $newUrl, $method, $statusCode, $protocol, $body) { $start = Request::of( @@ -361,59 +307,46 @@ public function testRedirect(): BlackBox\Proof $protocol, ), )); - $inner = new class($this, $start, $newUrl, $statusCode, $protocol, $expected) implements Transport { - public function __construct( - private $test, - private $start, - private $newUrl, - private $statusCode, - private $protocol, - private $expected, - public int $calls = 0, - ) { - } - - public function __invoke(Request $request): Either - { - ++$this->calls; - - if ($this->calls === 1) { - $this->test->assertSame($this->start, $request); - - return Either::left(new Redirection( - $this->start, - Response::of( - $this->statusCode, - $this->protocol, - Headers::of( - Location::of($this->newUrl), - ), + $calls = 0; + $inner = Transport::via(function($request) use (&$calls, $start, $newUrl, $statusCode, $protocol, $expected) { + ++$calls; + + if ($calls === 1) { + $this->assertSame($start, $request); + + return Either::left(new Redirection( + $start, + Response::of( + $statusCode, + $protocol, + Headers::of( + Location::of($newUrl), ), - )); - } - - $this->test->assertSame($this->start->method(), $request->method()); - $this->test->assertFalse($request->url()->authority()->equals(Authority::none())); - $this->test->assertTrue($request->url()->path()->absolute()); - // not a direct comparison as new url might be a relative path - $this->test->assertStringEndsWith( - $this->newUrl->path()->toString(), - $request->url()->path()->toString(), - ); - $this->test->assertSame($this->newUrl->query(), $request->url()->query()); - $this->test->assertSame($this->newUrl->fragment(), $request->url()->fragment()); - $this->test->assertSame($this->start->headers(), $request->headers()); - $this->test->assertSame($this->start->body(), $request->body()); - - return $this->expected; + ), + )); } - }; - $fulfill = FollowRedirections::of($inner); + + $this->assertSame($start->method(), $request->method()); + $this->assertFalse($request->url()->authority()->equals(Authority::none())); + $this->assertTrue($request->url()->path()->absolute()); + // not a direct comparison as new url might be a relative path + $this->assertStringEndsWith( + $newUrl->path()->toString(), + $request->url()->path()->toString(), + ); + $this->assertSame($newUrl->query(), $request->url()->query()); + $this->assertSame($newUrl->fragment(), $request->url()->fragment()); + $this->assertSame($start->headers(), $request->headers()); + $this->assertSame($start->body(), $request->body()); + + return $expected; + }); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); $this->assertEquals($expected, $result); - $this->assertSame(2, $inner->calls); + $this->assertSame(2, $calls); }); } @@ -423,21 +356,21 @@ public function testDoesntRedirectUnsafeMethods(): BlackBox\Proof ->forAll( FUrl::any(), FUrl::any(), - Set\Elements::of(...Method::cases())->filter( + Set::of(...Method::cases())->filter( static fn($method) => $method !== Method::get && $method !== Method::head, ), - Set\Elements::of( + Set::of( StatusCode::movedPermanently, StatusCode::found, StatusCode::temporaryRedirect, StatusCode::permanentlyRedirect, ), - Set\Elements::of( + Set::of( ProtocolVersion::v10, ProtocolVersion::v11, ProtocolVersion::v20, ), - Set\Unicode::strings(), + Set::strings()->unicode(), ) ->prove(function($firstUrl, $newUrl, $method, $statusCode, $protocol, $body) { $start = Request::of( @@ -457,18 +390,8 @@ public function testDoesntRedirectUnsafeMethods(): BlackBox\Proof ), ), )); - $inner = new class($expected) implements Transport { - public function __construct( - private $expected, - ) { - } - - public function __invoke(Request $request): Either - { - return $this->expected; - } - }; - $fulfill = FollowRedirections::of($inner); + $inner = Transport::via(static fn() => $expected); + $fulfill = Transport::followRedirections($inner); $result = $fulfill($start); diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 0a22ceb..90c7f3b 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -4,12 +4,10 @@ namespace Tests\Innmind\HttpTransport; use Innmind\HttpTransport\{ - Logger, - Curl, Transport, Success, }; -use Innmind\TimeContinuum\Clock; +use Innmind\Time\Clock; use Innmind\Http\{ Request, Method, @@ -28,20 +26,12 @@ class LoggerTest extends TestCase public function setUp(): void { - $this->fulfill = Logger::psr( - Curl::of(Clock::live()), + $this->fulfill = Transport::logger( + Transport::curl(Clock::live()), new NullLogger, ); } - public function testInterface() - { - $this->assertInstanceOf( - Transport::class, - $this->fulfill, - ); - } - public function testFulfill() { $request = Request::of( diff --git a/tests/RedirectionTest.php b/tests/RedirectionTest.php index 326594b..f74db54 100644 --- a/tests/RedirectionTest.php +++ b/tests/RedirectionTest.php @@ -19,7 +19,7 @@ class RedirectionTest extends TestCase { public function testAcceptRedirectionfulResponses() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() === StatusCode\Range::redirection) ->foreach(function($code) { $request = Request::of( @@ -40,7 +40,7 @@ public function testAcceptRedirectionfulResponses() public function testRejectOtherKindOfResponse() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() !== StatusCode\Range::redirection) ->foreach(function($code) { $request = Request::of( diff --git a/tests/ServerErrorTest.php b/tests/ServerErrorTest.php index fa82c6b..6d7d66b 100644 --- a/tests/ServerErrorTest.php +++ b/tests/ServerErrorTest.php @@ -19,7 +19,7 @@ class ServerErrorTest extends TestCase { public function testAcceptServerErrorfulResponses() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() === StatusCode\Range::serverError) ->foreach(function($code) { $request = Request::of( @@ -40,7 +40,7 @@ public function testAcceptServerErrorfulResponses() public function testRejectOtherKindOfResponse() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() !== StatusCode\Range::serverError) ->foreach(function($code) { $request = Request::of( diff --git a/tests/SuccessTest.php b/tests/SuccessTest.php index 88f965b..b397bf8 100644 --- a/tests/SuccessTest.php +++ b/tests/SuccessTest.php @@ -19,7 +19,7 @@ class SuccessTest extends TestCase { public function testAcceptSuccessfulResponses() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() === StatusCode\Range::successful) ->foreach(function($code) { $request = Request::of( @@ -40,7 +40,7 @@ public function testAcceptSuccessfulResponses() public function testRejectOtherKindOfResponse() { - Sequence::of(...StatusCode::cases()) + $_ = Sequence::of(...StatusCode::cases()) ->filter(static fn($code) => $code->range() !== StatusCode\Range::successful) ->foreach(function($code) { $request = Request::of(