Made it possible to validate servers.spec options when updating them via API. Migrated config:edit to use the API to reduce code duplication.
This commit is contained in:
@@ -4,13 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\API\Backend;
|
||||
|
||||
use App\Libs\Attributes\Route\Delete;
|
||||
use App\Libs\Attributes\Route\Get;
|
||||
use App\Libs\Attributes\Route\Patch;
|
||||
use App\Libs\Attributes\Route\Post;
|
||||
use App\Libs\Attributes\Route\Route;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\ConfigFile;
|
||||
use App\Libs\DataUtil;
|
||||
use App\Libs\Exceptions\ValidationException;
|
||||
use App\Libs\HTTP_STATUS;
|
||||
use App\Libs\Traits\APITraits;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
@@ -20,20 +18,30 @@ final class Option
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
#[Get(Index::URL . '/{name:backend}/option/{option}[/]', name: 'backend.option')]
|
||||
public function viewOption(iRequest $request, array $args = []): iResponse
|
||||
#[Route(['GET', 'POST', 'PATCH', 'DELETE'], Index::URL . '/{name:backend}/option[/{option}[/]]')]
|
||||
public function __invoke(iRequest $request, array $args = []): iResponse
|
||||
{
|
||||
if (null === ($name = ag($args, 'name'))) {
|
||||
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (null === ($option = ag($args, 'option'))) {
|
||||
return api_error('Invalid value for option path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
|
||||
|
||||
if (false === $list->has($name)) {
|
||||
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (false === str_starts_with($option, 'options.')) {
|
||||
$data = DataUtil::fromRequest($request);
|
||||
|
||||
if (null === ($option = ag($args, 'option', $data->get('key')))) {
|
||||
return api_error('No option key was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$isInternalRequest = true === (bool)$request->getAttribute('INTERNAL_REQUEST', false);
|
||||
|
||||
if (false === str_starts_with($option, 'options.') && !$isInternalRequest) {
|
||||
return api_error(
|
||||
"Invalid option. Option path parameter keys must start with 'options.'",
|
||||
"Invalid option key was given. Option keys must start with 'options.'",
|
||||
HTTP_STATUS::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
@@ -43,21 +51,63 @@ final class Option
|
||||
return api_error(r("Invalid option '{key}'.", ['key' => $option]), HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
|
||||
if ('GET' === $request->getMethod()) {
|
||||
if (false === $list->has($name . '.' . $option)) {
|
||||
return api_error(r("Option '{option}' not found in backend '{name}' config.", [
|
||||
'option' => $option,
|
||||
'name' => $name
|
||||
]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (false === $list->has($name)) {
|
||||
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
return $this->viewOption($spec, $list->get("{$name}.{$option}"));
|
||||
}
|
||||
|
||||
if (false === $list->has($name . '.' . $option)) {
|
||||
if ('DELETE' === $request->getMethod() && false === $list->has("{$name}.{$option}")) {
|
||||
return api_error(r("Option '{option}' not found in backend '{name}' config.", [
|
||||
'option' => $option,
|
||||
'name' => $name
|
||||
]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$value = $list->get($name . '.' . $option);
|
||||
settype($value, ag($spec, 'type', 'string'));
|
||||
if ('DELETE' === $request->getMethod()) {
|
||||
if (null !== ($value = $list->get($name . '.' . $option))) {
|
||||
settype($value, ag($spec, 'type', 'string'));
|
||||
}
|
||||
$list->delete("{$name}.{$option}");
|
||||
} else {
|
||||
if (null !== ($value = $data->get('value'))) {
|
||||
settype($value, ag($spec, 'type', 'string'));
|
||||
}
|
||||
|
||||
if (ag_exists($spec, 'validate')) {
|
||||
try {
|
||||
$value = $spec['validate']($value, $spec);
|
||||
} catch (ValidationException $e) {
|
||||
return api_error(r("Value validation for '{key}' failed. {error}", [
|
||||
'key' => $option,
|
||||
'error' => $e->getMessage()
|
||||
]), HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
$list->set("{$name}.{$option}", $value);
|
||||
}
|
||||
|
||||
$list->persist();
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_OK, [
|
||||
'key' => $option,
|
||||
'value' => $value,
|
||||
'type' => ag($spec, 'type', 'string'),
|
||||
'description' => ag($spec, 'description', ''),
|
||||
]);
|
||||
}
|
||||
|
||||
public function viewOption(array $spec, mixed $value): iResponse
|
||||
{
|
||||
if (null !== $value) {
|
||||
settype($value, ag($spec, 'type', 'string'));
|
||||
}
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_OK, [
|
||||
'key' => $spec['key'],
|
||||
@@ -67,170 +117,4 @@ final class Option
|
||||
]);
|
||||
}
|
||||
|
||||
#[Post(Index::URL . '/{name:backend}/option[/]', name: 'backend.option.add')]
|
||||
public function addOption(iRequest $request, array $args = []): iResponse
|
||||
{
|
||||
if (null === ($name = ag($args, 'name'))) {
|
||||
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
|
||||
|
||||
if (false === $list->has($name)) {
|
||||
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$data = DataUtil::fromRequest($request);
|
||||
|
||||
if (null === ($option = $data->get('key'))) {
|
||||
return api_error('No option key was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (false === str_starts_with($option, 'options.')) {
|
||||
return api_error(
|
||||
"Invalid option key was given. Option keys must start with 'options.'",
|
||||
HTTP_STATUS::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$spec = getServerColumnSpec($option);
|
||||
if (empty($spec)) {
|
||||
return api_error(r("Invalid option '{key}'.", ['key' => $option]), HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if ($list->has($name . '.' . $option)) {
|
||||
return api_error(r("Option '{option}' already exists in backend '{name}'.", [
|
||||
'option' => $option,
|
||||
'name' => $name
|
||||
]), HTTP_STATUS::HTTP_CONFLICT);
|
||||
}
|
||||
|
||||
$value = $data->get('value');
|
||||
settype($value, ag($spec, 'type', 'string'));
|
||||
|
||||
$list->set($name . '.' . $option, $value)->persist();
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_OK, [
|
||||
'key' => $option,
|
||||
'value' => $value,
|
||||
'type' => ag($spec, 'type', 'string'),
|
||||
'description' => ag($spec, 'description', ''),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Patch(Index::URL . '/{name:backend}/option/{option}[/]', name: 'backend.option.update')]
|
||||
public function updateOption(iRequest $request, array $args = []): iResponse
|
||||
{
|
||||
if (null === ($name = ag($args, 'name'))) {
|
||||
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (null === ($option = ag($args, 'option'))) {
|
||||
return api_error('Invalid value for option parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (false === str_starts_with($option, 'options.')) {
|
||||
return api_error(
|
||||
"Invalid option key was given. Option keys must start with 'options.'",
|
||||
HTTP_STATUS::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$spec = getServerColumnSpec($option);
|
||||
if (empty($spec)) {
|
||||
return api_error(r("Invalid option '{key}'.", ['key' => $option]), HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
|
||||
if (false === $list->has($name)) {
|
||||
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (false === $list->has($name . '.' . $option)) {
|
||||
return api_error(r("Option '{option}' not found in backend '{name}' config.", [
|
||||
'option' => $option,
|
||||
'name' => $name
|
||||
]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (true === (bool)ag($spec, 'immutable', false)) {
|
||||
return api_error(r("Option '{option}' is immutable.", [
|
||||
'option' => $option,
|
||||
]), HTTP_STATUS::HTTP_CONFLICT);
|
||||
}
|
||||
|
||||
$data = DataUtil::fromRequest($request);
|
||||
|
||||
if (null === ($value = $data->get('value'))) {
|
||||
return api_error(r("No value was provided for '{key}'.", [
|
||||
'key' => $option,
|
||||
]), HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
settype($value, ag($spec, 'type', 'string'));
|
||||
|
||||
$oldValue = $list->get($name . '.' . $option);
|
||||
if (null !== $oldValue) {
|
||||
settype($oldValue, ag($spec, 'type', 'string'));
|
||||
}
|
||||
|
||||
if ($oldValue !== $value) {
|
||||
$list->set($name . '.' . $option, $value)->persist();
|
||||
}
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_OK, [
|
||||
'key' => $option,
|
||||
'value' => $value,
|
||||
'type' => ag($spec, 'type', 'string'),
|
||||
'description' => ag($spec, 'description', ''),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Delete(Index::URL . '/{name:backend}/option/{option}[/]', name: 'backend.option.delete')]
|
||||
public function deleteOption(iRequest $request, array $args = []): iResponse
|
||||
{
|
||||
if (null === ($name = ag($args, 'name'))) {
|
||||
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (null === ($option = ag($args, 'option'))) {
|
||||
return api_error('Invalid value for option option parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (false === str_starts_with($option, 'options.')) {
|
||||
return api_error(
|
||||
"Invalid option key was given. Option keys must start with 'options.'",
|
||||
HTTP_STATUS::HTTP_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$spec = getServerColumnSpec($option);
|
||||
if (empty($spec)) {
|
||||
return api_error(r("Invalid option '{key}'.", ['key' => $option]), HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
|
||||
if (false === $list->has($name)) {
|
||||
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (false === $list->has($name . '.' . $option)) {
|
||||
return api_error(r("Option '{option}' not found in backend '{name}' config.", [
|
||||
'option' => $option,
|
||||
'name' => $name
|
||||
]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$value = $list->get($name . '.' . $option);
|
||||
settype($value, ag($spec, 'type', 'string'));
|
||||
|
||||
$list->delete($name . '.' . $option)->persist();
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_OK, [
|
||||
'key' => $option,
|
||||
'value' => $value,
|
||||
'type' => ag($spec, 'type', 'string'),
|
||||
'description' => ag($spec, 'description', ''),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use App\Libs\Attributes\Route\Route;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\DataUtil;
|
||||
use App\Libs\EnvFile;
|
||||
use App\Libs\Exceptions\InvalidArgumentException;
|
||||
use App\Libs\Exceptions\ValidationException;
|
||||
use App\Libs\HTTP_STATUS;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
@@ -124,14 +124,21 @@ final class Env
|
||||
return api_response(HTTP_STATUS::HTTP_NOT_MODIFIED);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$value = $this->setType($spec, $value);
|
||||
|
||||
if (false === $this->checkValue($spec, $value)) {
|
||||
throw new InvalidArgumentException(r("Invalid value for '{key}'.", ['key' => $key]));
|
||||
if (true === is_string($value)) {
|
||||
// -- check if the string contains space but not quoted.
|
||||
// symfony/dotenv throws an exception if the value contains a space but not quoted.
|
||||
if (str_contains($value, ' ') && (!str_starts_with($value, '"') || !str_ends_with($value, '"'))) {
|
||||
throw new ValidationException('The value must be "quoted string", as it contains a space.');
|
||||
}
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
|
||||
if (true === ag_exists($spec, 'validate')) {
|
||||
$value = $spec['validate']($value, $spec);
|
||||
}
|
||||
} catch (ValidationException $e) {
|
||||
return api_error(r("Value validation for '{key}' failed. {error}", [
|
||||
'key' => $key,
|
||||
'error' => $e->getMessage()
|
||||
@@ -148,31 +155,6 @@ final class Env
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value is valid.
|
||||
*
|
||||
* @param array $spec the specification for the key.
|
||||
* @param mixed $value the value to check.
|
||||
*
|
||||
* @return bool true if the value is valid, false otherwise.
|
||||
*/
|
||||
private function checkValue(array $spec, mixed $value): bool
|
||||
{
|
||||
if (true === is_string($value)) {
|
||||
// -- check if the string contains space but not quoted.
|
||||
// symfony/dotenv throws an exception if the value contains a space but not quoted.
|
||||
if (str_contains($value, ' ') && (!str_starts_with($value, '"') || !str_ends_with($value, '"'))) {
|
||||
throw new InvalidArgumentException('The value must be "quoted string", as it contains a space.');
|
||||
}
|
||||
}
|
||||
|
||||
if (ag_exists($spec, 'validate')) {
|
||||
return (bool)$spec['validate']($value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Information about the key.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user