ValidationContext.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using TARGET = SharpGLTF.IO.JsonSerializable;
  6. namespace SharpGLTF.Validation
  7. {
  8. /// <summary>
  9. /// Utility class used in the process of model validation.
  10. /// </summary>
  11. [System.Diagnostics.DebuggerStepThrough]
  12. public struct ValidationContext
  13. {
  14. #region constructor
  15. public ValidationContext(ValidationResult result, TARGET target)
  16. {
  17. _Result = result;
  18. _Target = target;
  19. }
  20. #endregion
  21. #region data
  22. private readonly TARGET _Target;
  23. private readonly ValidationResult _Result;
  24. #endregion
  25. #region properties
  26. public Schema2.ModelRoot Root => _Result.Root;
  27. public ValidationResult Result => _Result;
  28. public bool TryFix => Result.Mode == Validation.ValidationMode.TryFix;
  29. #endregion
  30. #region API
  31. public ValidationContext GetContext(TARGET target) { return _Result.GetContext(target); }
  32. public void AddSchemaError(ValueLocation location, string message) { AddSchemaError(location.ToString(_Target, message)); }
  33. public bool TryFixLinkOrError(ValueLocation location, string message)
  34. {
  35. if (TryFix) AddLinkWarning(location.ToString(_Target, message));
  36. else AddLinkError(location.ToString(_Target, message));
  37. return TryFix;
  38. }
  39. public bool TryFixDataOrError(ValueLocation location, string message)
  40. {
  41. if (TryFix) AddDataWarning(location.ToString(_Target, message));
  42. else AddDataError(location.ToString(_Target, message));
  43. return TryFix;
  44. }
  45. public void AddLinkError(ValueLocation location, string message) { AddLinkError(location.ToString(_Target, message)); }
  46. public void AddLinkWarning(String format, params object[] args) { AddLinkWarning(String.Format(format, args)); }
  47. public void AddDataError(ValueLocation location, string message) { AddDataError(location.ToString(_Target, message)); }
  48. public void AddDataWarning(ValueLocation location, string message) { AddDataWarning(location.ToString(_Target, message)); }
  49. public void AddSemanticWarning(String format, params object[] args) { AddSemanticWarning(String.Format(format, args)); }
  50. public void AddSemanticError(String format, params object[] args) { AddSemanticError(String.Format(format, args)); }
  51. public void AddLinkWarning(string message)
  52. {
  53. var ex = new LinkException(_Target, message);
  54. _Result.AddWarning(ex);
  55. }
  56. public void AddLinkError(string message)
  57. {
  58. var ex = new LinkException(_Target, message);
  59. _Result.AddError(ex);
  60. }
  61. public void AddSchemaError(string message)
  62. {
  63. var ex = new SchemaException(_Target, message);
  64. _Result.AddError(ex);
  65. }
  66. public void AddDataWarning(string message)
  67. {
  68. var ex = new DataException(_Target, message);
  69. _Result.AddWarning(ex);
  70. }
  71. public void AddDataError(string message)
  72. {
  73. var ex = new DataException(_Target, message);
  74. _Result.AddError(ex);
  75. }
  76. public void AddSemanticError(String message)
  77. {
  78. var ex = new SemanticException(_Target, message);
  79. _Result.AddError(ex);
  80. }
  81. public void AddSemanticWarning(String message)
  82. {
  83. var ex = new SemanticException(_Target, message);
  84. _Result.AddWarning(ex);
  85. }
  86. #endregion
  87. #region schema errors
  88. public bool CheckSchemaIsDefined<T>(ValueLocation location, T value)
  89. where T : class
  90. {
  91. if (value != null) return true;
  92. AddSchemaError(location, "must be defined.");
  93. return false;
  94. }
  95. public bool CheckSchemaIsDefined<T>(ValueLocation location, T? value)
  96. where T : struct
  97. {
  98. if (value.HasValue) return true;
  99. AddSchemaError(location, "must be defined.");
  100. return false;
  101. }
  102. public bool CheckSchemaNonNegative(ValueLocation location, int? value)
  103. {
  104. if ((value ?? 0) >= 0) return true;
  105. AddSchemaError(location, "must be a non-negative integer.");
  106. return false;
  107. }
  108. public void CheckSchemaIsInRange<T>(ValueLocation location, T value, T minInclusive, T maxInclusive)
  109. where T : IComparable<T>
  110. {
  111. if (value.CompareTo(minInclusive) == -1) AddSchemaError(location, $"is below minimum {minInclusive} value: {value}");
  112. if (value.CompareTo(maxInclusive) == +1) AddSchemaError(location, $"is above maximum {maxInclusive} value: {value}");
  113. }
  114. public void CheckSchemaIsMultipleOf(ValueLocation location, int value, int multiple)
  115. {
  116. if ((value % multiple) == 0) return;
  117. AddSchemaError(location, $"Value {value} is not a multiple of {multiple}.");
  118. }
  119. public void CheckSchemaIsJsonSerializable(ValueLocation location, Object value)
  120. {
  121. if (IO.JsonUtils.IsSerializable(value)) return;
  122. AddSchemaError(location, "Invalid JSON data.");
  123. }
  124. #pragma warning disable CA1054 // Uri parameters should not be strings
  125. public void CheckSchemaIsValidURI(ValueLocation location, string gltfURI)
  126. {
  127. if (string.IsNullOrEmpty(gltfURI)) return;
  128. if (gltfURI.StartsWith("data:", StringComparison.Ordinal))
  129. {
  130. // check decoding
  131. return;
  132. }
  133. if (Uri.TryCreate(gltfURI, UriKind.Relative, out Uri xuri)) return;
  134. AddSchemaError(location, $"Invalid URI '{gltfURI}'.");
  135. }
  136. #pragma warning restore CA1054 // Uri parameters should not be strings
  137. #endregion
  138. #region semantic errors
  139. #endregion
  140. #region data errors
  141. public void CheckVertexIndex(ValueLocation location, UInt32 vertexIndex, UInt32 vertexCount, UInt32 vertexRestart)
  142. {
  143. if (vertexIndex == vertexRestart)
  144. {
  145. AddDataError(location, $"is a primitive restart value ({vertexIndex})");
  146. return;
  147. }
  148. if (vertexIndex >= vertexCount)
  149. {
  150. AddDataError(location, $"has a value ({vertexIndex}) that exceeds number of available vertices ({vertexCount})");
  151. return;
  152. }
  153. }
  154. public bool CheckIsFinite(ValueLocation location, System.Numerics.Vector2? value)
  155. {
  156. if (!value.HasValue) return true;
  157. if (value.Value._IsFinite()) return true;
  158. AddDataError(location, $"is NaN or Infinity.");
  159. return false;
  160. }
  161. public bool CheckIsFinite(ValueLocation location, System.Numerics.Vector3? value)
  162. {
  163. if (!value.HasValue) return true;
  164. if (value.Value._IsFinite()) return true;
  165. AddDataError(location, "is NaN or Infinity.");
  166. return false;
  167. }
  168. public bool CheckIsFinite(ValueLocation location, System.Numerics.Vector4? value)
  169. {
  170. if (!value.HasValue) return true;
  171. if (value.Value._IsFinite()) return true;
  172. AddDataError(location, "is NaN or Infinity.");
  173. return false;
  174. }
  175. public bool CheckIsFinite(ValueLocation location, System.Numerics.Quaternion? value)
  176. {
  177. if (!value.HasValue) return true;
  178. if (value.Value._IsFinite()) return true;
  179. AddDataError(location, "is NaN or Infinity.");
  180. return false;
  181. }
  182. public bool TryFixUnitLengthOrError(ValueLocation location, System.Numerics.Vector3? value)
  183. {
  184. if (!value.HasValue) return false;
  185. if (!CheckIsFinite(location, value)) return false;
  186. if (value.Value.IsValidNormal()) return false;
  187. return TryFixDataOrError(location, $"is not of unit length: {value.Value.Length()}.");
  188. }
  189. public bool TryFixTangentOrError(ValueLocation location, System.Numerics.Vector4 tangent)
  190. {
  191. if (TryFixUnitLengthOrError(location, new System.Numerics.Vector3(tangent.X, tangent.Y, tangent.Z))) return true;
  192. if (tangent.W == 1 || tangent.W == -1) return false;
  193. return TryFixDataOrError(location, $"has invalid value: {tangent.W}. Must be 1.0 or -1.0.");
  194. }
  195. public void CheckIsInRange(ValueLocation location, System.Numerics.Vector4 v, float minInclusive, float maxInclusive)
  196. {
  197. CheckIsInRange(location, v.X, minInclusive, maxInclusive);
  198. CheckIsInRange(location, v.Y, minInclusive, maxInclusive);
  199. CheckIsInRange(location, v.Z, minInclusive, maxInclusive);
  200. CheckIsInRange(location, v.W, minInclusive, maxInclusive);
  201. }
  202. public void CheckIsInRange(ValueLocation location, float value, float minInclusive, float maxInclusive)
  203. {
  204. if (value < minInclusive) AddDataError(location, $"is below minimum {minInclusive} value: {value}");
  205. if (value > maxInclusive) AddDataError(location, $"is above maximum {maxInclusive} value: {value}");
  206. }
  207. public bool CheckIsMatrix(ValueLocation location, System.Numerics.Matrix4x4? matrix)
  208. {
  209. if (matrix == null) return true;
  210. if (!matrix.Value._IsFinite())
  211. {
  212. AddDataError(location, "is NaN or Infinity.");
  213. return false;
  214. }
  215. if (!System.Numerics.Matrix4x4.Decompose(matrix.Value, out System.Numerics.Vector3 s, out System.Numerics.Quaternion r, out System.Numerics.Vector3 t))
  216. {
  217. AddDataError(location, "is not decomposable to TRS.");
  218. return false;
  219. }
  220. return true;
  221. }
  222. #endregion
  223. #region link errors
  224. public bool CheckArrayIndexAccess<T>(ValueLocation location, int? index, IReadOnlyList<T> array)
  225. {
  226. return CheckArrayRangeAccess(location, index, 1, array);
  227. }
  228. public bool CheckArrayRangeAccess<T>(ValueLocation location, int? offset, int length, IReadOnlyList<T> array)
  229. {
  230. if (!offset.HasValue) return true;
  231. if (!CheckSchemaNonNegative(location, offset)) return false;
  232. if (length <= 0)
  233. {
  234. AddSchemaError(location, "Invalid length");
  235. return false;
  236. }
  237. if (array == null)
  238. {
  239. AddLinkError(location, $"Index {offset} exceeds the number of available items (null).");
  240. return false;
  241. }
  242. if (offset > array.Count - length)
  243. {
  244. if (length == 1) AddLinkError(location, $"Index {offset} exceeds the number of available items ({array.Count}).");
  245. else AddLinkError(location, $"Index {offset}+{length} exceeds the number of available items ({array.Count}).");
  246. return false;
  247. }
  248. return true;
  249. }
  250. public bool CheckLinkMustBeAnyOf<T>(ValueLocation location, T value, params T[] values)
  251. {
  252. if (values.Contains(value)) return true;
  253. var validValues = string.Join(" ", values);
  254. AddLinkError(location, $"value {value} is invalid. Must be one of {validValues}");
  255. return false;
  256. }
  257. public bool CheckLinksInCollection<T>(ValueLocation location, IEnumerable<T> collection)
  258. where T : class
  259. {
  260. int idx = 0;
  261. if (collection == null)
  262. {
  263. AddLinkError(location, "Is NULL.");
  264. return false;
  265. }
  266. var uniqueInstances = new HashSet<T>();
  267. foreach (var v in collection)
  268. {
  269. if (v == null)
  270. {
  271. AddLinkError((location, idx), "Is NULL.");
  272. return false;
  273. }
  274. else if (uniqueInstances.Contains(v))
  275. {
  276. AddSchemaError((location, idx), "Is duplicated.");
  277. return false;
  278. }
  279. uniqueInstances.Add(v);
  280. ++idx;
  281. }
  282. return true;
  283. }
  284. public void UnsupportedExtensionError(String message)
  285. {
  286. AddLinkError(message);
  287. }
  288. #endregion
  289. }
  290. public struct ValueLocation
  291. {
  292. public static implicit operator ValueLocation(int index) { return new ValueLocation(string.Empty, index); }
  293. public static implicit operator ValueLocation(int? index) { return new ValueLocation(string.Empty, index ?? 0); }
  294. public static implicit operator ValueLocation(string name) { return new ValueLocation(name); }
  295. public static implicit operator ValueLocation((string name, int index) tuple) { return new ValueLocation(tuple.name, tuple.index); }
  296. public static implicit operator ValueLocation((string name, int? index) tuple) { return new ValueLocation(tuple.name, tuple.index ?? 0); }
  297. public static implicit operator String(ValueLocation location) { return location.ToString(); }
  298. private ValueLocation(string name, int idx1 = -1)
  299. {
  300. _Name = name;
  301. _Index = idx1;
  302. }
  303. private readonly string _Name;
  304. private readonly int _Index;
  305. public override string ToString()
  306. {
  307. if (_Index >= 0) return $"{_Name}[{_Index}]";
  308. return _Name;
  309. }
  310. public string ToString(TARGET target, string message)
  311. {
  312. return ToString(target) + " " + message;
  313. }
  314. public string ToString(TARGET target)
  315. {
  316. if (target == null) return this.ToString();
  317. var name = target.GetType().Name;
  318. var pinfo = target.GetType().GetProperty("LogicalIndex");
  319. if (pinfo != null)
  320. {
  321. var idx = pinfo.GetValue(target);
  322. name += $"[{idx}]";
  323. }
  324. return name + this.ToString();
  325. }
  326. }
  327. }