File: Backchannel\ResourceSnapshotMapper.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.Tool.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Dashboard.Utils;
using Aspire.Shared.Model;
using Aspire.Shared.Model.Serialization;
 
namespace Aspire.Cli.Backchannel;
 
/// <summary>
/// Maps <see cref="ResourceSnapshot"/> to <see cref="ResourceJson"/> for serialization.
/// </summary>
internal static class ResourceSnapshotMapper
{
    /// <summary>
    /// Maps a list of <see cref="ResourceSnapshot"/> to a list of <see cref="ResourceJson"/>.
    /// </summary>
    /// <param name="snapshots">The resource snapshots to map.</param>
    /// <param name="dashboardBaseUrl">Optional base URL of the Aspire Dashboard for generating resource URLs.</param>
    public static List<ResourceJson> MapToResourceJsonList(IEnumerable<ResourceSnapshot> snapshots, string? dashboardBaseUrl = null)
    {
        var snapshotList = snapshots.ToList();
        return snapshotList.Select(s => MapToResourceJson(s, snapshotList, dashboardBaseUrl)).ToList();
    }
 
    /// <summary>
    /// Maps a <see cref="ResourceSnapshot"/> to <see cref="ResourceJson"/>.
    /// </summary>
    /// <param name="snapshot">The resource snapshot to map.</param>
    /// <param name="allSnapshots">All resource snapshots for resolving relationships.</param>
    /// <param name="dashboardBaseUrl">Optional base URL of the Aspire Dashboard for generating resource URLs.</param>
    public static ResourceJson MapToResourceJson(ResourceSnapshot snapshot, IReadOnlyList<ResourceSnapshot> allSnapshots, string? dashboardBaseUrl = null)
    {
        var urls = snapshot.Urls
            .Select(u => new ResourceUrlJson
            {
                Name = u.Name,
                DisplayName = u.DisplayProperties?.DisplayName,
                Url = u.Url,
                IsInternal = u.IsInternal
            })
            .ToArray();
 
        var volumes = snapshot.Volumes
            .Select(v => new ResourceVolumeJson
            {
                Source = v.Source,
                Target = v.Target,
                MountType = v.MountType,
                IsReadOnly = v.IsReadOnly
            })
            .ToArray();
 
        var healthReports = snapshot.HealthReports
            .Select(h => new ResourceHealthReportJson
            {
                Name = h.Name,
                Status = h.Status,
                Description = h.Description,
                ExceptionMessage = h.ExceptionText
            })
            .ToArray();
 
        var environment = snapshot.EnvironmentVariables
            .Where(e => e.IsFromSpec)
            .Select(e => new ResourceEnvironmentVariableJson
            {
                Name = e.Name,
                Value = e.Value
            })
            .ToArray();
 
        var properties = snapshot.Properties
            .Select(p => new ResourcePropertyJson
            {
                Name = p.Key,
                Value = p.Value
            })
            .ToArray();
 
        // Build relationships by matching DisplayName
        var relationships = new List<ResourceRelationshipJson>();
        foreach (var relationship in snapshot.Relationships)
        {
            var matches = allSnapshots
                .Where(r => string.Equals(r.DisplayName, relationship.ResourceName, StringComparisons.ResourceName))
                .ToList();
 
            foreach (var match in matches)
            {
                relationships.Add(new ResourceRelationshipJson
                {
                    Type = relationship.Type,
                    ResourceName = match.Name
                });
            }
        }
 
        // Only include enabled commands
        var commands = snapshot.Commands
            .Where(c => string.Equals(c.State, "Enabled", StringComparison.OrdinalIgnoreCase))
            .Select(c => new ResourceCommandJson
            {
                Name = c.Name,
                Description = c.Description
            })
            .ToArray();
 
        // Get source information using the shared ResourceSourceViewModel
        var sourceViewModel = ResourceSource.GetSourceModel(snapshot.ResourceType, snapshot.Properties);
 
        // Generate dashboard URL for this resource if a base URL is provided
        string? dashboardUrl = null;
        if (!string.IsNullOrEmpty(dashboardBaseUrl))
        {
            var resourcePath = DashboardUrls.ResourcesUrl(snapshot.Name);
            dashboardUrl = DashboardUrls.CombineUrl(dashboardBaseUrl, resourcePath);
        }
 
        return new ResourceJson
        {
            Name = snapshot.Name,
            DisplayName = snapshot.DisplayName,
            ResourceType = snapshot.ResourceType,
            State = snapshot.State,
            StateStyle = snapshot.StateStyle,
            HealthStatus = snapshot.HealthStatus,
            Source = sourceViewModel?.Value,
            ExitCode = snapshot.ExitCode,
            CreationTimestamp = snapshot.CreatedAt,
            StartTimestamp = snapshot.StartedAt,
            StopTimestamp = snapshot.StoppedAt,
            DashboardUrl = dashboardUrl,
            Urls = urls,
            Volumes = volumes,
            Environment = environment,
            HealthReports = healthReports,
            Properties = properties,
            Relationships = relationships.ToArray(),
            Commands = commands
        };
    }
 
    /// <summary>
    /// Gets the display name for a resource, returning the unique name if there are multiple resources
    /// with the same display name (replicas).
    /// </summary>
    /// <param name="resource">The resource to get the name for.</param>
    /// <param name="allResources">All resources to check for duplicates.</param>
    /// <returns>The display name if unique, otherwise the unique resource name.</returns>
    public static string GetResourceName(ResourceSnapshot resource, IDictionary<string, ResourceSnapshot> allResources)
    {
        return GetResourceName(resource, allResources.Values);
    }
 
    /// <summary>
    /// Gets the display name for a resource, returning the unique name if there are multiple resources
    /// with the same display name (replicas).
    /// </summary>
    /// <param name="resource">The resource to get the name for.</param>
    /// <param name="allResources">All resources to check for duplicates.</param>
    /// <returns>The display name if unique, otherwise the unique resource name.</returns>
    public static string GetResourceName(ResourceSnapshot resource, IEnumerable<ResourceSnapshot> allResources)
    {
        var count = 0;
        foreach (var item in allResources)
        {
            // Skip hidden resources
            if (string.Equals(item.State, "Hidden", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }
 
            if (string.Equals(item.DisplayName, resource.DisplayName, StringComparisons.ResourceName))
            {
                count++;
                if (count >= 2)
                {
                    // There are multiple resources with the same display name so they're part of a replica set.
                    // Need to use the name which has a unique ID to tell them apart.
                    return resource.Name;
                }
            }
        }
 
        return resource.DisplayName ?? resource.Name;
    }
}