Skip to content

Commit d7636f7

Browse files
committed
feat: improves request header handling
1 parent 7935a54 commit d7636f7

File tree

2 files changed

+100
-6
lines changed

2 files changed

+100
-6
lines changed

src/Providers/Http/DTO/Request.php

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace WordPress\AiClient\Providers\Http\DTO;
66

77
use InvalidArgumentException;
8+
use JsonException;
89
use WordPress\AiClient\Common\AbstractDataTransferObject;
910
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
1011

@@ -47,6 +48,11 @@ class Request extends AbstractDataTransferObject
4748
*/
4849
protected array $headers;
4950

51+
/**
52+
* @var array<string, string> Map of lowercase header names to actual header names for fast lookup.
53+
*/
54+
protected array $headersMap;
55+
5056
/**
5157
* @var string|array<string, mixed>|null The request data.
5258
*/
@@ -73,6 +79,7 @@ public function __construct(HttpMethodEnum $method, string $uri, array $headers
7379
$this->method = $method;
7480
$this->uri = $uri;
7581
$this->headers = $this->normalizeHeaders($headers);
82+
$this->headersMap = $this->buildHeadersMap($this->headers);
7683
$this->data = $data;
7784
}
7885

@@ -120,6 +127,50 @@ public function getHeaders(): array
120127
return $this->headers;
121128
}
122129

130+
/**
131+
* Gets a specific header value.
132+
*
133+
* @since n.e.x.t
134+
*
135+
* @param string $name The header name (case-insensitive).
136+
* @return list<string>|null The header value(s) or null if not found.
137+
*/
138+
public function getHeader(string $name): ?array
139+
{
140+
$lower = strtolower($name);
141+
if (!isset($this->headersMap[$lower])) {
142+
return null;
143+
}
144+
return $this->headers[$this->headersMap[$lower]];
145+
}
146+
147+
/**
148+
* Gets the first value of a specific header.
149+
*
150+
* @since n.e.x.t
151+
*
152+
* @param string $name The header name (case-insensitive).
153+
* @return string|null The first header value or null if not found.
154+
*/
155+
public function getHeaderLine(string $name): ?string
156+
{
157+
$values = $this->getHeader($name);
158+
return $values !== null ? implode(', ', $values) : null;
159+
}
160+
161+
/**
162+
* Checks if a header exists.
163+
*
164+
* @since n.e.x.t
165+
*
166+
* @param string $name The header name (case-insensitive).
167+
* @return bool True if the header exists, false otherwise.
168+
*/
169+
public function hasHeader(string $name): bool
170+
{
171+
return isset($this->headersMap[strtolower($name)]);
172+
}
173+
123174
/**
124175
* Gets the request body.
125176
*
@@ -176,12 +227,8 @@ public function getBody(): ?string
176227
*/
177228
private function getContentType(): ?string
178229
{
179-
foreach ($this->headers as $name => $values) {
180-
if (strcasecmp($name, 'Content-Type') === 0) {
181-
return $values[0] ?? null;
182-
}
183-
}
184-
return null;
230+
$values = $this->getHeader('Content-Type');
231+
return $values !== null ? $values[0] : null;
185232
}
186233

187234
/**
@@ -231,6 +278,23 @@ private function normalizeHeaders(array $headers): array
231278
return $normalized;
232279
}
233280

281+
/**
282+
* Builds a map of lowercase header names to actual header names.
283+
*
284+
* @since n.e.x.t
285+
*
286+
* @param array<string, list<string>> $headers The headers.
287+
* @return array<string, string> The headers map.
288+
*/
289+
private function buildHeadersMap(array $headers): array
290+
{
291+
$map = [];
292+
foreach (array_keys($headers) as $name) {
293+
$map[strtolower($name)] = $name;
294+
}
295+
return $map;
296+
}
297+
234298
/**
235299
* Gets the request data.
236300
*

tests/unit/Providers/Http/HttpTransporterTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,36 @@ public function testSendPostRequestWithArrayDataAsForm(): void
236236
$this->assertEquals('name=test&value=123', (string) $sentRequest->getBody());
237237
}
238238

239+
/**
240+
* Tests case-insensitive header access in Request.
241+
*
242+
* @return void
243+
*/
244+
public function testRequestCaseInsensitiveHeaders(): void
245+
{
246+
// Arrange
247+
$headers = [
248+
'Content-Type' => 'application/json',
249+
'X-Custom-Header' => 'value',
250+
];
251+
$request = new Request(HttpMethodEnum::GET(), 'https://api.example.com', $headers);
252+
253+
// Assert - getHeader
254+
$this->assertEquals(['application/json'], $request->getHeader('Content-Type'));
255+
$this->assertEquals(['application/json'], $request->getHeader('content-type'));
256+
$this->assertEquals(['application/json'], $request->getHeader('CONTENT-TYPE'));
257+
258+
// Assert - getHeaderLine
259+
$this->assertEquals('application/json', $request->getHeaderLine('Content-Type'));
260+
$this->assertEquals('application/json', $request->getHeaderLine('content-type'));
261+
262+
// Assert - hasHeader
263+
$this->assertTrue($request->hasHeader('Content-Type'));
264+
$this->assertTrue($request->hasHeader('content-type'));
265+
$this->assertTrue($request->hasHeader('X-CUSTOM-HEADER'));
266+
$this->assertFalse($request->hasHeader('Non-Existent'));
267+
}
268+
239269
/**
240270
* Tests using discovery when no dependencies provided.
241271
*

0 commit comments

Comments
 (0)