File: NugetSearch\NugetToolSearchApiRequest.cs
Web Access
Project: ..\..\..\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Collections.Specialized;
using System.Web;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
 
namespace Microsoft.DotNet.Cli.NugetSearch;
 
internal class NugetToolSearchApiRequest : INugetToolSearchApiRequest
{
    public async Task<string> GetResult(NugetSearchApiParameter nugetSearchApiParameter)
    {
        var queryUrl = await ConstructUrl(
            nugetSearchApiParameter.SearchTerm,
            nugetSearchApiParameter.Skip,
            nugetSearchApiParameter.Take,
            nugetSearchApiParameter.Prerelease);
 
        var httpClient = new HttpClient();
        using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(10));
        HttpResponseMessage response = await httpClient.GetAsync(queryUrl, cancellation.Token);
        if (!response.IsSuccessStatusCode)
        {
            if ((int)response.StatusCode >= 500 && (int)response.StatusCode < 600)
            {
                throw new NugetSearchApiRequestException(
                    string.Format(
                        CliStrings.RetriableNugetSearchFailure,
                        queryUrl.AbsoluteUri, response.ReasonPhrase, response.StatusCode));
            }
 
            throw new NugetSearchApiRequestException(
                string.Format(
                    CliStrings.NonRetriableNugetSearchFailure,
                    queryUrl.AbsoluteUri, response.ReasonPhrase, response.StatusCode));
        }
 
        return await response.Content.ReadAsStringAsync(cancellation.Token);
    }
 
    internal static async Task<Uri> ConstructUrl(string searchTerm = null, int? skip = null, int? take = null,
        bool prerelease = false, Uri domainAndPathOverride = null)
    {
        var uriBuilder = new UriBuilder(domainAndPathOverride ?? await DomainAndPath());
        NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);
        if (!string.IsNullOrWhiteSpace(searchTerm))
        {
            query["q"] = searchTerm;
        }
 
        query["packageType"] = "dotnettool";
 
        // This is a field for internal nuget back
        // compatibility should be "2.0.0" for all new API usage
        query["semVerLevel"] = "2.0.0";
 
        if (skip.HasValue)
        {
            query["skip"] = skip.Value.ToString();
        }
 
        if (take.HasValue)
        {
            query["take"] = take.Value.ToString();
        }
 
        if (prerelease)
        {
            query["prerelease"] = "true";
        }
 
        uriBuilder.Query = query.ToString();
 
        return uriBuilder.Uri;
    }
 
    // More detail on this API https://github.com/dotnet/sdk/issues/12038
    private static async Task<Uri> DomainAndPath()
    {
        var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");
        var resource = await repository.GetResourceAsync<ServiceIndexResourceV3>();
        var uris = resource.GetServiceEntryUris("SearchQueryService/3.5.0");
        return uris[0];
    }
}