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();
}
}