/// <summary>
        /// Selects all elements of a `IEnumerable` property for validation.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <typeparam name="TItem">The type of an individual item in the `IEnumerable` property.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="selector">
        ///     The property selector expression.
        ///     <para>
        ///         The expression must be a `MemberExpression` (e.g. `x => x.Property`) or a `ParameterExpression`
        ///         to select all elements of the current value (e.g. `x => x`).
        ///     </para>
        /// </param>
        /// <param name="action">The action to perform for each item of the selected `IEnumerable` property.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> ForEach <T, TItem>(this IValidatorContext <T> validator,
                                                               Expression <Func <T, IEnumerable <TItem> > > selector, Action <IValidatorContext <TItem> > action)
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));
            ValidationInternals.ValidateNotNull(selector, nameof(selector));
            ValidationInternals.ValidateNotNull(action, nameof(action));

            var name = (selector.Body is ParameterExpression)
                ? null
                : ValidationInternals.GetPropertyName(selector);
            var value = selector.Compile().Invoke(validator.Value);

            if (value is null)
            {
                return(validator);
            }

            int i = 0;

            foreach (var item in value)
            {
                var context = (name is null)
                    ? new ValidatorContext <TItem>(item, $"{ValidationInternals.FormatName(validator.Path, null)}[{i++}]")
                    : new ValidatorContext <TItem>(item, validator.Path, $"{name}[{i++}]");
                action.Invoke(context);
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the uri does not contain an absolute uri.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> Absolute <T>(this IValidatorContext <T> validator)
            where T : Uri
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (!validator.Value.IsAbsoluteUri)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.UriMustBeAbsolute, ValidationInternals.FormatName(validator.Path, null)),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is not less than or equal to the given threshold.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="threshold">The threshold value.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> LessThanOrEqualTo <T>(this IValidatorContext <T> validator, T threshold)
            where T : struct, IComparable <T>
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (validator.Value.CompareTo(threshold) > 0)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.ArgumentMustBeGreaterThan, ValidationInternals.FormatName(validator.Path, null), threshold),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the stream is not seekable.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> Seekable <T>(this IValidatorContext <T> validator)
            where T : Stream
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (!validator.Value.CanSeek)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.StreamMustBeSeekable, ValidationInternals.FormatName(validator.Path, null)),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is equal to one of the blacklisted values.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="values">The blacklisted values.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> NotEqual <T>(this IValidatorContext <T> validator, params T[] values)
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            var isValidValue = values.All(x => !EqualityComparer <T> .Default.Equals(validator.Value, x));

            if (!isValidValue)
            {
                throw new ArgumentNullException(validator.Path.First(), string.Format(CultureInfo.InvariantCulture,
                                                                                      Resources.ArgumentMustNotBeOneOf, ValidationInternals.FormatName(validator.Path, null),
                                                                                      string.Join(", ", values.Select(x => $"'{x}'").ToList())));
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is not in the given range.
        /// <para>
        ///     This range check is "inclusive" which means that the lower- and upper- limits are both valid values.
        /// </para>
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="min">The minimum allowed value.</param>
        /// <param name="max">The maximum allowed value.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> InRange <T>(this IValidatorContext <T> validator, T min, T max)
            where T : struct, IComparable <T>
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            try
            {
                validator.GreaterThanOrEqualTo(min);
                validator.LessThanOrEqualTo(max);
            }
            catch (ArgumentException e)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.ArgumentMustBeInRange, ValidationInternals.FormatName(validator.Path, null), min, max),
                                            validator.Path.First(), e);
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is equal to the blacklisted value.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="value">The blacklisted value.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> NotEqual <T>(this IValidatorContext <T> validator, T value)
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (EqualityComparer <T> .Default.Equals(validator.Value, value))
            {
                throw new ArgumentNullException(validator.Path.First(), string.Format(CultureInfo.InvariantCulture,
                                                                                      Resources.ArgumentMustNotBeValue, ValidationInternals.FormatName(validator.Path, null), value));
            }

            return(validator);
        }
        /// <inheritdoc cref="NotNullOrEmpty{T}(ZySharp.Validation.IValidatorContext{T})"/>
        public static IValidatorContext <T?> NotNullOrEmpty <T>(this IValidatorContext <T?> validator)
            where T : struct
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (!validator.Value.HasValue)
            {
                throw new ArgumentNullException(validator.Path.First(), string.Format(CultureInfo.InvariantCulture,
                                                                                      Resources.ArgumentMustNotBeNullOrEmpty, ValidationInternals.FormatName(validator.Path, null)));
            }

            try
            {
                validator.NotEmpty();
            }
            catch (ArgumentException)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.ArgumentMustNotBeNullOrEmpty, ValidationInternals.FormatName(validator.Path, null)),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is `null` or empty (contains the default value of the current type).
        /// <para>
        ///     This validation as well applies to empty strings or `IEnumerable` types without at least one element.
        /// </para>
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> NotNullOrEmpty <T>(this IValidatorContext <T> validator)
            where T : class
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            try
            {
                validator.NotNull();
            }
            catch (ArgumentNullException)
            {
                throw new ArgumentNullException(validator.Path.First(), string.Format(CultureInfo.InvariantCulture,
                                                                                      Resources.ArgumentMustNotBeNullOrEmpty, ValidationInternals.FormatName(validator.Path, null)));
            }

            var isEmptyEnum   = (validator.Value is IEnumerable enumerable) && !enumerable.GetEnumerator().MoveNext();
            var isEmptyString = validator.Value is string { Length : 0 };

            if (isEmptyEnum || isEmptyString)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.ArgumentMustNotBeNullOrEmpty, ValidationInternals.FormatName(validator.Path, null)),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is empty (contains the default value of the current type).
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> NotEmpty <T>(this IValidatorContext <T> validator)
            where T : struct
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (validator.Value.Equals(default(T)))
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.ArgumentMustNotBeEmpty, ValidationInternals.FormatName(validator.Path, null)),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is `null`.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> NotNull <T>(this IValidatorContext <T> validator)
            where T : class
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (validator.Value is null)
            {
                throw new ArgumentNullException(validator.Path.First(), string.Format(CultureInfo.InvariantCulture,
                                                                                      Resources.ArgumentMustNotBeNull, ValidationInternals.FormatName(validator.Path, null)));
            }

            return(validator);
        }