trypost/app/Services/Automation/AutomationConfigValidator.php
Paulo Castellano 2bd2e72656 Validate webhook payload template is JSON before it can run
A webhook node parses its payload template as JSON before resolving
placeholders, so a template with unquoted {{ }} placeholders or any malformed
JSON could be saved, tested, and activated — only to fail midway through a run.

Reject it up front instead: AutomationConfigValidator is the single source of
truth for per-node config issues (keyed to the field the editor surfaces them
under), enforced on save (field errors), on activate, and before a test run.
The editor mirrors the check to disable Test/Activate with a clear reason, and
the test panel now surfaces the server's message instead of a generic toast.
2026-06-13 16:03:19 -03:00

59 lines
1.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Automation;
use App\Enums\Automation\Node\Type as NodeType;
/**
* Single source of truth for per-node config validation. Walks an automation's
* nodes and reports every config issue, delegating to the type-specific
* validators. Shared by save (field-keyed errors), activation, and the editor
* test run so a misconfigured node is rejected up front with a clear message
* instead of failing midway through execution.
*/
final class AutomationConfigValidator
{
public function __construct(
private GenerateNodeValidator $generateValidator,
private WebhookNodeValidator $webhookValidator,
) {}
/**
* Every config issue across the given nodes, in node order.
*
* @param array<int, array<string, mixed>> $nodes
* @return list<array{node_index: int, field: string, message: string}>
*/
public function issues(array $nodes): array
{
$issues = [];
foreach ($nodes as $index => $node) {
$config = (array) data_get($node, 'data', []);
[$field, $message] = match (data_get($node, 'type')) {
NodeType::Generate->value => ['accounts', $this->generateValidator->issueFor($config)],
NodeType::Webhook->value => ['payload_template', $this->webhookValidator->issueFor($config)],
default => [null, null],
};
if ($message !== null) {
$issues[] = ['node_index' => $index, 'field' => $field, 'message' => $message];
}
}
return $issues;
}
/**
* The first config issue's message, or null when every node is runnable.
*
* @param array<int, array<string, mixed>> $nodes
*/
public function firstMessage(array $nodes): ?string
{
return $this->issues($nodes)[0]['message'] ?? null;
}
}