using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EmbedIO.Internal;
using EmbedIO.Utilities;
using Swan;
using Swan.Collections;
using Swan.Configuration;
namespace EmbedIO.Routing
{
///
/// Implements the logic for resolving a context and a URL path against a list of routes,
/// possibly handling different HTTP methods via different handlers.
///
/// The type of the data used to select a suitable handler
/// for a context.
/// The type of the route resolver.
///
public abstract class RouteResolverCollectionBase : ConfiguredObject
where TResolver : RouteResolverBase
{
private readonly List _resolvers = new List();
///
/// Associates some data and a route to a handler.
///
/// Data used to determine which contexts are
/// suitable to be handled by .
/// The to match URL paths against.
/// A callback used to handle matching contexts.
///
/// is .
/// - or -
/// is .
///
/// The method
/// returned .
///
///
///
public void Add(TData data, RouteMatcher matcher, RouteHandlerCallback handler)
{
matcher = Validate.NotNull(nameof(matcher), matcher);
handler = Validate.NotNull(nameof(handler), handler);
GetResolver(matcher).Add(data, handler);
}
///
/// Associates some data and a route to a synchronous handler.
///
/// Data used to determine which contexts are
/// suitable to be handled by .
/// The to match URL paths against.
/// A callback used to handle matching contexts.
///
/// is .
/// - or -
/// is .
///
/// The method
/// returned .
///
///
///
public void Add(TData data, RouteMatcher matcher, SyncRouteHandlerCallback handler)
{
matcher = Validate.NotNull(nameof(matcher), matcher);
handler = Validate.NotNull(nameof(handler), handler);
GetResolver(matcher).Add(data, handler);
}
///
/// Asynchronously matches a URL path against ;
/// if the match is successful, tries to handle the specified
/// using handlers selected according to data extracted from the context.
/// Registered resolvers are tried in the same order they were added by calling
/// .
///
/// The context to handle.
/// A , representing the ongoing operation,
/// that will return a result in the form of one of the constants.
///
public async Task ResolveAsync(IHttpContext context)
{
var result = RouteResolutionResult.RouteNotMatched;
foreach (var resolver in _resolvers)
{
var resolverResult = await resolver.ResolveAsync(context).ConfigureAwait(false);
OnResolverCalled(context, resolver, resolverResult);
if (resolverResult == RouteResolutionResult.Success)
return RouteResolutionResult.Success;
// This is why RouteResolutionResult constants must not be reordered.
if (resolverResult > result)
result = resolverResult;
}
return result;
}
///
/// Locks this collection, preventing further additions.
///
public void Lock() => LockConfiguration();
///
protected override void OnBeforeLockConfiguration()
{
foreach (var resolver in _resolvers)
resolver.Lock();
}
///
/// Called by
/// and to create an instance
/// of that can resolve the specified route.
/// If this method returns , an
/// is thrown by the calling method.
///
/// The to match URL paths against.
/// A newly-constructed instance of .
protected abstract TResolver CreateResolver(RouteMatcher matcher);
///
/// Called by when a resolver's
/// ResolveAsync method has been called
/// to resolve a context.
/// This callback method may be used e.g. for logging or testing.
///
/// The context to handle.
/// The resolver just called.
/// The result returned by .ResolveAsync.
protected virtual void OnResolverCalled(IHttpContext context, TResolver resolver, RouteResolutionResult result)
{
}
private TResolver GetResolver(RouteMatcher matcher)
{
var resolver = _resolvers.FirstOrDefault(r => r.Matcher.Equals(matcher));
if (resolver != null)
return resolver;
resolver = CreateResolver(matcher);
_resolvers.Add(resolver ?? throw SelfCheck.Failure($"{nameof(CreateResolver)} returned null."));
return resolver;
}
}
}