File: Matching\EndpointComparer.cs
Web Access
Project: src\src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj (Microsoft.AspNetCore.Routing)
// 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;
using Microsoft.AspNetCore.Http;
 
namespace Microsoft.AspNetCore.Routing.Matching;
 
// Use to sort and group Endpoints. RouteEndpoints are sorted before other implementations.
//
// NOTE:
// When ordering endpoints, we compare the route templates as an absolute last resort.
// This is used as a factor to ensure that we always have a predictable ordering
// for tests, errors, etc.
//
// When we group endpoints we don't consider the route template, because we're trying
// to group endpoints not separate them.
//
// TLDR:
//  IComparer implementation considers the template string as a tiebreaker.
//  IEqualityComparer implementation does not.
//  This is cool and good.
internal sealed class EndpointComparer : IComparer<Endpoint>, IEqualityComparer<Endpoint>
{
    private readonly IComparer<Endpoint>[] _comparers;
 
    public EndpointComparer(IEndpointComparerPolicy[] policies)
    {
        // Order, Precedence, (others)...
        _comparers = new IComparer<Endpoint>[2 + policies.Length];
        _comparers[0] = OrderComparer.Instance;
        _comparers[1] = PrecedenceComparer.Instance;
        for (var i = 0; i < policies.Length; i++)
        {
            _comparers[i + 2] = policies[i].Comparer;
        }
    }
 
    public int Compare(Endpoint? x, Endpoint? y)
    {
        // We don't expose this publicly, and we should never call it on
        // a null endpoint.
        Debug.Assert(x != null);
        Debug.Assert(y != null);
 
        var compare = CompareCore(x, y);
 
        // Since we're sorting, use the route template as a last resort.
        return compare == 0 ? ComparePattern(x, y) : compare;
    }
 
    private static int ComparePattern(Endpoint x, Endpoint y)
    {
        // A RouteEndpoint always comes before a non-RouteEndpoint, regardless of its RawText value
        var routeEndpointX = x as RouteEndpoint;
        var routeEndpointY = y as RouteEndpoint;
 
        if (routeEndpointX != null)
        {
            if (routeEndpointY != null)
            {
                return string.Compare(routeEndpointX.RoutePattern.RawText, routeEndpointY.RoutePattern.RawText, StringComparison.OrdinalIgnoreCase);
            }
 
            return 1;
        }
        else if (routeEndpointY != null)
        {
            return -1;
        }
 
        return 0;
    }
 
    public bool Equals(Endpoint? x, Endpoint? y)
    {
        // We don't expose this publicly, and we should never call it on
        // a null endpoint.
        Debug.Assert(x != null);
        Debug.Assert(y != null);
 
        return CompareCore(x, y) == 0;
    }
 
    public int GetHashCode(Endpoint obj)
    {
        // This should not be possible to call publicly.
        Debug.Fail("We don't expect this to be called.");
        throw new System.NotImplementedException();
    }
 
    private int CompareCore(Endpoint x, Endpoint y)
    {
        for (var i = 0; i < _comparers.Length; i++)
        {
            var compare = _comparers[i].Compare(x, y);
            if (compare != 0)
            {
                return compare;
            }
        }
 
        return 0;
    }
 
    private sealed class OrderComparer : IComparer<Endpoint>
    {
        public static readonly IComparer<Endpoint> Instance = new OrderComparer();
 
        public int Compare(Endpoint? x, Endpoint? y)
        {
            var routeEndpointX = x as RouteEndpoint;
            var routeEndpointY = y as RouteEndpoint;
 
            if (routeEndpointX != null)
            {
                if (routeEndpointY != null)
                {
                    return routeEndpointX.Order.CompareTo(routeEndpointY.Order);
                }
 
                return 1;
            }
            else if (routeEndpointY != null)
            {
                return -1;
            }
 
            return 0;
        }
    }
 
    private sealed class PrecedenceComparer : IComparer<Endpoint>
    {
        public static readonly IComparer<Endpoint> Instance = new PrecedenceComparer();
 
        public int Compare(Endpoint? x, Endpoint? y)
        {
            var routeEndpointX = x as RouteEndpoint;
            var routeEndpointY = y as RouteEndpoint;
 
            if (routeEndpointX != null)
            {
                if (routeEndpointY != null)
                {
                    return routeEndpointX.RoutePattern.InboundPrecedence
                        .CompareTo(routeEndpointY.RoutePattern.InboundPrecedence);
                }
 
                return 1;
            }
            else if (routeEndpointY != null)
            {
                return -1;
            }
 
            return 0;
        }
    }
}