File: Constraints\ConstraintsExtensions.cs
Web Access
Project: src\src\sdk\src\TemplateEngine\Microsoft.TemplateEngine.Edge\Microsoft.TemplateEngine.Edge.csproj (Microsoft.TemplateEngine.Edge)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.TemplateEngine.Utils;

namespace Microsoft.TemplateEngine.Edge.Constraints
{
    internal static class Extensions
    {
        /// <summary>
        /// Attempts to parse input configuration string (presumably string or json array of strings) into enumeration of strings.
        /// </summary>
        /// <param name="args">Input configuration string.</param>
        /// <returns>Enumeration of parsed tokens.</returns>
        /// <exception cref="ConfigurationException">Thrown on unexpected input - not a valid json string or array of string or an empty array.</exception>
        public static IEnumerable<string> ParseArrayOfConstraintStrings(this string? args)
        {
            JsonNode token = ParseConstraintJsonNode(args);

            if (token.GetValueKind() == JsonValueKind.String)
            {
                return new[] { token.GetValue<string>() ?? throw new ConfigurationException(string.Format(LocalizableStrings.Constraint_Error_ArgumentHasEmptyString, args)) };
            }

            JsonArray array = token.ToConstraintsJsonArray(args, true);

            return array.Select(value =>
            {
                if (value == null || value.GetValueKind() != JsonValueKind.String)
                {
                    throw new ConfigurationException(string.Format(LocalizableStrings.Constraint_Error_ArgumentHasEmptyString, args));
                }

                string? strValue = value.GetValue<string>();
                if (string.IsNullOrEmpty(strValue))
                {
                    throw new ConfigurationException(string.Format(LocalizableStrings.Constraint_Error_ArgumentHasEmptyString, args));
                }

                return strValue!;
            });
        }

        /// <summary>
        /// Attempts to parse input configuration string (presumably string or json array of strings) into enumeration of JObjects.
        /// </summary>
        /// <param name="args">Input configuration string.</param>
        /// <returns>Enumeration of parsed JObject tokens.</returns>
        /// <exception cref="ConfigurationException">Thrown on unexpected input - not a valid json array or an empty array.</exception>
        public static IEnumerable<JsonObject> ParseArrayOfConstraintJObjects(this string? args)
        {
            JsonNode token = ParseConstraintJsonNode(args);
            JsonArray array = token.ToConstraintsJsonArray(args, false);

            return array.Select(value =>
            {
                if (value is not JsonObject jObj)
                {
                    throw new ConfigurationException(string.Format(LocalizableStrings.Constraint_Error_InvalidJsonArray_Objects, args));
                }

                return jObj;
            });
        }

        /// <summary>
        /// Attempts to parse given string and return the version specification (throws <see cref="ConfigurationException"/> if unsuccessful).
        /// checks version in the following order:
        /// NuGet exact version
        /// NuGet floating version
        /// NuGet version range
        /// Legacy template engine exact version
        /// Legacy template engine version range.
        /// </summary>
        /// <param name="versionString">Version string to be parsed.</param>
        /// <returns>IVersionSpecification instance representing the given string representation of the version.</returns>
        /// <exception cref="ConfigurationException">Thrown if given string is not recognized as any valid version format.</exception>
        public static IVersionSpecification ParseVersionSpecification(this string versionString)
        {
            IVersionSpecification? versionInstance = null;

            if (NuGetVersionSpecification.TryParse(versionString, out NuGetVersionSpecification? exactNuGetVersion))
            {
                versionInstance = exactNuGetVersion;
            }
            else if (NuGetFloatRangeSpecification.TryParse(versionString, out NuGetFloatRangeSpecification? floatVersion))
            {
                versionInstance = floatVersion;
            }
            else if (NuGetVersionRangeSpecification.TryParse(versionString, out NuGetVersionRangeSpecification? rangeNuGetVersion))
            {
                versionInstance = rangeNuGetVersion;
            }
            else if (ExactVersionSpecification.TryParse(versionString, out IVersionSpecification? exactVersion))
            {
                versionInstance = exactVersion;
            }
            else if (RangeVersionSpecification.TryParse(versionString, out IVersionSpecification? rangeVersion))
            {
                versionInstance = rangeVersion;
            }

            if (versionInstance == null)
            {
                throw new ConfigurationException(string.Format(LocalizableStrings.Constraint_Error_InvalidVersion, versionString));
            }

            return versionInstance;
        }

        private static JsonNode ParseConstraintJsonNode(this string? args)
        {
            if (string.IsNullOrWhiteSpace(args))
            {
                throw new ConfigurationException(LocalizableStrings.Constraint_Error_ArgumentsNotSpecified);
            }

            JsonNode? token;
            try
            {
                token = JsonNode.Parse(args!);
            }
            catch (Exception e)
            {
                throw new ConfigurationException(string.Format(LocalizableStrings.Constraint_Error_InvalidJson, args), e);
            }

            return token ?? throw new ConfigurationException(string.Format(LocalizableStrings.Constraint_Error_InvalidJson, args));
        }

        private static JsonArray ToConstraintsJsonArray(this JsonNode token, string? args, bool isStringTypeAllowed)
        {
            if (token is not JsonArray array)
            {
                throw new ConfigurationException(string.Format(
                    isStringTypeAllowed
                        ? LocalizableStrings.Constraint_Error_InvalidJsonType_StringOrArray
                        : LocalizableStrings.Constraint_Error_InvalidJsonType_Array,
                    args));
            }

            if (array.Count == 0)
            {
                throw new ConfigurationException(string.Format(LocalizableStrings.Constraint_Error_ArrayHasNoObjects, args));
            }

            return array;
        }
    }
}