using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Errors; namespace PostFundManagement.Application.Services.Shared; public sealed partial class HashService(IHashids hasher) : IService { [GeneratedRegex(@"\A\b[0-9a-fA-F]+\b\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)] private static partial Regex HexHashRegex { get; } [GeneratedRegex(@"\A[0-9a-fA-F]{32}\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)] private static partial Regex Md5Regex { get; } [GeneratedRegex(@"\A[0-9a-fA-F]{64}\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)] private static partial Regex Sha256Regex { get; } public static bool IsMd5Hash(string? value) => !string.IsNullOrWhiteSpace(value) && Md5Regex.IsMatch(value); public static bool IsSha256Hash(string? value) => !string.IsNullOrWhiteSpace(value) && Sha256Regex.IsMatch(value); public static string? StringToSha256Hash(string? input) => string.IsNullOrEmpty(input) ? null : Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input))); public static string? StreamToSha256Hash(Stream stream) => stream is null ? null : Convert.ToHexString(SHA256.HashData(stream)); public static string? BytesToSha256Hash(byte[] bytes) => bytes is null ? null : Convert.ToHexString(SHA256.HashData(bytes)); public static Result ToMd5Hash(string input) { if (string.IsNullOrEmpty(input)) return Result.Fail(ValidationError.Create("Input content cannot be null or empty for MD5 processing.")); byte[] bytes = MD5.HashData(Encoding.UTF8.GetBytes(input)); return Result.Ok(Convert.ToHexString(bytes).ToLowerInvariant()); } public Result HashEncodeHex(string input) => string.IsNullOrWhiteSpace(input) || !HexHashRegex.IsMatch(input) ? Result.Fail(ValidationError.Create("Input must be a valid hexadecimal string.")) : Result.Ok(hasher.EncodeHex(input)); public Result HashEncodeIntId(int id) => id < 0 ? Result.Fail(ValidationError.Create("Id cannot be negative.")) : Result.Ok(hasher.Encode(id)); public Result HashEncodeLongId(long id) => id < 0 ? Result.Fail(ValidationError.Create("Id cannot be negative.")) : Result.Ok(hasher.EncodeLong(id)); public Result DecodeIntIdHash(string hash) { if (string.IsNullOrWhiteSpace(hash)) return Result.Fail(ValidationError.Create("Invalid token layout.")); int[] decoded = hasher.Decode(hash); return decoded.Length == 1 ? Result.Ok(decoded[0]) : Result.Fail(ValidationError.Create("Invalid or modified Int hash token.")); } public Result DecodeLongIdHash(string hash) { if (string.IsNullOrWhiteSpace(hash)) return Result.Fail(ValidationError.Create("Invalid token layout.")); long[] decoded = hasher.DecodeLong(hash); return decoded.Length == 1 ? Result.Ok(decoded[0]) : Result.Fail(ValidationError.Create("Invalid or modified Long hash token.")); } public async Task> DecodeHexHash(string hex) => await Result.Try((Func>>)(async () => { string decoded = hasher.DecodeHex(hex); return string.IsNullOrEmpty(decoded) ? Result.Fail(ValidationError.Create("Invalid or corrupted hex hash.")) : Result.Ok(decoded); })); }