using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using Newtonsoft.Json.Linq; using NpgsqlTypes; using Pf2e.Database.Model.Common.I18n; namespace Pf2e.Scrapper.Processors; /// /// Класс для работы с JSON-объектом. /// /// Название объекта, ассоциированного с вводом. /// JSON-объект, который будет использоваться для доступа к данным. /// [DI] Набор интерфейсов, разрешающих значения перечислений. internal class ProcessorInput(string name, JObject input, IEnumerable enumSolvers) { /// /// Название объекта, ассоциированного с вводом. /// public string Name { get; } = name; /// /// Входящий JSON-объект, который содержит данные для обработки. /// public JObject Input { get; } = input; /// /// Интерфейсы, разрешающие значения перечислений. /// public IEnumerable EnumSolvers { get; } = enumSolvers; /// /// Значение из JSON по заданному пути, которое обязательно должно существовать. /// /// Путь в JSON. /// Объект типа `T` или `null`, если не удалось найти значение. /// Если значение не найдено. public virtual T GetRequired(string path) { JToken token = Input.SelectToken(path) ?? throw new NullReferenceException($"'{path}' is null"); return token.ToObject() ?? throw new NullReferenceException($"'{path}' is null"); } /// /// Значение из JSON по заданному пути, которое может не существовать. /// /// Путь в JSON /// Объект типа `T` или `null`, если значение не найдено public virtual T? GetOptional(string path) { JToken? token = Input.SelectToken(path) ?? null; return (token is null || token.Type == JTokenType.Null) ? default! : token.ToObject() ?? default; } /// /// Значение из JSON по заданному пути с возможностью предоставить значение по умолчанию. /// /// Путь в JSON. /// Значение, которое возвращается, если значение не найдено. /// Объект типа `T` или `defaultValue`, если значение не найдено. public virtual T GetOptional(string path, T defaultValue) { JToken? token = Input.SelectToken(path) ?? null; if (token is null || token.Type == JTokenType.Null) { return defaultValue; } return token.ToObject() ?? defaultValue; } /// /// Преобразование значения из JSON по заданному пути в перечисление с использованием `PgName` атрибута. /// /// Путь в JSON. /// Перечисление T или выбрасывает исключение, если значение не соответствует жодному варианту. public virtual T EnumRequired(string path) where T : Enum { string value = GetOptional(path) ?? throw new NullReferenceException($"Value in path {path} null or not exists"); return Enum(value); } /// /// Преобразование значения из JSON по заданному пути в перечисление с возможностью предоставить значение по умолчанию. /// /// Путь в JSON /// Значение, которое возвращается, если значение не найдено. /// Значение перечисления T или defaultValue public virtual T EnumOptional(string path, T defaultValue) where T : Enum { string? value = GetOptional(path); if (value is null) { return defaultValue; } try { return Enum(value); } catch (InvalidEnumStringException) { return defaultValue; } } /// /// Преобразование значения из JSON по заданному пути в строку локализации для заданного типа локализации. /// /// Путь в JSON. /// Тип локализации. /// Объект с добавленным значением для заданного типа. public virtual LocaleString GetRequiredLocale(string path, LocaleType type) { return new() { [type] = GetRequired(path) }; } /// /// Преобразование значения из JSON по заданному пути в строку локализации для заданного типа локализации. /// /// Путь в JSON. /// Тип локализации. /// Объект или null, если значение не найдено. public virtual LocaleString? GetOptionalLocale(string path, LocaleType type) { string? value = GetOptional(path); if (value is null) { return null; } return new() { [type] = value }; } /// /// Преобразование значения из JSON в перечисление. /// /// Значение, которое нужно преобразовать /// Объект типа `T`, если преобразование успешно, иначе выбрасывает исключение public virtual T Enum(string value) where T : Enum { IEnumerable> solvers = EnumSolvers .Where(solver => solver.GetType().IsAssignableTo(typeof(EnumSolver))) .Cast>(); foreach (EnumSolver solver in solvers) { T? result = solver.SolveEnum(value); if (result is not null) { return result; } } foreach (System.Reflection.FieldInfo field in typeof(T).GetFields()) { List variants = [field.Name]; if ( Attribute.GetCustomAttribute(field, typeof(PgNameAttribute)) is PgNameAttribute pgAttribute ) { variants.Add(pgAttribute.PgName); } if (variants.Contains(value)) { return (T)field.GetValue(null)!; } } throw new InvalidEnumStringException(typeof(T), value); } /// /// Создание контрольной суммы объекта. /// /// Использовать шестнадцатеричные цифры в нижнем регистре. /// Контрольная сумма объекта. public string Md5(bool isLowercase = false) { byte[] byteHash = MD5.HashData(Encoding.UTF8.GetBytes(Input.ToString())); string hash = Convert.ToHexString(byteHash).Replace("-", ""); return isLowercase ? hash.ToLower() : hash; } /// /// Исключение при неверном значении для перечисленния. /// /// Тип перечисленния. /// Некорректное значение. public class InvalidEnumStringException(Type type, string value) : Exception($"Invalid value '{value}' for enum type {type.FullName}.") { } } /// /// Интерфейс для решения значений перечислений. /// internal interface IEnumSolver { } /// /// Абстрактный класс для разрешения значений перечислений. /// /// Тип перечисления, который будет разрешаться. public abstract class EnumSolver : IEnumSolver where TEnum : Enum { /// /// Словарь, содержащий данные для разрешения значений перечислений. /// public abstract Dictionary Data { get; } /// /// Метод для разрешения значения перечисления по строковому вводу. /// /// Строка, представляющая значение перечисления. /// Элемент перечисления , если найден; иначе null. public TEnum? SolveEnum(string input) { return Data.GetValueOrDefault(input); } }