Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search options builder for search adapter #52

Merged
merged 3 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase;

namespace Dfe.Data.SearchPrototype.Infrastructure.Builders
{
/// <summary>
/// Provides an abstraction on which to establish the behaviour
/// used to build configured <see cref="SearchOptions" /> instances.
/// </summary>
public interface ISearchOptionsBuilder
{
/// <summary>
/// Sets the number of search items to retrieve.
/// </summary>
/// <param name="size">
/// The number of results to return as specified.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
ISearchOptionsBuilder WithSize(int? size);

/// <summary>
/// Sets the mode of search to invoke, i.e. All or Any.
/// </summary>
/// <param name="searchMode">
/// The mode of search to invoke, i.e. any search terms may match (Any),
/// or all search terms must match (All).
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
ISearchOptionsBuilder WithSearchMode(SearchMode searchMode);

/// <summary>
/// Sets the option to include the total search results count in the search response.
/// </summary>
/// <param name="includeTotalCount">
/// The boolean value used to instruct the total count to be added to the response, or otherwise.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
ISearchOptionsBuilder WithIncludeTotalCount(bool? includeTotalCount);

/// <summary>
/// Sets the fields on which to establish the search.
/// </summary>
/// <param name="searchFields">
/// List of fields over which to specify the search.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
ISearchOptionsBuilder WithSearchFields(IList<string>? searchFields);

/// <summary>
/// Sets the facets to include in the search response.
/// </summary>
/// <param name="facets">
/// List of facets to include in the search response.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
ISearchOptionsBuilder WithFacets(IList<string>? facets);

/// <summary>
/// Sets the filters on which to establish the search.
/// </summary>
/// <param name="filters">
/// List of filters on which to establish the search.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
ISearchOptionsBuilder WithFilters(IList<FilterRequest>? filters);

/// <summary>
/// Builds the configured instance of the <see cref="SearchOptions"/> type requested.
/// </summary>
/// <returns>
/// The fully configured (built) <see cref="SearchOptions"/> instance.
/// </returns>
SearchOptions Build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using Dfe.Data.Common.Infrastructure.CognitiveSearch.Filtering;
using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.Usecase;

namespace Dfe.Data.SearchPrototype.Infrastructure.Builders
{
/// <summary>
/// Provides a concrete implementation of the <see cref="ISearchOptionsBuilder"/> abstraction,
/// which establishes a configured <see cref="SearchOptions" /> instance which conforms to the prescribed behaviour.
/// </summary>
public sealed class SearchOptionsBuilder : ISearchOptionsBuilder
{
private readonly SearchOptions _searchOptions;
private readonly ISearchFilterExpressionsBuilder _searchFilterExpressionsBuilder;

private SearchMode? _searchMode;
private int? _size;
private bool? _includeTotalCount;
private IList<string>? _searchFields;
private IList<string>? _facets;
private IList<FilterRequest>? _filters;

/// <summary>
/// The following <see cref="ISearchFilterExpressionsBuilder"/> dependency provides the
/// behaviour on which to generate fully configured, search filter string expressions based
/// on the provisioned request, the complete implementation of which is defined in the IOC container.
/// </summary>
/// <param name="searchFilterExpressionsBuilder">
/// Builds the search filter expression required by Azure AI Search
/// </param>
public SearchOptionsBuilder(ISearchFilterExpressionsBuilder searchFilterExpressionsBuilder)
{
_searchFilterExpressionsBuilder = searchFilterExpressionsBuilder;
_searchOptions = new SearchOptions();
}

/// <summary>
/// Sets the number of search items to retrieve.
/// </summary>
/// <param name="size">
/// The number of results to return as specified.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
public ISearchOptionsBuilder WithSize(int? size)
{
_size = size;
return this;
}

/// <summary>
/// Sets the mode of search to invoke, i.e. All or Any.
/// </summary>
/// <param name="searchMode">
/// The mode of search to invoke, i.e. any search terms may match (Any),
/// or all search terms must match (All).
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
public ISearchOptionsBuilder WithSearchMode(SearchMode searchMode)
{
_searchMode = searchMode;
return this;
}

/// <summary>
/// Sets the option to include the total search results count in the search response.
/// </summary>
/// <param name="includeTotalCount">
/// The boolean value used to instruct the total count to be added to the response, or otherwise.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
public ISearchOptionsBuilder WithIncludeTotalCount(bool? includeTotalCount)
{
_includeTotalCount = includeTotalCount;
return this;
}

/// <summary>
/// Sets the fields on which to establish the search.
/// </summary>
/// <param name="searchFields">
/// List of fields over which to specify the search.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
public ISearchOptionsBuilder WithSearchFields(IList<string>? searchFields)
{
_searchFields = searchFields;
return this;
}

/// <summary>
/// Sets the facets to include in the search response.
/// </summary>
/// <param name="facets">
/// List of facets to include in the search response.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
public ISearchOptionsBuilder WithFacets(IList<string>? facets)
{
_facets = facets;
return this;
}

/// <summary>
/// Sets the filters on which to establish the search.
/// </summary>
/// <param name="filters">
/// List of filters on which to establish the search.
/// </param>
/// <returns>
/// The updated builder instance.
/// </returns>
public ISearchOptionsBuilder WithFilters(IList<FilterRequest>? filters)
{
_filters = filters;
return this;
}

/// <summary>
/// Builds the configured instance of the <see cref="SearchOptions"/> type requested.
/// </summary>
/// <returns>
/// The fully configured (built) <see cref="SearchOptions"/> instance.
/// </returns>
public SearchOptions Build()
{
_searchOptions.SearchMode = _searchMode;
_searchOptions.Size = _size;
_searchOptions.IncludeTotalCount = _includeTotalCount;
_searchFields?.ToList().ForEach(_searchOptions.SearchFields.Add);
_facets?.ToList().ForEach(_searchOptions.Facets.Add);

if (_filters?.Count > 0)
{
_searchOptions.Filter =
_searchFilterExpressionsBuilder.BuildSearchFilterExpressions(
_filters.Select(filterRequest =>
new SearchFilterRequest(filterRequest.FilterName, filterRequest.FilterValues)));
}

return _searchOptions;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using Dfe.Data.Common.Infrastructure.CognitiveSearch.Filtering;
using Dfe.Data.Common.Infrastructure.CognitiveSearch.SearchByKeyword;
using Dfe.Data.SearchPrototype.Common.Mappers;
using Dfe.Data.SearchPrototype.Infrastructure.Builders;
using Dfe.Data.SearchPrototype.Infrastructure.Options;
using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters;
using Dfe.Data.SearchPrototype.SearchForEstablishments.Models;
Expand All @@ -19,10 +19,10 @@ namespace Dfe.Data.SearchPrototype.Infrastructure;
public sealed class CognitiveSearchServiceAdapter<TSearchResult> : ISearchServiceAdapter where TSearchResult : class
{
private readonly ISearchByKeywordService _searchByKeywordService;
private readonly IMapper<Pageable<AzureModels.SearchResult<TSearchResult>>, EstablishmentResults> _searchResultMapper;
private readonly IMapper<Pageable<SearchResult<TSearchResult>>, EstablishmentResults> _searchResultMapper;
private readonly IMapper<Dictionary<string, IList<AzureModels.FacetResult>>, EstablishmentFacets> _facetsMapper;
private readonly AzureSearchOptions _azureSearchOptions;
private readonly ISearchFilterExpressionsBuilder _searchFilterExpressionsBuilder;
private readonly ISearchOptionsBuilder _searchOptionsBuilder;

/// <summary>
/// The following dependencies include the core cognitive search service definition,
Expand All @@ -40,22 +40,22 @@ public sealed class CognitiveSearchServiceAdapter<TSearchResult> : ISearchServic
/// <param name="facetsMapper">
/// Maps the raw Azure search response to the required <see cref="EstablishmentFacets"/>
/// </param>
/// <param name="searchFilterExpressionsBuilder">
/// Builds the search filter expression required by Azure AI Search
/// <param name="searchOptionsBuilder">
/// Builds the search options by Azure AI Search
/// </param>
public CognitiveSearchServiceAdapter(
ISearchByKeywordService searchByKeywordService,
IOptions<AzureSearchOptions> azureSearchOptions,
IMapper<Pageable<AzureModels.SearchResult<TSearchResult>>, EstablishmentResults> searchResultMapper,
IMapper<Pageable<SearchResult<TSearchResult>>, EstablishmentResults> searchResultMapper,
IMapper<Dictionary<string, IList<AzureModels.FacetResult>>, EstablishmentFacets> facetsMapper,
ISearchFilterExpressionsBuilder searchFilterExpressionsBuilder)
ISearchOptionsBuilder searchOptionsBuilder)
{
ArgumentNullException.ThrowIfNull(azureSearchOptions.Value);
_azureSearchOptions = azureSearchOptions.Value;
_searchByKeywordService = searchByKeywordService;
_searchResultMapper = searchResultMapper;
_facetsMapper = facetsMapper;
_searchFilterExpressionsBuilder = searchFilterExpressionsBuilder;
_searchOptionsBuilder = searchOptionsBuilder;
}

/// <summary>
Expand All @@ -78,25 +78,15 @@ public CognitiveSearchServiceAdapter(
/// </exception>
public async Task<SearchResults> SearchAsync(SearchServiceAdapterRequest searchServiceAdapterRequest)
{
SearchOptions searchOptions = new()
{
SearchMode = (SearchMode)_azureSearchOptions.SearchMode,
Size = _azureSearchOptions.Size,
IncludeTotalCount = _azureSearchOptions.IncludeTotalCount,
};

searchServiceAdapterRequest.SearchFields?.ToList()
.ForEach(searchOptions.SearchFields.Add);

searchServiceAdapterRequest.Facets?.ToList()
.ForEach(searchOptions.Facets.Add);

if (searchServiceAdapterRequest.SearchFilterRequests?.Count > 0)
{
searchOptions.Filter = _searchFilterExpressionsBuilder.BuildSearchFilterExpressions(
searchServiceAdapterRequest.SearchFilterRequests
.Select(filterRequest => new SearchFilterRequest(filterRequest.FilterName, filterRequest.FilterValues)));
}
SearchOptions searchOptions =
_searchOptionsBuilder
.WithSearchMode((SearchMode)_azureSearchOptions.SearchMode)
.WithSize(_azureSearchOptions.Size)
.WithIncludeTotalCount(_azureSearchOptions.IncludeTotalCount)
.WithSearchFields(searchServiceAdapterRequest.SearchFields)
.WithFacets(searchServiceAdapterRequest.Facets)
.WithFilters(searchServiceAdapterRequest.SearchFilterRequests)
.Build();

Response<SearchResults<TSearchResult>> searchResults =
await _searchByKeywordService.SearchAsync<TSearchResult>(
Expand Down
2 changes: 2 additions & 0 deletions Dfe.Data.SearchPrototype/Infrastructure/CompositionRoot.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Azure;
using Azure.Search.Documents.Models;
using Dfe.Data.SearchPrototype.Common.Mappers;
using Dfe.Data.SearchPrototype.Infrastructure.Builders;
using Dfe.Data.SearchPrototype.Infrastructure.Mappers;
using Dfe.Data.SearchPrototype.Infrastructure.Options;
using Dfe.Data.SearchPrototype.SearchForEstablishments.ByKeyword.ServiceAdapters;
Expand Down Expand Up @@ -52,6 +53,7 @@ public static void AddCognitiveSearchAdaptorServices(this IServiceCollection ser
.Bind(settings));

services.AddScoped(typeof(ISearchServiceAdapter), typeof(CognitiveSearchServiceAdapter<DataTransferObjects.Establishment>));
services.AddScoped<ISearchOptionsBuilder, SearchOptionsBuilder>();
services.AddSingleton(typeof(IMapper<Pageable<SearchResult<DataTransferObjects.Establishment>>, EstablishmentResults>), typeof(PageableSearchResultsToEstablishmentResultsMapper));
services.AddSingleton<IMapper<Dictionary<string, IList<Azure.Search.Documents.Models.FacetResult>>, EstablishmentFacets>, AzureFacetResultToEstablishmentFacetsMapper>();
services.AddSingleton<IMapper<DataTransferObjects.Establishment, Address>, AzureSearchResultToAddressMapper>();
Expand Down
Loading