Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## 0.1.2 Under development

- fix(security): reject link targets a browser would normalize, and move exception messages to the `Exception\Message` enum.

## 0.1.1 September 13, 2026

- feat(panel-view): add filterable tables, trace frames, safe links, and SQL text styling.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
/**
* Exception message templates authored by this package.
*
* Use {@see Message::getMessage()} to format a template with `sprintf()` arguments.
* Use {@see PanelViewMessage::getMessage()} to format a template with `sprintf()` arguments.
*/
enum Message: string
enum PanelViewMessage: string
{
/**
* Indicates that a column style is not a {@see \PHPForge\Debug\ColumnStyle} case.
Expand Down
22 changes: 11 additions & 11 deletions src/PanelView.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use InvalidArgumentException;
use JsonSerializable;
use PHPForge\Debug\Exception\Message;
use PHPForge\Debug\Exception\PanelViewMessage;

use function count;
use function in_array;
Expand Down Expand Up @@ -491,7 +491,7 @@ public static function trace(array $frames): array
foreach ($frames as $frame) {
if (is_array($frame) === false) {
throw new InvalidArgumentException(
Message::TRACE_FRAME_INVALID->getMessage(),
PanelViewMessage::TRACE_FRAME_INVALID->getMessage(),
);
}

Expand Down Expand Up @@ -560,7 +560,7 @@ private static function headers(array $headers): array
foreach ($headers as $header) {
if (is_string($header) === false) {
throw new InvalidArgumentException(
Message::TABLE_HEADER_INVALID->getMessage(),
PanelViewMessage::TABLE_HEADER_INVALID->getMessage(),
);
}

Expand Down Expand Up @@ -699,7 +699,7 @@ private static function paragraphOf(mixed $paragraph): array

if (array_is_list($paragraph) === false) {
throw new InvalidArgumentException(
Message::PARAGRAPH_CONTENT_INVALID->getMessage(),
PanelViewMessage::PARAGRAPH_CONTENT_INVALID->getMessage(),
);
}

Expand All @@ -723,7 +723,7 @@ private static function rows(array $rows, int $columns): array
foreach ($rows as $row) {
if (is_array($row) === false || array_is_list($row) === false || count($row) !== $columns) {
throw new InvalidArgumentException(
Message::TABLE_ROW_WIDTH_INVALID->getMessage(),
PanelViewMessage::TABLE_ROW_WIDTH_INVALID->getMessage(),
);
}

Expand Down Expand Up @@ -756,13 +756,13 @@ private static function styles(array $styles, int $columns): array
foreach ($styles as $column => $style) {
if (is_int($column) === false || $column < 0 || $column >= $columns) {
throw new InvalidArgumentException(
Message::COLUMN_STYLE_KEY_INVALID->getMessage(),
PanelViewMessage::COLUMN_STYLE_KEY_INVALID->getMessage(),
);
}

if ($style instanceof ColumnStyle === false) {
throw new InvalidArgumentException(
Message::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class),
PanelViewMessage::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class),
);
}

Expand All @@ -789,23 +789,23 @@ private static function target(string $href): string
{
if (strpbrk($href, "\t\n\r") !== false || trim($href, "\x00..\x20") !== $href) {
throw new InvalidArgumentException(
Message::LINK_TARGET_NORMALIZED->getMessage(),
PanelViewMessage::LINK_TARGET_NORMALIZED->getMessage(),
);
}

$parts = parse_url($href);

if ($parts === false) {
throw new InvalidArgumentException(
Message::LINK_TARGET_UNPARSABLE->getMessage(),
PanelViewMessage::LINK_TARGET_UNPARSABLE->getMessage(),
);
}

$scheme = $parts['scheme'] ?? null;

if ($scheme !== null && in_array(strtolower($scheme), ['http', 'https', 'mailto'], true) === false) {
throw new InvalidArgumentException(
Message::LINK_TARGET_SCHEME_INVALID->getMessage($scheme),
PanelViewMessage::LINK_TARGET_SCHEME_INVALID->getMessage($scheme),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);
}

Expand All @@ -822,7 +822,7 @@ private static function target(string $href): string
private static function unsupportedInline(mixed $value): InvalidArgumentException
{
return new InvalidArgumentException(
Message::INLINE_CONTENT_INVALID->getMessage(get_debug_type($value)),
PanelViewMessage::INLINE_CONTENT_INVALID->getMessage(get_debug_type($value)),
);
}
}
28 changes: 14 additions & 14 deletions tests/PanelViewTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use InvalidArgumentException;
use PHPForge\Debug\{ColumnStyle, PanelView, Tone};
use PHPForge\Debug\Exception\Message;
use PHPForge\Debug\Exception\PanelViewMessage;
use PHPForge\Debug\Tests\Provider\{InlineScalarProvider, LinkTargetProvider};
use PHPUnit\Framework\Attributes\DataProviderExternal;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -263,7 +263,7 @@ public function testThrowInvalidArgumentExceptionForAssociativeParagraph(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::PARAGRAPH_CONTENT_INVALID->getMessage(),
PanelViewMessage::PARAGRAPH_CONTENT_INVALID->getMessage(),
);

PanelView::create()->emptyState('Empty', ['first' => 'A']);
Expand All @@ -274,7 +274,7 @@ public function testThrowInvalidArgumentExceptionForBrowserNormalizedLinkTarget(
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::LINK_TARGET_NORMALIZED->getMessage(),
PanelViewMessage::LINK_TARGET_NORMALIZED->getMessage(),
);

PanelView::link(
Expand All @@ -288,7 +288,7 @@ public function testThrowInvalidArgumentExceptionForExecutableLinkTarget(string
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::LINK_TARGET_SCHEME_INVALID->getMessage($scheme),
PanelViewMessage::LINK_TARGET_SCHEME_INVALID->getMessage($scheme),
);

PanelView::link(
Expand All @@ -301,7 +301,7 @@ public function testThrowInvalidArgumentExceptionForForgedExecutableLink(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::LINK_TARGET_SCHEME_INVALID->getMessage('javascript'),
PanelViewMessage::LINK_TARGET_SCHEME_INVALID->getMessage('javascript'),
);

PanelView::create()
Expand All @@ -319,7 +319,7 @@ public function testThrowInvalidArgumentExceptionForForgedInlineValue(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::INLINE_CONTENT_INVALID->getMessage('array'),
PanelViewMessage::INLINE_CONTENT_INVALID->getMessage('array'),
);

PanelView::create()->paragraph(['kind' => 'text']);
Expand All @@ -329,7 +329,7 @@ public function testThrowInvalidArgumentExceptionForNonArrayTraceFrame(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::TRACE_FRAME_INVALID->getMessage(),
PanelViewMessage::TRACE_FRAME_INVALID->getMessage(),
);

PanelView::trace(['not a frame']);
Expand All @@ -339,7 +339,7 @@ public function testThrowInvalidArgumentExceptionForNonColumnStyle(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class),
PanelViewMessage::COLUMN_STYLE_INVALID->getMessage(ColumnStyle::class),
);

PanelView::create()->table(['One'], [['a']], styles: [0 => 'pill']);
Expand All @@ -349,7 +349,7 @@ public function testThrowInvalidArgumentExceptionForNonInlineObject(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::INLINE_CONTENT_INVALID->getMessage('stdClass'),
PanelViewMessage::INLINE_CONTENT_INVALID->getMessage('stdClass'),
);

PanelView::create()->paragraph(new stdClass());
Expand All @@ -359,7 +359,7 @@ public function testThrowInvalidArgumentExceptionForNonListRow(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::TABLE_ROW_WIDTH_INVALID->getMessage(),
PanelViewMessage::TABLE_ROW_WIDTH_INVALID->getMessage(),
);

PanelView::create()->table(['One'], ['not a row']);
Expand All @@ -369,7 +369,7 @@ public function testThrowInvalidArgumentExceptionForNonStringHeader(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::TABLE_HEADER_INVALID->getMessage(),
PanelViewMessage::TABLE_HEADER_INVALID->getMessage(),
);

PanelView::create()->table([1], [[1]]);
Expand All @@ -379,7 +379,7 @@ public function testThrowInvalidArgumentExceptionForRowWidthMismatch(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::TABLE_ROW_WIDTH_INVALID->getMessage(),
PanelViewMessage::TABLE_ROW_WIDTH_INVALID->getMessage(),
);

PanelView::create()->table(['One'], [[]]);
Expand All @@ -389,7 +389,7 @@ public function testThrowInvalidArgumentExceptionForUnknownStyledColumn(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::COLUMN_STYLE_KEY_INVALID->getMessage(),
PanelViewMessage::COLUMN_STYLE_KEY_INVALID->getMessage(),
);

PanelView::create()->table(['One'], [['a']], styles: [1 => ColumnStyle::PILL]);
Expand All @@ -399,7 +399,7 @@ public function testThrowInvalidArgumentExceptionForUnparsableLinkTarget(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
Message::LINK_TARGET_UNPARSABLE->getMessage(),
PanelViewMessage::LINK_TARGET_UNPARSABLE->getMessage(),
);

PanelView::link(
Expand Down
Loading