Skip to content

Commit

Permalink
Refactor BaGet (loic-sharma#261)
Browse files Browse the repository at this point in the history
1. Moves business logic out of controllers and into `BaGet.Core`. For example, the `PackageContentController` passes requests to `IBaGetPackageContentService` and maps results to HTTP responses. In effect, controllers are just translate HTTP translators.
2. BaGet's services for the NuGet protocol implement the same interfaces as BaGet's client libraries for the NuGet protocol. For example, `AzureSearchService` and `SearchClient` both implement `ISearchService`
3. Requests and responses are codified by models in `BaGet.Protocol`. For example, both `SearchController.SearchAsync` and `SearchClient.SearchAsync` return a `SearchResponse`
  • Loading branch information
loic-sharma authored Aug 14, 2019
1 parent 4d627fa commit 3ae40a3
Show file tree
Hide file tree
Showing 90 changed files with 2,108 additions and 1,253 deletions.
188 changes: 110 additions & 78 deletions src/BaGet.Azure/Search/AzureSearchService.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BaGet.Core.Entities;
using BaGet.Core.Indexing;
using BaGet.Core.Search;
using BaGet.Core.ServiceIndex;
using BaGet.Protocol;
using Microsoft.Azure.Search;
using NuGet.Versioning;

Expand All @@ -13,58 +16,53 @@ namespace BaGet.Azure.Search
using QueryType = Microsoft.Azure.Search.Models.QueryType;
using SearchParameters = Microsoft.Azure.Search.Models.SearchParameters;

public class AzureSearchService : ISearchService
public class AzureSearchService : IBaGetSearchService
{
private readonly BatchIndexer _indexer;
private readonly SearchIndexClient _searchClient;
private readonly IBaGetUrlGenerator _url;
private readonly IFrameworkCompatibilityService _frameworks;

public AzureSearchService(
BatchIndexer indexer,
SearchIndexClient searchClient,
IBaGetUrlGenerator url,
IFrameworkCompatibilityService frameworks)
{
_indexer = indexer ?? throw new ArgumentNullException(nameof(indexer));
_searchClient = searchClient ?? throw new ArgumentNullException(nameof(searchClient));
_url = url ?? throw new ArgumentNullException(nameof(url));
_frameworks = frameworks ?? throw new ArgumentNullException(nameof(frameworks));
}

public Task IndexAsync(Package package) => _indexer.IndexAsync(package.Id);

public async Task<IReadOnlyList<SearchResult>> SearchAsync(
string query,
int skip = 0,
int take = 20,
bool includePrerelease = true,
bool includeSemVer2 = true,
string packageType = null,
string framework = null)
public async Task IndexAsync(Package package, CancellationToken cancellationToken)
{
query = BuildSeachQuery(query, packageType, framework);

var searchFilters = SearchFilters.Default;

if (includePrerelease)
{
searchFilters |= SearchFilters.IncludePrerelease;
}
await _indexer.IndexAsync(package.Id);
}

if (includeSemVer2)
{
searchFilters |= SearchFilters.IncludeSemVer2;
}
public async Task<SearchResponse> SearchAsync(SearchRequest request, CancellationToken cancellationToken = default)
{
return await SearchAsync(BaGetSearchRequest.FromSearchRequest(request), cancellationToken);
}

var search = await _searchClient.Documents.SearchAsync<PackageDocument>(query, new SearchParameters
public async Task<SearchResponse> SearchAsync(BaGetSearchRequest request, CancellationToken cancellationToken = default)
{
var query = BuildSeachQuery(request);
var filter = BuildSearchFilter(request);
var parameters = new SearchParameters
{
IncludeTotalResultCount = true,
QueryType = QueryType.Full,
Skip = skip,
Top = take,
Filter = $"searchFilters eq '{searchFilters}'"
});
Skip = request.Skip,
Top = request.Take,
Filter = filter
};

var response = await _searchClient.Documents.SearchAsync<PackageDocument>(query, parameters, cancellationToken: cancellationToken);

var results = new List<SearchResult>();

foreach (var result in search.Results)
foreach (var result in response.Results)
{
var document = result.Document;
var versions = new List<SearchResultVersion>();
Expand All @@ -76,81 +74,115 @@ public async Task<IReadOnlyList<SearchResult>> SearchAsync(

for (var i = 0; i < document.Versions.Length; i++)
{
versions.Add(new SearchResultVersion(
NuGetVersion.Parse(document.Versions[i]),
long.Parse(document.VersionDownloads[i])));
var downloads = long.Parse(document.VersionDownloads[i]);
var version = NuGetVersion.Parse(document.Versions[i]);
var url = _url.GetRegistrationLeafUrl(document.Id, version);

versions.Add(new SearchResultVersion(url, version, downloads));
}

results.Add(new SearchResult
{
Id = document.Id,
Version = NuGetVersion.Parse(document.Version),
Description = document.Description,
Authors = document.Authors,
IconUrl = document.IconUrl,
LicenseUrl = document.LicenseUrl,
Summary = document.Summary,
Tags = document.Tags,
Title = document.Title,
TotalDownloads = document.TotalDownloads,
Versions = versions.AsReadOnly()
});
results.Add(new SearchResult(
document.Id,
NuGetVersion.Parse(document.Version),
document.Description,
document.Authors,
document.IconUrl,
document.LicenseUrl,
document.ProjectUrl,
_url.GetRegistrationIndexUrl(document.Id),
document.Summary,
document.Tags,
document.Title,
document.TotalDownloads,
versions));
}

return results.AsReadOnly();
return new SearchResponse(
response.Count.Value,
results,
SearchContext.Default(_url.GetPackageMetadataResourceUrl()));
}

public async Task<AutocompleteResponse> AutocompleteAsync(AutocompleteRequest request, CancellationToken cancellationToken = default)
{
// TODO: Do a prefix search on the package id field.
// TODO: Support versions autocomplete.
// See: https://github.com/loic-sharma/BaGet/issues/291
var parameters = new SearchParameters
{
IncludeTotalResultCount = true,
Skip = request.Skip,
Top = request.Take,
};

var response = await _searchClient.Documents.SearchAsync<PackageDocument>(request.Query, parameters, cancellationToken: cancellationToken);
var results = response.Results
.Select(r => r.Document.Id)
.ToList()
.AsReadOnly();

return new AutocompleteResponse(response.Count.Value, results, AutocompleteContext.Default);
}

public async Task<DependentsResponse> FindDependentsAsync(DependentsRequest request, CancellationToken cancellationToken = default)
{
// TODO: Escape packageId.
var query = $"dependencies:{request.PackageId.ToLowerInvariant()}";
var parameters = new SearchParameters
{
IncludeTotalResultCount = true,
QueryType = QueryType.Full,
Skip = request.Skip,
Top = request.Take,
};

var response = await _searchClient.Documents.SearchAsync<PackageDocument>(query, parameters, cancellationToken: cancellationToken);
var results = response.Results
.Select(r => r.Document.Id)
.ToList()
.AsReadOnly();

return new DependentsResponse(response.Count.Value, results);
}

private string BuildSeachQuery(string query, string packageType, string framework)
private string BuildSeachQuery(BaGetSearchRequest request)
{
if (!string.IsNullOrEmpty(query))
var query = string.Empty;
if (!string.IsNullOrEmpty(request.Query))
{
query = query.TrimEnd().TrimEnd('*') + '*';
query = request.Query.TrimEnd().TrimEnd('*') + '*';
}

if (!string.IsNullOrEmpty(packageType))
if (!string.IsNullOrEmpty(request.PackageType))
{
query = $"+packageTypes:{packageType} {query}";
query = $"+packageTypes:{request.PackageType} {query}";
}

if (!string.IsNullOrEmpty(framework))
if (!string.IsNullOrEmpty(request.Framework))
{
var frameworks = _frameworks.FindAllCompatibleFrameworks(framework);
var frameworks = _frameworks.FindAllCompatibleFrameworks(request.Framework);

query = $"+frameworks:({string.Join(" ", frameworks)}) {query}";
}

return query;
}

public async Task<IReadOnlyList<string>> AutocompleteAsync(string query, int skip = 0, int take = 20)
private string BuildSearchFilter(BaGetSearchRequest request)
{
var search = await _searchClient.Documents.SearchAsync<PackageDocument>(query, new SearchParameters
{
Skip = skip,
Top = take,
});
var searchFilters = SearchFilters.Default;

return search.Results
.Select(r => r.Document.Id)
.ToList()
.AsReadOnly();
}
if (request.IncludePrerelease)
{
searchFilters |= SearchFilters.IncludePrerelease;
}

public async Task<IReadOnlyList<string>> FindDependentsAsync(string packageId, int skip = 0, int take = 20)
{
// TODO: Escape packageId.
var query = $"dependencies:{packageId.ToLowerInvariant()}";
var search = await _searchClient.Documents.SearchAsync<PackageDocument>(query, new SearchParameters
if (request.IncludeSemVer2)
{
QueryType = QueryType.Full,
Skip = skip,
Top = take,
});
searchFilters |= SearchFilters.IncludeSemVer2;
}

return search.Results
.Select(r => r.Document.Id)
.ToList()
.AsReadOnly();
return $"searchFilters eq '{searchFilters}'";
}
}
}
2 changes: 1 addition & 1 deletion src/BaGet.Azure/Search/BatchIndexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Text;
using System.Threading.Tasks;
using BaGet.Core.Entities;
using BaGet.Core.State;
using BaGet.Core.Metadata;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Microsoft.Extensions.Logging;
Expand Down
Loading

0 comments on commit 3ae40a3

Please sign in to comment.