|
| 1 | +using System.Collections.Immutable; |
| 2 | +using System.Diagnostics.CodeAnalysis; |
| 3 | +using System.Globalization; |
| 4 | +using KeyLoad.Comparisons.Features.BenchmarkComparisons.TimeSeries.Intensive; |
| 5 | +using Microsoft.Extensions.Configuration; |
| 6 | +using Npgsql; |
| 7 | + |
| 8 | +namespace KeyLoad.ComparisonHost.Features.BenchmarkComparisons; |
| 9 | + |
| 10 | +internal static class TimeSeriesIntensiveHostInput |
| 11 | +{ |
| 12 | + internal static InvalidOperationException Invalid() => new(TimeSeriesIntensiveHostConstants.InvalidCode); |
| 13 | + |
| 14 | + internal static string Required(IConfiguration configuration, string key) |
| 15 | + { |
| 16 | + var value = configuration[key]; |
| 17 | + if (!IsPrivateText(value)) |
| 18 | + { |
| 19 | + throw Invalid(); |
| 20 | + } |
| 21 | + return value; |
| 22 | + } |
| 23 | + |
| 24 | + private static bool IsPrivateText([NotNullWhen(true)] string? value) => !string.IsNullOrWhiteSpace(value) |
| 25 | + && !value.Contains(TimeSeriesIntensiveHostConstants.CarriageReturn, StringComparison.Ordinal) |
| 26 | + && !value.Contains(TimeSeriesIntensiveHostConstants.LineFeed, StringComparison.Ordinal); |
| 27 | + |
| 28 | + internal static long ReadJob(IConfiguration configuration) |
| 29 | + { |
| 30 | + var value = Required(configuration, TimeSeriesIntensiveHostConstants.JobId); |
| 31 | + if (!long.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out var result) |
| 32 | + || result <= TimeSeriesIntensiveHostConstants.FirstIndex || value != result.ToString(CultureInfo.InvariantCulture)) |
| 33 | + { |
| 34 | + throw Invalid(); |
| 35 | + } |
| 36 | + return result; |
| 37 | + } |
| 38 | + |
| 39 | + internal static string ReadOutput(IConfiguration configuration) |
| 40 | + { |
| 41 | + var value = Required(configuration, ComparisonHostConstants.Output); |
| 42 | + if (!Path.IsPathFullyQualified(value)) |
| 43 | + { |
| 44 | + throw Invalid(); |
| 45 | + } |
| 46 | + return Path.GetFullPath(value); |
| 47 | + } |
| 48 | + |
| 49 | + internal static void ValidateNativeSection(IConfiguration configuration, TimeSeriesIntensiveTargetKind target) |
| 50 | + { |
| 51 | + var allowed = target == TimeSeriesIntensiveTargetKind.KeyLoad |
| 52 | + ? TimeSeriesIntensiveHostConstants.KeyLoadNativeFields : TimeSeriesIntensiveHostConstants.TimescaleNativeFields; |
| 53 | + var section = configuration.GetSection(TimeSeriesIntensiveHostConstants.NativeSection); |
| 54 | + var children = section.GetChildren().Take(allowed.Length + TimeSeriesIntensiveHostConstants.OverflowLookahead).ToArray(); |
| 55 | + if (section.Value is not null || children.Length > allowed.Length) |
| 56 | + { |
| 57 | + throw Invalid(); |
| 58 | + } |
| 59 | + foreach (var child in children) |
| 60 | + { |
| 61 | + if (!allowed.Contains(child.Key, StringComparer.OrdinalIgnoreCase) |
| 62 | + || !string.Equals(child.Key, TimeSeriesIntensiveHostConstants.EndpointsField, StringComparison.OrdinalIgnoreCase) |
| 63 | + && !string.Equals(child.Key, TimeSeriesIntensiveHostConstants.VoterIdsField, StringComparison.OrdinalIgnoreCase) |
| 64 | + && child.GetChildren().Any()) |
| 65 | + { |
| 66 | + throw Invalid(); |
| 67 | + } |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + internal static ImmutableArray<string> ReadArray(IConfiguration configuration, string key, int count) |
| 72 | + { |
| 73 | + var section = configuration.GetSection(key); |
| 74 | + var children = section.GetChildren().Take(count + TimeSeriesIntensiveHostConstants.OverflowLookahead).ToArray(); |
| 75 | + if (section.Value is not null || children.Length != count) |
| 76 | + { |
| 77 | + throw Invalid(); |
| 78 | + } |
| 79 | + var values = ImmutableArray.CreateBuilder<string>(count); |
| 80 | + for (var index = TimeSeriesIntensiveHostConstants.FirstIndex; index < count; index++) |
| 81 | + { |
| 82 | + var suffix = index.ToString(CultureInfo.InvariantCulture); |
| 83 | + var child = children.SingleOrDefault(item => item.Key == suffix); |
| 84 | + if (child is null || child.GetChildren().Any()) |
| 85 | + { |
| 86 | + throw Invalid(); |
| 87 | + } |
| 88 | + values.Add(Required(configuration, child.Path)); |
| 89 | + } |
| 90 | + return values.MoveToImmutable(); |
| 91 | + } |
| 92 | + |
| 93 | + internal static ImmutableArray<Uri> ReadEndpoints(IConfiguration configuration, string key, int count, bool tcp) |
| 94 | + => ValidateAuthorities(ReadArray(configuration, key, count), tcp); |
| 95 | + |
| 96 | + internal static ImmutableArray<Uri> ValidateAuthorities(ImmutableArray<string> values, bool tcp) |
| 97 | + { |
| 98 | + var endpoints = ImmutableArray.CreateBuilder<Uri>(values.Length); |
| 99 | + foreach (var value in values) |
| 100 | + { |
| 101 | + endpoints.Add(ReadAuthority(value, tcp)); |
| 102 | + } |
| 103 | + var result = endpoints.MoveToImmutable(); |
| 104 | + if (result.Select(item => item.GetLeftPart(UriPartial.Authority)).Distinct(StringComparer.OrdinalIgnoreCase).Count() != values.Length) |
| 105 | + { |
| 106 | + throw Invalid(); |
| 107 | + } |
| 108 | + return result; |
| 109 | + } |
| 110 | + |
| 111 | + private static Uri ReadAuthority(string value, bool tcp) |
| 112 | + { |
| 113 | + if (value.Any(static character => char.IsWhiteSpace(character) || char.IsControl(character)) |
| 114 | + || !Uri.TryCreate(value, UriKind.Absolute, out var endpoint) || !endpoint.IsWellFormedOriginalString() |
| 115 | + || string.IsNullOrWhiteSpace(endpoint.Host) || endpoint.UserInfo.Length != TimeSeriesIntensiveHostConstants.FirstIndex |
| 116 | + || endpoint.Query.Length != TimeSeriesIntensiveHostConstants.FirstIndex || endpoint.Fragment.Length != TimeSeriesIntensiveHostConstants.FirstIndex |
| 117 | + || !string.IsNullOrEmpty(endpoint.AbsolutePath) && endpoint.AbsolutePath != TimeSeriesIntensiveHostConstants.RootPath |
| 118 | + || (tcp ? endpoint.Scheme != TimeSeriesIntensiveHostConstants.TcpScheme |
| 119 | + || endpoint.Port is < TimeSeriesIntensiveHostConstants.MinimumPort or > TimeSeriesIntensiveHostConstants.MaximumPort |
| 120 | + : endpoint.Scheme != Uri.UriSchemeHttp && endpoint.Scheme != Uri.UriSchemeHttps)) |
| 121 | + { |
| 122 | + throw Invalid(); |
| 123 | + } |
| 124 | + return endpoint; |
| 125 | + } |
| 126 | + |
| 127 | + internal static Guid ReadIncarnation(IConfiguration configuration) |
| 128 | + { |
| 129 | + var value = Required(configuration, TimeSeriesIntensiveHostConstants.Incarnation); |
| 130 | + if (!Guid.TryParseExact(value, TimeSeriesIntensiveHostConstants.GuidD, out var result) |
| 131 | + || result == Guid.Empty || value != result.ToString(TimeSeriesIntensiveHostConstants.GuidD, CultureInfo.InvariantCulture)) |
| 132 | + { |
| 133 | + throw Invalid(); |
| 134 | + } |
| 135 | + return result; |
| 136 | + } |
| 137 | + |
| 138 | + internal static void RejectPresent(IConfiguration configuration, string key) |
| 139 | + { |
| 140 | + var section = configuration.GetSection(key); |
| 141 | + if (section.Value is not null || section.GetChildren().Any()) |
| 142 | + { |
| 143 | + throw Invalid(); |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + internal static string ReadConnection(IConfiguration configuration) |
| 148 | + { |
| 149 | + var value = Required(configuration, TimeSeriesIntensiveHostConstants.ConnectionString); |
| 150 | + try |
| 151 | + { |
| 152 | + var parsed = new NpgsqlConnectionStringBuilder(value); |
| 153 | + if (!IsPrivateText(parsed.Host) || parsed.Host.Contains(TimeSeriesIntensiveHostConstants.HostSeparator, StringComparison.Ordinal) |
| 154 | + || parsed.Port is < TimeSeriesIntensiveHostConstants.MinimumPort or > TimeSeriesIntensiveHostConstants.MaximumPort |
| 155 | + || !IsPrivateText(parsed.Username) || !IsPrivateText(parsed.Password)) |
| 156 | + { |
| 157 | + throw Invalid(); |
| 158 | + } |
| 159 | + return value; |
| 160 | + } |
| 161 | + catch (ArgumentException) |
| 162 | + { |
| 163 | + throw Invalid(); |
| 164 | + } |
| 165 | + catch (FormatException) |
| 166 | + { |
| 167 | + throw Invalid(); |
| 168 | + } |
| 169 | + catch (OverflowException) |
| 170 | + { |
| 171 | + throw Invalid(); |
| 172 | + } |
| 173 | + } |
| 174 | +} |
0 commit comments