Skip to content

Commit a6a7c47

Browse files
committed
refactor: cover monolog http bridge with mago
1 parent 4b31e80 commit a6a7c47

8 files changed

Lines changed: 96 additions & 118 deletions

File tree

Justfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ analyze-mago *args:
7777
src/bridge/symfony/http-foundation-telemetry \
7878
src/bridge/telemetry/otlp \
7979
src/bridge/filesystem/async-aws \
80-
src/bridge/filesystem/azure
80+
src/bridge/filesystem/azure \
81+
src/bridge/monolog/http
8182

8283
# Auto-fix code style (Mago format + lint --fix) and GitHub Actions findings (zizmor --fix).
8384
fix:

src/bridge/monolog/http/src/Flow/Bridge/Monolog/Http/Config/RequestConfig.php

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,16 @@
88
use Flow\Bridge\Monolog\Http\Sanitization\Sanitizer;
99
use Flow\Bridge\Monolog\Http\Sanitization\SanitizerFactory;
1010

11-
use function is_array;
1211
use function sprintf;
1312

1413
final readonly class RequestConfig
1514
{
1615
/**
17-
* @var array<string, array<string, mixed>|Sanitizer>
16+
* @var array<string, Sanitizer>
1817
*/
1918
private array $sanitizers;
2019

2120
/**
22-
* @param bool $withMethod
23-
* @param bool $withUri
24-
* @param bool $withBody
25-
* @param int $bodySizeLimit
2621
* @param array<string> $headers
2722
* @param array<string, array<string, mixed>|Sanitizer> $sanitizers
2823
*/
@@ -39,21 +34,18 @@ public function __construct(
3934
foreach ($sanitizers as $key => $sanitizer) {
4035
if ($sanitizer instanceof Sanitizer) {
4136
$initializedSanitizers[$key] = $sanitizer;
42-
} elseif (is_array($sanitizer)) {
43-
try {
44-
$initializedSanitizers[$key] = SanitizerFactory::fromArray($sanitizer);
45-
} catch (InvalidArgumentException $e) {
46-
throw new InvalidArgumentException(
47-
sprintf('Sanitizer for key "%s" could not be created from array: %s', $key, $e->getMessage()),
48-
0,
49-
$e,
50-
);
51-
}
52-
} else {
53-
throw new InvalidArgumentException(sprintf(
54-
'Sanitizer for key "%s" must be an instance of Sanitizer or an array that can be converted to a Sanitizer',
55-
$key,
56-
));
37+
38+
continue;
39+
}
40+
41+
try {
42+
$initializedSanitizers[$key] = SanitizerFactory::fromArray($sanitizer);
43+
} catch (InvalidArgumentException $e) {
44+
throw new InvalidArgumentException(
45+
sprintf('Sanitizer for key "%s" could not be created from array: %s', $key, $e->getMessage()),
46+
0,
47+
$e,
48+
);
5749
}
5850
}
5951

src/bridge/monolog/http/src/Flow/Bridge/Monolog/Http/Config/ResponseConfig.php

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,16 @@
88
use Flow\Bridge\Monolog\Http\Sanitization\Sanitizer;
99
use Flow\Bridge\Monolog\Http\Sanitization\SanitizerFactory;
1010

11-
use function is_array;
1211
use function sprintf;
1312

1413
final readonly class ResponseConfig
1514
{
1615
/**
17-
* @var array<string, array<string, mixed>|Sanitizer>
16+
* @var array<string, Sanitizer>
1817
*/
1918
private array $sanitizers;
2019

2120
/**
22-
* @param bool $withReasonPhrase
23-
* @param bool $withStatus
24-
* @param bool $withBody
25-
* @param int $bodySizeLimit
2621
* @param array<int> $withoutStatusCodes
2722
* @param array<string> $headers
2823
* @param array<string, array<string, mixed>|Sanitizer> $sanitizers
@@ -53,21 +48,18 @@ public function __construct(
5348
foreach ($sanitizers as $key => $sanitizer) {
5449
if ($sanitizer instanceof Sanitizer) {
5550
$initializedSanitizers[$key] = $sanitizer;
56-
} elseif (is_array($sanitizer)) {
57-
try {
58-
$initializedSanitizers[$key] = SanitizerFactory::fromArray($sanitizer);
59-
} catch (InvalidArgumentException $e) {
60-
throw new InvalidArgumentException(
61-
sprintf('Sanitizer for key "%s" could not be created from array: %s', $key, $e->getMessage()),
62-
0,
63-
$e,
64-
);
65-
}
66-
} else {
67-
throw new InvalidArgumentException(sprintf(
68-
'Sanitizer for key "%s" must be an instance of Sanitizer or an array that can be converted to a Sanitizer',
69-
$key,
70-
));
51+
52+
continue;
53+
}
54+
55+
try {
56+
$initializedSanitizers[$key] = SanitizerFactory::fromArray($sanitizer);
57+
} catch (InvalidArgumentException $e) {
58+
throw new InvalidArgumentException(
59+
sprintf('Sanitizer for key "%s" could not be created from array: %s', $key, $e->getMessage()),
60+
0,
61+
$e,
62+
);
7163
}
7264
}
7365

src/bridge/monolog/http/src/Flow/Bridge/Monolog/Http/PSR7Processor.php

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@
1313

1414
use function array_filter;
1515
use function array_is_list;
16+
use function array_keys;
1617
use function count;
1718
use function Flow\Types\DSL\type_array;
19+
use function Flow\Types\DSL\type_map;
20+
use function Flow\Types\DSL\type_mixed;
21+
use function Flow\Types\DSL\type_string;
1822
use function in_array;
1923
use function is_array;
2024
use function is_string;
@@ -31,19 +35,21 @@ public function __construct(
3135

3236
public function __invoke(LogRecord $record): LogRecord
3337
{
34-
$context = type_array()->assert($record->context);
38+
$context = $record->context;
3539

36-
foreach ($context as $key => $val) {
37-
if ($val instanceof RequestInterface) {
38-
$context[$key] = $this->normalizeRequest($val);
40+
foreach (array_keys($context) as $key) {
41+
if ($context[$key] instanceof RequestInterface) {
42+
$context[$key] = $this->normalizeRequest($context[$key]);
3943

4044
if (empty($context[$key])) {
4145
unset($context[$key]);
4246
}
47+
48+
continue;
4349
}
4450

45-
if ($val instanceof ResponseInterface) {
46-
$context[$key] = $this->normalizeResponse($val);
51+
if ($context[$key] instanceof ResponseInterface) {
52+
$context[$key] = $this->normalizeResponse($context[$key]);
4753

4854
if (empty($context[$key])) {
4955
unset($context[$key]);
@@ -85,14 +91,19 @@ private function normalizeRequest(RequestInterface $request): array
8591
$request->getBody()->rewind();
8692

8793
if ($this->isJson($body)) {
88-
$decodedBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
89-
$decodedBody = type_array()->assert($decodedBody);
90-
/** @var array<string, mixed> $sanitizedBody */
91-
$sanitizedBody = array_is_list($decodedBody) ? [] : $decodedBody;
92-
$body = $this->recursiveSanitize($sanitizedBody, $this->config->request->sanitizers());
94+
$decodedBody = type_array()->assert(json_decode($body, true, 512, JSON_THROW_ON_ERROR));
95+
96+
if (!array_is_list($decodedBody)) {
97+
$sanitizedBody = $this->recursiveSanitize(
98+
type_map(type_string(), type_mixed())->assert($decodedBody),
99+
$this->config->request->sanitizers(),
100+
);
101+
} else {
102+
$sanitizedBody = [];
103+
}
93104

94105
$requestData['body'] = substr(
95-
json_encode($body, JSON_THROW_ON_ERROR),
106+
json_encode($sanitizedBody, JSON_THROW_ON_ERROR),
96107
0,
97108
$this->config->request->bodySizeLimit(),
98109
);
@@ -112,7 +123,11 @@ private function normalizeRequest(RequestInterface $request): array
112123
if ($this->config->request->includeHeaders()) {
113124
$requestData['headers'] = array_filter(
114125
$request->getHeaders(),
115-
fn(string $header) => in_array(strtolower($header), $this->config->request->includeHeaders(), true),
126+
fn(int|string $header) => in_array(
127+
strtolower((string) $header),
128+
$this->config->request->includeHeaders(),
129+
true,
130+
),
116131
ARRAY_FILTER_USE_KEY,
117132
);
118133
}
@@ -144,14 +159,19 @@ private function normalizeResponse(ResponseInterface $response): array
144159
$response->getBody()->rewind();
145160

146161
if ($this->isJson($body)) {
147-
$decodedBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
148-
$decodedBody = type_array()->assert($decodedBody);
149-
/** @var array<string, mixed> $sanitizedBody */
150-
$sanitizedBody = array_is_list($decodedBody) ? [] : $decodedBody;
151-
$body = $this->recursiveSanitize($sanitizedBody, $this->config->response->sanitizers());
162+
$decodedBody = type_array()->assert(json_decode($body, true, 512, JSON_THROW_ON_ERROR));
163+
164+
if (!array_is_list($decodedBody)) {
165+
$sanitizedBody = $this->recursiveSanitize(
166+
type_map(type_string(), type_mixed())->assert($decodedBody),
167+
$this->config->response->sanitizers(),
168+
);
169+
} else {
170+
$sanitizedBody = [];
171+
}
152172

153173
$responseData['body'] = substr(
154-
json_encode($body, JSON_THROW_ON_ERROR),
174+
json_encode($sanitizedBody, JSON_THROW_ON_ERROR),
155175
0,
156176
$this->config->response->bodySizeLimit(),
157177
);
@@ -171,7 +191,11 @@ private function normalizeResponse(ResponseInterface $response): array
171191
if ($this->config->response->includeHeaders()) {
172192
$responseData['headers'] = array_filter(
173193
$response->getHeaders(),
174-
fn(string $header) => in_array(strtolower($header), $this->config->response->includeHeaders(), true),
194+
fn(int|string $header) => in_array(
195+
strtolower((string) $header),
196+
$this->config->response->includeHeaders(),
197+
true,
198+
),
175199
ARRAY_FILTER_USE_KEY,
176200
);
177201
}
@@ -193,16 +217,18 @@ private function recursiveSanitize(array $data, array $sanitizers): array
193217
return $data;
194218
}
195219

196-
foreach ($data as $key => $value) {
197-
if (is_array($value)) {
198-
/** @phpstan-var array<string, mixed> $value */
199-
$data[$key] = $this->recursiveSanitize($value, $sanitizers);
220+
foreach (array_keys($data) as $key) {
221+
if (is_array($data[$key]) && !array_is_list($data[$key])) {
222+
$data[$key] = $this->recursiveSanitize(
223+
type_map(type_string(), type_mixed())->assert($data[$key]),
224+
$sanitizers,
225+
);
200226

201227
continue;
202228
}
203229

204-
if (is_string($value) && isset($sanitizers[$key])) {
205-
$data[$key] = $sanitizers[$key]->sanitize($value);
230+
if (is_string($data[$key]) && isset($sanitizers[$key])) {
231+
$data[$key] = $sanitizers[$key]->sanitize($data[$key]);
206232
}
207233
}
208234

src/bridge/monolog/http/src/Flow/Bridge/Monolog/Http/Sanitization/SanitizerFactory.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ public static function fromArray(array $data): Sanitizer
2424
throw new InvalidArgumentException('Sanitizer type is required');
2525
}
2626

27-
$type = $data['type'];
28-
$type = type_string()->assert($type);
27+
$type = type_string()->assert($data['type']);
2928

3029
return match ($type) {
3130
'mask' => Mask::fromArray($data),

src/bridge/monolog/http/tests/Flow/Bridge/Monolog/Http/Tests/Unit/Config/RequestConfigTest.php

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,4 @@ public function test_constructor_throws_exception_when_sanitizer_array_is_invali
7979
'invalid' => [],
8080
]);
8181
}
82-
83-
public function test_constructor_throws_exception_when_sanitizer_is_not_an_instance_of_sanitizer_or_array(): void
84-
{
85-
$this->expectException(InvalidArgumentException::class);
86-
$this->expectExceptionMessage(
87-
'Sanitizer for key "invalid" must be an instance of Sanitizer or an array that can be converted to a Sanitizer',
88-
);
89-
90-
new RequestConfig(
91-
/** @phpstan-ignore-next-line */
92-
sanitizers: [
93-
'invalid' => 'not a sanitizer',
94-
],
95-
);
96-
}
9782
}

src/bridge/monolog/http/tests/Flow/Bridge/Monolog/Http/Tests/Unit/Config/ResponseConfigTest.php

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,4 @@ public function test_constructor_throws_exception_when_sanitizer_array_is_invali
7979
'invalid' => [],
8080
]);
8181
}
82-
83-
public function test_constructor_throws_exception_when_sanitizer_is_not_an_instance_of_sanitizer_or_array(): void
84-
{
85-
$this->expectException(InvalidArgumentException::class);
86-
$this->expectExceptionMessage(
87-
'Sanitizer for key "invalid" must be an instance of Sanitizer or an array that can be converted to a Sanitizer',
88-
);
89-
90-
new ResponseConfig(
91-
/** @phpstan-ignore-next-line */
92-
sanitizers: [
93-
'invalid' => 'not a sanitizer',
94-
],
95-
);
96-
}
9782
}

src/bridge/monolog/http/tests/Flow/Bridge/Monolog/Http/Tests/Unit/PSR7ProcessorSanitizationTest.php

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
use Monolog\LogRecord;
1515
use Nyholm\Psr7\Factory\Psr17Factory;
1616

17-
use function assert;
1817
use function Flow\Bridge\Monolog\Http\DSL\mask;
19-
use function is_array;
18+
use function Flow\Types\DSL\type_array;
19+
use function Flow\Types\DSL\type_string;
2020

2121
final class PSR7ProcessorSanitizationTest extends FlowTestCase
2222
{
@@ -56,17 +56,16 @@ public function test_sanitizing_request_fields(): void
5656
context: ['request' => $request],
5757
));
5858

59-
/** @phpstan-ignore-next-line */
60-
$requestData = json_decode((string) $record->context['request']['body'], true);
61-
assert(is_array($requestData));
59+
$request = type_array()->assert($record->context['request']);
60+
$requestData = type_array()->assert(json_decode(type_string()->assert($request['body']), true));
6261

6362
static::assertEquals('john_doe', $requestData['username']);
6463
static::assertEquals('***************', $requestData['password']);
6564
static::assertEquals('john@example.com', $requestData['email']);
6665
static::assertEquals('###############', $requestData['access_token']);
67-
assert(is_array($requestData['data']));
68-
static::assertEquals('se***********', $requestData['data']['key']);
69-
static::assertEquals('public_value', $requestData['data']['value']);
66+
$nestedData = type_array()->assert($requestData['data']);
67+
static::assertEquals('se***********', $nestedData['key']);
68+
static::assertEquals('public_value', $nestedData['value']);
7069
}
7170

7271
public function test_sanitizing_response_fields(): void
@@ -106,16 +105,15 @@ public function test_sanitizing_response_fields(): void
106105
context: ['response' => $response],
107106
));
108107

109-
/** @phpstan-ignore-next-line */
110-
$responseData = json_decode((string) $record->context['response']['body'], true);
111-
assert(is_array($responseData));
108+
$response = type_array()->assert($record->context['response']);
109+
$responseData = type_array()->assert(json_decode(type_string()->assert($response['body']), true));
112110

113111
static::assertEquals('success', $responseData['status']);
114-
assert(is_array($responseData['data']));
115-
assert(is_array($responseData['data']['user']));
116-
static::assertEquals(123, $responseData['data']['user']['id']);
117-
static::assertEquals('john_doe', $responseData['data']['user']['username']);
118-
static::assertEquals('*********************', $responseData['data']['user']['credentials']);
119-
static::assertEquals('sen############', $responseData['data']['user']['access_token']);
112+
$data = type_array()->assert($responseData['data']);
113+
$user = type_array()->assert($data['user']);
114+
static::assertEquals(123, $user['id']);
115+
static::assertEquals('john_doe', $user['username']);
116+
static::assertEquals('*********************', $user['credentials']);
117+
static::assertEquals('sen############', $user['access_token']);
120118
}
121119
}

0 commit comments

Comments
 (0)