using System.Text.RegularExpressions; namespace Kit.Helpers.Validation { /// /// Проверки форматов часто встречающихся реквизитов (ИНН, email, телефон, СНИЛС). Чистые /// предикаты без побочных эффектов и текста ошибок: возвращают true/false, решение о /// throw и формулировку сообщения принимает вызывающий код. Реализация — только BCL /// (, LINQ), без сторонних зависимостей. null/пустая строка → false /// (обязательность и «пропуск пустого опционального поля» — забота вызывающего). /// public static class FormatCheck { // Прагматичная проверка email: одна @, непустые локальная и доменная части, точка в домене. private static readonly Regex EmailRegex = new(@"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.Compiled); // Веса контрольных разрядов ИНН (ФНС): 10-й разряд; 11-й и 12-й — для 12-значного. private static readonly int[] InnWeights10 = { 2, 4, 10, 3, 5, 9, 4, 6, 8 }; private static readonly int[] InnWeights11 = { 7, 2, 4, 10, 3, 5, 9, 4, 6, 8 }; private static readonly int[] InnWeights12 = { 3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8 }; /// ИНН: только цифры, длина 10 (ЮЛ) или 12 (ИП/ФЛ), контрольная сумма сходится. public static bool IsInn(string? value) { if (string.IsNullOrWhiteSpace(value)) return false; var digits = value.Trim(); if (!digits.All(char.IsDigit) || (digits.Length != 10 && digits.Length != 12)) return false; return IsInnChecksumValid(digits); } /// Email по прагматичному шаблону (одна @, точка в домене), длина ≤ 254. public static bool IsEmail(string? value) { if (string.IsNullOrWhiteSpace(value)) return false; var email = value.Trim(); return email.Length <= 254 && EmailRegex.IsMatch(email); } /// Телефон: 10–15 цифр; допустимы «+», пробел, дефис, скобки как разделители. public static bool IsPhone(string? value) { if (string.IsNullOrWhiteSpace(value)) return false; var phone = value.Trim(); var hasInvalidChar = phone.Any(character => !char.IsDigit(character) && character is not ('+' or '-' or ' ' or '(' or ')')); if (hasInvalidChar) return false; var digitsCount = phone.Count(char.IsDigit); return digitsCount is >= 10 and <= 15; } /// СНИЛС: 11 цифр (разделители игнорируются), контрольная сумма сходится. public static bool IsSnils(string? value) { if (string.IsNullOrWhiteSpace(value)) return false; var digits = new string(value.Where(char.IsDigit).ToArray()); return digits.Length == 11 && IsSnilsChecksumValid(digits); } /// /// Строка идентификаторов для фильтров БД (формат fn_sys_range): целые числа /// и диапазоны через «..» или «-», элементы через запятую, пробелы игнорируются. /// Пример: «1,5,7..9, 11..15,19». У диапазона начало не больше конца. /// public static bool IsIdRange(string? value) { if (string.IsNullOrWhiteSpace(value)) return false; // Строгий разбор: пустой элемент (двойная запятая, хвостовая запятая) недопустим. var items = value.Split(','); return items.All(item => IsIdRangeItemValid(item.Trim())); } // Элемент строки идентификаторов: «42», «7..9» или «7-9»; диапазон не обратный. // Пробелы внутри элемента («2 .. 3») игнорируются. private static bool IsIdRangeItemValid(string item) { item = item.Replace(" ", string.Empty); if (item.Length == 0) return false; var separatorIndex = item.IndexOf("..", StringComparison.Ordinal); string lo; string hi; if (separatorIndex >= 0) { lo = item[..separatorIndex]; hi = item[(separatorIndex + 2)..]; } else { var dashIndex = item.IndexOf('-', StringComparison.Ordinal); if (dashIndex < 0) return IsPositiveInt(item); lo = item[..dashIndex]; hi = item[(dashIndex + 1)..]; } return IsPositiveInt(lo) && IsPositiveInt(hi) && int.Parse(lo) <= int.Parse(hi); } // Целое неотрицательное число без знака и ведущих «+». private static bool IsPositiveInt(string value) => value.Length > 0 && value.All(char.IsDigit); // Контрольная сумма ИНН: для 10-значного — один контрольный разряд, для 12-значного — два. private static bool IsInnChecksumValid(string inn) { var digits = inn.Select(character => character - '0').ToArray(); if (digits.Length == 10) return ControlDigit(digits, InnWeights10) == digits[9]; return ControlDigit(digits, InnWeights11) == digits[10] && ControlDigit(digits, InnWeights12) == digits[11]; } private static int ControlDigit(int[] digits, int[] weights) { var sum = weights.Select((weight, index) => weight * digits[index]).Sum(); return sum % 11 % 10; } // Контрольная сумма СНИЛС: взвешенная сумма первых 9 цифр (веса 9..1) mod 101 → последние две. private static bool IsSnilsChecksumValid(string snils) { var digits = snils.Select(character => character - '0').ToArray(); var sum = 0; for (var i = 0; i < 9; i++) sum += digits[i] * (9 - i); var control = sum % 101; if (control == 100) control = 0; var provided = digits[9] * 10 + digits[10]; return control == provided; } } }