lol backups and old site first commit
This commit is contained in:
+92
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WordPress\AiClient\Providers\Http\DTO;
|
||||
|
||||
use WordPress\AiClient\Common\AbstractDataTransferObject;
|
||||
use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface;
|
||||
/**
|
||||
* Class for HTTP request authentication using an API key.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @phpstan-type ApiKeyRequestAuthenticationArrayShape array{
|
||||
* apiKey: string
|
||||
* }
|
||||
*
|
||||
* @extends AbstractDataTransferObject<ApiKeyRequestAuthenticationArrayShape>
|
||||
*/
|
||||
class ApiKeyRequestAuthentication extends AbstractDataTransferObject implements RequestAuthenticationInterface
|
||||
{
|
||||
public const KEY_API_KEY = 'apiKey';
|
||||
/**
|
||||
* @var string The API key used for authentication.
|
||||
*/
|
||||
protected string $apiKey;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $apiKey The API key used for authentication.
|
||||
*/
|
||||
public function __construct(string $apiKey)
|
||||
{
|
||||
$this->apiKey = $apiKey;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public function authenticateRequest(\WordPress\AiClient\Providers\Http\DTO\Request $request): \WordPress\AiClient\Providers\Http\DTO\Request
|
||||
{
|
||||
// Add the API key to the request headers.
|
||||
return $request->withHeader('Authorization', 'Bearer ' . $this->apiKey);
|
||||
}
|
||||
/**
|
||||
* Gets the API key.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return string The API key.
|
||||
*/
|
||||
public function getApiKey(): string
|
||||
{
|
||||
return $this->apiKey;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return ApiKeyRequestAuthenticationArrayShape
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [self::KEY_API_KEY => $this->apiKey];
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public static function fromArray(array $array): self
|
||||
{
|
||||
static::validateFromArrayData($array, [self::KEY_API_KEY]);
|
||||
return new self($array[self::KEY_API_KEY]);
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public static function getJsonSchema(): array
|
||||
{
|
||||
return ['type' => 'object', 'properties' => [self::KEY_API_KEY => ['type' => 'string', 'title' => 'API Key', 'description' => 'The API key used for authentication.']], 'required' => [self::KEY_API_KEY]];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WordPress\AiClient\Providers\Http\DTO;
|
||||
|
||||
use JsonException;
|
||||
use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface;
|
||||
use WordPress\AiClient\Common\AbstractDataTransferObject;
|
||||
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
|
||||
use WordPress\AiClient\Providers\Http\Collections\HeadersCollection;
|
||||
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
|
||||
/**
|
||||
* Represents an HTTP request.
|
||||
*
|
||||
* This class encapsulates HTTP request data that can be converted
|
||||
* to PSR-7 requests by the HTTP transporter.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @phpstan-import-type RequestOptionsArrayShape from RequestOptions
|
||||
* @phpstan-type RequestArrayShape array{
|
||||
* method: string,
|
||||
* uri: string,
|
||||
* headers: array<string, list<string>>,
|
||||
* body?: string|null,
|
||||
* options?: RequestOptionsArrayShape
|
||||
* }
|
||||
*
|
||||
* @extends AbstractDataTransferObject<RequestArrayShape>
|
||||
*/
|
||||
class Request extends AbstractDataTransferObject
|
||||
{
|
||||
public const KEY_METHOD = 'method';
|
||||
public const KEY_URI = 'uri';
|
||||
public const KEY_HEADERS = 'headers';
|
||||
public const KEY_BODY = 'body';
|
||||
public const KEY_OPTIONS = 'options';
|
||||
/**
|
||||
* @var HttpMethodEnum The HTTP method.
|
||||
*/
|
||||
protected HttpMethodEnum $method;
|
||||
/**
|
||||
* @var string The request URI.
|
||||
*/
|
||||
protected string $uri;
|
||||
/**
|
||||
* @var HeadersCollection The request headers.
|
||||
*/
|
||||
protected HeadersCollection $headers;
|
||||
/**
|
||||
* @var array<string, mixed>|null The request data (for query params or form data).
|
||||
*/
|
||||
protected ?array $data = null;
|
||||
/**
|
||||
* @var string|null The request body (raw string content).
|
||||
*/
|
||||
protected ?string $body = null;
|
||||
/**
|
||||
* @var RequestOptions|null Request transport options.
|
||||
*/
|
||||
protected ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param HttpMethodEnum $method The HTTP method.
|
||||
* @param string $uri The request URI.
|
||||
* @param array<string, string|list<string>> $headers The request headers.
|
||||
* @param string|array<string, mixed>|null $data The request data.
|
||||
* @param RequestOptions|null $options The request transport options.
|
||||
*
|
||||
* @throws InvalidArgumentException If the URI is empty.
|
||||
*/
|
||||
public function __construct(HttpMethodEnum $method, string $uri, array $headers = [], $data = null, ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null)
|
||||
{
|
||||
if (empty($uri)) {
|
||||
throw new InvalidArgumentException('URI cannot be empty.');
|
||||
}
|
||||
$this->method = $method;
|
||||
$this->uri = $uri;
|
||||
$this->headers = new HeadersCollection($headers);
|
||||
// Separate data and body based on type
|
||||
if (is_string($data)) {
|
||||
$this->body = $data;
|
||||
} elseif (is_array($data)) {
|
||||
$this->data = $data;
|
||||
}
|
||||
$this->options = $options;
|
||||
}
|
||||
/**
|
||||
* Creates a deep clone of this request.
|
||||
*
|
||||
* Clones the headers collection and request options to ensure
|
||||
* the cloned request is independent of the original.
|
||||
* The HTTP method enum is immutable and can be safely shared.
|
||||
*
|
||||
* @since 0.4.2
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
// Clone headers collection
|
||||
$this->headers = clone $this->headers;
|
||||
// Clone request options if present (contains only primitives)
|
||||
if ($this->options !== null) {
|
||||
$this->options = clone $this->options;
|
||||
}
|
||||
// Note: $method is an immutable enum and can be safely shared
|
||||
}
|
||||
/**
|
||||
* Gets the HTTP method.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return HttpMethodEnum The HTTP method.
|
||||
*/
|
||||
public function getMethod(): HttpMethodEnum
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
/**
|
||||
* Gets the request URI.
|
||||
*
|
||||
* For GET requests with array data, appends the data as query parameters.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return string The URI.
|
||||
*/
|
||||
public function getUri(): string
|
||||
{
|
||||
// If GET request with data, append as query parameters
|
||||
if ($this->method === HttpMethodEnum::GET() && $this->data !== null && !empty($this->data)) {
|
||||
$separator = str_contains($this->uri, '?') ? '&' : '?';
|
||||
return $this->uri . $separator . http_build_query($this->data);
|
||||
}
|
||||
return $this->uri;
|
||||
}
|
||||
/**
|
||||
* Gets the request headers.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return array<string, list<string>> The headers.
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers->getAll();
|
||||
}
|
||||
/**
|
||||
* Gets a specific header value.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $name The header name (case-insensitive).
|
||||
* @return list<string>|null The header value(s) or null if not found.
|
||||
*/
|
||||
public function getHeader(string $name): ?array
|
||||
{
|
||||
return $this->headers->get($name);
|
||||
}
|
||||
/**
|
||||
* Gets header values as a comma-separated string.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $name The header name (case-insensitive).
|
||||
* @return string|null The header values as a comma-separated string, or null if not found.
|
||||
*/
|
||||
public function getHeaderAsString(string $name): ?string
|
||||
{
|
||||
return $this->headers->getAsString($name);
|
||||
}
|
||||
/**
|
||||
* Checks if a header exists.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $name The header name (case-insensitive).
|
||||
* @return bool True if the header exists, false otherwise.
|
||||
*/
|
||||
public function hasHeader(string $name): bool
|
||||
{
|
||||
return $this->headers->has($name);
|
||||
}
|
||||
/**
|
||||
* Gets the request body.
|
||||
*
|
||||
* For GET requests, returns null.
|
||||
* For POST/PUT/PATCH requests:
|
||||
* - If body is set, returns it as-is
|
||||
* - If data is set and Content-Type is JSON, returns JSON-encoded data
|
||||
* - If data is set and Content-Type is form, returns URL-encoded data
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return string|null The body.
|
||||
* @throws JsonException If the data cannot be encoded to JSON.
|
||||
*/
|
||||
public function getBody(): ?string
|
||||
{
|
||||
// GET requests don't have a body
|
||||
if (!$this->method->hasBody()) {
|
||||
return null;
|
||||
}
|
||||
// If body is set, return it as-is
|
||||
if ($this->body !== null) {
|
||||
return $this->body;
|
||||
}
|
||||
// If data is set, encode based on content type
|
||||
if ($this->data !== null) {
|
||||
$contentType = $this->getContentType();
|
||||
// JSON encoding
|
||||
if ($contentType !== null && stripos($contentType, 'application/json') !== \false) {
|
||||
return json_encode($this->data, \JSON_THROW_ON_ERROR);
|
||||
}
|
||||
// Default to URL encoding for forms
|
||||
return http_build_query($this->data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Gets the Content-Type header value.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return string|null The Content-Type header value or null if not set.
|
||||
*/
|
||||
private function getContentType(): ?string
|
||||
{
|
||||
$values = $this->getHeader('Content-Type');
|
||||
return $values !== null ? $values[0] : null;
|
||||
}
|
||||
/**
|
||||
* Returns a new instance with the specified header.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $name The header name.
|
||||
* @param string|list<string> $value The header value(s).
|
||||
* @return self A new instance with the header.
|
||||
*/
|
||||
public function withHeader(string $name, $value): self
|
||||
{
|
||||
$newHeaders = $this->headers->withHeader($name, $value);
|
||||
$new = clone $this;
|
||||
$new->headers = $newHeaders;
|
||||
return $new;
|
||||
}
|
||||
/**
|
||||
* Returns a new instance with the specified data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string|array<string, mixed> $data The request data.
|
||||
* @return self A new instance with the data.
|
||||
*/
|
||||
public function withData($data): self
|
||||
{
|
||||
$new = clone $this;
|
||||
if (is_string($data)) {
|
||||
$new->body = $data;
|
||||
$new->data = null;
|
||||
} elseif (is_array($data)) {
|
||||
$new->data = $data;
|
||||
$new->body = null;
|
||||
} else {
|
||||
$new->data = null;
|
||||
$new->body = null;
|
||||
}
|
||||
return $new;
|
||||
}
|
||||
/**
|
||||
* Gets the request data array.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return array<string, mixed>|null The request data array.
|
||||
*/
|
||||
public function getData(): ?array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
/**
|
||||
* Gets the request options.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return RequestOptions|null Request transport options when configured.
|
||||
*/
|
||||
public function getOptions(): ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
/**
|
||||
* Returns a new instance with the specified request options.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param RequestOptions|null $options The request options to apply.
|
||||
* @return self A new instance with the options.
|
||||
*/
|
||||
public function withOptions(?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options): self
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->options = $options;
|
||||
return $new;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public static function getJsonSchema(): array
|
||||
{
|
||||
return ['type' => 'object', 'properties' => [self::KEY_METHOD => ['type' => 'string', 'description' => 'The HTTP method.'], self::KEY_URI => ['type' => 'string', 'description' => 'The request URI.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The request headers.'], self::KEY_BODY => ['type' => ['string'], 'description' => 'The request body.'], self::KEY_OPTIONS => \WordPress\AiClient\Providers\Http\DTO\RequestOptions::getJsonSchema()], 'required' => [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]];
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return RequestArrayShape
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = [
|
||||
self::KEY_METHOD => $this->method->value,
|
||||
self::KEY_URI => $this->getUri(),
|
||||
// Include query params if GET with data
|
||||
self::KEY_HEADERS => $this->headers->getAll(),
|
||||
];
|
||||
// Include body if present (getBody() handles the conversion)
|
||||
$body = $this->getBody();
|
||||
if ($body !== null) {
|
||||
$array[self::KEY_BODY] = $body;
|
||||
}
|
||||
if ($this->options !== null) {
|
||||
$optionsArray = $this->options->toArray();
|
||||
if (!empty($optionsArray)) {
|
||||
$array[self::KEY_OPTIONS] = $optionsArray;
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public static function fromArray(array $array): self
|
||||
{
|
||||
static::validateFromArrayData($array, [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]);
|
||||
return new self(HttpMethodEnum::from($array[self::KEY_METHOD]), $array[self::KEY_URI], $array[self::KEY_HEADERS] ?? [], $array[self::KEY_BODY] ?? null, isset($array[self::KEY_OPTIONS]) ? \WordPress\AiClient\Providers\Http\DTO\RequestOptions::fromArray($array[self::KEY_OPTIONS]) : null);
|
||||
}
|
||||
/**
|
||||
* Creates a Request instance from a PSR-7 RequestInterface.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param RequestInterface $psrRequest The PSR-7 request to convert.
|
||||
* @return self A new Request instance.
|
||||
* @throws InvalidArgumentException If the HTTP method is not supported.
|
||||
*/
|
||||
public static function fromPsrRequest(RequestInterface $psrRequest): self
|
||||
{
|
||||
$method = HttpMethodEnum::from($psrRequest->getMethod());
|
||||
$uri = (string) $psrRequest->getUri();
|
||||
// Convert PSR-7 headers to array format expected by our constructor
|
||||
/** @var array<string, list<string>> $headers */
|
||||
$headers = $psrRequest->getHeaders();
|
||||
// Get body content
|
||||
$body = $psrRequest->getBody()->getContents();
|
||||
$bodyOrData = !empty($body) ? $body : null;
|
||||
return new self($method, $uri, $headers, $bodyOrData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WordPress\AiClient\Providers\Http\DTO;
|
||||
|
||||
use WordPress\AiClient\Common\AbstractDataTransferObject;
|
||||
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
|
||||
/**
|
||||
* Represents optional HTTP transport configuration for a single request.
|
||||
*
|
||||
* Provides mutable setters for working with timeouts and redirect handling.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @phpstan-type RequestOptionsArrayShape array{
|
||||
* timeout?: float|null,
|
||||
* connectTimeout?: float|null,
|
||||
* maxRedirects?: int|null
|
||||
* }
|
||||
*
|
||||
* @extends AbstractDataTransferObject<RequestOptionsArrayShape>
|
||||
*/
|
||||
class RequestOptions extends AbstractDataTransferObject
|
||||
{
|
||||
public const KEY_TIMEOUT = 'timeout';
|
||||
public const KEY_CONNECT_TIMEOUT = 'connectTimeout';
|
||||
public const KEY_MAX_REDIRECTS = 'maxRedirects';
|
||||
/**
|
||||
* @var float|null Maximum duration in seconds to wait for the full response.
|
||||
*/
|
||||
protected ?float $timeout = null;
|
||||
/**
|
||||
* @var float|null Maximum duration in seconds to wait for the initial connection.
|
||||
*/
|
||||
protected ?float $connectTimeout = null;
|
||||
/**
|
||||
* @var int|null Maximum number of redirects to follow. 0 disables redirects, null is unspecified.
|
||||
*/
|
||||
protected ?int $maxRedirects = null;
|
||||
/**
|
||||
* Sets the request timeout in seconds.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param float|null $timeout Timeout in seconds.
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException When timeout is negative.
|
||||
*/
|
||||
public function setTimeout(?float $timeout): void
|
||||
{
|
||||
$this->validateTimeout($timeout, self::KEY_TIMEOUT);
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
/**
|
||||
* Sets the connection timeout in seconds.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param float|null $timeout Connection timeout in seconds.
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException When timeout is negative.
|
||||
*/
|
||||
public function setConnectTimeout(?float $timeout): void
|
||||
{
|
||||
$this->validateTimeout($timeout, self::KEY_CONNECT_TIMEOUT);
|
||||
$this->connectTimeout = $timeout;
|
||||
}
|
||||
/**
|
||||
* Sets the maximum number of redirects to follow.
|
||||
*
|
||||
* Set to 0 to disable redirects, null for unspecified, or a positive integer
|
||||
* to enable redirects with a maximum count.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param int|null $maxRedirects Maximum redirects to follow, or 0 to disable, or null for unspecified.
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException When redirect count is negative.
|
||||
*/
|
||||
public function setMaxRedirects(?int $maxRedirects): void
|
||||
{
|
||||
if ($maxRedirects !== null && $maxRedirects < 0) {
|
||||
throw new InvalidArgumentException('Request option "maxRedirects" must be greater than or equal to 0.');
|
||||
}
|
||||
$this->maxRedirects = $maxRedirects;
|
||||
}
|
||||
/**
|
||||
* Gets the request timeout in seconds.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return float|null Timeout in seconds.
|
||||
*/
|
||||
public function getTimeout(): ?float
|
||||
{
|
||||
return $this->timeout;
|
||||
}
|
||||
/**
|
||||
* Gets the connection timeout in seconds.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return float|null Connection timeout in seconds.
|
||||
*/
|
||||
public function getConnectTimeout(): ?float
|
||||
{
|
||||
return $this->connectTimeout;
|
||||
}
|
||||
/**
|
||||
* Checks whether redirects are allowed.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return bool|null True when redirects are allowed (maxRedirects > 0),
|
||||
* false when disabled (maxRedirects = 0),
|
||||
* null when unspecified (maxRedirects = null).
|
||||
*/
|
||||
public function allowsRedirects(): ?bool
|
||||
{
|
||||
if ($this->maxRedirects === null) {
|
||||
return null;
|
||||
}
|
||||
return $this->maxRedirects > 0;
|
||||
}
|
||||
/**
|
||||
* Gets the maximum number of redirects to follow.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return int|null Maximum redirects or null when not specified.
|
||||
*/
|
||||
public function getMaxRedirects(): ?int
|
||||
{
|
||||
return $this->maxRedirects;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @return RequestOptionsArrayShape
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = [];
|
||||
if ($this->timeout !== null) {
|
||||
$data[self::KEY_TIMEOUT] = $this->timeout;
|
||||
}
|
||||
if ($this->connectTimeout !== null) {
|
||||
$data[self::KEY_CONNECT_TIMEOUT] = $this->connectTimeout;
|
||||
}
|
||||
if ($this->maxRedirects !== null) {
|
||||
$data[self::KEY_MAX_REDIRECTS] = $this->maxRedirects;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static function fromArray(array $array): self
|
||||
{
|
||||
$instance = new self();
|
||||
if (isset($array[self::KEY_TIMEOUT])) {
|
||||
$instance->setTimeout((float) $array[self::KEY_TIMEOUT]);
|
||||
}
|
||||
if (isset($array[self::KEY_CONNECT_TIMEOUT])) {
|
||||
$instance->setConnectTimeout((float) $array[self::KEY_CONNECT_TIMEOUT]);
|
||||
}
|
||||
if (isset($array[self::KEY_MAX_REDIRECTS])) {
|
||||
$instance->setMaxRedirects((int) $array[self::KEY_MAX_REDIRECTS]);
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public static function getJsonSchema(): array
|
||||
{
|
||||
return ['type' => 'object', 'properties' => [self::KEY_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the full response.'], self::KEY_CONNECT_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the initial connection.'], self::KEY_MAX_REDIRECTS => ['type' => ['integer', 'null'], 'minimum' => 0, 'description' => 'Maximum redirects to follow. 0 disables, null is unspecified.']], 'additionalProperties' => \false];
|
||||
}
|
||||
/**
|
||||
* Validates timeout values.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param float|null $value Timeout to validate.
|
||||
* @param string $fieldName Field name for the error message.
|
||||
*
|
||||
* @throws InvalidArgumentException When timeout is negative.
|
||||
*/
|
||||
private function validateTimeout(?float $value, string $fieldName): void
|
||||
{
|
||||
if ($value !== null && $value < 0) {
|
||||
throw new InvalidArgumentException(sprintf('Request option "%s" must be greater than or equal to 0.', $fieldName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace WordPress\AiClient\Providers\Http\DTO;
|
||||
|
||||
use WordPress\AiClient\Common\AbstractDataTransferObject;
|
||||
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
|
||||
use WordPress\AiClient\Providers\Http\Collections\HeadersCollection;
|
||||
/**
|
||||
* Represents an HTTP response.
|
||||
*
|
||||
* This class encapsulates HTTP response data that has been converted
|
||||
* from PSR-7 responses by the HTTP transporter.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @phpstan-type ResponseArrayShape array{
|
||||
* statusCode: int,
|
||||
* headers: array<string, list<string>>,
|
||||
* body?: string|null
|
||||
* }
|
||||
*
|
||||
* @extends AbstractDataTransferObject<ResponseArrayShape>
|
||||
*/
|
||||
class Response extends AbstractDataTransferObject
|
||||
{
|
||||
public const KEY_STATUS_CODE = 'statusCode';
|
||||
public const KEY_HEADERS = 'headers';
|
||||
public const KEY_BODY = 'body';
|
||||
/**
|
||||
* @var int The HTTP status code.
|
||||
*/
|
||||
protected int $statusCode;
|
||||
/**
|
||||
* @var HeadersCollection The response headers.
|
||||
*/
|
||||
protected HeadersCollection $headers;
|
||||
/**
|
||||
* @var string|null The response body.
|
||||
*/
|
||||
protected ?string $body;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param int $statusCode The HTTP status code.
|
||||
* @param array<string, string|list<string>> $headers The response headers.
|
||||
* @param string|null $body The response body.
|
||||
*
|
||||
* @throws InvalidArgumentException If the status code is invalid.
|
||||
*/
|
||||
public function __construct(int $statusCode, array $headers, ?string $body = null)
|
||||
{
|
||||
if ($statusCode < 100 || $statusCode >= 600) {
|
||||
throw new InvalidArgumentException('Invalid HTTP status code: ' . $statusCode);
|
||||
}
|
||||
$this->statusCode = $statusCode;
|
||||
$this->headers = new HeadersCollection($headers);
|
||||
$this->body = $body;
|
||||
}
|
||||
/**
|
||||
* Creates a deep clone of this response.
|
||||
*
|
||||
* Clones the headers collection to ensure the cloned
|
||||
* response is independent of the original.
|
||||
*
|
||||
* @since 0.4.2
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
// Clone headers collection
|
||||
$this->headers = clone $this->headers;
|
||||
}
|
||||
/**
|
||||
* Gets the HTTP status code.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return int The status code.
|
||||
*/
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->statusCode;
|
||||
}
|
||||
/**
|
||||
* Gets the response headers.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return array<string, list<string>> The headers.
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers->getAll();
|
||||
}
|
||||
/**
|
||||
* Gets a specific header value.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $name The header name (case-insensitive).
|
||||
* @return list<string>|null The header value(s) or null if not found.
|
||||
*/
|
||||
public function getHeader(string $name): ?array
|
||||
{
|
||||
return $this->headers->get($name);
|
||||
}
|
||||
/**
|
||||
* Gets header values as a comma-separated string.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $name The header name (case-insensitive).
|
||||
* @return string|null The header values as a comma-separated string or null if not found.
|
||||
*/
|
||||
public function getHeaderAsString(string $name): ?string
|
||||
{
|
||||
return $this->headers->getAsString($name);
|
||||
}
|
||||
/**
|
||||
* Gets the response body.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return string|null The body.
|
||||
*/
|
||||
public function getBody(): ?string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
/**
|
||||
* Checks if the response has a header.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $name The header name.
|
||||
* @return bool True if the header exists, false otherwise.
|
||||
*/
|
||||
public function hasHeader(string $name): bool
|
||||
{
|
||||
return $this->headers->has($name);
|
||||
}
|
||||
/**
|
||||
* Checks if the response indicates success.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return bool True if status code is 2xx, false otherwise.
|
||||
*/
|
||||
public function isSuccessful(): bool
|
||||
{
|
||||
return $this->statusCode >= 200 && $this->statusCode < 300;
|
||||
}
|
||||
/**
|
||||
* Gets the response data as an array.
|
||||
*
|
||||
* Attempts to decode the body as JSON. Returns null if the body
|
||||
* is empty or not valid JSON.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return array<string, mixed>|null The decoded data or null.
|
||||
*/
|
||||
public function getData(): ?array
|
||||
{
|
||||
if ($this->body === null || $this->body === '') {
|
||||
return null;
|
||||
}
|
||||
$data = json_decode($this->body, \true);
|
||||
if (json_last_error() !== \JSON_ERROR_NONE) {
|
||||
return null;
|
||||
}
|
||||
/** @var array<string, mixed>|null $data */
|
||||
return is_array($data) ? $data : null;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public static function getJsonSchema(): array
|
||||
{
|
||||
return ['type' => 'object', 'properties' => [self::KEY_STATUS_CODE => ['type' => 'integer', 'minimum' => 100, 'maximum' => 599, 'description' => 'The HTTP status code.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The response headers.'], self::KEY_BODY => ['type' => ['string', 'null'], 'description' => 'The response body.']], 'required' => [self::KEY_STATUS_CODE, self::KEY_HEADERS]];
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return ResponseArrayShape
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = [self::KEY_STATUS_CODE => $this->statusCode, self::KEY_HEADERS => $this->headers->getAll()];
|
||||
if ($this->body !== null) {
|
||||
$data[self::KEY_BODY] = $this->body;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public static function fromArray(array $array): self
|
||||
{
|
||||
static::validateFromArrayData($array, [self::KEY_STATUS_CODE, self::KEY_HEADERS]);
|
||||
return new self($array[self::KEY_STATUS_CODE], $array[self::KEY_HEADERS], $array[self::KEY_BODY] ?? null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user