-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathIValidatableExtension.cs
131 lines (117 loc) · 4.35 KB
/
IValidatableExtension.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using System;
using System.Collections;
using System.Collections.Generic;
using Screenplay;
using Object = UnityEngine.Object;
public static class IValidatableExtension
{
static readonly EmptyEnumerable _empty = new();
public static IEnumerable<(string name, IValidatable validatable)> NoSubValues(this IValidatable this_) => _empty;
/// <summary>
/// Return a collection containing sub values recursively, handles infinite recursion properly
/// </summary>
public static HashSet<IValidatable> GetAllSubValues(this IValidatable this_)
{
var checkedSet = new HashSet<IValidatable>();
var leftToCheck = new Queue<IValidatable>();
leftToCheck.Enqueue(this_);
checkedSet.Add(this_);
while (leftToCheck.Count > 0)
{
IValidatable current = leftToCheck.Dequeue();
foreach ((string name, IValidatable validatable) e in current.GetSubValues())
{
if (checkedSet.Add(e.validatable))
leftToCheck.Enqueue(e.validatable);
}
}
return checkedSet;
}
/// <summary>
/// Go through this object and its hierarchy to validate them all,
/// will throw an <see cref="ValidationException"/> with the appropriate data if it is invalid
/// </summary>
public static void ValidateAll(this IValidatable this_)
{
var checkedSet = new HashSet<IValidatable>();
var leftToCheck = new Queue<IValidatable>();
leftToCheck.Enqueue(this_);
checkedSet.Add(this_);
while (leftToCheck.Count > 0)
{
IValidatable current = leftToCheck.Dequeue();
try
{
current.ValidateSelf();
}
catch (Exception e2)
{
PathTo(this_, current, new HashSet<IValidatable>(), out string path, out Object closestObj);
throw new ValidationException(path, closestObj, e2);
}
foreach ((string name, IValidatable validatable) e in current.GetSubValues())
{
if (e.validatable == null)
{
PathTo(this_, current, new HashSet<IValidatable>(), out string path, out Object closestObj);
throw new ValidationException(path, closestObj, new Exception($"{current.GetType().Name}.{e.name} is null"));
}
if (checkedSet.Add(e.validatable))
leftToCheck.Enqueue(e.validatable);
}
}
}
static bool PathTo(IValidatable node, IValidatable query, HashSet<IValidatable> done, out string localPath, out Object closestObj)
{
if (!done.Add(node))
{
localPath = null;
closestObj = null;
return false;
}
if (node == null && query == null)
{
localPath = "null";
closestObj = null;
return true;
}
if (node == query)
{
closestObj = node as Object;
localPath = GetNameFor(node);
return true;
}
foreach ((string name, IValidatable validatable) e in node.GetSubValues())
{
if (PathTo(e.validatable, query, done, out localPath, out Object c))
{
localPath = $"{GetNameFor(node)} / {e.name} / {localPath}";
closestObj = c != null ? c : node as Object;
return true;
}
}
localPath = null;
closestObj = null;
return false;
static string GetNameFor(IValidatable v)
{
if (v is Object o && o != null)
return o.ToString();
return v.GetType().Name;
}
}
class EmptyEnumerable : IEnumerable<(string name, IValidatable validatable)>
{
readonly EmptyEnumerator _empty = new();
public IEnumerator<(string name, IValidatable validatable)> GetEnumerator() => _empty;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
class EmptyEnumerator : IEnumerator<(string name, IValidatable validatable)>
{
public (string name, IValidatable validatable) Current { get; }
object IEnumerator.Current => Current;
public bool MoveNext() => false;
public void Reset() { }
public void Dispose() { }
}
}
}