Skip to content

Commit

Permalink
Add react/promise v2 to v3 transition helper
Browse files Browse the repository at this point in the history
  • Loading branch information
SQKo committed Dec 10, 2023
1 parent c3327ad commit ac1c925
Show file tree
Hide file tree
Showing 12 changed files with 463 additions and 32 deletions.
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
}
],
"autoload": {
"files": [
"src/Discord/PromiseHelpers/bootstrap.php"
],
"psr-4": {
"Discord\\Http\\": "src/Discord",
"Tests\\Discord\\Http\\": "tests/Discord"
Expand All @@ -19,7 +22,7 @@
"php": "^7.4|^8.0",
"react/http": "^1.2",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"react/promise": "^2.2"
"react/promise": "^2.8 || ^3.1"
},
"suggest": {
"guzzlehttp/guzzle": "For alternative to ReactPHP/Http Browser"
Expand Down
6 changes: 3 additions & 3 deletions src/Discord/DriverInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
namespace Discord\Http;

use Psr\Http\Message\ResponseInterface;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\PromiseInterface;

/**
* Interface for an HTTP driver.
Expand All @@ -28,7 +28,7 @@ interface DriverInterface
*
* @param Request $request
*
* @return ExtendedPromiseInterface<ResponseInterface>
* @return \Discord\Http\PromiseHelpers\PolyFillPromiseInterface<ResponseInterface>|\React\Promise\ExtendedPromiseInterface<ResponseInterface>
*/
public function runRequest(Request $request): ExtendedPromiseInterface;
public function runRequest(Request $request): PromiseInterface;
}
6 changes: 3 additions & 3 deletions src/Discord/Drivers/Guzzle.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
namespace Discord\Http\Drivers;

use Discord\Http\DriverInterface;
use Discord\Http\PromiseHelpers\Deferred;
use Discord\Http\Request;
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\PromiseInterface;

/**
* guzzlehttp/guzzle driver for Discord HTTP client. (still with React Promise).
Expand Down Expand Up @@ -55,7 +55,7 @@ public function __construct(?LoopInterface $loop = null, array $options = [])
$this->client = new Client($options);
}

public function runRequest(Request $request): ExtendedPromiseInterface
public function runRequest(Request $request): PromiseInterface
{
// Create a React promise
$deferred = new Deferred();
Expand Down
9 changes: 5 additions & 4 deletions src/Discord/Drivers/React.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
namespace Discord\Http\Drivers;

use Discord\Http\DriverInterface;
use Discord\Http\PromiseHelpers\PolyFillPromiseInterface;
use Discord\Http\Request;
use React\EventLoop\LoopInterface;
use React\Http\Browser;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\PromiseInterface;
use React\Socket\Connector;

/**
Expand Down Expand Up @@ -54,12 +55,12 @@ public function __construct(LoopInterface $loop, array $options = [])
$this->browser = $browser->withRejectErrorResponse(false);
}

public function runRequest(Request $request): ExtendedPromiseInterface
public function runRequest(Request $request): PromiseInterface
{
return $this->browser->{$request->getMethod()}(
return new PolyFillPromiseInterface($this->browser->{$request->getMethod()}(
$request->getUrl(),
$request->getHeaders(),
$request->getContent()
);
));
}
}
40 changes: 20 additions & 20 deletions src/Discord/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
use Discord\Http\Exceptions\NotFoundException;
use Discord\Http\Exceptions\RateLimitException;
use Discord\Http\Exceptions\RequestFailedException;
use Discord\Http\PromiseHelpers\Deferred;
use Discord\Http\Multipart\MultipartBody;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\PromiseInterface;
use SplQueue;

/**
Expand All @@ -39,7 +39,7 @@ class Http
*
* @var string
*/
public const VERSION = 'v10.3.0';
public const VERSION = 'v10.4.0';

/**
* Current Discord HTTP API version.
Expand Down Expand Up @@ -160,9 +160,9 @@ public function setDriver(DriverInterface $driver): void
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PolyFillPromiseInterface|\React\Promise\ExtendedPromiseInterface
*/
public function get($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function get($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -178,9 +178,9 @@ public function get($url, $content = null, array $headers = []): ExtendedPromise
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PolyFillPromiseInterface|\React\Promise\ExtendedPromiseInterface
*/
public function post($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function post($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -196,9 +196,9 @@ public function post($url, $content = null, array $headers = []): ExtendedPromis
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PolyFillPromiseInterface|\React\Promise\ExtendedPromiseInterface
*/
public function put($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function put($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -214,9 +214,9 @@ public function put($url, $content = null, array $headers = []): ExtendedPromise
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PolyFillPromiseInterface|\React\Promise\ExtendedPromiseInterface
*/
public function patch($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function patch($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -232,9 +232,9 @@ public function patch($url, $content = null, array $headers = []): ExtendedPromi
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PolyFillPromiseInterface|\React\Promise\ExtendedPromiseInterface
*/
public function delete($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function delete($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -251,9 +251,9 @@ public function delete($url, $content = null, array $headers = []): ExtendedProm
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PolyFillPromiseInterface|\React\Promise\ExtendedPromiseInterface
*/
public function queueRequest(string $method, Endpoint $url, $content, array $headers = []): ExtendedPromiseInterface
public function queueRequest(string $method, Endpoint $url, $content, array $headers = []): PromiseInterface
{
$deferred = new Deferred();

Expand Down Expand Up @@ -318,16 +318,16 @@ protected function guessContent(&$content)
* @param Request $request
* @param Deferred $deferred
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PolyFillPromiseInterface|\React\Promise\ExtendedPromiseInterface
*/
protected function executeRequest(Request $request, Deferred $deferred = null): ExtendedPromiseInterface
protected function executeRequest(Request $request, Deferred $deferred = null): PromiseInterface
{
if ($deferred === null) {
$deferred = new Deferred();
}

if ($this->rateLimit) {
$deferred->reject($this->rateLimit);
$deferred->reject($this->rateLimit); // TODO handle resolve ratelimit

return $deferred->promise();
}
Expand Down Expand Up @@ -383,7 +383,7 @@ protected function executeRequest(Request $request, Deferred $deferred = null):
});
}

$deferred->reject($rateLimit->isGlobal() ? $this->rateLimit : $rateLimit);
$deferred->reject($rateLimit->isGlobal() ? $this->rateLimit : $rateLimit); // TODO handle resolve ratelimit
}
// Bad Gateway
// Cloudflare SSL Handshake error
Expand Down Expand Up @@ -476,7 +476,7 @@ protected function checkQueue(): void
--$this->waiting;
$this->checkQueue();
$deferred->resolve($result);
}, function ($e) use ($deferred) {
}, function ($e) use ($deferred) { // TODO handle resolve reject
--$this->waiting;
$this->checkQueue();
$deferred->reject($e);
Expand Down
26 changes: 26 additions & 0 deletions src/Discord/PromiseHelpers/CancellablePromiseInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/*
* This file is a part of the DiscordPHP-Http project.
*
* Copyright (c) 2021-present David Cole <[email protected]>
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE file.
*/

namespace Discord\Http\PromiseHelpers;

/**
* A transition helper from react/promise v2 to react/promise v3.
* Please do not use this polyfill class in place of real CancellablePromiseInterface.
*
* @see \React\Promise\CancellablePromiseInterface
*
* @internal Used internally for DiscordPHP v10
*
* @since 10.4.0
*/
interface CancellablePromiseInterface
{
public function cancel();
}
139 changes: 139 additions & 0 deletions src/Discord/PromiseHelpers/Deferred.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

/*
* This file is a part of the DiscordPHP-Http project.
*
* Copyright (c) 2021-present David Cole <[email protected]>
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE file.
*/

namespace Discord\Http\PromiseHelpers;

use React\Promise\Deferred as ReactDeferred;
use React\Promise\PromiseInterface;

/**
* A transition helper from react/promise v2 to react/promise v3.
* Please do not use this polyfill class in place of real Deferred.
*
* @see \React\Promise\Deferred
*
* @internal Used internally for DiscordPHP v10
*
* @since 10.4.0
*/
final class Deferred implements PromisorInterface
{
/**
* The actual Promisor
*/
public ReactDeferred $deferred;

/**
* Determine the installed package is Promise-v3
*/
public static bool $isPromiseV3 = false;

/**
* @var PolyFillPromiseInterface|PolyFillPromiseInterface
*/
private $promise;

/**
* @param callable|ReactDeferred $canceller Canceller callback or a Deferred to use
*
* @throws \InvalidArgumentException $canceller is not null or callable or a Deferred
*/
public function __construct($canceller = null)
{
if ($canceller instanceof ReactDeferred) {
$this->deferred = $canceller;
} elseif (null === $canceller || is_callable($canceller)) {
$this->deferred = new ReactDeferred($canceller);
} else {
throw new \InvalidArgumentException('$canceller must be either null or callable or Deferred');
}
}

/**
* @return PolyFillPromiseInterface|PromiseInterface|\React\Promise\ExtendedPromiseInterface
*/
public function promise()
{
if (!static::$isPromiseV3) {
// Just use the same react/promise v2 promise
return $this->deferred->promise();
}

if (null === $this->promise) {
// Wrap with the polyfill if user installed react/promise v3
$this->promise = new PolyFillPromiseInterface($this->deferred->promise());
}

return $this->promise;
}

/**
* @see React\Promise\Deferred::resolve()
*
* @return void
*/
public function resolve($value = null)
{
$this->deferred->resolve($value);
}

/**
* @see React\Promise\Deferred::reject()
*
* @param \Throwable $reason required in Promise-v3, will be resolved if not a throwable
*
* @throws \InvalidArgumentException $reason is null & react/promise is v3
*
* @return void
*/
public function reject($reason = null)
{
if (static::$isPromiseV3) {
if (null === $reason) {
$reason = new \InvalidArgumentException('reject($reason) must not be null');
} elseif (!($reason instanceof \Throwable)) {
return $this->deferred->resolve($reason);
}
}

$this->deferred->reject($reason);
}

/**
* Not supported
*
* @deprecated
*/
public function notify($update = null)
{
if (method_exists($this->deferred, 'notify')) {
$this->deferred->notify($update);
return;
}

throw new \BadMethodCallException('notify() is not supported with this polyfill and react/promise v3');
}

/**
* Not supported
*
* @deprecated
*/
public function progress($update = null)
{
if (method_exists($this->deferred, 'progress')) {
$this->deferred->progress($update);
return;
}

throw new \BadMethodCallException('progress() is not supported with this polyfill and react/promise v3');
}
}
Loading

0 comments on commit ac1c925

Please sign in to comment.