diff --git a/lib/Controllers/DefaultController.php b/lib/Controllers/DefaultController.php index 870f648..f49044e 100644 --- a/lib/Controllers/DefaultController.php +++ b/lib/Controllers/DefaultController.php @@ -222,151 +222,97 @@ class DefaultController extends ControllerAbstract { // ==================== Service Operations ===================== private function serviceList(string $tenantId, string $userId, array $data): mixed { - - $targets = null; - if (isset($data['targets']) && is_array($data['targets'])) { - $targets = ResourceIdentifiers::fromArray($data['targets']); - foreach ($targets as $target) { - if (!$target instanceof CollectionIdentifier && !$target instanceof ServiceIdentifier) { - throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service, provider:service:collection, or provider:service:collection:entity identifiers'); - } - } - } - + // Validate parameters + $targets = $this->optionalIdentifiers( + $data, + 'targets', + ServiceIdentifier::class, + 'Invalid parameter: targets must contain provider:service, provider:service:collection, or provider:service:collection:entity identifiers' + ); + // perform operation return $this->manager->serviceList($tenantId, $userId, $targets); } private function serviceFetch(string $tenantId, string $userId, array $data): mixed { - - if (!isset($data['provider'])) { - throw new InvalidArgumentException(self::ERR_MISSING_PROVIDER); - } - if (!is_string($data['provider'])) { - throw new InvalidArgumentException(self::ERR_INVALID_PROVIDER); - } - if (!isset($data['identifier'])) { - throw new InvalidArgumentException(self::ERR_MISSING_IDENTIFIER); - } - if (!is_string($data['identifier'])) { - throw new InvalidArgumentException(self::ERR_INVALID_IDENTIFIER); - } - - return $this->manager->serviceFetch($tenantId, $userId, $data['provider'], $data['identifier']); + // Validate parameters + $provider = $this->requireString($data, 'provider', self::ERR_MISSING_PROVIDER, self::ERR_INVALID_PROVIDER); + $identifier = $this->requireString($data, 'identifier', self::ERR_MISSING_IDENTIFIER, self::ERR_INVALID_IDENTIFIER); + // perform operation + return $this->manager->serviceFetch($tenantId, $userId, $provider, $identifier); } private function serviceExtant(string $tenantId, string $userId, array $data): mixed { - - if (!isset($data['targets'])) { - throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); - } - if (!is_array($data['targets'])) { - throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); - } - $targets = ResourceIdentifiers::fromArray($data['targets']); - foreach ($targets as $target) { - if (!$target instanceof ServiceIdentifier) { - throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service identifiers'); - } - } - + // Validate parameters + $targets = $this->requireIdentifiers( + $data, + 'targets', + ServiceIdentifier::class, + self::ERR_MISSING_TARGETS, + self::ERR_INVALID_TARGETS, + 'Invalid parameter: targets must contain provider:service identifiers' + ); + // perform operation return $this->manager->serviceExtant($tenantId, $userId, $targets); } private function serviceCreate(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['provider'])) { - throw new InvalidArgumentException(self::ERR_MISSING_PROVIDER); - } - if (!is_string($data['provider'])) { - throw new InvalidArgumentException(self::ERR_INVALID_PROVIDER); - } - if (!isset($data['data'])) { - throw new InvalidArgumentException(self::ERR_MISSING_DATA); - } - if (!is_array($data['data'])) { - throw new InvalidArgumentException(self::ERR_INVALID_DATA); - } - + // Validate parameters + $provider = $this->requireString($data, 'provider', self::ERR_MISSING_PROVIDER, self::ERR_INVALID_PROVIDER); + $properties = $this->requireArray($data, 'data', self::ERR_MISSING_DATA, self::ERR_INVALID_DATA); + // perform operation return $this->manager->serviceCreate( $tenantId, $userId, - $data['provider'], - $data['data'] + $provider, + $properties ); } private function serviceUpdate(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['provider'])) { - throw new InvalidArgumentException(self::ERR_MISSING_PROVIDER); - } - if (!is_string($data['provider'])) { - throw new InvalidArgumentException(self::ERR_INVALID_PROVIDER); - } - if (!isset($data['identifier'])) { - throw new InvalidArgumentException(self::ERR_MISSING_IDENTIFIER); - } - if (!is_string($data['identifier'])) { - throw new InvalidArgumentException(self::ERR_INVALID_IDENTIFIER); - } - if (!isset($data['data'])) { - throw new InvalidArgumentException(self::ERR_MISSING_DATA); - } - if (!is_array($data['data'])) { - throw new InvalidArgumentException(self::ERR_INVALID_DATA); - } + // Validate parameters + $provider = $this->requireString($data, 'provider', self::ERR_MISSING_PROVIDER, self::ERR_INVALID_PROVIDER); + $identifier = $this->requireString($data, 'identifier', self::ERR_MISSING_IDENTIFIER, self::ERR_INVALID_IDENTIFIER); + $properties = $this->requireArray($data, 'data', self::ERR_MISSING_DATA, self::ERR_INVALID_DATA); if (isset($data['delta']) && !is_bool($data['delta'])) { throw new InvalidArgumentException('Invalid parameter: delta must be a boolean'); } - + // perform operation return $this->manager->serviceUpdate( $tenantId, $userId, - $data['provider'], - $data['identifier'], - $data['data'], + $provider, + $identifier, + $properties, $data['delta'] ?? false, ); } private function serviceDelete(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['provider'])) { - throw new InvalidArgumentException(self::ERR_MISSING_PROVIDER); - } - if (!is_string($data['provider'])) { - throw new InvalidArgumentException(self::ERR_INVALID_PROVIDER); - } - if (!isset($data['identifier'])) { - throw new InvalidArgumentException(self::ERR_MISSING_IDENTIFIER); - } - if (!is_string($data['identifier'])) { - throw new InvalidArgumentException(self::ERR_INVALID_IDENTIFIER); - } - + // Validate parameters + $provider = $this->requireString($data, 'provider', self::ERR_MISSING_PROVIDER, self::ERR_INVALID_PROVIDER); + $identifier = $this->requireString($data, 'identifier', self::ERR_MISSING_IDENTIFIER, self::ERR_INVALID_IDENTIFIER); + // perform operation return $this->manager->serviceDelete( $tenantId, $userId, - $data['provider'], - $data['identifier'] + $provider, + $identifier ); } private function serviceTest(string $tenantId, string $userId, array $data): mixed { - - if (!isset($data['provider'])) { - throw new InvalidArgumentException(self::ERR_MISSING_PROVIDER); - } - if (!is_string($data['provider'])) { - throw new InvalidArgumentException(self::ERR_INVALID_PROVIDER); - } + // Validate parameters + $provider = $this->requireString($data, 'provider', self::ERR_MISSING_PROVIDER, self::ERR_INVALID_PROVIDER); if (!isset($data['identifier']) && !isset($data['location']) && !isset($data['identity'])) { throw new InvalidArgumentException('Either a service identifier or location and identity must be provided for service test'); } - + // perform operation return $this->manager->serviceTest( $tenantId, $userId, - $data['provider'], + $provider, $data['identifier'] ?? null, $data['location'] ?? null, $data['identity'] ?? null, @@ -376,118 +322,98 @@ class DefaultController extends ControllerAbstract { // ==================== Collection Operations ==================== private function collectionList(string $tenantId, string $userId, array $data): mixed { - $sources = null; - if (isset($data['sources']) && is_array($data['sources'])) { - $sources = ResourceIdentifiers::fromArray($data['sources']); - foreach ($sources as $source) { - if (!$source instanceof CollectionIdentifier && !$source instanceof ServiceIdentifier) { - throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service or provider:service:collection identifiers'); - } - } - } - + // Validate parameters + $sources = $this->optionalIdentifiers( + $data, + 'sources', + ServiceIdentifier::class, + 'Invalid parameter: sources must contain provider:service or provider:service:collection identifiers' + ); $filter = $data['filter'] ?? null; $sort = $data['sort'] ?? null; - + // perform operation return $this->manager->collectionList($tenantId, $userId, $sources, $filter, $sort); } private function collectionFetch(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['targets'])) { - throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); - } - if (!is_array($data['targets'])) { - throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); - } - - $targetIdentifiers = ResourceIdentifiers::fromArray($data['targets']); + // Validate parameters + $targetIdentifiers = $this->requireIdentifiers( + $data, + 'targets', + CollectionIdentifier::class, + self::ERR_MISSING_TARGETS, + self::ERR_INVALID_TARGETS, + self::ERR_TARGET_COLLECTION, + EntityIdentifier::class + ); + // perform operation + $list = []; foreach ($targetIdentifiers as $targetIdentifier) { - if (!$targetIdentifier instanceof CollectionIdentifier) { - throw new InvalidArgumentException(self::ERR_TARGET_COLLECTION); + $collection = $this->manager->collectionFetch($tenantId, $userId, $targetIdentifier); + if ($collection !== null) { + $list[(string) $targetIdentifier] = $collection; } } - - $list = $this->manager->collectionFetch( - $tenantId, - $userId, - $targetIdentifier - ); - return $list; + return (object) $list; } private function collectionExtant(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['targets'])) { - throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); - } - if (!is_array($data['targets'])) { - throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); - } - - $sources = ResourceIdentifiers::fromArray($data['targets']); - foreach ($sources as $source) { - if (!$source instanceof CollectionIdentifier) { - throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service:collection identifiers'); - } - } - + // Validate parameters + $sources = $this->requireIdentifiers( + $data, + 'targets', + CollectionIdentifier::class, + self::ERR_MISSING_TARGETS, + self::ERR_INVALID_TARGETS, + 'Invalid parameter: targets must contain provider:service:collection identifiers' + ); + // perform operation return $this->manager->collectionExtant($tenantId, $userId, $sources); } private function collectionCreate(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['provider'])) { - throw new InvalidArgumentException(self::ERR_MISSING_PROVIDER); + if (!isset($data['target'])) { + throw new InvalidArgumentException(self::ERR_MISSING_TARGET); } - if (!is_string($data['provider'])) { - throw new InvalidArgumentException(self::ERR_INVALID_PROVIDER); + if (!is_string($data['target'])) { + throw new InvalidArgumentException(self::ERR_INVALID_TARGET); } - if (!isset($data['service'])) { - throw new InvalidArgumentException(self::ERR_MISSING_SERVICE); + $targetIdentifier = ResourceIdentifier::fromString($data['target']); + if (!$targetIdentifier instanceof ServiceIdentifier || $targetIdentifier instanceof EntityIdentifier) { + throw new InvalidArgumentException('Invalid parameter: target must be provider:service or provider:service:collection'); } - if (!is_string($data['service'])) { - throw new InvalidArgumentException(self::ERR_INVALID_SERVICE); - } - if (!isset($data['properties'])) { - throw new InvalidArgumentException(self::ERR_MISSING_DATA); - } - if (!is_array($data['properties'])) { - throw new InvalidArgumentException(self::ERR_INVALID_DATA); - } - - $targetIdentifier = $this->collectionTargetOptional($data); - - return $this->manager->collectionCreate( - $tenantId, - $userId, - $data['provider'], - $data['service'], - $targetIdentifier, - $data['properties'], - $data['options'] ?? [] - ); + $properties = $this->requireArray($data, 'properties', self::ERR_MISSING_DATA, self::ERR_INVALID_DATA); + // perform operation + return $this->manager->collectionCreate($tenantId, $userId, $targetIdentifier, $properties, $data['options'] ?? []); } private function collectionUpdate(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['properties'])) { - throw new InvalidArgumentException(self::ERR_MISSING_DATA); - } - if (!is_array($data['properties'])) { - throw new InvalidArgumentException(self::ERR_INVALID_DATA); - } - - $targetIdentifier = $this->collectionTarget($data); - - return $this->manager->collectionUpdate( - $tenantId, - $userId, - $targetIdentifier, - $data['properties'] + // Validate parameters + $target = $this->requireIdentifier( + $data, + 'target', + CollectionIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET, + self::ERR_TARGET_COLLECTION ); + $properties = $this->requireArray($data, 'properties', self::ERR_MISSING_DATA, self::ERR_INVALID_DATA); + // perform operation + return $this->manager->collectionUpdate($tenantId, $userId, $target, $properties); } private function collectionDelete(string $tenantId, string $userId, array $data): mixed { - $targetIdentifier = $this->collectionTarget($data); - - $result = $this->manager->collectionDelete($tenantId, $userId, $targetIdentifier, $data['options'] ?? []); + // Validate parameters + $target = $this->requireIdentifier( + $data, + 'target', + CollectionIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET, + self::ERR_TARGET_COLLECTION + ); + // perform operation + $result = $this->manager->collectionDelete($tenantId, $userId, $target, $data['options'] ?? []); if (is_bool($result)) { return [ @@ -506,67 +432,79 @@ class DefaultController extends ControllerAbstract { } private function collectionCopy(string $tenantId, string $userId, array $data): mixed { - $sourceIdentifier = $this->collectionTarget($data, 'source'); - $targetIdentifier = $this->collectionTargetOptional($data); - + // Validate parameters + $sourceIdentifier = $this->requireIdentifier( + $data, + 'source', + CollectionIdentifier::class, + self::ERR_MISSING_SOURCE, + self::ERR_INVALID_SOURCE + ); + $targetIdentifier = $this->requireIdentifier( + $data, + 'target', + CollectionIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET + ); + // perform operation return $this->manager->collectionCopy($tenantId, $userId, $targetIdentifier, $sourceIdentifier); } private function collectionMove(string $tenantId, string $userId, array $data): mixed { - $sourceIdentifier = $this->collectionTarget($data, 'source'); - $targetIdentifier = $this->collectionTargetOptional($data); - + // Validate parameters + $sourceIdentifier = $this->requireIdentifier( + $data, + 'source', + CollectionIdentifier::class, + self::ERR_MISSING_SOURCE, + self::ERR_INVALID_SOURCE + ); + $targetIdentifier = $this->requireIdentifier( + $data, + 'target', + CollectionIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET + ); + // perform operation return $this->manager->collectionMove($tenantId, $userId, $targetIdentifier, $sourceIdentifier); } // ==================== Entity Operations ==================== private function entityListBulk(string $tenantId, string $userId, array $data): mixed { - - if (isset($data['sources'])) { - if (!is_array($data['sources'])) { - throw new InvalidArgumentException(self::ERR_INVALID_SOURCES); - } - - $sources = ResourceIdentifiers::fromArray($data['sources']); - foreach ($sources as $source) { - if (!$source instanceof ServiceIdentifier && !$source instanceof CollectionIdentifier) { - throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service or provider:service:collection identifiers'); - } - } - } else { - $sources = null; - } - + // Validate parameters + $sources = $this->optionalIdentifiers( + $data, + 'sources', + ServiceIdentifier::class, + 'Invalid parameter: sources must contain provider:service or provider:service:collection identifiers', + null, + self::ERR_INVALID_SOURCES + ); $filter = $data['filter'] ?? null; $sort = $data['sort'] ?? null; $range = $data['range'] ?? null; - + // perform operation return $this->manager->entityListBulk($tenantId, $userId, $sources, $filter, $sort, $range); } private function entityListStream(string $tenantId, string $userId, array $data, int $version, string $transaction): StreamedNdJsonResponse { - - if (isset($data['sources'])) { - if (!is_array($data['sources'])) { - throw new InvalidArgumentException(self::ERR_INVALID_SOURCES); - } - - $sources = ResourceIdentifiers::fromArray($data['sources']); - foreach ($sources as $source) { - if (!$source instanceof ServiceIdentifier && !$source instanceof CollectionIdentifier) { - throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service or provider:service:collection identifiers'); - } - } - } else { - $sources = null; - } - + // Validate parameters + $sources = $this->optionalIdentifiers( + $data, + 'sources', + ServiceIdentifier::class, + 'Invalid parameter: sources must contain provider:service or provider:service:collection identifiers', + null, + self::ERR_INVALID_SOURCES + ); $filter = $data['filter'] ?? null; $sort = $data['sort'] ?? null; $range = $data['range'] ?? null; - + // perform operation $entities = $this->manager->entityListStream($tenantId, $userId, $sources, $filter, $sort, $range); return new StreamedNdJsonResponse( @@ -578,141 +516,131 @@ class DefaultController extends ControllerAbstract { } private function entityFetch(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['targets'])) { - throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); - } - if (!is_array($data['targets'])) { - throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); - } - - $targets = ResourceIdentifiers::fromArray($data['targets']); - foreach ($targets as $target) { - if (!$target instanceof EntityIdentifier) { - throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service:collection:entity identifiers'); - } - } - - return $this->manager->entityFetchBulk( - $tenantId, - $userId, - ...$targets->all() + // Validate parameters + $targets = $this->requireIdentifiers( + $data, + 'targets', + EntityIdentifier::class, + self::ERR_MISSING_TARGETS, + self::ERR_INVALID_TARGETS, + 'Invalid parameter: targets must contain provider:service:collection:entity identifiers' ); + // perform operation + return (object) $this->manager->entityFetchBulk($tenantId, $userId, ...$targets->all()); } private function entityExtant(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['targets'])) { - throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); - } - if (!is_array($data['targets'])) { - throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); - } - - $targets = ResourceIdentifiers::fromArray($data['targets']); - foreach ($targets as $target) { - if (!$target instanceof CollectionIdentifier && !$target instanceof EntityIdentifier) { - throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service:collection or provider:service:collection:entity identifiers'); - } - } - + // Validate parameters + $targets = $this->requireIdentifiers( + $data, + 'targets', + CollectionIdentifier::class, + self::ERR_MISSING_TARGETS, + self::ERR_INVALID_TARGETS, + 'Invalid parameter: targets must contain provider:service:collection or provider:service:collection:entity identifiers' + ); + // perform operation return $this->manager->entityExtant($tenantId, $userId, $targets); } private function entityDelta(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['targets'])) { - throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); - } - if (!is_array($data['targets'])) { - throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); - } - - $targets = ResourceIdentifiers::fromArray($data['targets']); - foreach ($targets as $target) { - if (!$target instanceof CollectionIdentifier && !$target instanceof EntityIdentifier) { - throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service:collection or provider:service:collection:signature identifiers'); - } - } - + // Validate parameters + $targets = $this->requireIdentifiers( + $data, + 'targets', + CollectionIdentifier::class, + self::ERR_MISSING_TARGETS, + self::ERR_INVALID_TARGETS, + 'Invalid parameter: targets must contain provider:service:collection or provider:service:collection:signature identifiers' + ); + // perform operation return $this->manager->entityDelta($tenantId, $userId, $targets); } private function entityCreate(string $tenantId, string $userId, array $data = []): mixed { - if (!isset($data['properties'])) { - throw new InvalidArgumentException(self::ERR_MISSING_DATA); - } - if (!is_array($data['properties'])) { - throw new InvalidArgumentException(self::ERR_INVALID_DATA); - } - - $target = $this->collectionTarget($data); + // Validate parameters + $target = $this->requireIdentifier( + $data, + 'target', + CollectionIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET + ); + $properties = $this->requireArray($data, 'properties', self::ERR_MISSING_DATA, self::ERR_INVALID_DATA); $options = $data['options'] ?? []; - - return $this->manager->entityCreate($tenantId, $userId, $target, $data['properties'], $options); + // perform operation + return $this->manager->entityCreate($tenantId, $userId, $target, $properties, $options); } private function entityUpdate(string $tenantId, string $userId, array $data = []): mixed { - if (!isset($data['properties'])) { - throw new InvalidArgumentException(self::ERR_MISSING_DATA); - } - if (!is_array($data['properties'])) { - throw new InvalidArgumentException(self::ERR_INVALID_DATA); - } - - $target = $this->entityTarget($data); - - return $this->manager->entityModify($tenantId, $userId, $target, $data['properties']); + // Validate parameters + $target = $this->requireIdentifier( + $data, + 'target', + CollectionIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET + ); + $properties = $this->requireArray($data, 'properties', self::ERR_MISSING_DATA, self::ERR_INVALID_DATA); + // perform operation + return $this->manager->entityModify($tenantId, $userId, $target, $properties); } private function entityDelete(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['targets'])) { - throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); - } - if (!is_array($data['targets'])) { - throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); - } - - $targets = ResourceIdentifiers::fromArray($data['targets']); - foreach ($targets as $target) { - if (!$target instanceof EntityIdentifier) { - throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service:collection:entity identifiers'); - } - } - + // Validate parameters + $targets = $this->requireIdentifiers( + $data, + 'targets', + EntityIdentifier::class, + self::ERR_MISSING_TARGETS, + self::ERR_INVALID_TARGETS, + 'Invalid parameter: targets must contain provider:service:collection:entity identifiers' + ); + // perform operation return $this->manager->entityDelete($tenantId, $userId, ...$targets->all()); } private function entityMove(string $tenantId, string $userId, array $data): mixed { - return $this->entityRelocate($tenantId, $userId, $data, 'entityMove'); + return $this->entityMopy($tenantId, $userId, $data, 'entityMove'); } private function entityCopy(string $tenantId, string $userId, array $data): mixed { - return $this->entityRelocate($tenantId, $userId, $data, 'entityCopy'); + return $this->entityMopy($tenantId, $userId, $data, 'entityCopy'); } - private function entityRelocate(string $tenantId, string $userId, array $data, string $method): mixed { - if (!isset($data['sources'])) { - throw new InvalidArgumentException(self::ERR_MISSING_SOURCES); - } - if (!is_array($data['sources'])) { - throw new InvalidArgumentException(self::ERR_INVALID_SOURCES); - } - - $target = $this->collectionTarget($data); - - $sources = ResourceIdentifiers::fromArray($data['sources']); - foreach ($sources as $source) { - if (!$source instanceof EntityIdentifier) { - throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service:collection:entity identifiers'); - } - } - + private function entityMopy(string $tenantId, string $userId, array $data, string $method): mixed { + // Validate parameters + $target = $this->requireIdentifier( + $data, + 'target', + CollectionIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET + ); + $sources = $this->requireIdentifiers( + $data, + 'sources', + EntityIdentifier::class, + self::ERR_MISSING_SOURCES, + self::ERR_INVALID_SOURCES, + 'Invalid parameter: sources must contain provider:service:collection:entity identifiers' + ); + // perform operation return $this->manager->{$method}($tenantId, $userId, $target, ...$sources->all()); } // ==================== Entity Content Operations ==================== private function entityRead(string $tenantId, string $userId, array $data = []): mixed { - $target = $this->entityTarget($data); - + // Validate parameters + $target = $this->requireIdentifier( + $data, + 'target', + EntityIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET + ); + // perform operation $content = $this->manager->entityRead($tenantId, $userId, $target); return [ @@ -722,47 +650,44 @@ class DefaultController extends ControllerAbstract { } private function entityReadChunk(string $tenantId, string $userId, array $data = []): mixed { - if (!isset($data['offset']) || !is_int($data['offset'])) { - throw new InvalidArgumentException('Missing parameter: offset'); - } - if (!isset($data['length']) || !is_int($data['length'])) { - throw new InvalidArgumentException('Missing parameter: length'); - } - - $target = $this->entityTarget($data); - + // Validate parameters + $target = $this->requireIdentifier( + $data, + 'target', + EntityIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET + ); + $offset = $this->requireInt($data, 'offset'); + $length = $this->requireInt($data, 'length'); + // perform operation $chunk = $this->manager->entityReadChunk( $tenantId, $userId, $target, - $data['offset'], - $data['length'] + $offset, + $length ); return [ 'content' => $chunk !== null ? base64_encode($chunk) : null, 'encoding' => 'base64', - 'offset' => $data['offset'], + 'offset' => $offset, 'length' => $chunk !== null ? strlen($chunk) : 0, ]; } private function entityWrite(string $tenantId, string $userId, array $data = []): mixed { - if (!isset($data['content'])) { - throw new InvalidArgumentException(self::ERR_MISSING_DATA); - } - - $target = $this->entityTarget($data); - - // Decode content if base64 encoded - $content = $data['content']; - if (isset($data['encoding']) && $data['encoding'] === 'base64') { - $content = base64_decode($content); - if ($content === false) { - throw new InvalidArgumentException('Invalid base64 encoded content'); - } - } - + // Validate parameters + $target = $this->requireIdentifier( + $data, + 'target', + EntityIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET + ); + $content = $this->requireContent($data); + // perform operation $bytesWritten = $this->manager->entityWrite($tenantId, $userId, $target, $content); return [ @@ -771,35 +696,28 @@ class DefaultController extends ControllerAbstract { } private function entityWriteChunk(string $tenantId, string $userId, array $data = []): mixed { - if (!isset($data['offset']) || !is_int($data['offset'])) { - throw new InvalidArgumentException('Missing parameter: offset'); - } - if (!isset($data['content'])) { - throw new InvalidArgumentException(self::ERR_MISSING_DATA); - } - - $target = $this->entityTarget($data); - - // Decode content if base64 encoded - $content = $data['content']; - if (isset($data['encoding']) && $data['encoding'] === 'base64') { - $content = base64_decode($content); - if ($content === false) { - throw new InvalidArgumentException('Invalid base64 encoded content'); - } - } - + // Validate parameters + $target = $this->requireIdentifier( + $data, + 'target', + EntityIdentifier::class, + self::ERR_MISSING_TARGET, + self::ERR_INVALID_TARGET + ); + $offset = $this->requireInt($data, 'offset'); + $content = $this->requireContent($data); + // perform operation $bytesWritten = $this->manager->entityWriteChunk( $tenantId, $userId, $target, - $data['offset'], + $offset, $content ); return [ 'bytesWritten' => $bytesWritten, - 'offset' => $data['offset'], + 'offset' => $offset, ]; } @@ -807,19 +725,15 @@ class DefaultController extends ControllerAbstract { private function nodeList(string $tenantId, string $userId, array $data, int $version, string $transaction): StreamedNdJsonResponse { - if (!isset($data['sources'])) { - throw new InvalidArgumentException(self::ERR_MISSING_SOURCES); - } - if (!is_array($data['sources'])) { - throw new InvalidArgumentException(self::ERR_INVALID_SOURCES); - } - - $sources = ResourceIdentifiers::fromArray($data['sources']); - foreach ($sources as $source) { - if ($source instanceof EntityIdentifier || (!$source instanceof ServiceIdentifier && !$source instanceof CollectionIdentifier)) { - throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service or provider:service:collection identifiers'); - } - } + $sources = $this->requireIdentifiers( + $data, + 'sources', + ServiceIdentifier::class, + self::ERR_MISSING_SOURCES, + self::ERR_INVALID_SOURCES, + self::ERR_INVALID_SOURCES, + EntityIdentifier::class + ); $filter = $data['filter'] ?? null; $sort = $data['sort'] ?? null; @@ -843,39 +757,130 @@ class DefaultController extends ControllerAbstract { // ==================== Helper Methods ==================== - private function collectionTarget(array $data, string $key = 'target'): CollectionIdentifier { + /** + * Extract and validate a single identifier by instance class + * + * @template T of ResourceIdentifier + * @param class-string $class + * @return T + */ + private function requireIdentifier(array $data, string $key, string $class, string $missingError, string $invalidTypeError, ?string $invalidClassError = null): ResourceIdentifier { if (!isset($data[$key])) { - throw new InvalidArgumentException('Missing parameter: ' . $key); + throw new InvalidArgumentException($missingError); } if (!is_string($data[$key])) { - throw new InvalidArgumentException("Invalid parameter: $key must be a string"); + throw new InvalidArgumentException($invalidTypeError); } + $identifier = ResourceIdentifier::fromString($data[$key]); - if (!$identifier instanceof CollectionIdentifier) { - throw new InvalidArgumentException(self::ERR_TARGET_COLLECTION); + if (!$identifier instanceof $class) { + throw new InvalidArgumentException($invalidClassError ?? $invalidTypeError); } + return $identifier; } - private function collectionTargetOptional(array $data, string $key = 'target'): ?CollectionIdentifier { - if (!isset($data[$key]) || $data[$key] === null || $data[$key] === '') { + /** + * Extract and validate a list of identifiers by instance class + */ + private function requireIdentifiers(array $data, string $key, string $class, string $missingError, string $invalidArrayError, string $invalidItemError, ?string $excludeClass = null): ResourceIdentifiers { + if (!isset($data[$key])) { + throw new InvalidArgumentException($missingError); + } + if (!is_array($data[$key])) { + throw new InvalidArgumentException($invalidArrayError); + } + + $identifiers = ResourceIdentifiers::fromArray($data[$key]); + foreach ($identifiers as $identifier) { + if (!$identifier instanceof $class || ($excludeClass !== null && $identifier instanceof $excludeClass)) { + throw new InvalidArgumentException($invalidItemError); + } + } + + return $identifiers; + } + + /** + * Extract and validate a list of identifiers by instance class, returning null if the key is not present + */ + private function optionalIdentifiers(array $data, string $key, string $class, string $invalidItemError, ?string $excludeClass = null, ?string $invalidArrayError = null): ?ResourceIdentifiers { + if (!isset($data[$key])) { return null; } - return $this->collectionTarget($data, $key); + if (!is_array($data[$key])) { + if ($invalidArrayError !== null) { + throw new InvalidArgumentException($invalidArrayError); + } + return null; + } + + $identifiers = ResourceIdentifiers::fromArray($data[$key]); + foreach ($identifiers as $identifier) { + if (!$identifier instanceof $class || ($excludeClass !== null && $identifier instanceof $excludeClass)) { + throw new InvalidArgumentException($invalidItemError); + } + } + + return $identifiers; } - private function entityTarget(array $data, string $key = 'target'): EntityIdentifier { + /** + * Extract and validate a required string parameter, throwing when it is missing or not a string. + */ + private function requireString(array $data, string $key, string $missingError, string $invalidError): string { if (!isset($data[$key])) { - throw new InvalidArgumentException('Missing parameter: ' . $key); + throw new InvalidArgumentException($missingError); } if (!is_string($data[$key])) { - throw new InvalidArgumentException("Invalid parameter: $key must be a string"); + throw new InvalidArgumentException($invalidError); } - $identifier = ResourceIdentifier::fromString($data[$key]); - if (!$identifier instanceof EntityIdentifier) { - throw new InvalidArgumentException(self::ERR_TARGET_ENTITY); + + return $data[$key]; + } + + /** + * Extract and validate a required integer parameter, throwing when it is missing or not an int. + */ + private function requireInt(array $data, string $key, ?string $missingError = null): int { + if (!isset($data[$key]) || !is_int($data[$key])) { + throw new InvalidArgumentException($missingError ?? "Missing parameter: $key"); } - return $identifier; + + return $data[$key]; + } + + /** + * Extract and validate a required array parameter, throwing when it is missing or not an array. + */ + private function requireArray(array $data, string $key, string $missingError, string $invalidError): array { + if (!isset($data[$key])) { + throw new InvalidArgumentException($missingError); + } + if (!is_array($data[$key])) { + throw new InvalidArgumentException($invalidError); + } + + return $data[$key]; + } + + /** + * Extract and validate the required 'content' parameter, base64-decoding it when 'encoding' is 'base64'. + */ + private function requireContent(array $data): string { + if (!isset($data['content'])) { + throw new InvalidArgumentException(self::ERR_MISSING_DATA); + } + + $content = $data['content']; + if (isset($data['encoding']) && $data['encoding'] === 'base64') { + $content = base64_decode($content); + if ($content === false) { + throw new InvalidArgumentException('Invalid base64 encoded content'); + } + } + + return $content; } /**