From e3b730966b6aea332e0efca65427c72088b8c542 Mon Sep 17 00:00:00 2001 From: Constantine Nathanson Date: Wed, 29 Nov 2023 17:36:25 +0200 Subject: [PATCH] Add support for `fields` parameter in Search and Admin APIs --- src/Api/Admin/AssetsTrait.php | 75 +++++++++++-------- src/Api/Search/SearchQueryInterface.php | 7 +- src/Api/Search/SearchQueryTrait.php | 19 +++++ .../Admin/Assets/ListAssetsTest.php | 12 +++ tests/Unit/Admin/AssetsTest.php | 50 +++++++++++++ tests/Unit/Search/SearchApiTest.php | 8 +- 6 files changed, 138 insertions(+), 33 deletions(-) diff --git a/src/Api/Admin/AssetsTrait.php b/src/Api/Admin/AssetsTrait.php index 1c4539b0..c23d86a2 100644 --- a/src/Api/Admin/AssetsTrait.php +++ b/src/Api/Admin/AssetsTrait.php @@ -18,6 +18,7 @@ use Cloudinary\Asset\AssetType; use Cloudinary\Asset\DeliveryType; use Cloudinary\Asset\ModerationStatus; +use Cloudinary\StringUtils; /** * Enables you to manage the assets in your cloud. @@ -58,19 +59,9 @@ public function assets($options = []) $uri = [ApiEndPoint::ASSETS, $assetType]; ArrayUtils::appendNonEmpty($uri, ArrayUtils::get($options, DeliveryType::KEY)); - $params = ArrayUtils::whitelist( - $options, - [ - 'next_cursor', - 'max_results', - 'prefix', - 'tags', - 'context', - 'moderations', - 'direction', - 'start_at', - 'metadata', - ] + $params = array_merge( + self::prepareListAssetsParams($options), + ArrayUtils::whitelist($options, ['prefix', 'direction']) ); return $this->apiClient->get($uri, $params); @@ -93,10 +84,7 @@ public function assetsByTag($tag, $options = []) { $assetType = ArrayUtils::get($options, AssetType::KEY, AssetType::IMAGE); $uri = [ApiEndPoint::ASSETS, $assetType, 'tags', $tag]; - $params = ArrayUtils::whitelist( - $options, - ['next_cursor', 'max_results', 'tags', 'context', 'moderations', 'direction', 'metadata'] - ); + $params = self::prepareListAssetsParams($options); return $this->apiClient->get($uri, $params); } @@ -121,10 +109,7 @@ public function assetsByContext($key, $value = null, $options = []) { $assetType = ArrayUtils::get($options, AssetType::KEY, AssetType::IMAGE); $uri = [ApiEndPoint::ASSETS, $assetType, 'context']; - $params = ArrayUtils::whitelist( - $options, - ['next_cursor', 'max_results', 'tags', 'context', 'moderations', 'direction', 'metadata'] - ); + $params = self::prepareListAssetsParams($options); $params['key'] = $key; $params['value'] = $value; @@ -150,10 +135,7 @@ public function assetsByModeration($kind, $status, $options = []) $assetType = ArrayUtils::get($options, AssetType::KEY, AssetType::IMAGE); $uri = [ApiEndPoint::ASSETS, $assetType, 'moderations', $kind, $status]; - $params = ArrayUtils::whitelist( - $options, - ['next_cursor', 'max_results', 'tags', 'context', 'moderations', 'direction', 'metadata'] - ); + $params = self::prepareListAssetsParams($options); return $this->apiClient->get($uri, $params); } @@ -175,7 +157,7 @@ public function assetsByIds($publicIds, $options = []) $type = ArrayUtils::get($options, DeliveryType::KEY, DeliveryType::UPLOAD); $uri = [ApiEndPoint::ASSETS, $assetType, $type]; - $params = ArrayUtils::whitelist($options, ['public_ids', 'tags', 'moderations', 'context']); + $params = self::prepareAssetsParams($options); $params['public_ids'] = $publicIds; return $this->apiClient->get($uri, $params); @@ -196,7 +178,7 @@ public function assetsByAssetIds($assetIds, $options = []) { $uri = [ApiEndPoint::ASSETS, 'by_asset_ids']; - $params = ArrayUtils::whitelist($options, ['public_ids', 'tags', 'moderations', 'context']); + $params = self::prepareAssetsParams($options); $params['asset_ids'] = $assetIds; return $this->apiClient->get($uri, $params); @@ -218,10 +200,7 @@ public function assetsByAssetFolder($assetFolder, $options = []) { $uri = [ApiEndPoint::ASSETS, 'by_asset_folder']; - $params = ArrayUtils::whitelist( - $options, - ['next_cursor', 'max_results', 'tags', 'moderations', 'context'] - ); + $params = self::prepareListAssetsParams($options); $params['asset_folder'] = $assetFolder; return $this->apiClient->get($uri, $params); @@ -678,4 +657,38 @@ protected static function prepareAssetDetailsParams($options) ] ); } + + /** + * Prepares optional parameters for assets* API calls. + * + * @param array $options Additional options. + * + * @return array Optional parameters + * + * @internal + */ + protected static function prepareAssetsParams($options) + { + $params = ArrayUtils::whitelist($options, ['tags', 'context', 'metadata', 'moderations']); + $params['fields'] = ApiUtils::serializeSimpleApiParam((ArrayUtils::get($options, 'fields'))); + + return $params; + } + + /** + * Prepares optional parameters for assetsBy* API calls. + * + * @param array $options Additional options. + * + * @return array Optional parameters + * + * @internal + */ + protected static function prepareListAssetsParams($options) + { + return array_merge( + self::prepareAssetsParams($options), + ArrayUtils::whitelist($options, ['next_cursor', 'max_results', 'direction']) + ); + } } diff --git a/src/Api/Search/SearchQueryInterface.php b/src/Api/Search/SearchQueryInterface.php index 0f2324a5..26382cfb 100644 --- a/src/Api/Search/SearchQueryInterface.php +++ b/src/Api/Search/SearchQueryInterface.php @@ -19,6 +19,11 @@ interface SearchQueryInterface * @internal */ const WITH_FIELD = 'with_field'; + + /** + * @internal + */ + const FIELDS = 'fields'; /** * @internal */ @@ -34,5 +39,5 @@ interface SearchQueryInterface /** * @internal */ - const KEYS_WITH_UNIQUE_VALUES = [self::SORT_BY, self::AGGREGATE, self::WITH_FIELD]; + const KEYS_WITH_UNIQUE_VALUES = [self::SORT_BY, self::AGGREGATE, self::WITH_FIELD, self::FIELDS]; } diff --git a/src/Api/Search/SearchQueryTrait.php b/src/Api/Search/SearchQueryTrait.php index b32435fb..8e217658 100644 --- a/src/Api/Search/SearchQueryTrait.php +++ b/src/Api/Search/SearchQueryTrait.php @@ -17,6 +17,7 @@ trait SearchQueryTrait self::SORT_BY => [], self::AGGREGATE => [], self::WITH_FIELD => [], + self::FIELDS => [], ]; /** @@ -129,6 +130,24 @@ public function withField($value) return $this; } + /** + * The list of the fields to include for each asset in the response. + * + * @param array|string $fields The fields' names. + * + * @return $this + * + * @api + */ + public function fields($fields) + { + foreach (ArrayUtils::build($fields) as $field) { + $this->query[self::FIELDS][$field] = $field; + } + + return $this; + } + /** * Sets the search query. * diff --git a/tests/Integration/Admin/Assets/ListAssetsTest.php b/tests/Integration/Admin/Assets/ListAssetsTest.php index f4eb5e40..88dd1392 100644 --- a/tests/Integration/Admin/Assets/ListAssetsTest.php +++ b/tests/Integration/Admin/Assets/ListAssetsTest.php @@ -196,6 +196,18 @@ public function testListRawUploadedFiles() self::assertValidAsset($result['resources'][0], [AssetType::KEY => AssetType::RAW]); } + /** + * Get specific fields. + */ + public function testListImagesFields() + { + $result = self::$adminApi->assets(['fields' => ['tags', 'secure_url']]); + + self::assertArrayHasKey('tags', $result['resources'][0]); + self::assertArrayHasKey('secure_url', $result['resources'][0]); + self::assertArrayNotHasKey('url', $result['resources'][0]); + } + /** * Get a single uploaded asset with a given ID passed as a string. */ diff --git a/tests/Unit/Admin/AssetsTest.php b/tests/Unit/Admin/AssetsTest.php index f004bbb9..25c31ae0 100644 --- a/tests/Unit/Admin/AssetsTest.php +++ b/tests/Unit/Admin/AssetsTest.php @@ -24,6 +24,56 @@ final class AssetsTest extends UnitTestCase { use RequestAssertionsTrait; + + /** + * @return array[] + */ + public function listAssetsFieldsDataProvider() + { + return [ + [ + 'options' => [ + 'fields' => ['tags', 'secure_url'], + ], + 'url' => '/resources/image', + 'queryParams' => [ + 'fields' => 'tags,secure_url', + ], + ], + [ + 'options' => [ + 'fields' => 'context,url', + ], + 'url' => '/resources/image', + 'queryParams' => [ + 'fields' => 'context,url', + ], + ], + [ + 'options' => [ + 'fields' => "", + ], + 'url' => '/resources/image', + 'queryParams' => [], + ], + ]; + } + + /** + * Test list assets fields serialization. + * + * @dataProvider listAssetsFieldsDataProvider + */ + public function testListAssetFields($options, $url, $queryParams) + { + $mockAdminApi = new MockAdminApi(); + $mockAdminApi->assets($options); + $lastRequest = $mockAdminApi->getMockHandler()->getLastRequest(); + + self::assertRequestUrl($lastRequest, $url); + self::assertRequestQueryStringSubset($lastRequest, $queryParams); + } + /** * @return array[] */ diff --git a/tests/Unit/Search/SearchApiTest.php b/tests/Unit/Search/SearchApiTest.php index a98c2443..dc4652bb 100644 --- a/tests/Unit/Search/SearchApiTest.php +++ b/tests/Unit/Search/SearchApiTest.php @@ -16,7 +16,6 @@ use Cloudinary\Test\Helpers\MockSearchFoldersApi; use Cloudinary\Test\Helpers\MockSearchApi; use Cloudinary\Test\Helpers\RequestAssertionsTrait; -use Cloudinary\Test\Unit\Asset\AssetTestCase; use Cloudinary\Test\Unit\UnitTestCase; /** @@ -44,6 +43,9 @@ public function testExecuteWithParams() ->aggregate('resource_type') ->withField('tags') ->withField('image_metadata') + ->fields(['tags', 'context']) + ->fields('metadata') + ->fields('tags') ->execute(); $lastRequest = $mockSearchApi->getMockHandler()->getLastRequest(); @@ -56,6 +58,7 @@ public function testExecuteWithParams() ], 'aggregate' => ['format', 'resource_type'], 'with_field' => ['tags', 'image_metadata'], + 'fields' => ['tags', 'context', 'metadata'], 'expression' => 'format:png', 'max_results' => 10, 'next_cursor' => self::NEXT_CURSOR, @@ -84,6 +87,8 @@ public function testShouldNotDuplicateValues() ->withField('context') ->withField('context') ->withField('tags') + ->fields(['tags', 'context']) + ->fields('tags') ->execute(); $lastRequest = $mockSearchApi->getMockHandler()->getLastRequest(); @@ -96,6 +101,7 @@ public function testShouldNotDuplicateValues() ], 'aggregate' => ['format', 'resource_type'], 'with_field' => ['context', 'tags'], + 'fields' => ['tags', 'context'], ] ); }