using System; using System.Collections; using System.Collections.Generic; using System.Linq; using EmbedIO.Utilities; namespace EmbedIO.Routing { /// /// Represents a route resolved by a . /// This class may be used both as a dictionary of route parameter names and values, /// and a list of the values. /// Because of its double nature, this class cannot be enumerated directly. However, /// you may use the property to iterate over name / value pairs, and the /// property to iterate over values. /// When enumerated in a non-generic fashion via the interface, /// this class iterates over name / value pairs. /// #pragma warning disable CA1710 // Rename class to end in "Collection" public sealed class RouteMatch : IReadOnlyList, IReadOnlyDictionary #pragma warning restore CA1710 { private static readonly IReadOnlyList EmptyStringList = Array.Empty(); private readonly IReadOnlyList _values; internal RouteMatch(string path, IReadOnlyList names, IReadOnlyList values, string? subPath) { Path = path; Names = names; _values = values; SubPath = subPath; } /// /// Gets a instance that represents no match at all. /// /// /// The instance returned by this property /// has the following specifications: /// /// its Path property is the empty string; /// it has no parameters; /// its SubPath property is . /// /// This instance is only useful to initialize /// a non-nullable property of type , provided that it is subsequently /// set to a meaningful value before being used. /// public static RouteMatch None { get; } = new RouteMatch( string.Empty, Array.Empty(), Array.Empty(), null); /// /// Gets the URL path that was successfully matched against the route. /// public string Path { get; } /// /// For a base route, gets the part of that follows the matched route; /// for a non-base route, this property is always . /// public string? SubPath { get; } /// /// Gets a list of the names of the route's parameters. /// public IReadOnlyList Names { get; } /// public int Count => _values.Count; /// public IEnumerable Keys => Names; /// public IEnumerable Values => _values; /// /// Gets an interface that can be used /// to iterate over name / value pairs. /// public IEnumerable> Pairs => this; /// public string this[int index] => _values[index]; /// public string this[string key] { get { var count = Names.Count; for (var i = 0; i < count; i++) { if (Names[i] == key) { return _values[i]; } } throw new KeyNotFoundException("The parameter name was not found."); } } /// /// Returns a object equal to the one /// that would result by matching the specified URL path against a /// base route of "/". /// /// The URL path to match. /// A newly-constructed . /// /// This method assumes that /// is a valid, non-base URL path or route. Otherwise, the behavior of this method /// is unspecified. /// Ensure that you validate before /// calling this method, using either /// or . /// public static RouteMatch UnsafeFromRoot(string urlPath) => new RouteMatch(urlPath, EmptyStringList, EmptyStringList, urlPath); /// /// Returns a object equal to the one /// that would result by matching the specified URL path against /// the specified parameterless base route. /// /// The base route to match against. /// The URL path to match. /// A newly-constructed . /// /// This method assumes that is a /// valid base URL path, and /// is a valid, non-base URL path or route. Otherwise, the behavior of this method /// is unspecified. /// Ensure that you validate both parameters before /// calling this method, using either /// or . /// public static RouteMatch? UnsafeFromBasePath(string baseUrlPath, string urlPath) { var subPath = UrlPath.UnsafeStripPrefix(urlPath, baseUrlPath); return subPath == null ? null : new RouteMatch(urlPath, EmptyStringList, EmptyStringList, "/" + subPath); } /// public bool ContainsKey(string key) => Names.Any(n => n == key); /// public bool TryGetValue(string key, out string? value) { var count = Names.Count; for (var i = 0; i < count; i++) { if (Names[i] == key) { value = _values[i]; return true; } } value = null; return false; } /// /// Returns the index of the parameter with the specified name. /// /// The parameter name. /// The index of the parameter, or -1 if none of the /// route parameters have the specified name. public int IndexOf(string name) { var count = Names.Count; for (var i = 0; i < count; i++) { if (Names[i] == name) { return i; } } return -1; } /// IEnumerator> IEnumerable>.GetEnumerator() => Names.Zip(_values, (n, v) => new KeyValuePair(n, v)).GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => Pairs.GetEnumerator(); } }