Merge pull request #119 from dwango/feature/improve_error_checks_for_gltf

Feature/improve error checks for gltf
This commit is contained in:
ousttrue 2019-01-15 15:00:44 +09:00 committed by GitHub
commit a41617ff70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 371 additions and 48 deletions

View File

@ -28,7 +28,7 @@ namespace UniJSON
}
struct EnumTest
{
{
public HogeFuga EnumDefault;
[JsonSchema(EnumSerializationType =EnumSerializationType.AsInt)]
@ -104,6 +104,7 @@ namespace UniJSON
Assert.AreEqual(1, json.GetObjectCount());
Assert.AreEqual(1, json["Vector"][0].GetInt32());
}
#endregion
#region Deserialize

View File

@ -1,7 +1,6 @@
#pragma warning disable 0649
using NUnit.Framework;
namespace UniJSON
{
public class SchemaTests
@ -40,6 +39,24 @@ namespace UniJSON
Assert.AreEqual(0, parsed["properties"]["age"]["minimum"].GetInt32());
}
[JsonSchema(Title="MultipleConstraints")]
public class MultipleConstraints
{
[JsonSchema(Required = true, Minimum = 0, Maximum = 100)]
public int ranged;
}
[Test]
public void CreateFromClassWithMultipleConstraints()
{
var s = JsonSchema.FromType<MultipleConstraints>();
var v = s.Validator as JsonObjectValidator;
var rangedV = v.Properties["ranged"].Validator as JsonIntValidator;
Assert.AreEqual(0, rangedV.Minimum);
Assert.AreEqual(100, rangedV.Maximum);
}
public enum ProjectionType
{
Perspective,
@ -48,7 +65,7 @@ namespace UniJSON
class EnumStringTest
{
[JsonSchema(EnumSerializationType =EnumSerializationType.AsLowerString)]
[JsonSchema(EnumSerializationType = EnumSerializationType.AsLowerString)]
public ProjectionType type;
}
@ -81,7 +98,7 @@ namespace UniJSON
]
}
}
}
";
@ -117,7 +134,7 @@ namespace UniJSON
]
}
}
}
";
@ -129,6 +146,5 @@ namespace UniJSON
Assert.AreEqual(fromJson, fromType);
}
}
}

View File

@ -0,0 +1,81 @@
using NUnit.Framework;
namespace UniJSON
{
public class SerializeWithSchemaTests
{
[JsonSchema(Title="CheckConstraintsTest")]
public class CheckConstraintsTest
{
[JsonSchema(Minimum = 0)]
public int X;
[JsonSchema(Minimum = 10)] // Not required, thus ignored when the value violates the constraints
public int Y;
}
[Test]
public void TestCheckConstraints()
{
var obj = new CheckConstraintsTest()
{
X = 0,
Y = 0, // Will be excluded because 0 doesn't satisfy a requirement of "Minimum = 10"
};
var s = JsonSchema.FromType<CheckConstraintsTest>();
{
var c = new JsonSchemaValidationContext(obj);
Assert.Null(s.Validator.Validate(c, s));
}
var actual = s.Serialize(obj);
var expected = @"{""X"":0}";
Assert.AreEqual(expected, actual);
}
[JsonSchema(Title="ObjectNestedTest")]
public class ObjectNestedTest
{
public CheckConstraintsTest C;
}
[Test]
public void TestObjectNested()
{
var obj = new ObjectNestedTest()
{
C = new CheckConstraintsTest(),
};
var s = JsonSchema.FromType<ObjectNestedTest>();
{
var c = new JsonSchemaValidationContext(obj);
Assert.Null(s.Validator.Validate(c, s));
}
var actual = s.Serialize(obj);
var expected = @"{""C"":{""X"":0}}";
Assert.AreEqual(expected, actual);
}
[Test]
public void TestObjectNestedWithNull()
{
var obj = new ObjectNestedTest();
var s = JsonSchema.FromType<ObjectNestedTest>();
{
var c = new JsonSchemaValidationContext(obj);
Assert.Null(s.Validator.Validate(c, s));
}
var actual = s.Serialize(obj);
var expected = @"{}";
Assert.AreEqual(expected, actual);
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f1d1c3d9d4d20db409e9d5d9d671abbe
timeCreated: 1546930461
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -100,6 +100,12 @@ namespace UniJSON
{
var c = new JsonSchemaValidationContext("test");
{
var v = new JsonStringValidator();
Assert.Null(v.Validate(c, ""));
Assert.Null(v.Validate(c, "a"));
}
{
var v = new JsonStringValidator();
v.MinLength = 1;
@ -199,6 +205,89 @@ namespace UniJSON
Assert.True(c.IsEmpty());
}
class NotRequired
{
[JsonSchema(Minimum = 1)]
public int Value;
}
[Test]
public void ObjectValidatorForNotRequired()
{
{
var c = new JsonSchemaValidationContext("test")
{
EnableDiagnosisForNotRequiredFields = false, // Default behaviour
};
var s = JsonSchema.FromType<NotRequired>();
// An error is not returned because Value is not 'Required' and the diagnosis is not enabled
Assert.Null(s.Validator.Validate(c, new NotRequired { Value = 0 }));
Assert.True(c.IsEmpty());
}
{
var c = new JsonSchemaValidationContext("test")
{
EnableDiagnosisForNotRequiredFields = true,
};
var s = JsonSchema.FromType<NotRequired>();
Assert.NotNull(s.Validator.Validate(c, new NotRequired { Value = 0 }));
Assert.True(c.IsEmpty());
}
}
class NotRequiredWithIgnorable
{
[JsonSchema(Minimum = 2, ExplicitIgnorableValue = -1)]
public int Value;
}
[Test]
public void ObjectValidatorForNotRequiredWithIgnorable()
{
{
var c = new JsonSchemaValidationContext("test")
{
EnableDiagnosisForNotRequiredFields = false, // Default behaviour
};
var s = JsonSchema.FromType<NotRequiredWithIgnorable>();
// An error is not returned because Value is not 'Required' and the diagnosis is not enabled
Assert.Null(s.Validator.Validate(c, new NotRequiredWithIgnorable { Value = 0 }));
Assert.True(c.IsEmpty());
}
{
var c = new JsonSchemaValidationContext("test")
{
EnableDiagnosisForNotRequiredFields = true,
};
var s = JsonSchema.FromType<NotRequiredWithIgnorable>();
Assert.NotNull(s.Validator.Validate(c, new NotRequiredWithIgnorable { Value = 0 }));
Assert.True(c.IsEmpty());
}
{
var c = new JsonSchemaValidationContext("test")
{
EnableDiagnosisForNotRequiredFields = true,
};
var s = JsonSchema.FromType<NotRequiredWithIgnorable>();
// An error is NOT returned even though diagnosis is enabled because of an ignorable value is matched
Assert.Null(s.Validator.Validate(c, new NotRequiredWithIgnorable { Value = -1 }));
Assert.True(c.IsEmpty());
}
}
[Test]
public void DictionaryValidator()
{
@ -247,5 +336,55 @@ namespace UniJSON
Assert.True(c.IsEmpty());
}
class HasArrayOBject
{
[ItemJsonSchema(Minimum = 0.0, Maximum = 1.0)]
public float[] xs;
}
[Test]
public void HasArrayObjectValidator()
{
{
var c = new JsonSchemaValidationContext("test")
{
EnableDiagnosisForNotRequiredFields = true,
};
var s = JsonSchema.FromType<HasArrayOBject>();
Assert.Null(s.Validator.Validate(c, new HasArrayOBject { xs = new float[] {} }));
Assert.Null(s.Validator.Validate(c, new HasArrayOBject { xs = new float[] { 0.5f } }));
Assert.NotNull(s.Validator.Validate(c, new HasArrayOBject { xs = new float[] { 1.5f } }));
Assert.True(c.IsEmpty());
}
}
class HasListObject
{
[ItemJsonSchema(Minimum = 0.0, Maximum = 1.0)]
public List<float> xs;
}
[Test]
public void HasListObjectValidator()
{
{
var c = new JsonSchemaValidationContext("test")
{
EnableDiagnosisForNotRequiredFields = true,
};
var s = JsonSchema.FromType<HasListObject>();
Assert.Null(s.Validator.Validate(c, new HasListObject { xs = new List<float> {} }));
Assert.Null(s.Validator.Validate(c, new HasListObject { xs = new List<float> { 0.5f } }));
Assert.NotNull(s.Validator.Validate(c, new HasListObject { xs = new List<float> { 1.5f } }));
Assert.True(c.IsEmpty());
}
}
}
}

View File

@ -47,7 +47,8 @@ namespace UniJSON
}
else
{
typeof(FormatterExtensionsSerializer).GetMethod("Serialize").MakeGenericMethod(value.GetType()).Invoke(null, new object[] { f, value });
typeof(FormatterExtensionsSerializer).GetMethod("Serialize")
.MakeGenericMethod(value.GetType()).Invoke(null, new object[] { f, value });
}
}
@ -92,7 +93,7 @@ namespace UniJSON
var mi = typeof(IFormatter).GetMethod("Value", new Type[] { t });
if (mi != null)
{
// premitives
// primitives
var self = Expression.Parameter(typeof(IFormatter), "f");
var arg = Expression.Parameter(t, "value");
var call = Expression.Call(self, mi, arg);
@ -164,6 +165,7 @@ namespace UniJSON
return (IFormatter f, T value) => schema.Serialize(f, value);
}
//throw new NotImplementedException();
}

View File

@ -57,6 +57,9 @@ namespace UniJSON
/// </summary>
public bool SkipComparison { get; set; }
public object ExplicitIgnorableValue { private get; set; }
public int ExplicitIgnorableItemLength { private get; set; }
public override string ToString()
{
return string.Format("<{0}>", Title);
@ -175,7 +178,9 @@ namespace UniJSON
Title = a.Title,
Description = a.Description,
Validator = validator,
SkipComparison = skipComparison
SkipComparison = skipComparison,
ExplicitIgnorableValue = a.ExplicitIgnorableValue,
ExplicitIgnorableItemLength = a.ExplicitIgnorableItemLength,
};
return schema;
@ -383,9 +388,12 @@ namespace UniJSON
}
#endregion
public void Serialize<T>(IFormatter f, T o)
public void Serialize<T>(IFormatter f, T o, JsonSchemaValidationContext c = null)
{
var c = new JsonSchemaValidationContext(o);
if (c == null)
{
c = new JsonSchemaValidationContext(o);
}
var ex = Validator.Validate(c, o);
if (ex != null)
@ -404,14 +412,30 @@ namespace UniJSON
Validator.ToJsonScheama(f);
f.EndMap();
}
public bool IsExplicitlyIgnorableValue<T>(T obj)
{
if (obj == null)
{
return ExplicitIgnorableValue == null;
}
var iter = obj as System.Collections.ICollection;
if (ExplicitIgnorableItemLength != -1 && iter != null)
{
return iter.Count == ExplicitIgnorableItemLength;
}
return obj.Equals(ExplicitIgnorableValue);
}
}
public static class JsonSchemaExtensions
{
public static string Serialize<T>(this JsonSchema s, T o)
public static string Serialize<T>(this JsonSchema s, T o, JsonSchemaValidationContext c = null)
{
var f = new JsonFormatter();
s.Serialize(f, o);
s.Serialize(f, o, c);
return f.ToString();
}
}

View File

@ -55,6 +55,18 @@ namespace UniJSON
/// </summary>
public bool SkipSchemaComparison;
/// <summary>
/// Suppress errors if a value of the field which is not required by a schema is matched to this value.
/// This feature will be useful to ignore invalid value which is known.
/// </summary>
public object ExplicitIgnorableValue;
/// <summary>
/// Suppress errors if length of a value of the field which is not required by a schema is matched to this value.
/// This feature will be useful to ignore invalid value which is known.
/// </summary>
public int ExplicitIgnorableItemLength = -1;
public void Merge(BaseJsonSchemaAttribute rhs)
{
if (rhs == null) return;

View File

@ -7,6 +7,8 @@ namespace UniJSON
{
Stack<string> m_stack = new Stack<string>();
public bool EnableDiagnosisForNotRequiredFields = false;
public JsonSchemaValidationContext(object o)
{
Push(o.GetType().Name);

View File

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
@ -145,10 +146,12 @@ namespace UniJSON
}
var count = GenericCounter<T>.Count(o);
if (count == 0)
// Empty array is valid
/*if (count == 0)
{
return new JsonSchemaValidationException(context, "empty");
}
}*/
if (MaxItems.HasValue && count > MaxItems.Value)
{
@ -160,6 +163,36 @@ namespace UniJSON
return new JsonSchemaValidationException(context, "minItems");
}
if (Items == null)
{
return null; // There are no json schema for items, success
}
var v = Items.Validator;
var t = o.GetType();
IEnumerable iter = null;
if (t.IsArray)
{
iter = o as Array;
}
else if (t.GetIsGenericList())
{
iter = o as IList;
}
else
{
return new JsonSchemaValidationException(context, "non iterable object");
}
foreach(var e in iter)
{
var ex = v.Validate(context, e);
if (ex != null)
{
return ex;
}
};
return null;
}
@ -252,7 +285,7 @@ namespace UniJSON
}
}
public void Deserialize<T, U>(ListTreeNode<T> src, ref U dst)
public void Deserialize<T, U>(ListTreeNode<T> src, ref U dst)
where T : IListTreeItem, IValue<T>
{
src.Deserialize(ref dst);

View File

@ -4,7 +4,6 @@ using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace UniJSON
{
/// <summary>
@ -28,11 +27,11 @@ namespace UniJSON
get; set;
}
List<string> m_required = new List<string>();
HashSet<string> m_required = new HashSet<string>();
/// <summary>
/// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.3
/// </summary>
public List<string> Required
public HashSet<string> Required
{
get { return m_required; }
}
@ -302,18 +301,26 @@ namespace UniJSON
{
class ObjectValidator
{
delegate JsonSchemaValidationException FieldValidator(IJsonSchemaValidator v,
JsonSchemaValidationContext c, T o);
delegate JsonSchemaValidationException FieldValidator(
JsonSchema s, JsonSchemaValidationContext c, T o, bool isRequired);
Dictionary<string, FieldValidator> m_validators = new Dictionary<string, FieldValidator>();
static FieldValidator CreteFieldValidator<U>(Func<T, U> getter, string name)
{
return (v, c, o) =>
return (s, c, o, isRequired) =>
{
var v = s.Validator;
using (c.Push(name))
{
return v.Validate(c, getter(o));
var field = getter(o);
var ex = v.Validate(c, field);
if (ex != null && !isRequired && s.IsExplicitlyIgnorableValue(field))
{
return null;
}
return ex;
}
};
}
@ -341,32 +348,39 @@ namespace UniJSON
}
}
public JsonSchemaValidationException Validate(List<string> required, Dictionary<string, JsonSchema> properties,
public JsonSchemaValidationException Validate(
HashSet<string> required,
Dictionary<string, JsonSchema> properties,
JsonSchemaValidationContext c, T o)
{
foreach (var x in required)
foreach (var kv in properties)
{
JsonSchema s;
if(properties.TryGetValue(x, out s))
var fieldName = kv.Key;
var schema = kv.Value;
FieldValidator fv;
if (m_validators.TryGetValue(fieldName, out fv))
{
FieldValidator fv;
if (m_validators.TryGetValue(x, out fv))
var isRequired = required != null && required.Contains(fieldName);
var ex = fv(schema, c, o, isRequired);
if (ex != null)
{
var ex = fv(s.Validator, c, o);
if (ex != null)
if (isRequired // required fields must be checked
|| c.EnableDiagnosisForNotRequiredFields)
{
return ex;
}
}
}
}
return null;
}
}
static ObjectValidator s_validator;
public static JsonSchemaValidationException Validate(List<string> required,
public static JsonSchemaValidationException Validate(HashSet<string> required,
Dictionary<string, JsonSchema> properties,
JsonSchemaValidationContext c, T o)
{
@ -390,16 +404,7 @@ namespace UniJSON
return new JsonSchemaValidationException(c, "no properties");
}
if (Required != null)
{
var ex = GenericValidator<T>.Validate(Required, Properties, c, o);
if (ex != null)
{
return ex;
}
}
return null;
return GenericValidator<T>.Validate(Required, Properties, c, o);
}
static class GenericSerializer<T>
@ -511,7 +516,7 @@ namespace UniJSON
GenericSerializer<T>.Serialize(this, f, c, value);
}
static class GenericDeserializer<S, T>
static class GenericDeserializer<S, T>
where S : IListTreeItem, IValue<S>
{
delegate T Deserializer(ListTreeNode<S> src);
@ -596,7 +601,7 @@ namespace UniJSON
}
}
public void Deserialize<T, U>(ListTreeNode<T> src, ref U dst)
public void Deserialize<T, U>(ListTreeNode<T> src, ref U dst)
where T : IListTreeItem, IValue<T>
{
GenericDeserializer<T, U>.Deserialize(src, ref dst, Properties);

View File

@ -111,10 +111,6 @@ namespace UniJSON
}
var value = o as string;
if (value.All(x => Char.IsWhiteSpace(x)))
{
return new JsonSchemaValidationException(c, "whitespace");
}
if (MinLength.HasValue && value.Length < MinLength)
{