|
|
@@ -9,149 +9,244 @@ namespace SharpGLTF.Validation
|
|
|
/// <summary>
|
|
|
/// Utility class used in the process of model validation.
|
|
|
/// </summary>
|
|
|
- class ValidationContext
|
|
|
+ [System.Diagnostics.DebuggerStepThrough]
|
|
|
+ public struct ValidationContext
|
|
|
{
|
|
|
- // we should try to align validation errors to these issues:
|
|
|
- // https://github.com/KhronosGroup/glTF-Validator/blob/master/ISSUES.md
|
|
|
+ #region constructor
|
|
|
|
|
|
- #region data
|
|
|
+ public ValidationContext(ValidationResult result, TARGET target)
|
|
|
+ {
|
|
|
+ _Result = result;
|
|
|
+ _Target = target;
|
|
|
+ }
|
|
|
|
|
|
- private readonly List<Exception> _Errors = new List<Exception>();
|
|
|
- private readonly List<Exception> _Warnings = new List<Exception>();
|
|
|
+ #endregion
|
|
|
|
|
|
- public IEnumerable<Exception> Errors => _Errors;
|
|
|
+ #region data
|
|
|
|
|
|
- public bool HasErrors => _Errors.Count > 0;
|
|
|
+ private readonly TARGET _Target;
|
|
|
+ private readonly ValidationResult _Result;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
- #region errors
|
|
|
+ #region properties
|
|
|
|
|
|
- private void _AddError(Exception ex)
|
|
|
- {
|
|
|
- throw ex;
|
|
|
- _Errors.Add(ex);
|
|
|
- }
|
|
|
+ public ValidationResult Result => _Result;
|
|
|
|
|
|
- public void AddError(TARGET target, String message)
|
|
|
- {
|
|
|
- _AddError(new ModelException(target, message));
|
|
|
- }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region API
|
|
|
+
|
|
|
+ public ValidationContext GetContext(TARGET target) { return _Result.GetContext(target); }
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region schema errors
|
|
|
|
|
|
- public void CheckIsDefined<T>(TARGET target, string property, T? value)
|
|
|
+ public bool CheckIsDefined<T>(string property, T? value)
|
|
|
where T : struct
|
|
|
{
|
|
|
- if (!value.HasValue) AddSchemaError(target, property, ErrorCodes.UNDEFINED_PROPERTY);
|
|
|
+ if (value.HasValue) return true;
|
|
|
+
|
|
|
+ AddSchemaError(property, ErrorCodes.UNDEFINED_PROPERTY);
|
|
|
+
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
- public void CheckIndex(TARGET target, string property, int? index, int maxExclusive)
|
|
|
+ public void CheckIndex(string property, int? index, int maxExclusive)
|
|
|
{
|
|
|
- if (index.HasValue)
|
|
|
- {
|
|
|
- if (index.Value < 0) AddSchemaError(target, property, ErrorCodes.INVALID_INDEX, index);
|
|
|
- if (index.Value >= maxExclusive) AddSchemaError(target, property, ErrorCodes.VALUE_NOT_IN_RANGE, index);
|
|
|
- }
|
|
|
+ if (!index.HasValue) return;
|
|
|
+
|
|
|
+ if (index.Value < 0) AddSchemaError(property, ErrorCodes.INVALID_INDEX, index);
|
|
|
+ if (index.Value >= maxExclusive) AddSchemaError(property, ErrorCodes.VALUE_NOT_IN_RANGE, index);
|
|
|
}
|
|
|
|
|
|
- public void CheckMultipleOf(TARGET target, string property, int value, int multiple)
|
|
|
+ public void CheckMultipleOf(string property, int value, int multiple)
|
|
|
{
|
|
|
- if ((value % multiple) != 0) AddSchemaError(target, property, ErrorCodes.VALUE_MULTIPLE_OF, value, multiple);
|
|
|
+ if ((value % multiple) == 0) return;
|
|
|
+
|
|
|
+ AddSchemaError(property, ErrorCodes.VALUE_MULTIPLE_OF, value, multiple);
|
|
|
}
|
|
|
|
|
|
- public void CheckJsonSerializable(TARGET target, string property, Object value)
|
|
|
+ public void CheckJsonSerializable(string property, Object value)
|
|
|
{
|
|
|
- if (!IO.JsonUtils.IsSerializable(value)) AddSchemaError(target, property, ErrorCodes.INVALID_JSON, string.Empty);
|
|
|
+ if (IO.JsonUtils.IsSerializable(value)) return;
|
|
|
+
|
|
|
+ AddSchemaError(property, ErrorCodes.INVALID_JSON, string.Empty);
|
|
|
}
|
|
|
|
|
|
- public void AddSchemaError(TARGET target, string property, String format, params object[] args)
|
|
|
+ public void AddSchemaError(string property, String format, params object[] args)
|
|
|
{
|
|
|
- _AddError(new SchemaException(target, property + " " + String.Format(format, args)));
|
|
|
+ var message = property + " " + String.Format(format, args);
|
|
|
+
|
|
|
+ var ex = new SchemaException(_Target, message);
|
|
|
+
|
|
|
+ _Result.AddError(ex);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region semantic errors
|
|
|
|
|
|
- public void AddSemanticError(TARGET target, String message)
|
|
|
+ public void AddSemanticError(String message)
|
|
|
{
|
|
|
- _Errors.Add(new SemanticException(target, message));
|
|
|
+ var ex = new SemanticException(_Target, message);
|
|
|
+
|
|
|
+ _Result.AddError(ex);
|
|
|
}
|
|
|
|
|
|
- public void AddSemanticWarning(TARGET target, String format, params object[] args)
|
|
|
+ public void AddSemanticWarning(String format, params object[] args)
|
|
|
{
|
|
|
- _Warnings.Add(new SemanticException(target, String.Format(format, args)));
|
|
|
+ var message = String.Format(format, args);
|
|
|
+
|
|
|
+ var ex = new SemanticException(_Target, message);
|
|
|
+
|
|
|
+ _Result.AddWarning(ex);
|
|
|
}
|
|
|
|
|
|
- public void AddSemanticError(TARGET target, String format, params object[] args)
|
|
|
+ public void AddSemanticError(String format, params object[] args)
|
|
|
{
|
|
|
- _AddError(new SemanticException(target, String.Format(format, args)));
|
|
|
+ var message = String.Format(format, args);
|
|
|
+
|
|
|
+ var ex = new SemanticException(_Target, message);
|
|
|
+
|
|
|
+ _Result.AddError(ex);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region data errors
|
|
|
|
|
|
- public void CheckVertexIndex(Schema2.Accessor target, int index, UInt32 vertexIndex, UInt32 vertexCount, UInt32 vertexRestart)
|
|
|
+ public void CheckVertexIndex(int index, UInt32 vertexIndex, UInt32 vertexCount, UInt32 vertexRestart)
|
|
|
{
|
|
|
- if (vertexIndex == vertexRestart) AddDataError(target, ErrorCodes.ACCESSOR_INDEX_PRIMITIVE_RESTART, index, vertexIndex);
|
|
|
- else if (vertexIndex >= vertexCount) AddDataError(target, ErrorCodes.ACCESSOR_INDEX_OOB, index, vertexIndex, vertexCount);
|
|
|
+ if (vertexIndex == vertexRestart) AddDataError(ErrorCodes.ACCESSOR_INDEX_PRIMITIVE_RESTART, index, vertexIndex);
|
|
|
+ else if (vertexIndex >= vertexCount) AddDataError(ErrorCodes.ACCESSOR_INDEX_OOB, index, vertexIndex, vertexCount);
|
|
|
}
|
|
|
|
|
|
- public void CheckDataIsFinite(Schema2.Accessor target, int index, System.Numerics.Vector3 v)
|
|
|
+ public void CheckDataIsFinite(int index, System.Numerics.Vector3 v)
|
|
|
{
|
|
|
- if (!v._IsFinite()) AddDataError(target, ErrorCodes.ACCESSOR_INVALID_FLOAT, index);
|
|
|
+ if (v._IsFinite()) return;
|
|
|
+
|
|
|
+ AddDataError(ErrorCodes.ACCESSOR_INVALID_FLOAT, index);
|
|
|
}
|
|
|
|
|
|
- public void CheckDataIsFinite(Schema2.Accessor target, int index, System.Numerics.Vector4 v)
|
|
|
+ public void CheckDataIsFinite(int index, System.Numerics.Vector4 v)
|
|
|
{
|
|
|
- if (!v._IsFinite()) AddDataError(target, ErrorCodes.ACCESSOR_INVALID_FLOAT, index);
|
|
|
+ if (v._IsFinite()) return;
|
|
|
+
|
|
|
+ AddDataError(ErrorCodes.ACCESSOR_INVALID_FLOAT, index);
|
|
|
}
|
|
|
|
|
|
- public void CheckDataIsUnitLength(Schema2.Accessor target, int index, System.Numerics.Vector3 v)
|
|
|
+ public void CheckDataIsUnitLength(int index, System.Numerics.Vector3 v)
|
|
|
{
|
|
|
- if (!v.IsValidNormal()) AddDataError(target, ErrorCodes.ACCESSOR_NON_UNIT, index, v.Length());
|
|
|
+ if (v.IsValidNormal()) return;
|
|
|
+
|
|
|
+ AddDataError(ErrorCodes.ACCESSOR_NON_UNIT, index, v.Length());
|
|
|
}
|
|
|
|
|
|
- public void CheckDataIsInRange(Schema2.Accessor target, int index, System.Numerics.Vector4 v, float minInclusive, float maxInclusive)
|
|
|
+ public void CheckDataIsInRange(int index, System.Numerics.Vector4 v, float minInclusive, float maxInclusive)
|
|
|
{
|
|
|
- CheckDataIsInRange(target, index, v.X, minInclusive, maxInclusive);
|
|
|
- CheckDataIsInRange(target, index, v.Y, minInclusive, maxInclusive);
|
|
|
- CheckDataIsInRange(target, index, v.Z, minInclusive, maxInclusive);
|
|
|
- CheckDataIsInRange(target, index, v.W, minInclusive, maxInclusive);
|
|
|
+ CheckDataIsInRange(index, v.X, minInclusive, maxInclusive);
|
|
|
+ CheckDataIsInRange(index, v.Y, minInclusive, maxInclusive);
|
|
|
+ CheckDataIsInRange(index, v.Z, minInclusive, maxInclusive);
|
|
|
+ CheckDataIsInRange(index, v.W, minInclusive, maxInclusive);
|
|
|
}
|
|
|
|
|
|
- public void CheckDataIsInRange(Schema2.Accessor target, int index, float v, float minInclusive, float maxInclusive)
|
|
|
+ public void CheckDataIsInRange(int index, float v, float minInclusive, float maxInclusive)
|
|
|
{
|
|
|
- if (v < minInclusive) AddDataError(target, ErrorCodes.ACCESSOR_ELEMENT_OUT_OF_MIN_BOUND, index, v);
|
|
|
- if (v > maxInclusive) AddDataError(target, ErrorCodes.ACCESSOR_ELEMENT_OUT_OF_MAX_BOUND, index, v);
|
|
|
+ if (v < minInclusive) AddDataError(ErrorCodes.ACCESSOR_ELEMENT_OUT_OF_MIN_BOUND, index, v);
|
|
|
+ if (v > maxInclusive) AddDataError(ErrorCodes.ACCESSOR_ELEMENT_OUT_OF_MAX_BOUND, index, v);
|
|
|
}
|
|
|
|
|
|
- public void CheckDataIsValidSign(Schema2.Accessor target, int index, float w)
|
|
|
+ public void CheckDataIsValidSign(int index, float w)
|
|
|
{
|
|
|
- if (w != 1 && w != -1) AddDataError(target, ErrorCodes.ACCESSOR_INVALID_SIGN, index, w);
|
|
|
+ if (w == 1 || w == -1) return;
|
|
|
+
|
|
|
+ AddDataError(ErrorCodes.ACCESSOR_INVALID_SIGN, index, w);
|
|
|
}
|
|
|
|
|
|
- public void AddDataError(TARGET target, String format, params object[] args)
|
|
|
+ public void AddDataError(String format, params object[] args)
|
|
|
{
|
|
|
- _AddError(new DataException(target, String.Format(format, args)));
|
|
|
+ var message = String.Format(format, args);
|
|
|
+
|
|
|
+ var ex = new DataException(_Target, message);
|
|
|
+
|
|
|
+ _Result.AddError(ex);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region link errors
|
|
|
|
|
|
- public void UnsupportedExtensionError(TARGET target, String message)
|
|
|
+ public bool CheckReferenceIndex<T>(string property, int? index, IReadOnlyList<T> collection)
|
|
|
+ {
|
|
|
+ if (!index.HasValue) return true;
|
|
|
+ if (index.Value >= 0 && index.Value < collection.Count) return true;
|
|
|
+
|
|
|
+ AddLinkError(ErrorCodes.UNRESOLVED_REFERENCE, property);
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void UnsupportedExtensionError(String message)
|
|
|
+ {
|
|
|
+ AddLinkError(message);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void AddLinkError(String format, params object[] args)
|
|
|
+ {
|
|
|
+ var message = String.Format(format, args);
|
|
|
+
|
|
|
+ var ex = new LinkException(_Target, message);
|
|
|
+
|
|
|
+ _Result.AddError(ex);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void AddLinkWarning(String format, params object[] args)
|
|
|
+ {
|
|
|
+ var message = String.Format(format, args);
|
|
|
+
|
|
|
+ var ex = new LinkException(_Target, message);
|
|
|
+
|
|
|
+ _Result.AddWarning(ex);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+ }
|
|
|
+
|
|
|
+ public sealed class ValidationResult
|
|
|
+ {
|
|
|
+ #region data
|
|
|
+
|
|
|
+ private readonly List<Exception> _Errors = new List<Exception>();
|
|
|
+ private readonly List<Exception> _Warnings = new List<Exception>();
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region properties
|
|
|
+
|
|
|
+ public IEnumerable<Exception> Errors => _Errors;
|
|
|
+
|
|
|
+ public bool HasErrors => _Errors.Count > 0;
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region API
|
|
|
+
|
|
|
+ public ValidationContext GetContext(TARGET target) { return new ValidationContext(this, target); }
|
|
|
+
|
|
|
+ public void AddWarning(ModelException ex)
|
|
|
{
|
|
|
- AddLinkError(target, message);
|
|
|
+ _Warnings.Add(ex);
|
|
|
}
|
|
|
|
|
|
- public void AddLinkError(TARGET target, String format, params object[] args)
|
|
|
+ public void AddError(ModelException ex)
|
|
|
{
|
|
|
- _AddError(new LinkException(target, String.Format(format, args)));
|
|
|
+ #if DEBUG
|
|
|
+ throw ex;
|
|
|
+ #else
|
|
|
+ _Errors.Add(ex);
|
|
|
+ #endif
|
|
|
}
|
|
|
|
|
|
#endregion
|