1
0
mirror of https://github.com/php/pie.git synced 2026-03-23 23:12:17 +01:00

Validate schema for PIE settings file

This commit is contained in:
James Titcumb
2025-10-11 11:44:00 +01:00
parent 943fe19552
commit ded16e607c
4 changed files with 136 additions and 16 deletions

View File

@@ -432,12 +432,6 @@ parameters:
count: 1
path: src/Platform/TargetPlatform.php
-
message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(mixed\)\: mixed\)\|null, Closure\(array\)\: non\-empty\-array given\.$#'
identifier: argument.type
count: 1
path: src/SelfManage/Update/FetchPieReleaseFromGitHub.php
-
message: '#^Dead catch \- Php\\Pie\\SelfManage\\Verify\\GithubCliNotAvailable is never thrown in the try block\.$#'
identifier: catch.neverThrown
@@ -675,7 +669,7 @@ parameters:
-
message: '#^Parameter \#4 \$body of class Composer\\Util\\Http\\Response constructor expects string\|null, string\|false given\.$#'
identifier: argument.type
count: 2
count: 1
path: test/unit/SelfManage/Update/FetchPieReleaseFromGitHubTest.php
-

View File

@@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/php/pie/main/resources/pie-config-schema.json",
"title": "PIE configuration file schema",
"description": "Schema for PIE tool configuration file",
"type": "object",
"properties": {
"channel": {
"type": "string",
"description": "Which update channel to use when running self-update",
"enum": [
"nightly",
"preview",
"stable"
]
}
}
}

View File

@@ -4,15 +4,17 @@ declare(strict_types=1);
namespace Php\Pie;
use Composer\Json\JsonFile;
use Php\Pie\SelfManage\Update\Channel;
use function array_key_exists;
use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function is_array;
use function json_decode;
use function json_encode;
use function mkdir;
use function rtrim;
use const DIRECTORY_SEPARATOR;
use const JSON_PRETTY_PRINT;
@@ -27,16 +29,39 @@ use const JSON_THROW_ON_ERROR;
*/
class Settings
{
private const PIE_SETTINGS_FILE_NAME = 'pie-settings.json';
private const PIE_SETTINGS_SCHEMA_FILE_NAME = __DIR__ . '/../resources/pie-settings-schema.json';
private const PIE_SETTINGS_FILE_NAME = 'pie-settings.json';
public function __construct(private readonly string $pieWorkingDirectory)
{
}
private function pieSettingsFullPath(): string
{
$workDir = rtrim($this->pieWorkingDirectory, DIRECTORY_SEPARATOR);
if (! file_exists($workDir)) {
mkdir($workDir, recursive: true);
}
return $workDir . DIRECTORY_SEPARATOR . self::PIE_SETTINGS_FILE_NAME;
}
/** @phpstan-assert PieSettings $settingsBlob */
private function validateSchema(mixed $settingsBlob): void
{
JsonFile::validateJsonSchema(
self::PIE_SETTINGS_FILE_NAME,
$settingsBlob,
JsonFile::STRICT_SCHEMA,
self::PIE_SETTINGS_SCHEMA_FILE_NAME,
);
}
/** @phpstan-return PieSettings */
private function read(): array
{
$pieSettingsFileName = $this->pieWorkingDirectory . DIRECTORY_SEPARATOR . self::PIE_SETTINGS_FILE_NAME;
$pieSettingsFileName = $this->pieSettingsFullPath();
if (! file_exists($pieSettingsFileName)) {
return [];
}
@@ -48,18 +73,17 @@ class Settings
$config = json_decode($content, true, flags: JSON_THROW_ON_ERROR);
// @todo schema validation
$this->validateSchema($config);
return is_array($config) ? $config : [];
return $config;
}
/** @param PieSettings $config */
/** @param array<array-key, mixed> $config */
private function write(array $config): void
{
// @todo schema validation
$this->validateSchema($config);
$pieSettingsFileName = $this->pieWorkingDirectory . DIRECTORY_SEPARATOR . self::PIE_SETTINGS_FILE_NAME;
file_put_contents($pieSettingsFileName, json_encode($config, flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
file_put_contents($this->pieSettingsFullPath(), json_encode($config, flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
}
public function updateChannel(): Channel

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Php\PieUnitTest;
use Composer\Json\JsonValidationException;
use Composer\Util\Filesystem;
use Php\Pie\SelfManage\Update\Channel;
use Php\Pie\Settings;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use function file_get_contents;
use function file_put_contents;
use function mkdir;
use function str_replace;
use function sys_get_temp_dir;
use function trim;
use function uniqid;
use const DIRECTORY_SEPARATOR;
#[CoversClass(Settings::class)]
final class SettingsTest extends TestCase
{
public function testReadingInvalidConfigThrowsException(): void
{
$workingDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_settings_test', true) . DIRECTORY_SEPARATOR;
mkdir($workingDir, recursive: true);
file_put_contents($workingDir . 'pie-settings.json', '{"channel":"surprise! not a valid value"}');
$settings = new Settings($workingDir);
$this->expectException(JsonValidationException::class);
$settings->updateChannel();
}
public function testNewSettingsJsonCanBeCreated(): void
{
$workingDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_settings_test', true) . DIRECTORY_SEPARATOR;
$settings = new Settings($workingDir);
self::assertSame(Channel::Stable, $settings->updateChannel());
$settings->changeUpdateChannel(Channel::Preview);
self::assertSame(Channel::Preview, $settings->updateChannel());
self::assertSame(
trim(<<<'JSON'
{
"channel": "preview"
}
JSON),
str_replace("\r\n", "\n", (string) file_get_contents($workingDir . 'pie-settings.json')),
);
(new Filesystem())->remove($workingDir);
}
public function testExistingSettingsCanBeUpdated(): void
{
$workingDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_settings_test', true) . DIRECTORY_SEPARATOR;
mkdir($workingDir, recursive: true);
file_put_contents($workingDir . 'pie-settings.json', '{"channel": "stable"}');
$settings = new Settings($workingDir);
self::assertSame(Channel::Stable, $settings->updateChannel());
$settings->changeUpdateChannel(Channel::Preview);
self::assertSame(Channel::Preview, $settings->updateChannel());
self::assertSame(
trim(<<<'JSON'
{
"channel": "preview"
}
JSON),
str_replace("\r\n", "\n", (string) file_get_contents($workingDir . 'pie-settings.json')),
);
(new Filesystem())->remove($workingDir);
}
}