File: ListPackage\ListPackageJsonRenderer.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.CommandLine.XPlat\NuGet.CommandLine.XPlat.csproj (NuGet.CommandLine.XPlat)
// 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.IO;
using System.Linq;
using Newtonsoft.Json;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Protocol;
using NuGet.Versioning;

namespace NuGet.CommandLine.XPlat.ListPackage
{
    /// <summary>
    /// json format renderer for dotnet list package command
    /// </summary>
    internal class ListPackageJsonRenderer : IReportRenderer
    {
        private const int ReportOutputVersion = 1;
        private const string VersionProperty = "version";
        private const string ParametersProperty = "parameters";
        private const string ProblemsProperty = "problems";
        private const string SourcesProperty = "sources";
        private const string ProjectProperty = "project";
        private const string ProjectsProperty = "projects";
        private const string FrameworksProperty = "frameworks";
        private const string FrameworkProperty = "framework";
        private const string PathProperty = "path";
        private const string TopLevelPackagesProperty = "topLevelPackages";
        private const string TransitivePackagesProperty = "transitivePackages";
        private const string IdProperty = "id";
        private const string RequestedVersionProperty = "requestedVersion";
        private const string ResolvedVersionProperty = "resolvedVersion";
        private const string AutoReferencedProperty = "autoReferenced";
        private const string SeverityProperty = "severity";
        private const string AdvisoryUrlProperty = "advisoryurl";
        private const string VulnerabilitiesProperty = "vulnerabilities";
        private const string LatestVersionProperty = "latestVersion";
        private const string DeprecationReasonsProperty = "deprecationReasons";
        private const string AlternativePackageProperty = "alternativePackage";
        private const string VersionRangeProperty = "versionRange";
        private const string LevelProperty = "level";
        private const string WarningProperty = "warning";
        private const string TextProperty = "text";
        private const string ErrorProperty = "error";

        protected readonly List<ReportProblem> _problems = new();
        protected TextWriter _writer;

        public ListPackageJsonRenderer(TextWriter textWriter = null)
        {
            _writer = textWriter ?? Console.Out;
        }

        public void AddProblem(ProblemType problemType, string text)
        {
            _problems.Add(new ReportProblem(problemType, string.Empty, text));
        }

        public IEnumerable<ReportProblem> GetProblems()
        {
            return _problems;
        }

        public void Render(ListPackageReportModel listPackageReportModel)
        {
            // Aggregate problems from projects.
            _problems.AddRange(listPackageReportModel.Projects.Where(p => p.ProjectProblems != null).SelectMany(p => p.ProjectProblems));
            var jsonRenderedOutput = WriteJson(listPackageReportModel);
            _writer.WriteLine(jsonRenderedOutput);
        }

        internal string WriteJson(ListPackageReportModel listPackageReportModel)
        {
            using (var writer = new StringWriter())
            {
                using (var jsonWriter = new JsonTextWriter(writer))
                {
                    jsonWriter.Formatting = Formatting.Indented;
                    WriteJson(jsonWriter, listPackageReportModel);
                }

                return writer.ToString();
            }
        }

        private void WriteJson(JsonWriter writer, ListPackageReportModel listPackageReportModel)
        {
            ListPackageArgs listPackageArgs = listPackageReportModel.ListPackageArgs;
            writer.WriteStartObject();

            writer.WritePropertyName(VersionProperty);
            writer.WriteValue(ReportOutputVersion);

            writer.WritePropertyName(ParametersProperty);
            writer.WriteValue(PathUtility.GetPathWithForwardSlashes(listPackageArgs.ArgumentText));

            if (_problems?.Count > 0)
            {
                WriteProblems(writer, _problems);
            }

            WriteSources(writer, listPackageReportModel);
            WriteProjects(writer, listPackageReportModel.Projects, listPackageReportModel.ListPackageArgs);
            writer.WriteEndObject();
        }

        private static void WriteProblems(JsonWriter writer, IEnumerable<ReportProblem> reportProblems)
        {
            writer.WritePropertyName(ProblemsProperty);
            writer.WriteStartArray();

            foreach (ReportProblem reportProblem in reportProblems)
            {
                writer.WriteStartObject();

                if (!string.IsNullOrEmpty(reportProblem.Project))
                {
                    writer.WritePropertyName(ProjectProperty);
                    writer.WriteValue(PathUtility.GetPathWithForwardSlashes(reportProblem.Project));
                }

                writer.WritePropertyName(LevelProperty);
                writer.WriteValue(reportProblem.ProblemType == ProblemType.Error ? ErrorProperty : WarningProperty);
                writer.WritePropertyName(TextProperty);
                writer.WriteValue(PathUtility.GetPathWithForwardSlashes(reportProblem.Text));
                writer.WriteEndObject();
            }

            writer.WriteEndArray();
        }

        private static void WriteSources(JsonWriter writer, ListPackageReportModel listPackageReportModel)
        {
            if (listPackageReportModel.ListPackageArgs.ReportType == ReportType.Default)
            {
                // generic list is offline.
                return;
            }

            writer.WritePropertyName(SourcesProperty);
            writer.WriteStartArray();

            if (listPackageReportModel.ListPackageArgs.ReportType == ReportType.Vulnerable && listPackageReportModel.AuditSourcesUsed.Count > 0)
            {
                foreach (PackageSource packageSource in listPackageReportModel.AuditSourcesUsed)
                {
                    writer.WriteValue(PathUtility.GetPathWithForwardSlashes(packageSource.Source));
                }
            }
            else
            {
                foreach (PackageSource packageSource in listPackageReportModel.ListPackageArgs.PackageSources)
                {
                    writer.WriteValue(PathUtility.GetPathWithForwardSlashes(packageSource.Source));
                }
            }

            writer.WriteEndArray();
        }

        private static void WriteProjects(JsonWriter writer, List<ListPackageProjectModel> reportProjects, ListPackageArgs listPackageArgs)
        {
            writer.WritePropertyName(ProjectsProperty);
            writer.WriteStartArray();

            foreach (ListPackageProjectModel reportProject in reportProjects)
            {
                writer.WriteStartObject();

                writer.WritePropertyName(PathProperty);
                writer.WriteValue(PathUtility.GetPathWithForwardSlashes(reportProject.ProjectPath));

                if (reportProject.TargetFrameworkPackages?.Count > 0)
                {
                    writer.WritePropertyName(FrameworksProperty);

                    WriteFrameworkPackage(writer, reportProject.TargetFrameworkPackages, listPackageArgs);

                }

                writer.WriteEndObject();
            }

            writer.WriteEndArray();
        }

        private static void WriteFrameworkPackage(JsonWriter writer, List<ListPackageReportFrameworkPackage> reportFrameworkPackages, ListPackageArgs listPackageArgs)
        {
            writer.WriteStartArray();

            foreach (ListPackageReportFrameworkPackage reportFrameworkPackage in reportFrameworkPackages)
            {
                writer.WriteStartObject();
                writer.WritePropertyName(FrameworkProperty);
                writer.WriteValue(reportFrameworkPackage.TargetAlias);
                WriteTopLevelPackages(writer, TopLevelPackagesProperty, reportFrameworkPackage.TopLevelPackages, listPackageArgs);
                WriteTransitivePackages(writer, TransitivePackagesProperty, reportFrameworkPackage.TransitivePackages, listPackageArgs);
                writer.WriteEndObject();
            }

            writer.WriteEndArray();
        }

        private static void WriteTopLevelPackages(JsonWriter writer, string property, List<ListReportPackage> topLevelPackages, ListPackageArgs listPackageArgs)
        {
            if (topLevelPackages != null)
            {
                writer.WritePropertyName(property);

                writer.WriteStartArray();
                foreach (ListReportPackage topLevelPackage in topLevelPackages)
                {
                    writer.WriteStartObject();
                    writer.WritePropertyName(IdProperty);
                    writer.WriteValue(topLevelPackage.PackageId);

                    writer.WritePropertyName(RequestedVersionProperty);
                    writer.WriteValue(topLevelPackage.RequestedVersion);

                    writer.WritePropertyName(ResolvedVersionProperty);
                    writer.WriteValue(topLevelPackage.ResolvedVersion);

                    if (topLevelPackage.AutoReference)
                    {
                        writer.WritePropertyName(AutoReferencedProperty);
                        writer.WriteValue("true");
                    }

                    switch (listPackageArgs.ReportType)
                    {
                        case ReportType.Outdated:
                            writer.WritePropertyName(LatestVersionProperty);
                            writer.WriteValue(topLevelPackage.LatestVersion);
                            break;
                        case ReportType.Deprecated:
                            WriteDeprecations(writer, topLevelPackage);
                            break;
                        case ReportType.Vulnerable:
                            WriteVulnerabilities(writer, topLevelPackage.Vulnerabilities);
                            break;
                        default:
                            break;
                    }

                    writer.WriteEndObject();
                }
                writer.WriteEndArray();
            }
        }

        private static void WriteTransitivePackages(JsonWriter writer, string property, List<ListReportPackage> transitivePackages, ListPackageArgs listPackageArgs)
        {
            if (!listPackageArgs.IncludeTransitive)
            {
                return;
            }

            if (transitivePackages?.Count > 0)
            {
                writer.WritePropertyName(property);

                writer.WriteStartArray();
                foreach (ListReportPackage transitivePackage in transitivePackages)
                {
                    writer.WriteStartObject();
                    writer.WritePropertyName(IdProperty);
                    writer.WriteValue(transitivePackage.PackageId);

                    writer.WritePropertyName(ResolvedVersionProperty);
                    writer.WriteValue(transitivePackage.ResolvedVersion);

                    switch (listPackageArgs.ReportType)
                    {
                        case ReportType.Outdated:
                            writer.WritePropertyName(LatestVersionProperty);
                            writer.WriteValue(transitivePackage.LatestVersion);
                            break;
                        case ReportType.Deprecated:
                            WriteDeprecations(writer, transitivePackage);
                            break;
                        case ReportType.Vulnerable:
                            WriteVulnerabilities(writer, transitivePackage.Vulnerabilities);
                            break;
                        default:
                            break;
                    }


                    writer.WriteEndObject();
                }
                writer.WriteEndArray();
            }
        }

        private static void WriteDeprecations(JsonWriter writer, ListReportPackage listPackage)
        {
            if (listPackage.DeprecationReasons != null)
            {
                writer.WritePropertyName(DeprecationReasonsProperty);
                writer.WriteStartArray();

                foreach (var deprecationReason in listPackage.DeprecationReasons?.Reasons)
                {
                    writer.WriteValue(deprecationReason);
                }

                writer.WriteEndArray();
            }

            if (listPackage.AlternativePackage != null)
            {
                writer.WritePropertyName(AlternativePackageProperty);
                writer.WriteStartObject();
                writer.WritePropertyName(IdProperty);
                writer.WriteValue(listPackage.AlternativePackage.PackageId);
                writer.WritePropertyName(VersionRangeProperty);

                var versionRangeString = VersionRangeFormatter.Instance.Format(
                    "p",
                    listPackage.AlternativePackage.Range,
                    VersionRangeFormatter.Instance);

                writer.WriteValue(versionRangeString);
                writer.WriteEndObject();
            }
        }

        private static void WriteVulnerabilities(JsonWriter writer, List<PackageVulnerabilityMetadata> vulnerabilities)
        {
            if (vulnerabilities == null)
            {
                return;
            }

            writer.WritePropertyName(VulnerabilitiesProperty);
            writer.WriteStartArray();

            foreach (PackageVulnerabilityMetadata vulnerability in vulnerabilities)
            {
                writer.WriteStartObject();
                var severity = (vulnerability?.Severity ?? -1) switch
                {
                    0 => "Low",
                    1 => "Moderate",
                    2 => "High",
                    3 => "Critical",
                    _ => string.Empty,
                };

                writer.WritePropertyName(SeverityProperty);
                writer.WriteValue(severity);

                writer.WritePropertyName(AdvisoryUrlProperty);
                writer.WriteValue(vulnerability.AdvisoryUrl);
                writer.WriteEndObject();
            }

            writer.WriteEndArray();
        }
    }
}