File: Resources\DependencyInfoResourceV3.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Protocol\NuGet.Protocol.csproj (NuGet.Protocol)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Frameworks;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;

namespace NuGet.Protocol
{
    /// <summary>
    /// Retrieves all packages and dependencies from a V3 source.
    /// </summary>
    public sealed class DependencyInfoResourceV3 : DependencyInfoResource
    {
        private readonly HttpSource _client;
        private readonly RegistrationResourceV3 _regResource;
        private readonly SourceRepository _source;

        /// <summary>
        /// Dependency info resource
        /// </summary>
        /// <param name="client">Http client</param>
        /// <param name="regResource">Registration blob resource</param>
        public DependencyInfoResourceV3(HttpSource client, RegistrationResourceV3 regResource, SourceRepository source)
        {
            if (client == null)
            {
                throw new ArgumentNullException(nameof(client));
            }

            if (regResource == null)
            {
                throw new ArgumentNullException(nameof(regResource));
            }

            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            _client = client;
            _regResource = regResource;
            _source = source;
        }

        /// <summary>
        /// Retrieve dependency info for a single package.
        /// </summary>
        /// <param name="package">package id and version</param>
        /// <param name="projectFramework">project target framework. This is used for finding the dependency group</param>
        /// <param name="token">cancellation token</param>
        /// <returns>
        /// Returns dependency info for the given package if it exists. If the package is not found null is
        /// returned.
        /// </returns>
        public override async Task<SourcePackageDependencyInfo> ResolvePackage(PackageIdentity package, NuGetFramework projectFramework, SourceCacheContext cacheContext, Common.ILogger log, CancellationToken token)
        {
            try
            {
                SourcePackageDependencyInfo result = null;

                // Construct the registration index url
                var uri = _regResource.GetUri(package.Id);

                // Retrieve the registration blob
                var singleVersion = new VersionRange(minVersion: package.Version, includeMinVersion: true, maxVersion: package.Version, includeMaxVersion: true);
                var regInfo = await ResolverMetadataClient.GetRegistrationInfo(_client, uri, package.Id, singleVersion, cacheContext, projectFramework, log, token);

                // regInfo is null if the package does not exist
                if (regInfo != null)
                {
                    result = GetPackagesFromRegistration(regInfo, token).FirstOrDefault();
                }

                return result;
            }
            catch (Exception ex)
            {
                // Wrap exceptions coming from the server with a user friendly message
                var error = String.Format(CultureInfo.CurrentCulture, Strings.Protocol_PackageMetadataError, package, _source);

                throw new FatalProtocolException(error, ex);
            }
        }

        /// <summary>
        /// Retrieve the available packages and their dependencies.
        /// </summary>
        /// <remarks>Includes prerelease packages</remarks>
        /// <param name="packageId">package Id to search</param>
        /// <param name="projectFramework">project target framework. This is used for finding the dependency group</param>
        /// <param name="token">cancellation token</param>
        /// <returns>available packages and their dependencies</returns>
        public override async Task<IEnumerable<SourcePackageDependencyInfo>> ResolvePackages(string packageId, NuGetFramework projectFramework, SourceCacheContext cacheContext, Common.ILogger log, CancellationToken token)
        {
            try
            {
                var results = new List<SourcePackageDependencyInfo>();

                // Construct the registration index url
                var uri = _regResource.GetUri(packageId);

                // Retrieve the registration blob
                var regInfo = await ResolverMetadataClient.GetRegistrationInfo(_client, uri, packageId, VersionRange.All, cacheContext, projectFramework, log, token);

                // regInfo is null if the package does not exist
                if (regInfo != null)
                {
                    var packages = GetPackagesFromRegistration(regInfo, token);
                    results.AddRange(packages);
                }

                return results;
            }
            catch (Exception ex)
            {
                // Wrap exceptions coming from the server with a user friendly message
                var error = string.Format(CultureInfo.CurrentCulture, Strings.Protocol_PackageMetadataError, packageId, _source);

                throw new FatalProtocolException(error, ex);
            }
        }

        /// <summary>
        /// Retrieve the available packages and their dependencies.
        /// </summary>
        /// <remarks>Includes prerelease packages</remarks>
        /// <param name="packageId">package Id to search</param>
        /// <param name="token">cancellation token</param>
        /// <returns>available packages and their dependencies</returns>
        public override Task<IEnumerable<RemoteSourceDependencyInfo>> ResolvePackages(string packageId, SourceCacheContext cacheContext, Common.ILogger log, CancellationToken token)
        {
            try
            {
                // Construct the registration index url
                var uri = _regResource.GetUri(packageId);

                // Retrieve the registration blob
                return ResolverMetadataClient.GetDependencies(_client, uri, packageId, VersionRange.All, cacheContext, log, token);
            }
            catch (Exception ex)
            {
                // Wrap exceptions coming from the server with a user friendly message
                var error = string.Format(CultureInfo.CurrentCulture, Strings.Protocol_PackageMetadataError, packageId, _source);

                throw new FatalProtocolException(error, ex);
            }
        }

        /// <summary>
        /// Retrieve dependency info from a registration blob
        /// </summary>
        private IEnumerable<SourcePackageDependencyInfo> GetPackagesFromRegistration(
            RegistrationInfo registration,
            CancellationToken token)
        {
            token.ThrowIfCancellationRequested();

            foreach (var pkgInfo in registration.Packages)
            {
                var dependencies = pkgInfo.Dependencies.Select(dep => new PackageDependency(dep.Id, dep.Range));

                yield return new SourcePackageDependencyInfo(
                    registration.Id,
                    pkgInfo.Version,
                    dependencies,
                    pkgInfo.Listed,
                    _source,
                    pkgInfo.PackageContent,
                    packageHash: null);
            }

            yield break;
        }
    }
}