Skip to content
This repository has been archived by the owner on Nov 7, 2018. It is now read-only.

Commit

Permalink
Add support for options validation (#266)
Browse files Browse the repository at this point in the history
  • Loading branch information
HaoK committed Jul 30, 2018
1 parent d86d445 commit cea619f
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 1 deletion.
20 changes: 20 additions & 0 deletions src/Microsoft.Extensions.Options/IValidateOptions.cs
@@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.Extensions.Options
{
/// <summary>
/// Interface used to validate options.
/// </summary>
/// <typeparam name="TOptions">The options type to validate.</typeparam>
public interface IValidateOptions<TOptions> where TOptions : class
{
/// <summary>
/// Validates a specific named options instance (or all when name is null).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>The <see cref="ValidateOptionsResult"/> result.</returns>
ValidateOptionsResult Validate(string name, TOptions options);
}
}
21 changes: 21 additions & 0 deletions src/Microsoft.Extensions.Options/OptionsBuilder.cs
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.Options
Expand Down Expand Up @@ -255,5 +256,25 @@ public virtual OptionsBuilder<TOptions> PostConfigure<TDep>(Action<TOptions, TDe
configureOptions));
return this;
}

public virtual OptionsBuilder<TOptions> Validate(Func<TOptions, bool> validation)
=> Validate(name: Options.DefaultName, validation: validation, failureMessage: "A validation error has occured.");

public virtual OptionsBuilder<TOptions> Validate(string name, Func<TOptions, bool> validation)
=> Validate(name: name, validation: validation, failureMessage: "A validation error has occured.");

public virtual OptionsBuilder<TOptions> Validate(Func<TOptions, bool> validation, string failureMessage)
=> Validate(name: Options.DefaultName, validation: validation, failureMessage: failureMessage);

public virtual OptionsBuilder<TOptions> Validate(string name, Func<TOptions, bool> validation, string failureMessage)
{
if (validation == null)
{
throw new ArgumentNullException(nameof(validation));
}

Services.AddSingleton<IValidateOptions<TOptions>>(new ValidateOptions<TOptions>(name, validation, failureMessage));
return this;
}
}
}
31 changes: 30 additions & 1 deletion src/Microsoft.Extensions.Options/OptionsFactory.cs
Expand Up @@ -13,16 +13,27 @@ public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions
{
private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
private readonly IEnumerable<IValidateOptions<TOptions>> _validations;

/// <summary>
/// Initializes a new instance with the specified options configurations.
/// </summary>
/// <param name="setups">The configuration actions to run.</param>
/// <param name="postConfigures">The initialization actions to run.</param>
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures)
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) : this(setups, postConfigures, validations: null)
{ }

/// <summary>
/// Initializes a new instance with the specified options configurations.
/// </summary>
/// <param name="setups">The configuration actions to run.</param>
/// <param name="postConfigures">The initialization actions to run.</param>
/// <param name="validations">The validations to run.</param>
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations)
{
_setups = setups;
_postConfigures = postConfigures;
_validations = validations;
}

public TOptions Create(string name)
Expand All @@ -43,6 +54,24 @@ public TOptions Create(string name)
{
post.PostConfigure(name, options);
}

if (_validations != null)
{
var failures = new List<string>();
foreach (var validate in _validations)
{
var result = validate.Validate(name, options);
if (result.Failed)
{
failures.Add(result.FailureMessage);
}
}
if (failures.Count > 0)
{
throw new OptionsValidationException(failures);
}
}

return options;
}
}
Expand Down
26 changes: 26 additions & 0 deletions src/Microsoft.Extensions.Options/OptionsValidationException.cs
@@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;

namespace Microsoft.Extensions.Options
{
/// <summary>
/// Thrown when options validation fails.
/// </summary>
public class OptionsValidationException : Exception
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="failureMessages">The validation failure messages.</param>
public OptionsValidationException(IEnumerable<string> failureMessages)
=> Failures = failureMessages ?? new List<string>();

/// <summary>
/// The validation failures.
/// </summary>
public IEnumerable<string> Failures { get; }
}
}
52 changes: 52 additions & 0 deletions src/Microsoft.Extensions.Options/ValidateOptions.cs
@@ -0,0 +1,52 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Microsoft.Extensions.Options
{
/// <summary>
/// Implementation of <see cref="IValidateOptions{TOptions}"/>
/// </summary>
/// <typeparam name="TOptions">The instance being validated.</typeparam>
public class ValidateOptions<TOptions> : IValidateOptions<TOptions> where TOptions : class
{
public ValidateOptions(string name, Func<TOptions, bool> validation, string failureMessage)
{
Name = name;
Validation = validation;
FailureMessage = failureMessage;
}

/// <summary>
/// The options name.
/// </summary>
public string Name { get; }

/// <summary>
/// The validation action.
/// </summary>
public Func<TOptions, bool> Validation { get; }

/// <summary>
/// The error to return when validation fails.
/// </summary>
public string FailureMessage { get; }

public ValidateOptionsResult Validate(string name, TOptions options)
{
// Null name is used to configure all named options.
if (Name == null || name == Name)
{
if ((Validation?.Invoke(options)).Value)
{
return ValidateOptionsResult.Success;
}
return ValidateOptionsResult.Fail(FailureMessage);
}

// Ignored if not validating this instance.
return ValidateOptionsResult.Skip;
}
}
}
49 changes: 49 additions & 0 deletions src/Microsoft.Extensions.Options/ValidateOptionsResult.cs
@@ -0,0 +1,49 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.Extensions.Options
{
/// <summary>
/// Represents the result of an options validation.
/// </summary>
public class ValidateOptionsResult
{
/// <summary>
/// Result when validation was skipped due to name not matching.
/// </summary>
public static readonly ValidateOptionsResult Skip = new ValidateOptionsResult() { Skipped = true };

/// <summary>
/// Validation was successful.
/// </summary>
public static readonly ValidateOptionsResult Success = new ValidateOptionsResult() { Skipped = true };

/// <summary>
/// True if validation was successful.
/// </summary>
public bool Succeeded { get; protected set; }

/// <summary>
/// True if validation was not run.
/// </summary>
public bool Skipped { get; protected set; }

/// <summary>
/// True if validation failed.
/// </summary>
public bool Failed { get; protected set; }

/// <summary>
/// Used to describe why validation failed.
/// </summary>
public string FailureMessage { get; protected set; }

/// <summary>
/// Returns a failure result.
/// </summary>
/// <param name="failureMessage">The reason for the failure.</param>
/// <returns>The failure result.</returns>
public static ValidateOptionsResult Fail(string failureMessage)
=> new ValidateOptionsResult { Failed = true, FailureMessage = failureMessage };
}
}

0 comments on commit cea619f

Please sign in to comment.