Update validator

This commit is contained in:
Jake Barnby
2025-12-08 23:47:30 +13:00
parent f667fdfdf6
commit 5666098ae6
@@ -2,6 +2,8 @@
namespace Appwrite\Utopia\Database\Validator;
use Appwrite\Utopia\Request\Model\Database\Legacy\TransactionOperation as TransactionOperationLegacy;
use Appwrite\Utopia\Request\Model\Database\TablesDB\TransactionOperation as TransactionOperationTablesDB;
use Utopia\Validator;
class Operation extends Validator
@@ -9,10 +11,7 @@ class Operation extends Validator
private string $description = '';
/** @var array<string> */
private array $required = [
'databaseId',
'action',
];
private array $required = [];
/** @var array<string, bool> */
private array $requiresDocumentId = [
@@ -39,38 +38,33 @@ class Operation extends Validator
];
/** @var array<string, bool> */
private array $actions = [
'create' => true,
'update' => true,
'upsert' => true,
'delete' => true,
'increment' => true,
'decrement' => true,
'bulkCreate' => true,
'bulkUpdate' => true,
'bulkUpsert' => true,
'bulkDelete' => true,
];
private array $actions = [];
private string $collectionIdName = '';
private string $documentIdName = '';
public function __construct(private readonly string $type)
{
switch ($this->type) {
case 'legacy':
$this->collectionIdName = 'collectionId';
$this->documentIdName = 'documentId';
break;
case 'tablesdb':
$this->collectionIdName = 'tableId';
$this->documentIdName = 'rowId';
break;
default:
throw new \InvalidArgumentException('Invalid type provided.');
$model = match ($this->type) {
'legacy' => new TransactionOperationLegacy(),
'tablesdb' => new TransactionOperationTablesDB(),
default => throw new \InvalidArgumentException('Invalid type provided.'),
};
// Get required fields from the model
$this->required = $model->getRequired();
// Get valid actions from the model's action rule enum
$rules = $model->getRules();
if (isset($rules['action']['enum'])) {
foreach ($rules['action']['enum'] as $action) {
$this->actions[$action] = true;
}
}
$this->required[] = $this->collectionIdName;
// Set collection/document ID names based on model rules
$this->collectionIdName = isset($rules['tableId']) ? 'tableId' : 'collectionId';
$this->documentIdName = isset($rules['rowId']) ? 'rowId' : 'documentId';
}
public function getDescription(): string
@@ -94,85 +88,70 @@ class Operation extends Validator
}
/**
* @param mixed $value
* @param TransactionOperationLegacy|TransactionOperationTablesDB $value
*/
public function isValid($value): bool
{
// Must be array‑like
if (!\is_array($value)) {
$this->description = 'Value must be an array';
$databaseId = $value->getDatabaseId();
$collectionId = $value->getCollectionId();
$documentId = $value->getDocumentId();
$action = $value->getAction();
$data = $value->getData();
// Validate databaseId is non-empty
if (!\is_string($databaseId) || \trim($databaseId) === '') {
$this->description = "Key 'databaseId' must be a non-empty string";
return false;
}
// Mandatory keys
foreach ($this->required as $key) {
if (!\array_key_exists($key, $value)) {
$this->description = "Missing required key: {$key}";
return false;
}
// Validate collectionId is non-empty
if (!\is_string($collectionId) || \trim($collectionId) === '') {
$this->description = "Key '{$this->collectionIdName}' must be a non-empty string";
return false;
}
// Required keys must be non‑empty
foreach ($this->required as $key) {
if (!\is_string($value[$key]) || \trim($value[$key]) === '') {
$this->description = "Key '{$key}' must be a non‑empty string";
return false;
}
// Validate action is non-empty
if (!\is_string($action) || \trim($action) === '') {
$this->description = "Key 'action' must be a non-empty string";
return false;
}
// Validate action
if (!isset($this->actions[$value['action']])) {
// Validate action is valid
if (!isset($this->actions[$action])) {
$this->description = "Key 'action' must be one of: " . \implode(', ', array_keys($this->actions));
return false;
}
// If action requires documentId, it must be present
$actionRequiresDocumentId = ($this->requiresDocumentId[$value['action']] ?? false) === true;
if ($actionRequiresDocumentId && !\array_key_exists($this->documentIdName, $value)) {
$this->description = "Key '$this->documentIdName' is required for action '{$value['action']}'";
// If action requires documentId, it must be present and non-empty
$actionRequiresDocumentId = ($this->requiresDocumentId[$action] ?? false) === true;
if ($actionRequiresDocumentId && ($documentId === null || \trim($documentId) === '')) {
$this->description = "Key '{$this->documentIdName}' is required for action '{$action}'";
return false;
}
if (\array_key_exists($this->documentIdName, $value)) {
if (!\is_string($value[$this->documentIdName]) || \trim($value[$this->documentIdName]) === '') {
$this->description = "Key '$this->documentIdName' must be a non-empty string";
return false;
}
// If documentId is provided, it must be non-empty
if ($documentId !== null && \trim($documentId) === '') {
$this->description = "Key '{$this->documentIdName}' must be a non-empty string";
return false;
}
// Data validation - only required for certain actions
if (isset($this->requiresData[$value['action']]) && $this->requiresData[$value['action']]) {
if (isset($this->requiresData[$action]) && $this->requiresData[$action]) {
// Data is required for this action
if (!\array_key_exists('data', $value)) {
if (empty($data)) {
$this->description = "Missing required key: data";
return false;
}
if (!\is_array($value['data'])) {
$this->description = "Key 'data' must be an array";
return false;
}
} elseif (\array_key_exists('data', $value)) {
// Data is optional but if provided, must be an array
if (!\is_array($value['data'])) {
$this->description = "Key 'data' must be an array";
return false;
}
}
// Bulk operation specific validations
$action = $value['action'];
// BulkUpdate and BulkDelete require queries
if (\in_array($action, ['bulkUpdate', 'bulkDelete'])) {
if (!\array_key_exists('data', $value) || !\is_array($value['data'])) {
$this->description = "Key 'data' must be an array for {$action}";
return false;
}
if (!\array_key_exists('queries', $value['data'])) {
if (!\array_key_exists('queries', $data)) {
$this->description = "Key 'queries' is required in data for {$action}";
return false;
}
if (!\is_array($value['data']['queries'])) {
if (!\is_array($data['queries'])) {
$this->description = "Key 'queries' must be an array for {$action}";
return false;
}
@@ -180,11 +159,11 @@ class Operation extends Validator
// BulkUpdate requires both queries and data
if ($action === 'bulkUpdate') {
if (!\array_key_exists('data', $value['data'])) {
if (!\array_key_exists('data', $data)) {
$this->description = "Key 'data' is required in data for {$action}";
return false;
}
if (!\is_array($value['data']['data'])) {
if (!\is_array($data['data'])) {
$this->description = "Key 'data.data' must be an array for {$action}";
return false;
}
@@ -192,18 +171,14 @@ class Operation extends Validator
// Increment and Decrement require specific keys
if (\in_array($action, ['increment', 'decrement'])) {
if (!\array_key_exists('data', $value) || !\is_array($value['data'])) {
$this->description = "Key 'data' must be an array for {$action}";
return false;
}
// Get the attribute key name based on type
$attributeKey = $this->type === 'tablesdb' ? 'column' : 'attribute';
if (!\array_key_exists($attributeKey, $value['data'])) {
if (!\array_key_exists($attributeKey, $data)) {
$this->description = "Key '{$attributeKey}' is required in data for {$action}";
return false;
}
// Validate 'value' is numeric if provided (defaults to 1 if omitted)
if (\array_key_exists('value', $value['data']) && !\is_numeric($value['data']['value'])) {
if (\array_key_exists('value', $data) && !\is_numeric($data['value'])) {
$this->description = "Key 'value' must be a numeric value for {$action}";
return false;
}