feat: add GetBundledScenario handler with fixture validation

This commit is contained in:
Keith Solomon
2026-07-25 23:47:09 -05:00
parent 1f9e76afef
commit b8451112d3
2 changed files with 233 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace BattleForge\Http\Handlers;
use BattleForge\Application\ScenarioSerializer;
use BattleForge\Domain\ScenarioValidator;
use BattleForge\Http\Request;
use BattleForge\Http\Response;
use InvalidArgumentException;
final class GetBundledScenario
{
private const ID_PATTERN = '/^[a-z0-9-]+$/';
public function __construct(private readonly string $scenariosDir)
{
}
/** @param array<string, string> $params */
public function handle(Request $request, array $params): Response
{
$id = (string) ($params['id'] ?? '');
if (preg_match(self::ID_PATTERN, $id) !== 1) {
return Response::json(404, ['error' => 'not found']);
}
$manifestPath = $this->scenariosDir . '/manifest.json';
$manifest = [];
if (is_file($manifestPath)) {
$decoded = json_decode((string) file_get_contents($manifestPath), true);
if (is_array($decoded)) {
$manifest = $decoded;
}
}
if (!isset($manifest[$id])) {
return Response::json(404, ['error' => 'not found']);
}
$fixturePath = $this->scenariosDir . '/' . $id . '.json';
$real = realpath($fixturePath);
$realDir = realpath($this->scenariosDir);
$insideBase = $real !== false
&& $realDir !== false
&& str_starts_with($real, $realDir . DIRECTORY_SEPARATOR);
if ($insideBase === false || !is_file($real)) {
return Response::json(404, ['error' => 'not found']);
}
$data = json_decode((string) file_get_contents($real), true, flags: JSON_THROW_ON_ERROR);
if (!is_array($data)) {
return Response::json(500, ['error' => 'fixture invalid', 'details' => ['Fixture JSON is not an object.']]);
}
try {
$scenario = ScenarioSerializer::scenarioFromArray($data);
} catch (InvalidArgumentException $exception) {
return Response::json(500, ['error' => 'fixture invalid', 'details' => [$exception->getMessage()]]);
}
$errors = ScenarioValidator::validate($scenario);
if ($errors !== []) {
return Response::json(500, ['error' => 'fixture invalid', 'details' => $errors]);
}
return Response::json(200, ScenarioSerializer::scenarioToArray($scenario));
}
}