diff options
Diffstat (limited to 'lib/public/Http')
-rw-r--r-- | lib/public/Http/Client/IClient.php | 439 | ||||
-rw-r--r-- | lib/public/Http/Client/IClientService.php | 22 | ||||
-rw-r--r-- | lib/public/Http/Client/IPromise.php | 83 | ||||
-rw-r--r-- | lib/public/Http/Client/IResponse.php | 42 | ||||
-rw-r--r-- | lib/public/Http/Client/LocalServerException.php | 15 | ||||
-rw-r--r-- | lib/public/Http/WellKnown/GenericResponse.php | 33 | ||||
-rw-r--r-- | lib/public/Http/WellKnown/IHandler.php | 30 | ||||
-rw-r--r-- | lib/public/Http/WellKnown/IRequestContext.php | 28 | ||||
-rw-r--r-- | lib/public/Http/WellKnown/IResponse.php | 21 | ||||
-rw-r--r-- | lib/public/Http/WellKnown/JrdResponse.php | 152 |
10 files changed, 865 insertions, 0 deletions
diff --git a/lib/public/Http/Client/IClient.php b/lib/public/Http/Client/IClient.php new file mode 100644 index 00000000000..e4f46d44e4d --- /dev/null +++ b/lib/public/Http/Client/IClient.php @@ -0,0 +1,439 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\Http\Client; + +/** + * Interface IClient + * + * @since 8.1.0 + */ +interface IClient { + + /** + * Default request timeout for requests + * + * @since 31.0.0 + */ + public const DEFAULT_REQUEST_TIMEOUT = 30; + + /** + * Sends a GET request + * @param string $uri + * @param array $options Array such as + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 8.1.0 + */ + public function get(string $uri, array $options = []): IResponse; + + /** + * Sends a HEAD request + * @param string $uri + * @param array $options Array such as + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 8.1.0 + */ + public function head(string $uri, array $options = []): IResponse; + + /** + * Sends a POST request + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 8.1.0 + */ + public function post(string $uri, array $options = []): IResponse; + + /** + * Sends a PUT request + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 8.1.0 + */ + public function put(string $uri, array $options = []): IResponse; + + /** + * Sends a PATCH request + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 29.0.0 + */ + public function patch(string $uri, array $options = []): IResponse; + + /** + * Sends a DELETE request + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 8.1.0 + */ + public function delete(string $uri, array $options = []): IResponse; + + /** + * Sends an OPTIONS request + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 8.1.0 + */ + public function options(string $uri, array $options = []): IResponse; + + /** + * Get the response of a Throwable thrown by the request methods when possible + * + * @param \Throwable $e + * @return IResponse + * @throws \Throwable When $e did not have a response + * @since 29.0.0 + */ + public function getResponseFromThrowable(\Throwable $e): IResponse; + + /** + * Sends a HTTP request + * @param string $method The HTTP method to use + * @param string $uri + * @param array $options Array such as + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IResponse + * @throws \Exception If the request could not get completed + * @since 29.0.0 + */ + public function request(string $method, string $uri, array $options = []): IResponse; + + /** + * Sends an asynchronous GET request + * @param string $uri + * @param array $options Array such as + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IPromise + * @since 28.0.0 + */ + public function getAsync(string $uri, array $options = []): IPromise; + + /** + * Sends an asynchronous HEAD request + * @param string $uri + * @param array $options Array such as + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IPromise + * @since 28.0.0 + */ + public function headAsync(string $uri, array $options = []): IPromise; + + /** + * Sends an asynchronous POST request + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IPromise + * @since 28.0.0 + */ + public function postAsync(string $uri, array $options = []): IPromise; + + /** + * Sends an asynchronous PUT request + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IPromise + * @since 28.0.0 + */ + public function putAsync(string $uri, array $options = []): IPromise; + + /** + * Sends an asynchronous DELETE request + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IPromise + * @since 28.0.0 + */ + public function deleteAsync(string $uri, array $options = []): IPromise; + + /** + * Sends an asynchronous OPTIONS request + * @param string $uri + * @param array $options Array such as + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, + * @return IPromise + * @since 28.0.0 + */ + public function optionsAsync(string $uri, array $options = []): IPromise; +} diff --git a/lib/public/Http/Client/IClientService.php b/lib/public/Http/Client/IClientService.php new file mode 100644 index 00000000000..148757e5b31 --- /dev/null +++ b/lib/public/Http/Client/IClientService.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\Http\Client; + +/** + * Interface IClientService + * + * @since 8.1.0 + */ +interface IClientService { + /** + * @return IClient + * @since 8.1.0 + */ + public function newClient(): IClient; +} diff --git a/lib/public/Http/Client/IPromise.php b/lib/public/Http/Client/IPromise.php new file mode 100644 index 00000000000..8778829af0e --- /dev/null +++ b/lib/public/Http/Client/IPromise.php @@ -0,0 +1,83 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Http\Client; + +use Exception; +use LogicException; + +/** + * A wrapper around Guzzle's PromiseInterface + * @see \GuzzleHttp\Promise\PromiseInterface + * @since 28.0.0 + */ +interface IPromise { + /** + * @since 28.0.0 + */ + public const STATE_PENDING = 'pending'; + /** + * @since 28.0.0 + */ + public const STATE_FULFILLED = 'fulfilled'; + /** + * @since 28.0.0 + */ + public const STATE_REJECTED = 'rejected'; + + /** + * Appends fulfillment and rejection handlers to the promise, and returns + * a new promise resolving to the return value of the called handler. + * + * @param ?callable(IResponse): void $onFulfilled Invoked when the promise fulfills. Gets an \OCP\Http\Client\IResponse passed in as argument + * @param ?callable(Exception): void $onRejected Invoked when the promise is rejected. Gets an \Exception passed in as argument + * + * @return IPromise + * @since 28.0.0 + */ + public function then( + ?callable $onFulfilled = null, + ?callable $onRejected = null, + ): IPromise; + + /** + * Get the state of the promise ("pending", "rejected", or "fulfilled"). + * + * The three states can be checked against the constants defined: + * STATE_PENDING, STATE_FULFILLED, and STATE_REJECTED. + * + * @return self::STATE_* + * @since 28.0.0 + */ + public function getState(): string; + + /** + * Cancels the promise if possible. + * + * @link https://github.com/promises-aplus/cancellation-spec/issues/7 + * @since 28.0.0 + */ + public function cancel(): void; + + /** + * Waits until the promise completes if possible. + * + * Pass $unwrap as true to unwrap the result of the promise, either + * returning the resolved value or throwing the rejected exception. + * + * If the promise cannot be waited on, then the promise will be rejected. + * + * @param bool $unwrap + * + * @return mixed + * + * @throws LogicException if the promise has no wait function or if the + * promise does not settle after waiting. + * @since 28.0.0 + */ + public function wait(bool $unwrap = true): mixed; +} diff --git a/lib/public/Http/Client/IResponse.php b/lib/public/Http/Client/IResponse.php new file mode 100644 index 00000000000..53032ef2a37 --- /dev/null +++ b/lib/public/Http/Client/IResponse.php @@ -0,0 +1,42 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\Http\Client; + +/** + * Interface IResponse + * + * @since 8.1.0 + */ +interface IResponse { + /** + * @return null|resource|string + * @since 8.1.0 + * @sicne 8.2.0 with stream enabled, the function returns null or a resource + */ + public function getBody(); + + /** + * @return int + * @since 8.1.0 + */ + public function getStatusCode(): int; + + /** + * @param string $key + * @return string + * @since 8.1.0 + */ + public function getHeader(string $key): string; + + /** + * @return array + * @since 8.1.0 + */ + public function getHeaders(): array; +} diff --git a/lib/public/Http/Client/LocalServerException.php b/lib/public/Http/Client/LocalServerException.php new file mode 100644 index 00000000000..bb18da13a15 --- /dev/null +++ b/lib/public/Http/Client/LocalServerException.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Http\Client; + +/** + * @since 19.0.0 + */ +class LocalServerException extends \RuntimeException { +} diff --git a/lib/public/Http/WellKnown/GenericResponse.php b/lib/public/Http/WellKnown/GenericResponse.php new file mode 100644 index 00000000000..c5279f075c9 --- /dev/null +++ b/lib/public/Http/WellKnown/GenericResponse.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Http\WellKnown; + +use OCP\AppFramework\Http\Response; + +/** + * @since 21.0.0 + */ +final class GenericResponse implements IResponse { + /** @var Response */ + private $response; + + /** + * @since 21.0.0 + */ + public function __construct(Response $response) { + $this->response = $response; + } + + /** + * @since 21.0.0 + */ + public function toHttpResponse(): Response { + return $this->response; + } +} diff --git a/lib/public/Http/WellKnown/IHandler.php b/lib/public/Http/WellKnown/IHandler.php new file mode 100644 index 00000000000..274d0241969 --- /dev/null +++ b/lib/public/Http/WellKnown/IHandler.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Http\WellKnown; + +/** + * Interface for an app handler that reacts to requests to Nextcloud's well + * known URLs, e.g. to a WebFinger + * + * @ref https://tools.ietf.org/html/rfc5785 + * + * @since 21.0.0 + */ +interface IHandler { + /** + * @param string $service the name of the well known service, e.g. 'webfinger' + * @param IRequestContext $context + * @param IResponse|null $previousResponse the response of the previous handler, if any + * + * @return IResponse|null a response object if the request could be handled, null otherwise + * + * @since 21.0.0 + */ + public function handle(string $service, IRequestContext $context, ?IResponse $previousResponse): ?IResponse; +} diff --git a/lib/public/Http/WellKnown/IRequestContext.php b/lib/public/Http/WellKnown/IRequestContext.php new file mode 100644 index 00000000000..241a9069904 --- /dev/null +++ b/lib/public/Http/WellKnown/IRequestContext.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Http\WellKnown; + +use OCP\IRequest; + +/** + * The context object for \OCP\Http\IWellKnownHandler::handle + * + * Objects of this type will transport any optional information, e.g. the request + * object through which the app well known handler can obtain URL parameters + * + * @since 21.0.0 + */ +interface IRequestContext { + /** + * @return IRequest + * + * @since 21.0.0 + */ + public function getHttpRequest(): IRequest; +} diff --git a/lib/public/Http/WellKnown/IResponse.php b/lib/public/Http/WellKnown/IResponse.php new file mode 100644 index 00000000000..2cf63043b63 --- /dev/null +++ b/lib/public/Http/WellKnown/IResponse.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Http\WellKnown; + +use OCP\AppFramework\Http\Response; + +/** + * @since 21.0.0 + */ +interface IResponse { + /** + * @since 21.0.0 + */ + public function toHttpResponse(): Response; +} diff --git a/lib/public/Http/WellKnown/JrdResponse.php b/lib/public/Http/WellKnown/JrdResponse.php new file mode 100644 index 00000000000..21ad948d148 --- /dev/null +++ b/lib/public/Http/WellKnown/JrdResponse.php @@ -0,0 +1,152 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Http\WellKnown; + +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; +use function array_filter; + +/** + * A JSON Document Format (JDF) response to a well-known request + * + * @ref https://tools.ietf.org/html/rfc6415#appendix-A + * @ref https://tools.ietf.org/html/rfc7033#section-4.4 + * + * @since 21.0.0 + */ +final class JrdResponse implements IResponse { + /** @var string */ + private $subject; + + /** @var string|null */ + private $expires; + + /** @var string[] */ + private $aliases = []; + + /** @var (string|null)[] */ + private $properties = []; + + /** @var mixed[] */ + private $links; + + /** + * @param string $subject https://tools.ietf.org/html/rfc7033#section-4.4.1 + * + * @since 21.0.0 + */ + public function __construct(string $subject) { + $this->subject = $subject; + } + + /** + * @param string $expires + * + * @return $this + * + * @since 21.0.0 + */ + public function setExpires(string $expires): self { + $this->expires = $expires; + + return $this; + } + + /** + * Add an alias + * + * @ref https://tools.ietf.org/html/rfc7033#section-4.4.2 + * + * @param string $alias + * + * @return $this + * + * @since 21.0.0 + */ + public function addAlias(string $alias): self { + $this->aliases[] = $alias; + + return $this; + } + + /** + * Add a property + * + * @ref https://tools.ietf.org/html/rfc7033#section-4.4.3 + * + * @param string $property + * @param string|null $value + * + * @return $this + * + * @since 21.0.0 + */ + public function addProperty(string $property, ?string $value): self { + $this->properties[$property] = $value; + + return $this; + } + + /** + * Add a link + * + * @ref https://tools.ietf.org/html/rfc7033#section-8.4 + * + * @param string $rel https://tools.ietf.org/html/rfc7033#section-4.4.4.1 + * @param string|null $type https://tools.ietf.org/html/rfc7033#section-4.4.4.2 + * @param string|null $href https://tools.ietf.org/html/rfc7033#section-4.4.4.3 + * @param string[]|null $titles https://tools.ietf.org/html/rfc7033#section-4.4.4.4 + * @param string|null $properties https://tools.ietf.org/html/rfc7033#section-4.4.4.5 + * + * @psalm-param array<string,(string|null)>|null $properties https://tools.ietf.org/html/rfc7033#section-4.4.4.5 + * + * @return JrdResponse + * @since 21.0.0 + */ + public function addLink(string $rel, + ?string $type, + ?string $href, + ?array $titles = [], + ?array $properties = []): self { + $this->links[] = array_filter([ + 'rel' => $rel, + 'type' => $type, + 'href' => $href, + 'titles' => $titles, + 'properties' => $properties, + ]); + + return $this; + } + + /** + * @since 21.0.0 + */ + public function toHttpResponse(): Response { + return new JSONResponse(array_filter([ + 'subject' => $this->subject, + 'expires' => $this->expires, + 'aliases' => $this->aliases, + 'properties' => $this->properties, + 'links' => $this->links, + ])); + } + + /** + * Does this response have any data attached to it? + * + * @since 21.0.0 + */ + public function isEmpty(): bool { + return $this->expires === null + && empty($this->aliases) + && empty($this->properties) + && empty($this->links); + } +} |