Sfoglia il codice sorgente

more validation tests

Vicente Penades 6 anni fa
parent
commit
9fbb08e393

+ 1 - 4
src/SharpGLTF.Core/IO/JsonSerializable.cs

@@ -18,10 +18,7 @@ namespace SharpGLTF.IO
             OnValidateReferences(context);
         }
 
-        protected virtual void OnValidateReferences(Validation.ValidationContext result)
-        {
-
-        }
+        protected virtual void OnValidateReferences(Validation.ValidationContext result) { }
 
         internal void Validate(Validation.ValidationContext context)
         {

+ 2 - 2
src/SharpGLTF.Core/Schema2/gltf.Asset.cs

@@ -95,14 +95,14 @@ namespace SharpGLTF.Schema2
 
             if (!Version.TryParse(_version, out Version ver))
             {
-                result.AddSemanticError(Validation.ErrorCodes.UNKNOWN_ASSET_MAJOR_VERSION, _version);
+                result.AddSemanticError("Version", $"Unknown glTF major asset version: {_version}.");
                 return;
             }
 
             if (Version < MINVERSION) result.AddSemanticError($"Minimum supported version is {MINVERSION} but found:{MinVersion}");
             if (MinVersion > MAXVERSION) result.AddSemanticError( $"Maximum supported version is {MAXVERSION} but found:{MinVersion}");
 
-            if (MinVersion > Version) result.AddSemanticWarning(Validation.WarnCodes.ASSET_MIN_VERSION_GREATER_THAN_VERSION, MinVersion, Version);
+            if (MinVersion > Version) result.AddSemanticWarning("Version", $"Asset minVersion '{MinVersion}' is greater than version '{Version}'.");
         }
 
         #endregion

+ 2 - 2
src/SharpGLTF.Core/Schema2/gltf.Mesh.cs

@@ -112,9 +112,9 @@ namespace SharpGLTF.Schema2
                 .Select(item => item.MorphTargetsCount)
                 .Distinct();
 
-            if (morphTargetsCount.Count() != 1) result.AddSemanticError(Validation.ErrorCodes.MESH_PRIMITIVES_UNEQUAL_TARGETS_COUNT);
+            if (morphTargetsCount.Count() != 1) result.AddSemanticError("Count", "All primitives must have the same number of morph targets.");
 
-            if (_weights.Count != 0 && morphTargetsCount.First() != _weights.Count) result.AddSemanticError(Validation.ErrorCodes.MESH_INVALID_WEIGHTS_COUNT, _weights.Count, morphTargetsCount.First());
+            if (_weights.Count != 0 && morphTargetsCount.First() != _weights.Count) result.AddSemanticError("Weights", $"The length of weights array ({_weights.Count}) does not match the number of morph targets({morphTargetsCount.First()}).");
         }
 
         #endregion

+ 4 - 4
src/SharpGLTF.Core/Schema2/gltf.MeshPrimitive.cs

@@ -236,7 +236,7 @@ namespace SharpGLTF.Schema2
 
             if (IndexAccessor != null)
             {
-                if (IndexAccessor.SourceBufferView.ByteStride != 0) result.AddLinkError(Validation.ErrorCodes.MESH_PRIMITIVE_INDICES_ACCESSOR_WITH_BYTESTRIDE);
+                if (IndexAccessor.SourceBufferView.ByteStride != 0) result.AddLinkError("bufferView.byteStride must not be defined for indices accessor.");
                 IndexAccessor.ValidateIndices(result, (uint)vertexCount, DrawPrimitiveType);
 
                 var incompatibleMode = false;
@@ -323,7 +323,7 @@ namespace SharpGLTF.Schema2
             var positions = GetVertexAccessor("POSITION");
             if (positions == null)
             {
-                result.AddSemanticWarning(Validation.WarnCodes.MESH_PRIMITIVE_NO_POSITION);
+                result.AddSemanticWarning("No POSITION attribute found.");
                 return;
             }
 
@@ -343,8 +343,8 @@ namespace SharpGLTF.Schema2
             var tangents = GetVertexAccessor("TANGENT");
             if (tangents == null) return;
 
-            if (GetVertexAccessor("NORMAL") == null) result.AddSemanticWarning(Validation.WarnCodes.MESH_PRIMITIVE_TANGENT_WITHOUT_NORMAL);
-            if (DrawPrimitiveType == PrimitiveType.POINTS) result.AddSemanticWarning(Validation.WarnCodes.MESH_PRIMITIVE_TANGENT_POINTS);
+            if (GetVertexAccessor("NORMAL") == null) result.AddSemanticWarning("TANGENT", "attribute without NORMAL found.");
+            if (DrawPrimitiveType == PrimitiveType.POINTS) result.AddSemanticWarning("TANGENT", "attribute defined for POINTS rendering mode.");
 
             tangents.ValidateTangents(result);
         }

+ 2 - 2
src/SharpGLTF.Core/Schema2/gltf.Node.cs

@@ -440,12 +440,12 @@ namespace SharpGLTF.Schema2
 
             if (mesh != null)
             {
-                if (skin == null && mesh.AllPrimitivesHaveJoints) result.AddLinkWarning(Validation.WarnCodes.NODE_SKINNED_MESH_WITHOUT_SKIN);
+                if (skin == null && mesh.AllPrimitivesHaveJoints) result.AddLinkWarning("Skin", "Node uses skinned mesh, but has no skin defined.");
             }
 
             if (skin != null)
             {
-                if (mesh == null || !mesh.AllPrimitivesHaveJoints) result.AddLinkError(Validation.ErrorCodes.NODE_SKIN_WITH_NON_SKINNED_MESH);
+                if (mesh == null || !mesh.AllPrimitivesHaveJoints) result.AddLinkError("Mesh", "Node has skin defined, but mesh has no joints data.");
             }
         }
 

+ 99 - 40
src/SharpGLTF.Core/Schema2/gltf.Serialization.Read.cs

@@ -51,6 +51,33 @@ namespace SharpGLTF.Schema2
 
     partial class ModelRoot
     {
+        #region validate
+
+        public static Validation.ValidationResult Validate(string filePath)
+        {
+            Guard.FilePathMustExist(filePath, nameof(filePath));
+
+            var settings = new ReadSettings(filePath);
+
+            using (var stream = File.OpenRead(filePath))
+            {
+                bool binaryFile = glb._Identify(stream);
+
+                if (binaryFile) return _ReadGLB(stream, settings).Item2;
+
+                string content = null;
+
+                using (var streamReader = new StreamReader(stream))
+                {
+                    content = streamReader.ReadToEnd();
+                }
+
+                return _ParseGLTF(content, settings).Item2;
+            }
+        }
+
+        #endregion
+
         #region read / load methods
 
         /// <summary>
@@ -130,27 +157,11 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="MODEL"/> instance.</returns>
         public static MODEL ReadGLB(Stream stream, ReadSettings settings)
         {
-            Guard.NotNull(stream, nameof(stream));
-            Guard.NotNull(settings, nameof(settings));
-
-            var chunks = glb.ReadBinaryFile(stream);
-
-            var dom = Encoding.UTF8.GetString(chunks[glb.CHUNKJSON]);
+            var mv = _ReadGLB(stream, settings);
 
-            if (chunks.ContainsKey(glb.CHUNKBIN))
-            {
-                var sourceReader = settings.FileReader;
+            if (mv.Item2.HasErrors) throw mv.Item2.Errors.FirstOrDefault();
 
-                settings.FileReader =
-                    key =>
-                    string.IsNullOrEmpty(key)
-                    ?
-                    new BYTES(chunks[glb.CHUNKBIN])
-                    :
-                    sourceReader.Invoke(key);
-            }
-
-            return ParseGLTF(dom, settings);
+            return mv.Item1;
         }
 
         /// <summary>
@@ -161,13 +172,11 @@ namespace SharpGLTF.Schema2
         /// <returns>A <see cref="MODEL"/> instance.</returns>
         public static MODEL ParseGLTF(String jsonContent, ReadSettings settings)
         {
-            Guard.NotNullOrEmpty(jsonContent, nameof(jsonContent));
-            Guard.NotNull(settings, nameof(settings));
+            var mv = _ParseGLTF(jsonContent, settings);
 
-            using (var tr = new StringReader(jsonContent))
-            {
-                return _Read(tr, settings);
-            }
+            if (mv.Item2.HasErrors) throw mv.Item2.Errors.FirstOrDefault();
+
+            return mv.Item1;
         }
 
         public static MODEL ReadFromDictionary(Dictionary<string, BYTES> files, string fileName)
@@ -180,9 +189,13 @@ namespace SharpGLTF.Schema2
 
             using (var m = new MemoryStream(jsonBytes.Array, jsonBytes.Offset, jsonBytes.Count))
             {
-                using (var s = new StreamReader(m))
+                using (var tr = new StreamReader(m))
                 {
-                    return _Read(s, settings);
+                    var mv = _Read(tr, settings);
+
+                    if (mv.Item2.HasErrors) throw mv.Item2.Errors.FirstOrDefault();
+
+                    return mv.Item1;
                 }
             }
         }
@@ -191,7 +204,43 @@ namespace SharpGLTF.Schema2
 
         #region reading core
 
-        private static MODEL _Read(TextReader textReader, ReadSettings settings)
+        private static (MODEL, Validation.ValidationResult) _ReadGLB(Stream stream, ReadSettings settings)
+        {
+            Guard.NotNull(stream, nameof(stream));
+            Guard.NotNull(settings, nameof(settings));
+
+            var chunks = glb.ReadBinaryFile(stream);
+
+            var dom = Encoding.UTF8.GetString(chunks[glb.CHUNKJSON]);
+
+            if (chunks.ContainsKey(glb.CHUNKBIN))
+            {
+                var sourceReader = settings.FileReader;
+
+                settings.FileReader =
+                    key =>
+                    string.IsNullOrEmpty(key)
+                    ?
+                    new BYTES(chunks[glb.CHUNKBIN])
+                    :
+                    sourceReader.Invoke(key);
+            }
+
+            return _ParseGLTF(dom, settings);
+        }
+
+        private static (MODEL, Validation.ValidationResult) _ParseGLTF(String jsonContent, ReadSettings settings)
+        {
+            Guard.NotNullOrEmpty(jsonContent, nameof(jsonContent));
+            Guard.NotNull(settings, nameof(settings));
+
+            using (var tr = new StringReader(jsonContent))
+            {
+                return _Read(tr, settings);
+            }
+        }
+
+        private static (MODEL, Validation.ValidationResult) _Read(TextReader textReader, ReadSettings settings)
         {
             Guard.NotNull(textReader, nameof(textReader));
             Guard.NotNull(settings, nameof(settings));
@@ -199,22 +248,31 @@ namespace SharpGLTF.Schema2
             using (var reader = new JsonTextReader(textReader))
             {
                 var root = new MODEL();
+                var vcontext = new Validation.ValidationResult(root);
+
+                if (!reader.Read())
+                {
+                    vcontext.AddError(new Validation.ModelException(root, "Json is empty"));
+                    return (null, vcontext);
+                }
 
                 try
                 {
-                    reader.Read();
                     root.Deserialize(reader);
                 }
                 catch (JsonReaderException rex)
                 {
-                    throw new Validation.SchemaException(root, rex);
+                    vcontext.AddError(new Validation.SchemaException(root, rex));
+                    return (null, vcontext);
                 }
 
-                // reference checking is mandatory and cannot be skipped
-                var result = new Validation.ValidationResult(root);
-                root.ValidateReferences(result.GetContext(root));
-                var ex = result.Errors.FirstOrDefault();
-                if (ex != null) throw ex;
+                // schema validation
+
+                root.ValidateReferences(vcontext.GetContext(root));
+                var ex = vcontext.Errors.FirstOrDefault();
+                if (ex != null) return (null, vcontext);
+
+                // resolve external references
 
                 foreach (var buffer in root._buffers)
                 {
@@ -226,15 +284,16 @@ namespace SharpGLTF.Schema2
                     image._ResolveUri(settings.FileReader);
                 }
 
+                // full validation
+
                 if (!settings.SkipValidation)
                 {
-                    result = new Validation.ValidationResult(root);
-                    root.Validate(result.GetContext(root));
-                    ex = result.Errors.FirstOrDefault();
-                    if (ex != null) throw ex;
+                    root.Validate(vcontext.GetContext(root));
+                    ex = vcontext.Errors.FirstOrDefault();
+                    if (ex != null) return (null, vcontext);
                 }
 
-                return root;
+                return (root, vcontext);
             }
         }
 

+ 0 - 1
src/SharpGLTF.Core/Schema2/gltf.Skin.cs

@@ -248,7 +248,6 @@ namespace SharpGLTF.Schema2
 
             if (_joints.Count < _jointsMinItems)
             {
-
                 // result.AddError(this, $"Expected at least {_jointsMinItems} Joints");
                 return;
             }

+ 0 - 119
src/SharpGLTF.Core/Validation/ErrorCodes.cs

@@ -1,119 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF.Validation
-{
-    static class ErrorCodes
-    {
-        #region DATA
-
-        public const string ACCESSOR_ANIMATION_INPUT_NEGATIVE = "Animation input accessor element at index {0} is negative: {1}.";
-        public const string ACCESSOR_ANIMATION_INPUT_NON_INCREASING = "Animation input accessor element at index {0} is less than or equal to previous: {1} <= {2}.";
-        public const string ACCESSOR_ELEMENT_OUT_OF_MAX_BOUND = "Accessor contains {0} element(s) greater than declared maximum value {1}.";
-        public const string ACCESSOR_ELEMENT_OUT_OF_MIN_BOUND = "Accessor contains {0} element(s) less than declared minimum value {1}.";
-        public const string ACCESSOR_INDECOMPOSABLE_MATRIX = "Matrix element at index {0} is not decomposable to TRS.";
-        public const string ACCESSOR_INDEX_OOB = "Indices accessor element at index {0} has vertex index {1} that exceeds number of available vertices {2}.";
-        public const string ACCESSOR_INDEX_PRIMITIVE_RESTART = "Indices accessor contains primitive restart value ({0}) at index {1}.";
-        public const string ACCESSOR_INVALID_FLOAT = "Accessor element at index {0} is NaN or Infinity.";
-        public const string ACCESSOR_INVALID_SIGN = "Accessor element at index {0} has invalid w component: {1}. Must be 1.0 or -1.0.";
-        public const string ACCESSOR_MAX_MISMATCH = "Declared maximum value for this component ({0}) does not match actual maximum({1}).";
-        public const string ACCESSOR_MIN_MISMATCH = "Declared minimum value for this component({0}) does not match actual minimum({1}).";
-        public const string ACCESSOR_NON_UNIT = "Accessor element at index {0} is not of unit length: {1}.";
-        public const string ACCESSOR_SPARSE_INDEX_OOB = "Accessor sparse indices element at index {0} is greater than or equal to the number of accessor elements: {1} >= {2}.";
-        public const string ACCESSOR_SPARSE_INDICES_NON_INCREASING = "Accessor sparse indices element at index {0} is less than or equal to previous: {1} <= {2}.";
-        public const string BUFFER_EMBEDDED_BYTELENGTH_MISMATCH = "Actual data length {0} is not equal to the declared buffer byteLength {1}.";
-        public const string BUFFER_EXTERNAL_BYTELENGTH_MISMATCH = "Actual data length {0} is less than the declared buffer byteLength {1}.";
-        public const string IMAGE_DATA_INVALID = "Image data is invalid. {0}";
-        public const string IMAGE_MIME_TYPE_INVALID = "Recognized image format '{0}' does not match declared image format '{1}'.";
-        public const string IMAGE_UNEXPECTED_EOS = "Unexpected end of image stream.";
-
-        #endregion
-
-        #region LINK
-
-        public const string ACCESSOR_SMALL_BYTESTRIDE = "Referenced bufferView's byteStride value {0} is less than accessor element's length {1}.";
-        public const string ACCESSOR_TOO_LONG = "Accessor(offset: {0}, length: {1}) does not fit referenced bufferView[% 3] length %4.";
-        public const string ACCESSOR_TOTAL_OFFSET_ALIGNMENT = "Accessor's total byteOffset {0} isn't a multiple of componentType length {1}.";
-        public const string ACCESSOR_USAGE_OVERRIDE = "Override of previously set accessor usage.Initial: '{0}', new: '{1}'.";
-        public const string ANIMATION_CHANNEL_TARGET_NODE_MATRIX = "Animation channel cannot target TRS properties of node with defined matrix.";
-        public const string ANIMATION_CHANNEL_TARGET_NODE_WEIGHTS_NO_MORPHS = "Animation channel cannot target WEIGHTS when mesh does not have morph targets.";
-        public const string ANIMATION_DUPLICATE_TARGETS = "Animation channel has the same target as channel {0}. 	Error";
-        public const string ANIMATION_SAMPLER_INPUT_ACCESSOR_INVALID_FORMAT = "Invalid Animation sampler input accessor format '{0}'. Must be one of ('%a', '%b', '%c').";
-        public const string ANIMATION_SAMPLER_INPUT_ACCESSOR_TOO_FEW_ELEMENTS = "Animation sampler output accessor with '{0}' interpolation must have at least {1} elements.Got {2}.";
-        public const string ANIMATION_SAMPLER_INPUT_ACCESSOR_WITHOUT_BOUNDS = "accessor.min and accessor.max must be defined for animation input accessor.";
-        public const string ANIMATION_SAMPLER_OUTPUT_ACCESSOR_INVALID_COUNT = "Animation sampler output accessor of count {0} expected.Found {1}.";
-        public const string ANIMATION_SAMPLER_OUTPUT_ACCESSOR_INVALID_FORMAT = "Invalid animation sampler output accessor format '{0}' for path '{2}'. Must be one of('%a', '%b', '%c').";
-        public const string ANIMATION_SAMPLER_OUTPUT_INTERPOLATION = "The same output accessor cannot be used both for spline and linear data.";
-        public const string BUFFER_MISSING_GLB_DATA = "Buffer refers to an unresolved GLB binary chunk.";
-        public const string BUFFER_NON_FIRST_GLB = "Buffer referring to GLB binary chunk must be the first.";
-        public const string BUFFER_VIEW_TARGET_OVERRIDE = "Override of previously set bufferView target or usage. Initial: '{0}', new: '{1}'.";
-        public const string BUFFER_VIEW_TOO_LONG = "BufferView does not fit buffer({0}) byteLength({1}).";
-        public const string INVALID_IBM_ACCESSOR_COUNT = "Accessor of count {0} expected.Found {1}.";
-        public const string MESH_PRIMITIVE_ACCESSOR_UNALIGNED = "Vertex attribute data must be aligned to 4-byte boundaries.";
-        public const string MESH_PRIMITIVE_ACCESSOR_WITHOUT_BYTESTRIDE = "bufferView.byteStride must be defined when two or more accessors use the same buffer view.";
-        public const string MESH_PRIMITIVE_ATTRIBUTES_ACCESSOR_INVALID_FORMAT = "Invalid accessor format '{0}' for this attribute semantic. Must be one of ('%a', '%b', '%c').";
-        public const string MESH_PRIMITIVE_INDICES_ACCESSOR_INVALID_FORMAT = "Invalid indices accessor format '{0}'. Must be one of('{1}', '{2}', '{3}').";
-        public const string MESH_PRIMITIVE_INDICES_ACCESSOR_WITH_BYTESTRIDE = "bufferView.byteStride must not be defined for indices accessor.";
-        public const string MESH_PRIMITIVE_MORPH_TARGET_INVALID_ATTRIBUTE_COUNT = "Base accessor has different count.";
-        public const string MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR = "No base accessor for this attribute semantic.";
-        public const string MESH_PRIMITIVE_POSITION_ACCESSOR_WITHOUT_BOUNDS = "accessor.min and accessor.max must be defined for POSITION attribute accessor.";
-        public const string MESH_PRIMITIVE_TOO_FEW_TEXCOORDS = "Material is incompatible with mesh primitive: Texture binding '{0}' needs 'TEXCOORD_{1}' attribute.";
-        public const string MESH_PRIMITIVE_UNEQUAL_ACCESSOR_COUNT = "All accessors of the same primitive must have the same count.";
-        public const string NODE_LOOP = "Node is a part of a node loop.";
-        public const string NODE_PARENT_OVERRIDE = "Value overrides parent of node {0}.";
-        public const string NODE_SKIN_WITH_NON_SKINNED_MESH = "Node has skin defined, but mesh has no joints data.";
-        public const string NODE_WEIGHTS_INVALID = "The length of weights array ({0}) does not match the number of morph targets({1}).";
-        public const string SCENE_NON_ROOT_NODE = "Node {0} is not a root node.";
-        public const string SKIN_IBM_INVALID_FORMAT = "Invalid IBM accessor format '{0}'. Must be one of ('%a', '%b', '%c').";
-        public const string UNDECLARED_EXTENSION = "Extension was not declared in extensionsUsed.";
-        public const string UNEXPECTED_EXTENSION_OBJECT = "Unexpected location for this extension.";
-        public const string UNRESOLVED_REFERENCE = "Unresolved reference: {0}.";
-
-        #endregion
-
-        #region SCHEMA
-
-        public const string ARRAY_LENGTH_NOT_IN_LIST = "Invalid array length {0}. Valid lengths are: ('%a', '%b', '%c').";
-        public const string ARRAY_TYPE_MISMATCH = "Type mismatch. Array element '{0}' is not a '{1}'.";
-        public const string DUPLICATE_ELEMENTS = "Duplicate element.";
-        public const string EMPTY_ENTITY = "Entity cannot be empty.";
-        public const string INVALID_INDEX = "Index must be a non-negative integer.";
-        public const string INVALID_JSON = "Invalid JSON data.Parser output: {0}";
-        public const string INVALID_URI = "Invalid URI '{0}'. Parser output: {1}";
-        public const string ONE_OF_MISMATCH = "Exactly one of ('{0}', '{1}', '{2}', '{3}') properties must be defined.";
-        public const string PATTERN_MISMATCH = "Value '{0}' does not match regexp pattern '{1}'.";
-        public const string TYPE_MISMATCH = "Type mismatch. Property value '{0}' is not a '{1}'.";
-        public const string UNDEFINED_PROPERTY = "Property '{0}' must be defined.";
-        public const string UNSATISFIED_DEPENDENCY = "Dependency failed. '{0}' must be defined.";
-        public const string VALUE_MULTIPLE_OF = "Value {0} is not a multiple of {1}.";
-        public const string VALUE_NOT_IN_RANGE = "Value {0} is out of range.";
-
-        #endregion
-
-        #region SEMANTIC
-
-        public const string ACCESSOR_MATRIX_ALIGNMENT = "Matrix accessors must be aligned to 4-byte boundaries.";
-        public const string ACCESSOR_NORMALIZED_INVALID = "Only (u)byte and (u)short accessors can be normalized.";
-        public const string ACCESSOR_OFFSET_ALIGNMENT = "Offset {0} is not a multiple of componentType length {1}.";
-        public const string ACCESSOR_SPARSE_COUNT_OUT_OF_RANGE = "Sparse accessor overrides more elements ({0}) than the base accessor contains({1}).";
-        public const string BUFFER_DATA_URI_MIME_TYPE_INVALID = "Buffer's Data URI MIME-Type must be 'application/octet-stream' or 'application/gltf-buffer'. Found '{0}' instead.";
-        public const string BUFFER_VIEW_INVALID_BYTE_STRIDE = "Only buffer views with raw vertex data can have byteStride.";
-        public const string BUFFER_VIEW_TOO_BIG_BYTE_STRIDE = "Buffer view's byteStride ({0}) is larger than byteLength ({1}).";
-        public const string CAMERA_ZFAR_LEQUAL_ZNEAR = "zfar must be greater than znear.";
-        public const string INVALID_GL_VALUE = "Invalid value {0} for GL type '{1}'.";
-        public const string KHR_LIGHTS_PUNCTUAL_LIGHT_SPOT_ANGLES = "outerConeAngle ({1}) is less than or equal to innerConeAngle({0}).";
-        public const string MESH_INVALID_WEIGHTS_COUNT = "The length of weights array ({0}) does not match the number of morph targets({1}).";
-        public const string MESH_PRIMITIVES_UNEQUAL_TARGETS_COUNT = "All primitives must have the same number of morph targets.";
-        public const string MESH_PRIMITIVE_INDEXED_SEMANTIC_CONTINUITY = "Indices for indexed attribute semantic '{0}' must start with 0 and be continuous.Total expected indices: {1}, total provided indices: {2}.";
-        public const string MESH_PRIMITIVE_INVALID_ATTRIBUTE = "Invalid attribute name.";
-        public const string MESH_PRIMITIVE_JOINTS_WEIGHTS_MISMATCH = "Number of JOINTS attribute semantics must match number of WEIGHTS.";
-        public const string NODE_MATRIX_NON_TRS = "Matrix must be decomposable to TRS.";
-        public const string NODE_MATRIX_TRS = "A node can have either a matrix or any combination of translation/rotation/scale (TRS) properties.";
-        public const string ROTATION_NON_UNIT = "Rotation quaternion must be normalized.";
-        public const string UNKNOWN_ASSET_MAJOR_VERSION = "Unknown glTF major asset version: {0}.";
-        public const string UNUSED_EXTENSION_REQUIRED = "Unused extension '{0}' cannot be required.";
-
-        #endregion
-    }
-}

+ 0 - 33
src/SharpGLTF.Core/Validation/InfoCodes.cs

@@ -1,33 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF.Validation
-{
-    static class InfoCodes
-    {
-        #region DATA
-
-        public const string ACCESSOR_INDEX_TRIANGLE_DEGENERATE = "Indices accessor contains {0} degenerate triangles.";
-        public const string DATA_URI_GLB = "Data URI is used in GLB container.";
-        public const string IMAGE_NPOT_DIMENSIONS = "Image has non-power-of-two dimensions: {0}x{1}.";
-
-        #endregion
-
-        #region LINK
-
-        public const string MESH_PRIMITIVE_UNUSED_TEXCOORD = "Material does not use texture coordinates sets with indices ('%a', '%b', '%c').";
-        public const string UNUSED_OBJECT = "This object may be unused.";
-
-        #endregion
-
-        #region SEMANTIC
-
-        public const string EXTRA_PROPERTY = "This property should not be defined as it will not be used.";
-        public const string NODE_EMPTY = "Empty node encountered.";
-        public const string NODE_MATRIX_DEFAULT = "Do not specify default transform matrix.";
-        public const string NON_OBJECT_EXTRAS = "Prefer JSON Objects for extras.";
-
-        #endregion
-    }
-}

+ 0 - 54
src/SharpGLTF.Core/Validation/ValidationContext.cs

@@ -166,8 +166,6 @@ namespace SharpGLTF.Validation
 
         #region semantic errors
 
-        
-
         #endregion
 
         #region data errors
@@ -411,56 +409,4 @@ namespace SharpGLTF.Validation
             return name + this.ToString();
         }
     }
-
-    [System.Diagnostics.DebuggerStepThrough]
-    public sealed class ValidationResult
-    {
-        #region lifecycle
-
-        public ValidationResult(Schema2.ModelRoot root)
-        {
-            _Root = root;
-        }
-
-        #endregion
-
-        #region data
-
-        private readonly Schema2.ModelRoot _Root;
-
-        private readonly List<Exception> _Errors = new List<Exception>();
-        private readonly List<Exception> _Warnings = new List<Exception>();
-
-        #endregion
-
-        #region properties
-
-        public Schema2.ModelRoot Root => _Root;
-
-        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)
-        {
-            _Warnings.Add(ex);
-        }
-
-        public void AddError(ModelException ex)
-        {
-            #if DEBUG
-            throw ex;
-            #else
-            _Errors.Add(ex);
-            #endif
-        }
-
-        #endregion
-    }
 }

+ 60 - 0
src/SharpGLTF.Core/Validation/ValidationResult.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using TARGET = SharpGLTF.IO.JsonSerializable;
+
+namespace SharpGLTF.Validation
+{
+    [System.Diagnostics.DebuggerStepThrough]
+    public sealed class ValidationResult
+    {
+        #region lifecycle
+
+        public ValidationResult(Schema2.ModelRoot root, bool instantThrow = false)
+        {
+            _Root = root;
+            _InstantThrow = instantThrow;
+        }
+
+        #endregion
+
+        #region data
+
+        private readonly Schema2.ModelRoot _Root;
+        private readonly bool _InstantThrow;
+
+        private readonly List<Exception> _Errors = new List<Exception>();
+        private readonly List<Exception> _Warnings = new List<Exception>();
+
+        #endregion
+
+        #region properties
+
+        public Schema2.ModelRoot Root => _Root;
+
+        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)
+        {
+            _Warnings.Add(ex);
+        }
+
+        public void AddError(ModelException ex)
+        {
+            if (_InstantThrow) throw ex;
+
+            _Errors.Add(ex);
+        }
+
+        #endregion
+    }
+}

+ 0 - 48
src/SharpGLTF.Core/Validation/WarnCodes.cs

@@ -1,48 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace SharpGLTF.Validation
-{
-    static class WarnCodes
-    {
-        #region DATA
-
-        public const string BUFFER_GLB_CHUNK_TOO_BIG = "GLB-stored BIN chunk contains {0} extra padding byte (s).";
-        public const string IMAGE_UNRECOGNIZED_FORMAT = "Image format not recognized.";
-
-        #endregion
-
-        #region LINK
-
-        public const string MESH_PRIMITIVE_INCOMPATIBLE_MODE = "Number of vertices or indices({0}) is not compatible with used drawing mode('{1}').";
-        public const string NODE_SKINNED_MESH_WITHOUT_SKIN = "Node uses skinned mesh, but has no skin defined.";
-        public const string UNSUPPORTED_EXTENSION = "Cannot validate an extension as it is not supported by the validator: '{0}'.";
-
-        #endregion
-
-        #region SCHEMA
-
-        public const string UNEXPECTED_PROPERTY = "Unexpected property.";
-        public const string VALUE_NOT_IN_LIST = "Invalid value '{0}'. Valid values are ('%a', '%b', '%c').";
-
-        #endregion
-
-        #region SEMANTIC
-
-        public const string ASSET_MIN_VERSION_GREATER_THAN_VERSION = "Asset minVersion '{0}' is greater than version '{1}'.";
-        public const string CAMERA_XMAG_YMAG_ZERO = "xmag and ymag must not be zero.";
-        public const string INTEGER_WRITTEN_AS_FLOAT = "Integer value is written with fractional part: {0}.";
-        public const string MATERIAL_ALPHA_CUTOFF_INVALID_MODE = "Alpha cutoff is supported only for 'MASK' alpha mode.";
-        public const string MESH_PRIMITIVES_UNEQUAL_JOINTS_COUNT = "All primitives should contain the same number of 'JOINTS' and 'WEIGHTS' attribute sets.";
-        public const string MESH_PRIMITIVE_TANGENT_POINTS = "TANGENT attribute defined for POINTS rendering mode.";
-        public const string MESH_PRIMITIVE_TANGENT_WITHOUT_NORMAL = "TANGENT attribute without NORMAL found.";
-        public const string MULTIPLE_EXTENSIONS = "Multiple extensions are defined for this object: ('%a', '%b', '%c').";
-        public const string MESH_PRIMITIVE_NO_POSITION = "No POSITION attribute found.";
-        public const string NON_RELATIVE_URI = "Non-relative URI found: {0}.";
-        public const string UNKNOWN_ASSET_MINOR_VERSION = "Unknown glTF minor asset version: {0}.";
-        public const string UNRESERVED_EXTENSION_PREFIX = "Extension uses unreserved extension prefix '{0}'.";
-
-        #endregion
-    }
-}

+ 5 - 1
tests/SharpGLTF.Tests/Schema2/LoadAndSave/LoadInvalidTests.cs

@@ -13,9 +13,13 @@ namespace SharpGLTF.Schema2.LoadAndSave
         [Test]
         public void LoadInvalidJsonModel()
         {
-            var path = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Invalid_Json.gltf");
+            var path = System.IO.Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Invalid_Json.gltf");            
 
             Assert.Throws<Validation.SchemaException>(() => ModelRoot.Load(path));
+
+            var validation = ModelRoot.Validate(path);
+
+            Assert.IsTrue(validation.HasErrors);
         }
     }
 }