// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace AWS.Deploy.Common.Recipes.Validation
{
///
/// Factory that builds the validators for a given option or recipe
///
public interface IValidatorFactory
{
///
/// Builds the validators that apply to the given option
///
/// Option to validate
/// Applies a filter to the list of validators
/// Array of validators for the given option
IOptionSettingItemValidator[] BuildValidators(OptionSettingItem optionSettingItem, Func? filter = null);
///
/// Builds the validators that apply to the given recipe
///
/// Recipe to validate
/// Array of validators for the given recipe
IRecipeValidator[] BuildValidators(RecipeDefinition recipeDefinition);
}
///
/// Builds and instances.
///
public class ValidatorFactory : IValidatorFactory
{
private readonly IServiceProvider _serviceProvider;
public ValidatorFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
private static readonly Dictionary _optionSettingItemValidatorTypeMapping = new()
{
{ OptionSettingItemValidatorList.Range, typeof(RangeValidator) },
{ OptionSettingItemValidatorList.Regex, typeof(RegexValidator) },
{ OptionSettingItemValidatorList.Required, typeof(RequiredValidator) },
{ OptionSettingItemValidatorList.DirectoryExists, typeof(DirectoryExistsValidator) },
{ OptionSettingItemValidatorList.DockerBuildArgs, typeof(DockerBuildArgsValidator) },
{ OptionSettingItemValidatorList.DotnetPublishArgs, typeof(DotnetPublishArgsValidator) },
{ OptionSettingItemValidatorList.ExistingResource, typeof(ExistingResourceValidator) },
{ OptionSettingItemValidatorList.FileExists, typeof(FileExistsValidator) },
{ OptionSettingItemValidatorList.StringLength, typeof(StringLengthValidator) },
{ OptionSettingItemValidatorList.InstanceType, typeof(LinuxInstanceTypeValidator) },
{ OptionSettingItemValidatorList.WindowsInstanceType, typeof(WindowsInstanceTypeValidator) },
{ OptionSettingItemValidatorList.SubnetsInVpc, typeof(SubnetsInVpcValidator) },
{ OptionSettingItemValidatorList.SecurityGroupsInVpc, typeof(SecurityGroupsInVpcValidator) },
{ OptionSettingItemValidatorList.Uri, typeof(UriValidator) },
{ OptionSettingItemValidatorList.Comparison, typeof(ComparisonValidator) },
{ OptionSettingItemValidatorList.VPCSubnetsInDifferentAZs, typeof(VPCSubnetsInDifferentAZsValidator) },
{ OptionSettingItemValidatorList.VpcExists, typeof(VpcExistsValidator) }
};
private static readonly Dictionary _recipeValidatorTypeMapping = new()
{
{ RecipeValidatorList.FargateTaskSizeCpuMemoryLimits, typeof(FargateTaskCpuMemorySizeValidator) },
{ RecipeValidatorList.ValidDockerfilePath, typeof(DockerfilePathValidator) }
};
public IOptionSettingItemValidator[] BuildValidators(OptionSettingItem optionSettingItem, Func? filter = null)
{
return optionSettingItem.Validators
.Where(validator => filter != null ? filter(validator) : true)
.Select(v => Activate(v.ValidatorType, v.Configuration, _optionSettingItemValidatorTypeMapping))
.OfType()
.ToArray();
}
public IRecipeValidator[] BuildValidators(RecipeDefinition recipeDefinition)
{
return recipeDefinition.Validators
.Select(v => Activate(v.ValidatorType, v.Configuration, _recipeValidatorTypeMapping))
.OfType()
.ToArray();
}
private object? Activate(TValidatorList validatorType, object? configuration, Dictionary typeMappings) where TValidatorList : struct
{
if (null == configuration)
{
var validatorInstance = ActivatorUtilities.CreateInstance(_serviceProvider, typeMappings[validatorType]);
if (validatorInstance == null)
throw new InvalidValidatorTypeException(DeployToolErrorCode.UnableToCreateValidatorInstance, $"Could not create an instance of validator type {validatorType}");
return validatorInstance;
}
if (configuration is JObject jObject)
{
var validatorInstance = JsonConvert.DeserializeObject(
JsonConvert.SerializeObject(jObject),
typeMappings[validatorType],
new JsonSerializerSettings
{
ContractResolver = new ServiceContractResolver(_serviceProvider)
});
if (validatorInstance == null)
throw new InvalidValidatorTypeException(DeployToolErrorCode.UnableToCreateValidatorInstance, $"Could not create an instance of validator type {validatorType}");
return validatorInstance;
}
return configuration;
}
}
///
/// Custom contract resolver that can inject services from an IServiceProvider
/// into the constructor of the type that is being deserialized from Json
///
public class ServiceContractResolver : DefaultContractResolver
{
private readonly IServiceProvider _serviceProvider;
public ServiceContractResolver(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.DefaultCreator = () => ActivatorUtilities.CreateInstance(_serviceProvider, objectType);
return contract;
}
}
}