|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Commands.Run.LaunchSettings;
internal class LaunchSettingsManager
{
private const string ProfilesKey = "profiles";
private const string CommandNameKey = "commandName";
private const string DefaultProfileCommandName = "Project";
private static readonly IReadOnlyDictionary<string, ILaunchSettingsProvider> _providers;
static LaunchSettingsManager()
{
_providers = new Dictionary<string, ILaunchSettingsProvider>
{
{ ProjectLaunchSettingsProvider.CommandNameValue, new ProjectLaunchSettingsProvider() }
};
}
public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSettingsPath, string? profileName = null)
{
var launchSettingsJsonContents = File.ReadAllText(launchSettingsPath);
try
{
var jsonDocumentOptions = new JsonDocumentOptions
{
CommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
using (var document = JsonDocument.Parse(launchSettingsJsonContents, jsonDocumentOptions))
{
var model = document.RootElement;
if (model.ValueKind != JsonValueKind.Object || !model.TryGetProperty(ProfilesKey, out var profilesObject) || profilesObject.ValueKind != JsonValueKind.Object)
{
return new LaunchSettingsApplyResult(false, CliCommandStrings.LaunchProfilesCollectionIsNotAJsonObject);
}
var selectedProfileName = profileName;
JsonElement profileObject;
if (string.IsNullOrEmpty(profileName))
{
var firstProfileProperty = profilesObject.EnumerateObject().FirstOrDefault(IsDefaultProfileType);
selectedProfileName = firstProfileProperty.Value.ValueKind == JsonValueKind.Object ? firstProfileProperty.Name : null;
profileObject = firstProfileProperty.Value;
}
else // Find a profile match for the given profileName
{
IEnumerable<JsonProperty> caseInsensitiveProfileMatches = [.. profilesObject
.EnumerateObject() // p.Name shouldn't fail, as profileObject enumerables here are only created from an existing JsonObject
.Where(p => string.Equals(p.Name, profileName, StringComparison.OrdinalIgnoreCase))];
if (caseInsensitiveProfileMatches.Count() > 1)
{
throw new GracefulException(CliCommandStrings.DuplicateCaseInsensitiveLaunchProfileNames,
string.Join(",\n", caseInsensitiveProfileMatches.Select(p => $"\t{p.Name}").ToArray()));
}
else if (!caseInsensitiveProfileMatches.Any())
{
return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.LaunchProfileDoesNotExist, profileName));
}
else
{
profileObject = profilesObject.GetProperty(caseInsensitiveProfileMatches.First().Name);
}
if (profileObject.ValueKind != JsonValueKind.Object)
{
return new LaunchSettingsApplyResult(false, CliCommandStrings.LaunchProfileIsNotAJsonObject);
}
}
if (profileObject.ValueKind == default)
{
foreach (var prop in profilesObject.EnumerateObject())
{
if (prop.Value.ValueKind == JsonValueKind.Object)
{
if (prop.Value.TryGetProperty(CommandNameKey, out var commandNameElement) && commandNameElement.ValueKind == JsonValueKind.String)
{
if (commandNameElement.GetString() is { } commandNameElementKey && _providers.ContainsKey(commandNameElementKey))
{
profileObject = prop.Value;
break;
}
}
}
}
}
if (profileObject.ValueKind == default)
{
return new LaunchSettingsApplyResult(false, CliCommandStrings.UsableLaunchProfileCannotBeLocated);
}
if (!profileObject.TryGetProperty(CommandNameKey, out var finalCommandNameElement)
|| finalCommandNameElement.ValueKind != JsonValueKind.String)
{
return new LaunchSettingsApplyResult(false, CliCommandStrings.UsableLaunchProfileCannotBeLocated);
}
string? commandName = finalCommandNameElement.GetString();
if (!TryLocateHandler(commandName, out ILaunchSettingsProvider? provider))
{
return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.LaunchProfileHandlerCannotBeLocated, commandName));
}
return provider.TryGetLaunchSettings(selectedProfileName, profileObject);
}
}
catch (JsonException ex)
{
return new LaunchSettingsApplyResult(false, string.Format(CliCommandStrings.DeserializationExceptionMessage, launchSettingsPath, ex.Message));
}
}
private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)]out ILaunchSettingsProvider? provider)
{
if (commandName == null)
{
provider = null;
return false;
}
return _providers.TryGetValue(commandName, out provider);
}
private static bool IsDefaultProfileType(JsonProperty profileProperty)
{
if (profileProperty.Value.ValueKind != JsonValueKind.Object
|| !profileProperty.Value.TryGetProperty(CommandNameKey, out var commandNameElement)
|| commandNameElement.ValueKind != JsonValueKind.String)
{
return false;
}
return string.Equals(commandNameElement.GetString(), DefaultProfileCommandName, StringComparison.Ordinal);
}
}
|