From f4d1db9a777f57f50eda499edcb971de9b576c5f Mon Sep 17 00:00:00 2001 From: Andrei Andrukhovich Date: Tue, 23 Dec 2025 18:59:23 -0700 Subject: [PATCH] cio-netcore changes --- .../client/modules/FacetTestsV2.cs | 273 ++++++++ .../client/modules/SearchabilityTestsV2.cs | 239 +++++++ src/constructor.io/client/modules/Catalog.cs | 590 ++++++++++++++++++ .../Catalog/DeleteSearchabilitiesV2Request.cs | 87 +++ .../models/Catalog/FacetV2GetAllResponse.cs | 25 + .../Catalog/PatchSearchabilitiesV2Request.cs | 69 ++ .../RetrieveSearchabilitiesV2Request.cs | 147 +++++ .../Catalog/SearchabilitiesV2Response.cs | 25 + .../models/Catalog/SearchabilityV2.cs | 78 +++ src/constructor.io/models/common/FacetV2.cs | 182 ++++++ 10 files changed, 1715 insertions(+) create mode 100644 src/Constructorio_NET.Tests/client/modules/FacetTestsV2.cs create mode 100644 src/Constructorio_NET.Tests/client/modules/SearchabilityTestsV2.cs create mode 100644 src/constructor.io/models/Catalog/DeleteSearchabilitiesV2Request.cs create mode 100644 src/constructor.io/models/Catalog/FacetV2GetAllResponse.cs create mode 100644 src/constructor.io/models/Catalog/PatchSearchabilitiesV2Request.cs create mode 100644 src/constructor.io/models/Catalog/RetrieveSearchabilitiesV2Request.cs create mode 100644 src/constructor.io/models/Catalog/SearchabilitiesV2Response.cs create mode 100644 src/constructor.io/models/Catalog/SearchabilityV2.cs create mode 100644 src/constructor.io/models/common/FacetV2.cs diff --git a/src/Constructorio_NET.Tests/client/modules/FacetTestsV2.cs b/src/Constructorio_NET.Tests/client/modules/FacetTestsV2.cs new file mode 100644 index 0000000..73cccea --- /dev/null +++ b/src/Constructorio_NET.Tests/client/modules/FacetTestsV2.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Constructorio_NET.Models; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +namespace Constructorio_NET.Tests +{ + [TestFixture] + public class FacetsTestV2 + { + private readonly string ApiKey = "ZqXaOfXuBWD4s3XzCI1q"; + private ConstructorioConfig Config; + private static List CreatedFacets = new List(); + + [OneTimeSetUp] + public void Setup() + { + JObject json = JObject.Parse(File.ReadAllText("./../../../../../.config/local.json")); + string testApiToken = json.SelectToken("TEST_API_TOKEN").Value(); + this.Config = new ConstructorioConfig(this.ApiKey, testApiToken); + } + + [SetUp] + public async Task Delay() + { + await Task.Delay(1000); + } + + [OneTimeTearDown] + public async Task Cleanup() + { + var constructorio = new ConstructorIO(Config); + foreach (FacetV2 facet in CreatedFacets) + { + try + { + await constructorio.Catalog.DeleteFacetConfigV2(facet.Name); + } + catch (Exception e) + { + // Do Nothing + Console.WriteLine(e); + } + } + } + + internal static FacetV2 CreateRandomFacet(FacetTypeV2 facetType) + { + string name = Guid.NewGuid().ToString(); + string pathInMetadata = $"metadata.{name}"; + + FacetV2 facet = new FacetV2(name, pathInMetadata, facetType); + + CreatedFacets.Add(facet); + + return facet; + } + + [Test] + public async Task CreateFacetConfigV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facet = CreateRandomFacet(FacetTypeV2.Multiple); + FacetV2 createdFacet = await constructorio.Catalog.CreateFacetConfigV2(facet); + + Assert.IsNotNull(createdFacet, "Facet not created"); + Assert.AreEqual(facet.Name, createdFacet.Name, message: "Facet created is different"); + Assert.AreEqual(facet.PathInMetadata, createdFacet.PathInMetadata, message: "PathInMetadata is different"); + } + + [Test] + public async Task CreateFacetConfigV2WithSection() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facet = CreateRandomFacet(FacetTypeV2.Multiple); + FacetV2 createdFacet = await constructorio.Catalog.CreateFacetConfigV2(facet, "Products"); + + Assert.IsNotNull(createdFacet, "Facet not created"); + Assert.AreEqual(facet.Name, createdFacet.Name, message: "Facet created is different"); + } + + [Test] + public async Task CreateRangedFacetConfigV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facet = CreateRandomFacet(FacetTypeV2.Range); + facet.RangeType = FacetRangeType.Static; + facet.RangeFormat = FacetRangeFormat.Boundaries; + FacetV2 createdFacet = await constructorio.Catalog.CreateFacetConfigV2(facet, "Products"); + + Assert.IsNotNull(createdFacet, "Facet not created"); + Assert.AreEqual(facet.Name, createdFacet.Name, message: "Facet created is different"); + } + + [Test] + public async Task CreateHierarchicalFacetConfigV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facet = CreateRandomFacet(FacetTypeV2.Hierarchical); + FacetV2 createdFacet = await constructorio.Catalog.CreateFacetConfigV2(facet, "Products"); + + Assert.IsNotNull(createdFacet, "Facet not created"); + Assert.AreEqual(facet.Name, createdFacet.Name, message: "Facet created is different"); + Assert.AreEqual(FacetTypeV2.Hierarchical, createdFacet.Type, "Facet type should be hierarchical"); + } + + [Test] + public async Task GetAllFacetConfigsV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2GetAllResponse facetResponse = await constructorio.Catalog.GetAllFacetConfigsV2(); + + Assert.IsTrue(facetResponse.Facets.Count > 0 || facetResponse.TotalCount >= 0, "Response should contain facets array"); + } + + [Test] + public async Task GetAllFacetConfigsV2WithSection() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2GetAllResponse facetResponse = await constructorio.Catalog.GetAllFacetConfigsV2(section: "Products"); + + Assert.IsNotNull(facetResponse, "Response should not be null"); + } + + [Test] + public async Task GetAllFacetConfigsV2WithPagination() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + PaginationOptions pagination = new PaginationOptions(); + pagination.NumResultsPerPage = 2; + pagination.Offset = 0; + FacetV2GetAllResponse facetResponse = await constructorio.Catalog.GetAllFacetConfigsV2(pagination); + + Assert.IsNotNull(facetResponse, "Response should not be null"); + } + + [Test] + public async Task GetFacetConfigV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facetToFetch = CreateRandomFacet(FacetTypeV2.Multiple); + await constructorio.Catalog.CreateFacetConfigV2(facetToFetch); + + FacetV2 facet = await constructorio.Catalog.GetFacetConfigV2(facetToFetch.Name); + + Assert.AreEqual(facet.Name, facetToFetch.Name, "Incorrect facet fetched"); + } + + [Test] + public async Task PartiallyUpdateFacetConfigV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facet = CreateRandomFacet(FacetTypeV2.Multiple); + facet.DisplayName = "unchanged"; + facet.Position = 1; + await constructorio.Catalog.CreateFacetConfigV2(facet); + + facet.DisplayName = "changed"; + facet.Position = 2; + + FacetV2 changedFacet = await constructorio.Catalog.PartiallyUpdateFacetConfigV2(facet); + + Assert.AreEqual(facet.DisplayName, changedFacet.DisplayName, "Facet display name not changed correctly"); + Assert.AreEqual(facet.Position, changedFacet.Position, "Facet position not changed correctly"); + } + + [Test] + public async Task ReplaceFacetConfigV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facet = CreateRandomFacet(FacetTypeV2.Multiple); + facet.DisplayName = "unchanged"; + facet.Position = 1; + await constructorio.Catalog.CreateFacetConfigV2(facet); + + facet.DisplayName = "changed"; + facet.Position = null; + + FacetV2 newFacet = await constructorio.Catalog.ReplaceFacetConfigV2(facet); + + Assert.AreEqual("changed", newFacet.DisplayName, "Display Name not properly updated."); + } + + [Test] + public async Task DeleteFacetConfigV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facetToDelete = CreateRandomFacet(FacetTypeV2.Multiple); + await constructorio.Catalog.CreateFacetConfigV2(facetToDelete); + + FacetV2 deletedFacet = await constructorio.Catalog.DeleteFacetConfigV2(facetToDelete.Name); + + Assert.AreEqual(deletedFacet.Name, facetToDelete.Name, "Wrong facet deleted"); + CreatedFacets.RemoveAt(CreatedFacets.Count - 1); + } + + [Test] + public async Task DeleteFacetConfigV2WithSection() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facetToDelete = CreateRandomFacet(FacetTypeV2.Multiple); + await constructorio.Catalog.CreateFacetConfigV2(facetToDelete, "Products"); + + FacetV2 deletedFacet = await constructorio.Catalog.DeleteFacetConfigV2(facetToDelete.Name, "Products"); + + Assert.AreEqual(deletedFacet.Name, facetToDelete.Name, "Wrong facet deleted"); + CreatedFacets.RemoveAt(CreatedFacets.Count - 1); + } + + [Test] + public async Task CreateFacetConfigV2WithNewFields() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + FacetV2 facet = CreateRandomFacet(FacetTypeV2.Multiple); + facet.Countable = false; + facet.OptionsLimit = 100; + FacetV2 createdFacet = await constructorio.Catalog.CreateFacetConfigV2(facet); + + Assert.IsNotNull(createdFacet, "Facet not created"); + Assert.AreEqual(facet.Name, createdFacet.Name, message: "Facet created is different"); + Assert.IsNotNull(createdFacet.CreatedAt, "CreatedAt should be set"); + } + + [Test] + public async Task BatchPartiallyUpdateFacetConfigsV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + List newFacets = new List(); + FacetV2 facet; + for (var i = 0; i < 3; ++i) + { + facet = CreateRandomFacet(FacetTypeV2.Multiple); + facet.DisplayName = "unchanged"; + facet.Position = 1; + await constructorio.Catalog.CreateFacetConfigV2(facet); + newFacets.Add(facet); + } + + newFacets[0].DisplayName = "changed"; + newFacets[1].Position = 2; + newFacets[2].DisplayName = "also_changed"; + + List changedFacets = await constructorio.Catalog.BatchPartiallyUpdateFacetConfigsV2(newFacets); + + Assert.AreEqual(newFacets[0].DisplayName, changedFacets[0].DisplayName, "Facet display name not changed correctly"); + Assert.AreEqual(newFacets[1].Position, changedFacets[1].Position, "Facet position not changed correctly"); + Assert.AreEqual(newFacets[2].DisplayName, changedFacets[2].DisplayName, "Facet display name not changed correctly"); + } + + [Test] + public async Task CreateOrReplaceFacetConfigsV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + List newFacets = new List(); + + for (var i = 0; i < 2; ++i) + { + FacetV2 facet = CreateRandomFacet(FacetTypeV2.Multiple); + facet.DisplayName = "original"; + newFacets.Add(facet); + } + + List createdFacets = await constructorio.Catalog.CreateOrReplaceFacetConfigsV2(newFacets); + + Assert.AreEqual(2, createdFacets.Count, "Should have created 2 facets"); + Assert.AreEqual(newFacets[0].Name, createdFacets[0].Name, "First facet name mismatch"); + Assert.AreEqual(newFacets[1].Name, createdFacets[1].Name, "Second facet name mismatch"); + } + } +} diff --git a/src/Constructorio_NET.Tests/client/modules/SearchabilityTestsV2.cs b/src/Constructorio_NET.Tests/client/modules/SearchabilityTestsV2.cs new file mode 100644 index 0000000..c96386f --- /dev/null +++ b/src/Constructorio_NET.Tests/client/modules/SearchabilityTestsV2.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Constructorio_NET.Models; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +namespace Constructorio_NET.Tests +{ + [TestFixture] + public class SearchabilityTestsV2 + { + private readonly string ApiKey = "ZqXaOfXuBWD4s3XzCI1q"; + private ConstructorioConfig Config; + private static List CreatedSearchabilities = new List(); + + [OneTimeSetUp] + public void Setup() + { + JObject json = JObject.Parse(File.ReadAllText("./../../../../../.config/local.json")); + string testApiToken = json.SelectToken("TEST_API_TOKEN").Value(); + this.Config = new ConstructorioConfig(this.ApiKey, testApiToken); + } + + [SetUp] + public async Task Delay() + { + await Task.Delay(1000); + } + + [OneTimeTearDown] + public async Task Cleanup() + { + var constructorio = new ConstructorIO(Config); + if (CreatedSearchabilities.Count > 0) + { + try + { + var deleteRequest = new DeleteSearchabilitiesV2Request(CreatedSearchabilities); + await constructorio.Catalog.DeleteSearchabilitiesV2(deleteRequest); + } + catch (Exception e) + { + // Do Nothing + Console.WriteLine(e); + } + } + } + + internal static SearchabilityV2 CreateRandomSearchability() + { + string name = $"test_field_{Guid.NewGuid().ToString().Substring(0, 8)}"; + + SearchabilityV2 searchability = new SearchabilityV2(name); + searchability.FuzzySearchable = true; + searchability.ExactSearchable = false; + searchability.Displayable = true; + searchability.Hidden = false; + + CreatedSearchabilities.Add(name); + + return searchability; + } + + [Test] + public async Task RetrieveSearchabilitiesV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + var request = new RetrieveSearchabilitiesV2Request(); + SearchabilitiesV2Response response = await constructorio.Catalog.RetrieveSearchabilitiesV2(request); + + Assert.IsNotNull(response, "Response should not be null"); + Assert.IsNotNull(response.Searchabilities, "Searchabilities list should not be null"); + } + + [Test] + public async Task RetrieveSearchabilitiesV2WithSection() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + var request = new RetrieveSearchabilitiesV2Request(); + request.Section = "Products"; + SearchabilitiesV2Response response = await constructorio.Catalog.RetrieveSearchabilitiesV2(request); + + Assert.IsNotNull(response, "Response should not be null"); + } + + [Test] + public async Task RetrieveSearchabilitiesV2WithPagination() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + var request = new RetrieveSearchabilitiesV2Request(); + request.NumResultsPerPage = 5; + request.Page = 1; + SearchabilitiesV2Response response = await constructorio.Catalog.RetrieveSearchabilitiesV2(request); + + Assert.IsNotNull(response, "Response should not be null"); + } + + [Test] + public async Task RetrieveSearchabilitiesV2WithFilters() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + var request = new RetrieveSearchabilitiesV2Request(); + request.FuzzySearchable = true; + SearchabilitiesV2Response response = await constructorio.Catalog.RetrieveSearchabilitiesV2(request); + + Assert.IsNotNull(response, "Response should not be null"); + } + + [Test] + public async Task RetrieveSearchabilitiesV2WithSorting() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + var request = new RetrieveSearchabilitiesV2Request(); + request.SortBy = "name"; + request.SortOrder = "ascending"; + SearchabilitiesV2Response response = await constructorio.Catalog.RetrieveSearchabilitiesV2(request); + + Assert.IsNotNull(response, "Response should not be null"); + } + + [Test] + public async Task PatchSearchabilitiesV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + var searchability = CreateRandomSearchability(); + var searchabilities = new List { searchability }; + var request = new PatchSearchabilitiesV2Request(searchabilities); + + SearchabilitiesV2Response response = await constructorio.Catalog.PatchSearchabilitiesV2(request); + + Assert.IsNotNull(response, "Response should not be null"); + Assert.IsTrue(response.Searchabilities.Count > 0, "Should have created at least one searchability"); + } + + [Test] + public async Task GetSearchabilityV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + + // First create a searchability + var searchability = CreateRandomSearchability(); + var searchabilities = new List { searchability }; + var createRequest = new PatchSearchabilitiesV2Request(searchabilities); + await constructorio.Catalog.PatchSearchabilitiesV2(createRequest); + + // Then get it by name + SearchabilityV2 result = await constructorio.Catalog.GetSearchabilityV2(searchability.Name); + + Assert.IsNotNull(result, "Response should not be null"); + Assert.AreEqual(searchability.Name, result.Name, "Name should match"); + } + + [Test] + public async Task PatchSearchabilityV2Single() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + + // First create a searchability + var searchability = CreateRandomSearchability(); + var searchabilities = new List { searchability }; + var createRequest = new PatchSearchabilitiesV2Request(searchabilities); + await constructorio.Catalog.PatchSearchabilitiesV2(createRequest); + + // Update it + searchability.Displayable = false; + SearchabilityV2 result = await constructorio.Catalog.PatchSearchabilityV2(searchability); + + Assert.IsNotNull(result, "Response should not be null"); + Assert.AreEqual(searchability.Name, result.Name, "Name should match"); + } + + [Test] + public async Task DeleteSearchabilitiesV2() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + + // First create a searchability + var searchability = CreateRandomSearchability(); + var searchabilities = new List { searchability }; + var createRequest = new PatchSearchabilitiesV2Request(searchabilities); + await constructorio.Catalog.PatchSearchabilitiesV2(createRequest); + + // Delete it + var deleteRequest = new DeleteSearchabilitiesV2Request(new List { searchability.Name }); + SearchabilitiesV2Response response = await constructorio.Catalog.DeleteSearchabilitiesV2(deleteRequest); + + Assert.IsNotNull(response, "Response should not be null"); + + // Remove from cleanup list since we already deleted it + CreatedSearchabilities.Remove(searchability.Name); + } + + [Test] + public async Task DeleteSearchabilityV2Single() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + + // First create a searchability + var searchability = CreateRandomSearchability(); + var searchabilities = new List { searchability }; + var createRequest = new PatchSearchabilitiesV2Request(searchabilities); + await constructorio.Catalog.PatchSearchabilitiesV2(createRequest); + + // Delete it by name + SearchabilityV2 result = await constructorio.Catalog.DeleteSearchabilityV2(searchability.Name); + + Assert.IsNotNull(result, "Response should not be null"); + Assert.AreEqual(searchability.Name, result.Name, "Name should match"); + + // Remove from cleanup list since we already deleted it + CreatedSearchabilities.Remove(searchability.Name); + } + + [Test] + public async Task SearchabilityV2HasNewFields() + { + ConstructorIO constructorio = new ConstructorIO(this.Config); + + // Create a searchability with the new v2 fields + var searchability = CreateRandomSearchability(); + searchability.Displayable = true; + searchability.Hidden = true; + + var searchabilities = new List { searchability }; + var request = new PatchSearchabilitiesV2Request(searchabilities); + SearchabilitiesV2Response response = await constructorio.Catalog.PatchSearchabilitiesV2(request); + + Assert.IsNotNull(response, "Response should not be null"); + Assert.IsTrue(response.Searchabilities.Count > 0, "Should have created at least one searchability"); + + // Verify the created searchability has the new fields + var created = response.Searchabilities.Find(s => s.Name == searchability.Name); + Assert.IsNotNull(created, "Created searchability should be in response"); + Assert.IsNotNull(created.CreatedAt, "CreatedAt should be set"); + } + } +} diff --git a/src/constructor.io/client/modules/Catalog.cs b/src/constructor.io/client/modules/Catalog.cs index b96f0c7..ca7afa8 100644 --- a/src/constructor.io/client/modules/Catalog.cs +++ b/src/constructor.io/client/modules/Catalog.cs @@ -1195,5 +1195,595 @@ public async Task UpdateSortOption(SortOptionsSingleRequest sortOpti return result ?? throw new ConstructorException("UpdateSortOption response data is malformed"); } + + #region V2 Facets + + internal string CreateFacetUrlV2(string section = "Products", string facetName = null, Hashtable queryParams = null) + { + List paths = new List { "v2", "facets" }; + if (facetName != null) + { + paths.Add(facetName); + } + + if (queryParams == null) + { + queryParams = new Hashtable(); + } + + if (!string.IsNullOrEmpty(section)) + { + queryParams.Add(Constants.SECTION, section); + } + + string url = MakeUrl(this.Options, paths, queryParams, OmitDtAndCQueryParams); + + return url; + } + + /// + /// Creates a v2 Facet Configuration. + /// + /// New v2 Facet Configuration to be created. + /// Section in which the facet is defined. + /// The cancellation token to abandon the request. + /// FacetV2 object representing the facet created. + public async Task CreateFacetConfigV2(FacetV2 facet, string section = "Products", CancellationToken cancellationToken = default) + { + if (facet == null) + { + throw new ArgumentNullException(nameof(facet)); + } + + string url = CreateFacetUrlV2(section); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + FacetV2 result; + try + { + result = await MakeHttpRequest(Options, HttpMethod.Post, url, requestHeaders, facet, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("CreateFacetConfigV2 response data is malformed"); + } + + /// + /// Gets all v2 facets in a particular section. + /// + /// PaginationOptions object for pagination. + /// Section in which the facet is defined. + /// The cancellation token to abandon the request. + /// List of v2 Facets in a given section. + public async Task GetAllFacetConfigsV2(PaginationOptions paginationOptions = null, string section = "Products", CancellationToken cancellationToken = default) + { + string url; + if (paginationOptions != null) + { + url = CreateFacetUrlV2(section, queryParams: paginationOptions.GetQueryParameters()); + } + else + { + url = CreateFacetUrlV2(section); + } + + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + FacetV2GetAllResponse result; + try + { + result = await MakeHttpRequest(Options, HttpMethod.Get, url, requestHeaders, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("GetAllFacetConfigsV2 response data is malformed"); + } + + /// + /// Gets a v2 facet given the facet name and section. + /// + /// Name of the facet. + /// Section in which the facet is defined. + /// The cancellation token to abandon the request. + /// FacetV2 object representing the facet. + public async Task GetFacetConfigV2(string facetName, string section = "Products", CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(facetName)) + { + throw new ArgumentException("facetName is required", nameof(facetName)); + } + + string url = CreateFacetUrlV2(section, facetName); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + FacetV2 result; + try + { + result = await MakeHttpRequest(Options, HttpMethod.Get, url, requestHeaders, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("GetFacetConfigV2 response data is malformed"); + } + + /// + /// Create or replace v2 facet configurations (batch operation). + /// Replacing will overwrite all other configurations, resetting them to defaults, except facet options. + /// + /// List of v2 facets to create or replace. + /// Section in which the facet is defined. + /// The cancellation token to abandon the request. + /// List of created or replaced v2 facet configurations. + public async Task> CreateOrReplaceFacetConfigsV2(List facets, string section = "Products", CancellationToken cancellationToken = default) + { + if (facets == null || facets.Count == 0) + { + throw new ArgumentException("facets cannot be null or empty", nameof(facets)); + } + + string url = CreateFacetUrlV2(section); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + Hashtable requestBody = new Hashtable + { + { "facets", facets } + }; + + List result; + try + { + result = await MakeHttpRequest>(Options, HttpMethod.Put, url, requestHeaders, requestBody, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("CreateOrReplaceFacetConfigsV2 response data is malformed"); + } + + /// + /// Partially updates a list of v2 facet configurations (batch operation). + /// + /// List of v2 facets fields to be updated. + /// Section in which the facet is defined. + /// The cancellation token to abandon the request. + /// List of updated v2 facet configurations. + public async Task> BatchPartiallyUpdateFacetConfigsV2(List facetFieldsList, string section = "Products", CancellationToken cancellationToken = default) + { + if (facetFieldsList == null || facetFieldsList.Count == 0) + { + throw new ArgumentException("facetFieldsList cannot be null or empty", nameof(facetFieldsList)); + } + + string url = CreateFacetUrlV2(section); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + Hashtable requestBody = new Hashtable + { + { "facets", facetFieldsList } + }; + + List result; + try + { + result = await MakeHttpRequest>(Options, HttpMethodPatch, url, requestHeaders, requestBody, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("BatchPartiallyUpdateFacetConfigsV2 response data is malformed"); + } + + /// + /// Replace an existing v2 facet configuration by facet name. + /// This will overwrite all other configurations, resetting them to defaults, except facet options. + /// + /// New v2 Facet Configuration to replace the existing one. + /// Section in which the facet is defined. + /// The cancellation token to abandon the request. + /// Replaced v2 facet configuration. + public async Task ReplaceFacetConfigV2(FacetV2 facet, string section = "Products", CancellationToken cancellationToken = default) + { + if (facet == null) + { + throw new ArgumentNullException(nameof(facet)); + } + + if (string.IsNullOrEmpty(facet.Name)) + { + throw new ArgumentException("facet.Name is required", nameof(facet)); + } + + string url = CreateFacetUrlV2(section, facet.Name); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + FacetV2 result; + try + { + result = await MakeHttpRequest(Options, HttpMethod.Put, url, requestHeaders, facet, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("ReplaceFacetConfigV2 response data is malformed"); + } + + /// + /// Partially updates a specific v2 facet configuration by facet name. + /// + /// v2 Facet fields to be updated. + /// Section in which the facet is defined. + /// The cancellation token to abandon the request. + /// Updated v2 facet configuration. + public async Task PartiallyUpdateFacetConfigV2(FacetV2 facetFields, string section = "Products", CancellationToken cancellationToken = default) + { + if (facetFields == null) + { + throw new ArgumentNullException(nameof(facetFields)); + } + + if (string.IsNullOrEmpty(facetFields.Name)) + { + throw new ArgumentException("facetFields.Name is required", nameof(facetFields)); + } + + string url = CreateFacetUrlV2(section, facetFields.Name); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + FacetV2 result; + try + { + result = await MakeHttpRequest(Options, HttpMethodPatch, url, requestHeaders, facetFields, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("PartiallyUpdateFacetConfigV2 response data is malformed"); + } + + /// + /// Deletes a v2 Facet Configuration. + /// + /// Name of the facet configuration/facet group. + /// Section in which the facet is defined. + /// The cancellation token to abandon the request. + /// FacetV2 object representing the facet configuration deleted. + public async Task DeleteFacetConfigV2(string facetName, string section = "Products", CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(facetName)) + { + throw new ArgumentException("facetName is required", nameof(facetName)); + } + + string url = CreateFacetUrlV2(section, facetName); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + FacetV2 result; + try + { + result = await MakeHttpRequest(Options, HttpMethod.Delete, url, requestHeaders, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("DeleteFacetConfigV2 response data is malformed"); + } + + #endregion + + #region V2 Searchabilities + + internal string CreateSearchabilitiesUrlV2(string section = "Products", string name = null, Hashtable queryParams = null) + { + List paths = new List { "v2", "searchabilities" }; + if (name != null) + { + paths.Add(name); + } + + if (queryParams == null) + { + queryParams = new Hashtable(); + } + + if (!string.IsNullOrEmpty(section)) + { + queryParams.Add(Constants.SECTION, section); + } + + string url = MakeUrl(this.Options, paths, queryParams, OmitDtAndCQueryParams); + + return url; + } + + /// + /// Retrieves v2 searchabilities with options for filtering and paginating. + /// + /// Constructorio's v2 retrieve searchabilities request object. + /// The cancellation token to abandon the request. + /// Constructorio's v2 Searchabilities response object. + public async Task RetrieveSearchabilitiesV2(RetrieveSearchabilitiesV2Request retrieveSearchabilitiesV2Request, CancellationToken cancellationToken = default) + { + if (retrieveSearchabilitiesV2Request == null) + { + throw new ArgumentNullException(nameof(retrieveSearchabilitiesV2Request)); + } + + SearchabilitiesV2Response result; + + try + { + var url = CreateSearchabilitiesUrlV2(retrieveSearchabilitiesV2Request.Section ?? "Products", queryParams: retrieveSearchabilitiesV2Request.GetRequestParameters()); + Dictionary requestHeaders = retrieveSearchabilitiesV2Request.GetRequestHeaders(); + AddAuthHeaders(this.Options, requestHeaders); + result = await MakeHttpRequest(Options, HttpMethod.Get, url, requestHeaders, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("RetrieveSearchabilitiesV2 response data is malformed"); + } + + /// + /// Gets a specific v2 searchability by name. + /// + /// Name of the searchability field. + /// Section name. Defaults to "Products". + /// The cancellation token to abandon the request. + /// SearchabilityV2 object representing the searchability. + public async Task GetSearchabilityV2(string name, string section = "Products", CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("name is required", nameof(name)); + } + + string url = CreateSearchabilitiesUrlV2(section, name); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + SearchabilityV2 result; + try + { + result = await MakeHttpRequest(Options, HttpMethod.Get, url, requestHeaders, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("GetSearchabilityV2 response data is malformed"); + } + + /// + /// Create or update v2 searchabilities (batch operation). + /// + /// Constructorio's v2 patch searchabilities request object. + /// The cancellation token to abandon the request. + /// Constructorio's v2 Searchabilities response object. + public async Task PatchSearchabilitiesV2(PatchSearchabilitiesV2Request patchSearchabilitiesV2Request, CancellationToken cancellationToken = default) + { + if (patchSearchabilitiesV2Request == null) + { + throw new ArgumentNullException(nameof(patchSearchabilitiesV2Request)); + } + + SearchabilitiesV2Response result; + Hashtable postbody = new Hashtable + { + { "searchabilities", patchSearchabilitiesV2Request.Searchabilities } + }; + + try + { + var url = CreateSearchabilitiesUrlV2(patchSearchabilitiesV2Request.Section ?? "Products", queryParams: patchSearchabilitiesV2Request.GetRequestParameters()); + Dictionary requestHeaders = patchSearchabilitiesV2Request.GetRequestHeaders(); + AddAuthHeaders(this.Options, requestHeaders); + result = await MakeHttpRequest(Options, HttpMethodPatch, url, requestHeaders, postbody, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("PatchSearchabilitiesV2 response data is malformed"); + } + + /// + /// Partially updates a specific v2 searchability by name. + /// + /// SearchabilityV2 fields to be updated. + /// Section name. Defaults to "Products". + /// Whether to skip index rebuild. + /// The cancellation token to abandon the request. + /// Updated SearchabilityV2 object. + public async Task PatchSearchabilityV2(SearchabilityV2 searchability, string section = "Products", bool? skipRebuild = null, CancellationToken cancellationToken = default) + { + if (searchability == null) + { + throw new ArgumentNullException(nameof(searchability)); + } + + if (string.IsNullOrEmpty(searchability.Name)) + { + throw new ArgumentException("searchability.Name is required", nameof(searchability)); + } + + Hashtable queryParams = new Hashtable(); + if (skipRebuild.HasValue) + { + queryParams.Add("skip_rebuild", skipRebuild.Value.ToString().ToLower()); + } + + string url = CreateSearchabilitiesUrlV2(section, searchability.Name, queryParams); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + SearchabilityV2 result; + try + { + result = await MakeHttpRequest(Options, HttpMethodPatch, url, requestHeaders, searchability, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("PatchSearchabilityV2 response data is malformed"); + } + + /// + /// Delete v2 searchabilities (batch operation). + /// + /// Constructorio's v2 delete searchabilities request object. + /// The cancellation token to abandon the request. + /// Constructorio's v2 Searchabilities response object. + public async Task DeleteSearchabilitiesV2(DeleteSearchabilitiesV2Request deleteSearchabilitiesV2Request, CancellationToken cancellationToken = default) + { + if (deleteSearchabilitiesV2Request == null) + { + throw new ArgumentNullException(nameof(deleteSearchabilitiesV2Request)); + } + + SearchabilitiesV2Response result; + + try + { + var url = CreateSearchabilitiesUrlV2(deleteSearchabilitiesV2Request.Section ?? "Products", queryParams: deleteSearchabilitiesV2Request.GetRequestParameters()); + Dictionary requestHeaders = deleteSearchabilitiesV2Request.GetRequestHeaders(); + AddAuthHeaders(this.Options, requestHeaders); + result = await MakeHttpRequest(Options, HttpMethod.Delete, url, requestHeaders, deleteSearchabilitiesV2Request.GetRequestBody(), cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("DeleteSearchabilitiesV2 response data is malformed"); + } + + /// + /// Delete a specific v2 searchability by name. + /// + /// Name of the searchability field to delete. + /// Section name. Defaults to "Products". + /// Whether to skip index rebuild. + /// The cancellation token to abandon the request. + /// Deleted SearchabilityV2 object. + public async Task DeleteSearchabilityV2(string name, string section = "Products", bool? skipRebuild = null, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("name is required", nameof(name)); + } + + Hashtable queryParams = new Hashtable(); + if (skipRebuild.HasValue) + { + queryParams.Add("skip_rebuild", skipRebuild.Value.ToString().ToLower()); + } + + string url = CreateSearchabilitiesUrlV2(section, name, queryParams); + Dictionary requestHeaders = new Dictionary(); + AddAuthHeaders(this.Options, requestHeaders); + + SearchabilityV2 result; + try + { + result = await MakeHttpRequest(Options, HttpMethod.Delete, url, requestHeaders, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + throw new ConstructorException(e); + } + + return result ?? throw new ConstructorException("DeleteSearchabilityV2 response data is malformed"); + } + + #endregion } } \ No newline at end of file diff --git a/src/constructor.io/models/Catalog/DeleteSearchabilitiesV2Request.cs b/src/constructor.io/models/Catalog/DeleteSearchabilitiesV2Request.cs new file mode 100644 index 0000000..0c0b523 --- /dev/null +++ b/src/constructor.io/models/Catalog/DeleteSearchabilitiesV2Request.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Constructorio_NET.Utils; + +namespace Constructorio_NET.Models +{ + /// + /// Constructor.io v2 Delete Searchabilities Request Class. + /// + public class DeleteSearchabilitiesV2Request + { + /// + /// Gets or sets the list of searchability names to delete. + /// + public List SearchabilityNames { get; set; } + + /// + /// Gets or sets the name of the section. Defaults to "Products". + /// + public string Section { get; set; } + + /// + /// Gets or sets a value indicating whether to skip index rebuild. + /// + public bool? SkipRebuild { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// List of searchability names to delete. + public DeleteSearchabilitiesV2Request(List searchabilityNames) + { + if (searchabilityNames == null || searchabilityNames.Count == 0) + { + throw new ArgumentException("searchabilityNames cannot be null or empty", nameof(searchabilityNames)); + } + + this.SearchabilityNames = searchabilityNames; + } + + /// + /// Get request parameters. + /// + /// Hashtable of request parameters. + public Hashtable GetRequestParameters() + { + Hashtable parameters = new Hashtable(); + + if (this.SkipRebuild.HasValue) + { + parameters.Add("skip_rebuild", this.SkipRebuild.Value.ToString().ToLower()); + } + + return parameters; + } + + /// + /// Get request headers. + /// + /// Dictionary of request headers. + public Dictionary GetRequestHeaders() + { + Dictionary requestHeaders = new Dictionary(); + + return requestHeaders; + } + + /// + /// Get the request body for the delete operation. + /// + /// Hashtable containing the searchabilities to delete. + public Hashtable GetRequestBody() + { + var searchabilities = new List(); + foreach (var name in this.SearchabilityNames) + { + searchabilities.Add(new Hashtable { { "name", name } }); + } + + return new Hashtable + { + { "searchabilities", searchabilities } + }; + } + } +} diff --git a/src/constructor.io/models/Catalog/FacetV2GetAllResponse.cs b/src/constructor.io/models/Catalog/FacetV2GetAllResponse.cs new file mode 100644 index 0000000..bce066e --- /dev/null +++ b/src/constructor.io/models/Catalog/FacetV2GetAllResponse.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Constructorio_NET.Models +{ + /// + /// Response object for retrieving all v2 facet configurations. + /// + [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] + public class FacetV2GetAllResponse + { + /// + /// Gets or sets the list of facet configurations. + /// + [JsonProperty("facets")] + public List Facets { get; set; } + + /// + /// Gets or sets the total count of facet configurations. + /// + [JsonProperty("total_count")] + public int TotalCount { get; set; } + } +} diff --git a/src/constructor.io/models/Catalog/PatchSearchabilitiesV2Request.cs b/src/constructor.io/models/Catalog/PatchSearchabilitiesV2Request.cs new file mode 100644 index 0000000..e11579e --- /dev/null +++ b/src/constructor.io/models/Catalog/PatchSearchabilitiesV2Request.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Constructorio_NET.Utils; + +namespace Constructorio_NET.Models +{ + /// + /// Constructor.io v2 Patch Searchabilities Request Class. + /// + public class PatchSearchabilitiesV2Request + { + /// + /// Gets or sets searchabilities to be created or updated. + /// + public List Searchabilities { get; set; } + + /// + /// Gets or sets the name of the section. Defaults to "Products". + /// + public string Section { get; set; } + + /// + /// Gets or sets a value indicating whether to skip index rebuild. + /// + public bool? SkipRebuild { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// List of searchabilities to create or update. + public PatchSearchabilitiesV2Request(List searchabilities) + { + if (searchabilities == null || searchabilities.Count == 0) + { + throw new ArgumentException("searchabilities cannot be null or empty", nameof(searchabilities)); + } + + this.Searchabilities = searchabilities; + } + + /// + /// Get request parameters. + /// + /// Hashtable of request parameters. + public Hashtable GetRequestParameters() + { + Hashtable parameters = new Hashtable(); + + if (this.SkipRebuild.HasValue) + { + parameters.Add("skip_rebuild", this.SkipRebuild.Value.ToString().ToLower()); + } + + return parameters; + } + + /// + /// Get request headers. + /// + /// Dictionary of request headers. + public Dictionary GetRequestHeaders() + { + Dictionary requestHeaders = new Dictionary(); + + return requestHeaders; + } + } +} diff --git a/src/constructor.io/models/Catalog/RetrieveSearchabilitiesV2Request.cs b/src/constructor.io/models/Catalog/RetrieveSearchabilitiesV2Request.cs new file mode 100644 index 0000000..d0f2224 --- /dev/null +++ b/src/constructor.io/models/Catalog/RetrieveSearchabilitiesV2Request.cs @@ -0,0 +1,147 @@ +using System.Collections; +using System.Collections.Generic; +using Constructorio_NET.Utils; + +namespace Constructorio_NET.Models +{ + /// + /// Constructor.io v2 Retrieve Searchabilities Request Class. + /// + public class RetrieveSearchabilitiesV2Request + { + /// + /// Gets or sets name of searchability field to filter for. + /// Supports exact matching (name=value) and substring matching (name=*value, value* or *value*). + /// + public string Name { get; set; } + + /// + /// Gets or sets the page of searchability results to retrieve. Can't be used with Offset. + /// + public int Page { get; set; } + + /// + /// Gets or sets the offset of results to skip. Can't be used with Page. + /// + public int Offset { get; set; } + + /// + /// Gets or sets the name of the section. Defaults to "Products". + /// + public string Section { get; set; } + + /// + /// Gets or sets the number of results per page to retrieve. Default: 20, Max: 1000. + /// + public int NumResultsPerPage { get; set; } + + /// + /// Gets or sets a value indicating whether to filter by fuzzy_searchable. + /// + public bool? FuzzySearchable { get; set; } + + /// + /// Gets or sets a value indicating whether to filter by exact_searchable. + /// + public bool? ExactSearchable { get; set; } + + /// + /// Gets or sets a value indicating whether to filter by displayable. + /// + public bool? Displayable { get; set; } + + /// + /// Gets or sets the match type for filters. Valid values are "and" or "or". Default is "and". + /// + public string MatchType { get; set; } + + /// + /// Gets or sets the criteria by which searchability results should be sorted. Valid value is "name". + /// + public string SortBy { get; set; } + + /// + /// Gets or sets the order by which searchability results should be sorted. Valid values are "descending" or "ascending". Default is "ascending". + /// + public string SortOrder { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public RetrieveSearchabilitiesV2Request() + { + } + + /// + /// Get request parameters. + /// + /// Hashtable of request parameters. + public Hashtable GetRequestParameters() + { + Hashtable parameters = new Hashtable(); + + if (!string.IsNullOrEmpty(this.Name)) + { + parameters.Add("name", this.Name); + } + + if (this.Page > 0) + { + parameters.Add(Constants.PAGE, this.Page); + } + + if (this.Offset > 0) + { + parameters.Add(Constants.OFFSET, this.Offset); + } + + if (this.NumResultsPerPage > 0) + { + parameters.Add(Constants.RESULTS_PER_PAGE, this.NumResultsPerPage); + } + + if (this.FuzzySearchable.HasValue) + { + parameters.Add("fuzzy_searchable", this.FuzzySearchable.Value.ToString().ToLower()); + } + + if (this.ExactSearchable.HasValue) + { + parameters.Add("exact_searchable", this.ExactSearchable.Value.ToString().ToLower()); + } + + if (this.Displayable.HasValue) + { + parameters.Add("displayable", this.Displayable.Value.ToString().ToLower()); + } + + if (!string.IsNullOrEmpty(this.MatchType)) + { + parameters.Add("match_type", this.MatchType); + } + + if (!string.IsNullOrEmpty(this.SortBy)) + { + parameters.Add(Constants.SORT_BY, this.SortBy); + } + + if (!string.IsNullOrEmpty(this.SortOrder)) + { + parameters.Add(Constants.SORT_ORDER, this.SortOrder); + } + + return parameters; + } + + /// + /// Get request headers. + /// + /// Dictionary of request headers. + public Dictionary GetRequestHeaders() + { + Dictionary requestHeaders = new Dictionary(); + + return requestHeaders; + } + } +} diff --git a/src/constructor.io/models/Catalog/SearchabilitiesV2Response.cs b/src/constructor.io/models/Catalog/SearchabilitiesV2Response.cs new file mode 100644 index 0000000..9ee9b03 --- /dev/null +++ b/src/constructor.io/models/Catalog/SearchabilitiesV2Response.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Constructorio_NET.Models +{ + /// + /// Response object for v2 searchabilities operations (GET, PATCH, DELETE). + /// + [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] + public class SearchabilitiesV2Response + { + /// + /// Gets or sets the list of searchability configurations. + /// + [JsonProperty("searchabilities")] + public List Searchabilities { get; set; } + + /// + /// Gets or sets the total count of searchability configurations. + /// + [JsonProperty("total_count")] + public int TotalCount { get; set; } + } +} diff --git a/src/constructor.io/models/Catalog/SearchabilityV2.cs b/src/constructor.io/models/Catalog/SearchabilityV2.cs new file mode 100644 index 0000000..20d82c9 --- /dev/null +++ b/src/constructor.io/models/Catalog/SearchabilityV2.cs @@ -0,0 +1,78 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Constructorio_NET.Models +{ + /// + /// Constructor.io v2 Searchability model. + /// Note: v2 removes percentage_presence, example_items, and type fields from v1. + /// + [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore, NamingStrategyType = typeof(SnakeCaseNamingStrategy))] + public class SearchabilityV2 + { + /// + /// Gets or sets name of the searchability field. + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// Gets or sets a value indicating whether searchability is fuzzy searchable. + /// Either exact_searchable or fuzzy_searchable can be true, but not both. + /// + [JsonProperty("fuzzy_searchable", NullValueHandling = NullValueHandling.Ignore)] + public bool? FuzzySearchable { get; set; } + + /// + /// Gets or sets a value indicating whether searchability is exact searchable. + /// Either exact_searchable or fuzzy_searchable can be true, but not both. + /// + [JsonProperty("exact_searchable", NullValueHandling = NullValueHandling.Ignore)] + public bool? ExactSearchable { get; set; } + + /// + /// Gets or sets a value indicating whether the field is displayable in the response within the results array. + /// + [JsonProperty("displayable", NullValueHandling = NullValueHandling.Ignore)] + public bool? Displayable { get; set; } + + /// + /// Gets or sets a value indicating whether the field is hidden by default in the response + /// but makes it available to retrieve via fmt_options[hidden_fields] parameter. + /// + [JsonProperty("hidden", NullValueHandling = NullValueHandling.Ignore)] + public bool? Hidden { get; set; } + + /// + /// Gets the creation date and time in ISO 8601 format. + /// Read-only field returned by the API. + /// + [JsonProperty("created_at")] + public string CreatedAt { get; private set; } + + /// + /// Gets the last updated date and time in ISO 8601 format. + /// Read-only field returned by the API. Null if never updated. + /// + [JsonProperty("updated_at")] + public string UpdatedAt { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the searchability field. + public SearchabilityV2(string name) + { + Name = name; + } + + /// + /// Initializes a new instance of the class. + /// Default constructor for deserialization. + /// + [JsonConstructor] + public SearchabilityV2() + { + } + } +} diff --git a/src/constructor.io/models/common/FacetV2.cs b/src/constructor.io/models/common/FacetV2.cs new file mode 100644 index 0000000..0ed1fff --- /dev/null +++ b/src/constructor.io/models/common/FacetV2.cs @@ -0,0 +1,182 @@ +using System.Collections; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + +namespace Constructorio_NET.Models +{ + /// + /// Constructorio Supported v2 Facet Types + /// + [JsonConverter(typeof(StringEnumConverter), typeof(SnakeCaseNamingStrategy))] + public enum FacetTypeV2 + { + /// + /// Used for facet groups with categorical values. + /// + Multiple, + + /// + /// Used for facet groups with hierarchical values. + /// + Hierarchical, + + /// + /// Used for facet groups with numerical values. Required for displaying a slider or a list of range buckets. + /// + Range + } + + /// + /// Constructorio v2 Facet. + /// + [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore, NamingStrategyType = typeof(SnakeCaseNamingStrategy))] + public class FacetV2 + { + /// + /// The facet name used in the catalog. Must be unique inside the section and key. + /// + public string Name { get; set; } + + /// + /// The path in metadata of each item where this facet is present. Must be unique inside the section and key. + /// Required for creating a new facet configuration. + /// + public string PathInMetadata { get; set; } + + /// + /// Type of facet group. Can be 'multiple', 'hierarchical', or 'range'. + /// + public FacetTypeV2 Type { get; set; } + + /// + /// Display name for end users. + /// Default value is `null`, in which case `Name` will be used. + /// + public string DisplayName { get; set; } + + /// + /// Default criterion to sort this facet group. Overridden by `Position` attribute on facet options. + /// Defaults to `relevance`. + /// + public FacetSortOrder? SortOrder { get; set; } + + /// + /// Sort direction for `SortOrderBy`. + /// Default value is `false` for SortOrderBy = `value` and `true` otherwise. + /// + public bool? SortDescending { get; set; } + + /// + /// Specifies whether the range buckets are determined dynamically (based on the values of matching facet options) or statically. + /// Required if FacetType = `range` & RangeFormat = `options`. + /// + public FacetRangeType? RangeType { get; set; } + + /// + /// Format of range facets. Determines whether the facet is configured to display as a slider (in which case the search endpoint will return only min & max values) or as a list of buckets. + /// Required if FaceType = `range`. + /// + public FacetRangeFormat? RangeFormat { get; set; } + + /// + /// Used to create inclusive buckets. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Include)] + public FacetRangeInclusive? RangeInclusive { get; set; } + + // Used by Json.Net to determine whether or not to serialize RangeInclusive. + public bool ShouldSerializeRangeInclusive() + { + if (RangeInclusive == null) return false; + if (RangeInclusive == FacetRangeInclusive.Null) RangeInclusive = null; + return true; + } + + /// + /// Defines the cut-off points for generating static range buckets. Expects list of sorted numbers (like [10, 25, 40]). + /// Required if FacetType = `range` & BucketSize = null. + /// + public List RangeLimits { get; set; } + + /// + /// Specifies filter behavior given multiple filters on the same facet (e.g: color: yellow & blue) are selected. + /// + public FacetMatchType? MatchType { get; set; } + + /// + /// Used to slot facet groups to fixed positions. Must be >= 1. + /// When set to null, position will not be serialized and server defaults apply. + /// + public int? Position { get; set; } + + /// + /// Specifies whether the facet is hidden from users. Use this for facet data that you don't want shown to end users, but that isn't sensitive. + /// Defaults to `false`. + /// + public bool? Hidden { get; set; } + + /// + /// Specifies whether the facet is protected from users. Setting this to true will require authentication to view the facet. + /// Defaults to `false`. + /// + public bool? Protected { get; set; } + + /// + /// Specifies whether counts for each facet option should be calculated and shown in the response. + /// Setting this to false will skip counting these options, improving performance for facets with high cardinality. + /// Defaults to `true`. + /// + public bool? Countable { get; set; } + + /// + /// Maximum number of options of facet type `multiple` to return in search responses. + /// If absent, the default limit is applied. Minimum: 0, Maximum: 4000, Default: 500. + /// + public int? OptionsLimit { get; set; } + + /// + /// Dictionary with any extra facet data. + /// Defaults to `{}` (empty dictionary) + /// + public Hashtable Data { get; set; } + + /// + /// Facet creation date and time in ISO 8601 format. + /// Read-only field returned by the API. + /// + [JsonProperty("created_at")] + public string CreatedAt { get; private set; } + + /// + /// Last facet update date and time in ISO 8601 format. + /// Read-only field returned by the API. + /// + [JsonProperty("updated_at")] + public string UpdatedAt { get; private set; } + + /// + /// Initializes a new instance of the class. + /// Object model for adding/updating v2 facet configurations. + /// + /// + /// + /// + public FacetV2(string name, string pathInMetadata, FacetTypeV2 type) + { + this.Name = name; + this.PathInMetadata = pathInMetadata; + this.Type = type; + } + + /// + /// Initializes a new instance of the class. + /// Default constructor for deserialization. + /// + [JsonConstructor] + public FacetV2() + { + } + } +}