From 3740ddd883322e5639aad69ebc5cae8eb655fbff Mon Sep 17 00:00:00 2001 From: Constantine Nathanson Date: Fri, 1 Dec 2023 18:32:07 +0200 Subject: [PATCH] Add support for `Fields` parameter in Search and Admin APIs --- .../AdminApi/BrowseResourcesTest.cs | 16 ++++ .../SearchApi/SearchApiTest.cs | 83 +++++++++++-------- .../AssetsManagement/ListResourcesParams.cs | 10 +++ CloudinaryDotNet/Search/SearchBaseFluent.cs | 31 ++++++- 4 files changed, 106 insertions(+), 34 deletions(-) diff --git a/CloudinaryDotNet.IntegrationTests/AdminApi/BrowseResourcesTest.cs b/CloudinaryDotNet.IntegrationTests/AdminApi/BrowseResourcesTest.cs index 99e6c7cf..99327962 100644 --- a/CloudinaryDotNet.IntegrationTests/AdminApi/BrowseResourcesTest.cs +++ b/CloudinaryDotNet.IntegrationTests/AdminApi/BrowseResourcesTest.cs @@ -390,6 +390,22 @@ public void TestListResourcesByTag() AssertListResourcesByTagResult(result); } + [Test, RetryWithDelay] + public void TestListResourcesFields() + { + var result = m_cloudinary.ListResources(new ListResourcesParams + { + Fields = new[] { "tags", "secure_url"} + }); + + Assert.GreaterOrEqual(result.Resources.Count(), 1, result.Error?.Message); + + + Assert.IsNotEmpty(result.Resources[0].AssetId); + Assert.IsNotNull(result.Resources[0].SecureUrl); + Assert.IsNull(result.Resources[0].Url); + } + [Test, RetryWithDelay] public async Task TestListResourcesByTagAsync() { diff --git a/CloudinaryDotNet.IntegrationTests/SearchApi/SearchApiTest.cs b/CloudinaryDotNet.IntegrationTests/SearchApi/SearchApiTest.cs index b5e733b2..15d6d4ba 100644 --- a/CloudinaryDotNet.IntegrationTests/SearchApi/SearchApiTest.cs +++ b/CloudinaryDotNet.IntegrationTests/SearchApi/SearchApiTest.cs @@ -15,32 +15,34 @@ class SearchApiTest : IntegrationTestBase private string m_singleResourcePublicId; private string[] m_publicIdsSorted; private Dictionary m_assetIds = new Dictionary(); - private const int INDEXING_WAIT_TIME = 5000; + private const int IndexingWaitTime = 5000; - private const string SORT_FIELD = "public_id"; - private const string SORT_DIRECTION_ASC = "asc"; + private const string SortField = "public_id"; + private const string SortDirectionAsc = "asc"; - private const string AGG_FIELD_VALUE = "resource_type"; - private const string METADATA_FIELD_NAME = "image_metadata"; - private const string STRUCTURED_METADATA_FIELD_NAME = "metadata"; - private const string TAGS_FIELD_NAME = "tags"; - private const string CONTEXT_FIELD_NAME = "context"; - private const string IMAGE_ANALYSIS_FIELD_NAME = "image_analysis"; + private const string AggFieldValue = "resource_type"; + private const string MetadataFieldName = "image_metadata"; + private const string StructuredMetadataFieldName = "metadata"; + private const string TagsFieldName = "tags"; + private const string ContextFieldName = "context"; + private const string ImageAnalysisFieldName = "image_analysis"; + private const string SecureUrlFieldName = "secure_url"; + private const string UrlFieldName = "url"; - private const int FIRST_PAGE_SIZE = 1; - private const int SECOND_PAGE_SIZE = 2; - private const int RESOURCES_COUNT = 3; + private const int FirstPageSize = 1; + private const int SecondPageSize = 2; + private const int ResourcesCount = 3; [OneTimeSetUp] public void InitSearchTests() { m_searchTag = GetMethodTag(); m_expressionTag = $"tags:{m_searchTag}"; - m_publicIdsSorted = new string[RESOURCES_COUNT]; + m_publicIdsSorted = new string[ResourcesCount]; CreateMetadataField("metadata_search"); - for (var i = 0; i < RESOURCES_COUNT; i++) + for (var i = 0; i < ResourcesCount; i++) { var publicId = GetUniquePublicId(); var uploadParams = new ImageUploadParams @@ -62,7 +64,7 @@ public void InitSearchTests() Array.Sort(m_publicIdsSorted); m_expressionPublicId = $"public_id: {m_publicIdsSorted[0]}"; - Thread.Sleep(INDEXING_WAIT_TIME); + Thread.Sleep(IndexingWaitTime); } [TestCase("asset_id=")] @@ -81,7 +83,7 @@ public void TestSearchApiFindResourcesByTag() var result = m_cloudinary.Search().Expression(m_expressionTag).Execute(); Assert.NotNull(result.Resources, result.Error?.Message); - Assert.AreEqual(RESOURCES_COUNT, result.Resources.Count); + Assert.AreEqual(ResourcesCount, result.Resources.Count); } [Test, RetryWithDelay] @@ -94,21 +96,21 @@ public void TestSearchResourceByPublicId() [Test, RetryWithDelay] public void TestPaginateResourcesLimitedByTagAndOrderedByAscendingPublicId() { - var result = m_cloudinary.Search().MaxResults(FIRST_PAGE_SIZE) - .Expression(m_expressionTag).SortBy(SORT_FIELD, SORT_DIRECTION_ASC).Execute(); + var result = m_cloudinary.Search().MaxResults(FirstPageSize) + .Expression(m_expressionTag).SortBy(SortField, SortDirectionAsc).Execute(); Assert.NotNull(result.Resources, result.Error?.Message); - Assert.AreEqual(FIRST_PAGE_SIZE, result.Resources.Count); - Assert.AreEqual(RESOURCES_COUNT, result.TotalCount); + Assert.AreEqual(FirstPageSize, result.Resources.Count); + Assert.AreEqual(ResourcesCount, result.TotalCount); Assert.AreEqual(m_publicIdsSorted.First(), result.Resources.First().PublicId); - result = m_cloudinary.Search().MaxResults(SECOND_PAGE_SIZE) - .Expression(m_expressionTag).SortBy(SORT_FIELD, SORT_DIRECTION_ASC) + result = m_cloudinary.Search().MaxResults(SecondPageSize) + .Expression(m_expressionTag).SortBy(SortField, SortDirectionAsc) .NextCursor(result.NextCursor).Execute(); Assert.NotNull(result.Resources, result.Error?.Message); - Assert.AreEqual(SECOND_PAGE_SIZE, result.Resources.Count); - Assert.AreEqual(RESOURCES_COUNT, result.TotalCount); + Assert.AreEqual(SecondPageSize, result.Resources.Count); + Assert.AreEqual(ResourcesCount, result.TotalCount); Assert.AreEqual(m_publicIdsSorted.Last(), result.Resources.Last().PublicId); Assert.True(string.IsNullOrEmpty(result.Resources[0].Folder)); @@ -134,31 +136,46 @@ public void TestPaginateResourcesLimitedByTagAndOrderedByAscendingPublicId() public void TestSearchAggregate() { var result = m_cloudinary.Search() - .Expression(m_expressionTag).Aggregate(AGG_FIELD_VALUE).Execute(); + .Expression(m_expressionTag).Aggregate(AggFieldValue).Execute(); AssertSupportsAggregation(result); Assert.NotNull(result.Resources, result.Error?.Message); - Assert.AreEqual(RESOURCES_COUNT, result.Resources.Count); + Assert.AreEqual(ResourcesCount, result.Resources.Count); Assert.IsNotEmpty(result.Aggregations); } [Test, RetryWithDelay] public void TestSearchWithField() { - var result = m_cloudinary.Search().MaxResults(FIRST_PAGE_SIZE) - .Expression(m_expressionTag).WithField(METADATA_FIELD_NAME).Execute(); + var result = m_cloudinary.Search().MaxResults(FirstPageSize) + .Expression(m_expressionTag).WithField(MetadataFieldName).Execute(); Assert.NotNull(result.Resources, result.Error?.Message); - Assert.AreEqual(FIRST_PAGE_SIZE, result.Resources.Count); + Assert.AreEqual(FirstPageSize, result.Resources.Count); Assert.IsNotEmpty(result.Resources.First().ImageMetadata); } + [Test, RetryWithDelay] + public void TestSearchFields() + { + var result = m_cloudinary.Search().MaxResults(FirstPageSize) + .Expression(m_expressionTag).Fields(MetadataFieldName) + .Fields(new List{SecureUrlFieldName, SortField}).Execute(); + + Assert.NotNull(result.Resources, result.Error?.Message); + Assert.AreEqual(FirstPageSize, result.Resources.Count); + Assert.IsNotEmpty(result.Resources.First().PublicId); + Assert.IsNotEmpty(result.Resources.First().ImageMetadata); + Assert.IsNotEmpty(result.Resources.First().SecureUrl); + Assert.IsNull(result.Resources.First().Url); + } + [Test, RetryWithDelay] public void TestRootResponseFieldsAreParsed() { - var result = m_cloudinary.Search().MaxResults(FIRST_PAGE_SIZE) - .Expression(m_expressionTag).Aggregate(AGG_FIELD_VALUE).Execute(); + var result = m_cloudinary.Search().MaxResults(FirstPageSize) + .Expression(m_expressionTag).Aggregate(AggFieldValue).Execute(); AssertSupportsAggregation(result); @@ -173,8 +190,8 @@ public void TestRootResponseFieldsAreParsed() public void TestResourceResponseFieldsAreParsed() { var result = m_cloudinary.Search().Expression($"public_id: {m_singleResourcePublicId}") - .WithField(METADATA_FIELD_NAME).WithField(IMAGE_ANALYSIS_FIELD_NAME) - .WithField(CONTEXT_FIELD_NAME).WithField(TAGS_FIELD_NAME).WithField(STRUCTURED_METADATA_FIELD_NAME) + .WithField(MetadataFieldName).WithField(ImageAnalysisFieldName) + .WithField(ContextFieldName).WithField(TagsFieldName).WithField(StructuredMetadataFieldName) .Execute(); var foundResource = result.Resources.First(); diff --git a/CloudinaryDotNet/Actions/AssetsManagement/ListResourcesParams.cs b/CloudinaryDotNet/Actions/AssetsManagement/ListResourcesParams.cs index e26fd6ae..5db61e00 100644 --- a/CloudinaryDotNet/Actions/AssetsManagement/ListResourcesParams.cs +++ b/CloudinaryDotNet/Actions/AssetsManagement/ListResourcesParams.cs @@ -44,6 +44,11 @@ public class ListResourcesParams : BaseParams /// public bool Metadata { get; set; } + /// + /// Gets or sets a list of fields to return in the response. + /// + public string[] Fields { get; set; } + /// /// Gets or sets when a listing request has more results to return than , /// the value is returned as part of the response. You can then specify this value as @@ -87,6 +92,11 @@ public override SortedDictionary ToParamsDictionary() AddParam(dict, "tags", Tags); AddParam(dict, "moderations", Moderations); AddParam(dict, "context", Context); + if (Fields != null) + { + AddParam(dict, "fields", string.Join(",", Fields)); + } + AddParam(dict, "direction", Direction); AddParam(dict, "type", Type); AddParam(dict, "metadata", Metadata); diff --git a/CloudinaryDotNet/Search/SearchBaseFluent.cs b/CloudinaryDotNet/Search/SearchBaseFluent.cs index 931b67c1..17e53578 100644 --- a/CloudinaryDotNet/Search/SearchBaseFluent.cs +++ b/CloudinaryDotNet/Search/SearchBaseFluent.cs @@ -19,6 +19,7 @@ public abstract class SearchBaseFluent private List> sortByParam; private List aggregateParam; private List withFieldParam; + private List fieldsParam; private Dictionary searchParams; /// @@ -32,6 +33,7 @@ public SearchBaseFluent(ApiShared api) sortByParam = new List>(); aggregateParam = new List(); withFieldParam = new List(); + fieldsParam = new List(); } /// @@ -103,6 +105,28 @@ public T WithField(string field) return (T)this; } + /// + /// The name of the asset attribute to keep for each asset in the response. + /// + /// The name of field. + /// The search provider with additional asset attribute defined. + public T Fields(string field) + { + fieldsParam.Add(field); + return (T)this; + } + + /// + /// The list of the names of the asset attributes to keep for each asset in the response. + /// + /// The names of fields. + /// The search provider with additional asset attribute defined. + public T Fields(IEnumerable fields) + { + fieldsParam.AddRange(fields); + return (T)this; + } + /// /// Set sort parameter. If this parameter is not provided then the results are sorted by descending /// creation date. Valid sort directions are 'asc' or 'desc'. @@ -124,13 +148,18 @@ public T SortBy(string field, string dir) /// Search parameters as dictionary. public Dictionary ToQuery() { - Dictionary queryParams = new Dictionary(searchParams); + var queryParams = new Dictionary(searchParams); if (withFieldParam.Count > 0) { queryParams.Add("with_field", withFieldParam.Distinct()); } + if (fieldsParam.Count > 0) + { + queryParams.Add("fields", fieldsParam.Distinct()); + } + if (sortByParam.Count > 0) { queryParams.Add("sort_by", sortByParam.GroupBy(d => d.Keys.First()).Select(l => l.Last()));