| 1 | using CascadeIDE.Cockpit.Composition.HostSurface; |
| 2 | using CascadeIDE.Models; |
| 3 | |
| 4 | namespace CascadeIDE.Services; |
| 5 | |
| 6 | internal interface ISettingsValidationSpecification |
| 7 | { |
| 8 | IEnumerable<string> Validate(CascadeIdeSettings settings); |
| 9 | } |
| 10 | |
| 11 | internal sealed class DisplaySettingsValidationSpecification : ISettingsValidationSpecification |
| 12 | { |
| 13 | public IEnumerable<string> Validate(CascadeIdeSettings settings) |
| 14 | { |
| 15 | var display = settings.Display; |
| 16 | |
| 17 | var im = display.Mount; |
| 18 | if (!IsScore(im.MinSa)) |
| 19 | yield return "[display.mount] min_sa must be in [0..1]."; |
| 20 | if (!IsScore(im.MinPerformance)) |
| 21 | yield return "[display.mount] min_performance must be in [0..1]."; |
| 22 | if (!IsScore(im.MaxWorkload)) |
| 23 | yield return "[display.mount] max_workload must be in [0..1]."; |
| 24 | |
| 25 | for (var i = 0; i < im.Rules.Count; i++) |
| 26 | { |
| 27 | var rule = im.Rules[i]; |
| 28 | if (string.IsNullOrWhiteSpace(rule.Style)) |
| 29 | yield return $"[display.mount.rules][{i}] style is required."; |
| 30 | if (rule.SaScore is { } sa && !IsScore(sa)) |
| 31 | yield return $"[display.mount.rules][{i}] sa_score must be in [0..1]."; |
| 32 | if (rule.PerformanceScore is { } perf && !IsScore(perf)) |
| 33 | yield return $"[display.mount.rules][{i}] performance_score must be in [0..1]."; |
| 34 | if (rule.WorkloadScore is { } workload && !IsScore(workload)) |
| 35 | yield return $"[display.mount.rules][{i}] workload_score must be in [0..1]."; |
| 36 | } |
| 37 | |
| 38 | if (display.Instruments is { Count: > 0 } routing) |
| 39 | { |
| 40 | foreach (var kv in routing) |
| 41 | { |
| 42 | if (string.IsNullOrWhiteSpace(kv.Key)) |
| 43 | { |
| 44 | yield return "[display.instruments] key must not be empty."; |
| 45 | continue; |
| 46 | } |
| 47 | |
| 48 | var k = kv.Key.Trim(); |
| 49 | var isPrimary = k.Equals(InstrumentRoutingSlotKeys.PfdPrimary, StringComparison.OrdinalIgnoreCase) |
| 50 | || k.Equals(InstrumentRoutingSlotKeys.MfdPrimary, StringComparison.OrdinalIgnoreCase); |
| 51 | var isStrip = InstrumentRoutingSlotKeys.IsStatusStripKey(k); |
| 52 | |
| 53 | if (!isPrimary && !isStrip) |
| 54 | { |
| 55 | yield return $"[display.instruments] unknown key '{k}' (expected {InstrumentRoutingSlotKeys.PfdPrimary}, {InstrumentRoutingSlotKeys.MfdPrimary}, {InstrumentRoutingSlotKeys.PfdStatusStrip}, or {InstrumentRoutingSlotKeys.ForwardStatusStrip})."; |
| 56 | } |
| 57 | |
| 58 | if (string.IsNullOrWhiteSpace(kv.Value)) |
| 59 | yield return $"[display.instruments] value for '{k}' is required."; |
| 60 | else if (isStrip) |
| 61 | { |
| 62 | if (!InstrumentStatusStripRouting.TryParse(kv.Value, out var showStrip, out _)) |
| 63 | yield return $"[display.instruments] unknown value for '{k}' (expected {InstrumentStatusStripRouting.None} or background_status)."; |
| 64 | } |
| 65 | else if (!InstrumentRoutingAliasResolver.TryResolve(kv.Value, out _)) |
| 66 | yield return $"[display.instruments] unknown instrument alias or id for '{k}'."; |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | private static bool IsScore(double value) => value is >= 0 and <= 1; |
| 72 | } |
| 73 | |