mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 11:38:15 +08:00
Feature/adding some re route specific validation tests (#590)
* started refactoring json config validation stack because ive made it crap * inject validators from DI rather than instanciating, next step mock them * added some unit tests for specific validators on the train yey
This commit is contained in:
@ -27,7 +27,7 @@ namespace Ocelot.Configuration.File
|
||||
public string Period { get; set; }
|
||||
|
||||
public double PeriodTimespan { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of requests that a client can make in a defined period
|
||||
/// </summary>
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.Configuration.Validator
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Errors;
|
||||
|
||||
public class ConfigurationValidationResult
|
||||
{
|
||||
public ConfigurationValidationResult(bool isError)
|
||||
@ -11,12 +11,6 @@ namespace Ocelot.Configuration.Validator
|
||||
Errors = new List<Error>();
|
||||
}
|
||||
|
||||
public ConfigurationValidationResult(bool isError, Error error)
|
||||
{
|
||||
IsError = isError;
|
||||
Errors = new List<Error> { error };
|
||||
}
|
||||
|
||||
public ConfigurationValidationResult(bool isError, List<Error> errors)
|
||||
{
|
||||
IsError = isError;
|
||||
|
@ -1,46 +1,42 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Errors;
|
||||
using Ocelot.Responses;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ocelot.Configuration.Validator
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using FluentValidation;
|
||||
using File;
|
||||
using Errors;
|
||||
using Responses;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using Requester;
|
||||
using ServiceDiscovery;
|
||||
|
||||
public class FileConfigurationFluentValidator : AbstractValidator<FileConfiguration>, IConfigurationValidator
|
||||
{
|
||||
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||
private readonly List<ServiceDiscoveryFinderDelegate> _serviceDiscoveryFinderDelegates;
|
||||
public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider)
|
||||
|
||||
public FileConfigurationFluentValidator(IServiceProvider provider, ReRouteFluentValidator reRouteFluentValidator, FileGlobalConfigurationFluentValidator fileGlobalConfigurationFluentValidator)
|
||||
{
|
||||
_qosDelegatingHandlerDelegate = provider.GetService<QosDelegatingHandlerDelegate>();
|
||||
_serviceDiscoveryFinderDelegates = provider
|
||||
.GetServices<ServiceDiscoveryFinderDelegate>()
|
||||
.ToList();
|
||||
|
||||
RuleFor(configuration => configuration.ReRoutes)
|
||||
.SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, _qosDelegatingHandlerDelegate));
|
||||
.SetCollectionValidator(reRouteFluentValidator);
|
||||
|
||||
RuleFor(configuration => configuration.GlobalConfiguration)
|
||||
.SetValidator(new FileGlobalConfigurationFluentValidator(_qosDelegatingHandlerDelegate));
|
||||
.SetValidator(fileGlobalConfigurationFluentValidator);
|
||||
|
||||
RuleForEach(configuration => configuration.ReRoutes)
|
||||
.Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes))
|
||||
.WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate");
|
||||
|
||||
RuleForEach(configuration => configuration.ReRoutes)
|
||||
.Must((config, reRoute) => HaveServiceDiscoveryProviderRegitered(reRoute, config.GlobalConfiguration.ServiceDiscoveryProvider))
|
||||
.Must((config, reRoute) => HaveServiceDiscoveryProviderRegistered(reRoute, config.GlobalConfiguration.ServiceDiscoveryProvider))
|
||||
.WithMessage((config, reRoute) => $"Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?");
|
||||
|
||||
RuleFor(configuration => configuration.GlobalConfiguration.ServiceDiscoveryProvider)
|
||||
.Must((config) => HaveServiceDiscoveryProviderRegitered(config))
|
||||
.Must(HaveServiceDiscoveryProviderRegistered)
|
||||
.WithMessage((config, reRoute) => $"Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?");
|
||||
|
||||
RuleForEach(configuration => configuration.ReRoutes)
|
||||
@ -60,7 +56,7 @@ namespace Ocelot.Configuration.Validator
|
||||
.WithMessage((config, aggregateReRoute) => $"{nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} contains ReRoute with specific RequestIdKey, this is not possible with Aggregates");
|
||||
}
|
||||
|
||||
private bool HaveServiceDiscoveryProviderRegitered(FileReRoute reRoute, FileServiceDiscoveryProvider serviceDiscoveryProvider)
|
||||
private bool HaveServiceDiscoveryProviderRegistered(FileReRoute reRoute, FileServiceDiscoveryProvider serviceDiscoveryProvider)
|
||||
{
|
||||
if (string.IsNullOrEmpty(reRoute.ServiceName))
|
||||
{
|
||||
@ -75,7 +71,7 @@ namespace Ocelot.Configuration.Validator
|
||||
return _serviceDiscoveryFinderDelegates.Any();
|
||||
}
|
||||
|
||||
private bool HaveServiceDiscoveryProviderRegitered(FileServiceDiscoveryProvider serviceDiscoveryProvider)
|
||||
private bool HaveServiceDiscoveryProviderRegistered(FileServiceDiscoveryProvider serviceDiscoveryProvider)
|
||||
{
|
||||
if(serviceDiscoveryProvider == null)
|
||||
{
|
||||
@ -87,12 +83,7 @@ namespace Ocelot.Configuration.Validator
|
||||
return true;
|
||||
}
|
||||
|
||||
if(string.IsNullOrEmpty(serviceDiscoveryProvider.Type))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _serviceDiscoveryFinderDelegates.Any();
|
||||
return string.IsNullOrEmpty(serviceDiscoveryProvider.Type) || _serviceDiscoveryFinderDelegates.Any();
|
||||
}
|
||||
|
||||
public async Task<Response<ConfigurationValidationResult>> IsValid(FileConfiguration configuration)
|
||||
|
@ -1,21 +1,14 @@
|
||||
using FluentValidation;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using Requester;
|
||||
using FluentValidation;
|
||||
using File;
|
||||
|
||||
public class FileGlobalConfigurationFluentValidator : AbstractValidator<FileGlobalConfiguration>
|
||||
{
|
||||
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||
|
||||
public FileGlobalConfigurationFluentValidator(QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate)
|
||||
public FileGlobalConfigurationFluentValidator(FileQoSOptionsFluentValidator fileQoSOptionsFluentValidator)
|
||||
{
|
||||
_qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate;
|
||||
|
||||
RuleFor(configuration => configuration.QoSOptions)
|
||||
.SetValidator(new FileQoSOptionsFluentValidator(_qosDelegatingHandlerDelegate));
|
||||
RuleFor(configuration => configuration.QoSOptions)
|
||||
.SetValidator(fileQoSOptionsFluentValidator);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
using FluentValidation;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using File;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Requester;
|
||||
|
||||
public class FileQoSOptionsFluentValidator : AbstractValidator<FileQoSOptions>
|
||||
{
|
||||
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||
|
||||
public FileQoSOptionsFluentValidator(QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate)
|
||||
public FileQoSOptionsFluentValidator(IServiceProvider provider)
|
||||
{
|
||||
_qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate;
|
||||
|
||||
_qosDelegatingHandlerDelegate = provider.GetService<QosDelegatingHandlerDelegate>();
|
||||
|
||||
When(qosOptions => qosOptions.TimeoutValue > 0 && qosOptions.ExceptionsAllowedBeforeBreaking > 0, () => {
|
||||
RuleFor(qosOptions => qosOptions)
|
||||
.Must(HaveQosHandlerRegistered)
|
||||
|
@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.Configuration.Validator
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using Errors;
|
||||
|
||||
public class FileValidationFailedError : Error
|
||||
{
|
||||
public FileValidationFailedError(string message) : base(message, OcelotErrorCode.FileValidationFailedError)
|
||||
public FileValidationFailedError(string message)
|
||||
: base(message, OcelotErrorCode.FileValidationFailedError)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
using FluentValidation;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
public class HostAndPortValidator : AbstractValidator<FileHostAndPort>
|
||||
{
|
||||
public HostAndPortValidator()
|
||||
{
|
||||
RuleFor(r => r.Host).NotEmpty().WithMessage("When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using ReRoute.Host or Ocelot cannot find your service!");
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using FluentValidation;
|
||||
using Ocelot.Configuration.File;
|
||||
|
||||
public class HostAndPortValidator : AbstractValidator<FileHostAndPort>
|
||||
{
|
||||
public HostAndPortValidator()
|
||||
{
|
||||
RuleFor(r => r.Host)
|
||||
.NotEmpty()
|
||||
.WithMessage("When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using ReRoute.Host or Ocelot cannot find your service!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Configuration.Validator
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Responses;
|
||||
|
||||
public interface IConfigurationValidator
|
||||
{
|
||||
Task<Response<ConfigurationValidationResult>> IsValid(FileConfiguration configuration);
|
||||
|
@ -1,59 +1,77 @@
|
||||
namespace Ocelot.Configuration.Validator
|
||||
{
|
||||
using System;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Ocelot.Configuration.File;
|
||||
using File;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Requester;
|
||||
|
||||
public class ReRouteFluentValidator : AbstractValidator<FileReRoute>
|
||||
{
|
||||
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
|
||||
private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate;
|
||||
|
||||
public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate)
|
||||
public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, HostAndPortValidator hostAndPortValidator, FileQoSOptionsFluentValidator fileQoSOptionsFluentValidator)
|
||||
{
|
||||
_authenticationSchemeProvider = authenticationSchemeProvider;
|
||||
_qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate;
|
||||
|
||||
RuleFor(reRoute => reRoute.QoSOptions)
|
||||
.SetValidator(new FileQoSOptionsFluentValidator(_qosDelegatingHandlerDelegate));
|
||||
.SetValidator(fileQoSOptionsFluentValidator);
|
||||
|
||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||
.Must(path => path.StartsWith("/"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||
.NotEmpty()
|
||||
.WithMessage("{PropertyName} cannot be empty");
|
||||
|
||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||
.Must(path => !path.Contains("//"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
||||
.NotEmpty()
|
||||
.WithMessage("{PropertyName} cannot be empty");
|
||||
|
||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||
.Must(path => !path.Contains("//"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
||||
When(reRoute => !string.IsNullOrEmpty(reRoute.DownstreamPathTemplate), () =>
|
||||
{
|
||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||
.Must(path => path.StartsWith("/"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||
|
||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||
.Must(path => path.StartsWith("/"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||
.Must(path => !path.Contains("//"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
||||
|
||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
||||
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
||||
});
|
||||
|
||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
||||
When(reRoute => !string.IsNullOrEmpty(reRoute.UpstreamPathTemplate), () =>
|
||||
{
|
||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||
.Must(path => !path.Contains("//"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
||||
|
||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||
.Must(path => path.StartsWith("/"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||
|
||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||
.Must(path => !path.Contains("https://") && !path.Contains("http://"))
|
||||
.WithMessage("{PropertyName} {PropertyValue} contains scheme");
|
||||
});
|
||||
|
||||
When(reRoute => reRoute.RateLimitOptions.EnableRateLimiting, () =>
|
||||
{
|
||||
RuleFor(reRoute => reRoute.RateLimitOptions.Period)
|
||||
.NotEmpty()
|
||||
.WithMessage("RateLimitOptions.Period is empty");
|
||||
|
||||
RuleFor(reRoute => reRoute.RateLimitOptions)
|
||||
.Must(IsValidPeriod)
|
||||
.WithMessage("RateLimitOptions.Period does not contain integer then s (second), m (minute), h (hour), d (day) e.g. 1m for 1 minute period");
|
||||
});
|
||||
|
||||
RuleFor(reRoute => reRoute.RateLimitOptions)
|
||||
.Must(IsValidPeriod)
|
||||
.WithMessage("RateLimitOptions.Period does not contains (s,m,h,d)");
|
||||
|
||||
RuleFor(reRoute => reRoute.AuthenticationOptions)
|
||||
.MustAsync(IsSupportedAuthenticationProviders)
|
||||
.WithMessage("{PropertyValue} is unsupported authentication provider");
|
||||
.WithMessage("{PropertyName} {PropertyValue} is unsupported authentication provider");
|
||||
|
||||
When(reRoute => string.IsNullOrEmpty(reRoute.ServiceName), () => {
|
||||
RuleFor(r => r.DownstreamHostAndPorts).NotEmpty()
|
||||
@ -62,7 +80,7 @@
|
||||
|
||||
When(reRoute => string.IsNullOrEmpty(reRoute.ServiceName), () => {
|
||||
RuleFor(reRoute => reRoute.DownstreamHostAndPorts)
|
||||
.SetCollectionValidator(new HostAndPortValidator());
|
||||
.SetCollectionValidator(hostAndPortValidator);
|
||||
});
|
||||
}
|
||||
|
||||
@ -82,9 +100,22 @@
|
||||
|
||||
private static bool IsValidPeriod(FileRateLimitRule rateLimitOptions)
|
||||
{
|
||||
string period = rateLimitOptions.Period;
|
||||
if (string.IsNullOrEmpty(rateLimitOptions.Period))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !rateLimitOptions.EnableRateLimiting || period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d");
|
||||
var period = rateLimitOptions.Period;
|
||||
|
||||
var secondsRegEx = new Regex("^[0-9]+s");
|
||||
var minutesRegEx = new Regex("^[0-9]+m");
|
||||
var hoursRegEx = new Regex("^[0-9]+h");
|
||||
var daysRegEx = new Regex("^[0-9]+d");
|
||||
|
||||
return secondsRegEx.Match(period).Success
|
||||
|| minutesRegEx.Match(period).Success
|
||||
|| hoursRegEx.Match(period).Success
|
||||
|| daysRegEx.Match(period).Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,10 @@ namespace Ocelot.DependencyInjection
|
||||
Services.TryAddSingleton<IInternalConfigurationCreator, FileInternalConfigurationCreator>();
|
||||
Services.TryAddSingleton<IInternalConfigurationRepository, InMemoryInternalConfigurationRepository>();
|
||||
Services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
|
||||
Services.AddSingleton<HostAndPortValidator>();
|
||||
Services.AddSingleton<ReRouteFluentValidator>();
|
||||
Services.AddSingleton<FileGlobalConfigurationFluentValidator>();
|
||||
Services.AddSingleton<FileQoSOptionsFluentValidator>();
|
||||
Services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
|
||||
Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
|
||||
Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
|
||||
|
Reference in New Issue
Block a user