public void NullTest() { Assert.Null(MPJson.From(null).Value); Assert.Equal(DBNull.Value, MPJson.Null.Value); Assert.Equal(JsonType.Null, MPJson.Null.Type); Assert.True(MPJson.Null.HasValue); Assert.False(MPJson.Null.IsUndefined); Assert.Equal("null", MPJson.Null.ToString()); Assert.Equal(JsonType.Null, new MPJson(null).Type); }
public void UndefinedTest() { Assert.Null(default(MPJson).Value); Assert.Equal(JsonType.Undefined, default(MPJson).Type); Assert.Equal(JsonType.Undefined, MPJson.From(null).Type); Assert.True(default(MPJson).IsUndefined); Assert.False(default(MPJson).HasValue); Assert.Null(MPJson.Undefined.Value); Assert.Equal(JsonType.Undefined, MPJson.Undefined.Type); Assert.True(MPJson.Undefined.IsUndefined); Assert.False(MPJson.Undefined.HasValue); Assert.Equal("undefined", MPJson.Undefined.ToString()); }
/// <summary> /// Pushes an error onto the errors list with the given parameters /// </summary> /// <param name="keyword"></param> /// <param name="key"></param> /// <param name="actual"></param> /// <param name="expected"></param> /// <param name="instancePath"></param> /// <param name="schemaPath"></param> internal bool ReportError(Keyword keyword, Subschema schema, object actual = null, object expected = null, object instance = null) { // Errors being suppressed don't directly affect success result if (suppressionLevel > 0 || errorAction == null) { return(true); } if (schema != null) { if (expected == null) { expected = schema.GetData(keyword); } } validationArgs.Actual = MPJson.From(actual); validationArgs.Instance = MPJson.From(instance ?? actual); validationArgs.Expected = expected; validationArgs.Schema = schema; validationArgs.ValidationError = null; if (keyword >= 0) { validationArgs.ErrorType = (ErrorType)keyword; SchemaPointer.Push(keyword); } else { validationArgs.ErrorType = (ErrorType)SchemaPointer.Keyword; } errorAction(this, validationArgs); if (keyword >= 0) { SchemaPointer.Pop(); } if (AbortOnError) { errorAction = null; } return(errorAction == null); }
public void ArrayTest() { object[] array = new object[] { 1.0, 2.0, 3.0 }; MPJson json1 = MPJson.From(array); MPJson json2 = MPJson.From(array.Clone()); MPJson ja = MPJson.Array(1.0, 2.0, 3.0); Assert.Equal(JsonType.Array, json1.Type); Assert.Equal(JsonType.Array, json2.Type); Assert.Equal(JsonType.Array, ja.Type); Assert.True(json1.HasValue); Assert.False(json1.IsUndefined); Assert.Equal(json1, json2); Assert.Equal(json1, ja); Assert.Equal(1.0, ja[0]); Assert.Equal(2.0, ja[1]); Assert.Equal(3.0, ja[2]); Assert.Equal(JsonType.Undefined, json1["x"].Type); Assert.Equal("[ 1, 2, 3 ]", ja.ToString()); Assert.Equal(3, ja.Count()); Assert.Empty(ja.Keys); }
private bool ValidateArray(Subschema schema, object[] array) { bool finalResult = true; for (var mask = schema.Flags & (SchemaFlags.ArrayProperties & SchemaFlags.AllProperties); mask != 0; mask = (SchemaFlags)RemoveLowestBit((long)mask)) { Keyword keyword = (Keyword)IndexOfLowestBit((long)mask); Subschema subschema; Subschema[] schemaArray; bool result = true; bool respond = true; switch (keyword) { case Keyword.AdditionalItems: // https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.8.2.4 // AdditionalItems is ignored if Items is not present subschema = schema.AdditionalItems; schemaArray = schema.Items as Subschema[]; respond = false; if (schemaArray != null && schemaArray.Length < array.Length) { SchemaPointer.Push(Keyword.AdditionalItems); for (int i = schemaArray.Length; i < array.Length; i++) { result &= Validate(subschema, array[i], -1, i); if (!result && !IsRecordingErrors) { break; } } SchemaPointer.Pop(); } break; case Keyword.Contains: subschema = schema.Contains; double minContains = schema.MinContains; double maxContains = schema.MaxContains; result = minContains == 0; int contains = 0; SchemaPointer.Push(Keyword.Contains); TurnOffErrors(); for (int i = 0; i < array.Length; i++) { bool elementResult = Validate(subschema, array[i], -1, i); if (elementResult) { contains++; if (contains >= minContains) { result = true; if (contains + (array.Length - i - 1) <= maxContains) { break; } } } } TurnOnErrors(); SchemaPointer.Pop(); if (contains > maxContains) { result = false; } break; case Keyword.Items: object items = schema.Items; SchemaPointer.Push(Keyword.Items); respond = false; if ((subschema = items as Subschema) != null) { for (int i = 0; i < array.Length; i++) { result &= Validate(subschema, array[i], -1, i); if (!result && !IsRecordingErrors) { break; } } } else { schemaArray = (Subschema[])items; result = true; for (int i = Math.Min(array.Length, schemaArray.Length) - 1; i >= 0; i--) { result &= Validate(schemaArray[i], array[i], i, i); if (!result && !IsRecordingErrors) { break; } } } SchemaPointer.Pop(); break; case Keyword.MaxItems: result = array.Length <= schema.MaxItems; break; case Keyword.MinItems: result = array.Length >= schema.MinItems; break; case Keyword.UniqueItems: var uniques = new HashSet <MPJson>(); foreach (object v in array) { if (!uniques.Add(MPJson.From(v))) { result = false; break; } } break; } if (!result) { finalResult = false; if (respond ? ReportError(keyword, schema, array) : !IsRecordingErrors) { return(false); } } } return(finalResult); }
private bool ValidateGeneric(Subschema schema, object instance) { bool finalResult = true; for (SchemaFlags mask = schema.Flags & (SchemaFlags.GenericProperties & SchemaFlags.AllProperties); mask != 0; mask = (SchemaFlags)RemoveLowestBit((long)mask)) { Keyword keyword = (Keyword)IndexOfLowestBit((long)mask); bool? result = null; Subschema[] schemaArray; int index; switch (keyword) { case Keyword.If: int level = TurnOffErrors(); result = Validate(schema.If, instance, Keyword.If); RestoreSuppressionLevel(level); result = result == true ? Validate(schema.Then, instance, Keyword.Then) : Validate(schema.Else, instance, Keyword.Else); break; case Keyword.Not: level = TurnOffErrors(); result = !Validate(schema.Not, instance, Keyword.Not); RestoreSuppressionLevel(level); break; case Keyword.AllOf: result = true; schemaArray = schema.AllOf; SchemaPointer.Push(keyword); for (index = 0; index < schemaArray.Length; index++) { if (!Validate(schemaArray[index], instance, index)) { finalResult = false; if (!IsRecordingErrors) { break; } } } SchemaPointer.Pop(); break; case Keyword.AnyOf: result = false; level = TurnOffErrors(); schemaArray = schema.AnyOf; SchemaPointer.Push(keyword); for (index = 0; index < schemaArray.Length; index++) { if (Validate(schemaArray[index], instance, index)) { result = true; break; } } SchemaPointer.Pop(); RestoreSuppressionLevel(level); break; case Keyword.OneOf: result = false; level = TurnOffErrors(); schemaArray = schema.OneOf; SchemaPointer.Push(keyword); for (index = 0; index < schemaArray.Length; index++) { if (Validate(schemaArray[index], instance, index)) { result = !result; if (result == false) { break; } } } SchemaPointer.Pop(); RestoreSuppressionLevel(level); break; case Keyword.Enum: result = false; // TODO: Provide support for type coercion foreach (object v in (object[])schema.Enum.Value) { if (MPJson.Matches(v, instance)) { result = true; break; } } break; case Keyword.Const: // TODO: Provide support for string to value coercion result = MPJson.Matches(schema.Const.Value, instance); break; case Keyword._Ref: var r = schema.Ref; if (r.Version != 0) { var draft = new MPSchema(MPJson.From(instance), r.Version); result = draft.IsValid; break; // This was checked on creation } var rSchema = r.Schema; result = rSchema != null && ReferenceUsageIsControlled(); if (result == true && !Validate(rSchema, instance, Keyword._Ref)) { finalResult = false; } break; case Keyword.Metadata: break; } if (result == false) { finalResult = false; if (ReportError(keyword, schema, instance)) { return(false); } } } return(finalResult); }
/// <summary> /// Gets any metadata attached to the schema by name /// </summary> /// <param name="key"></param> /// <returns></returns> public MPJson GetMetadata(string key) { object result = Metadata?.GetProperty(key); return(result != null?MPJson.From(result) : MPJson.Undefined); }
/// <summary> /// Builds a schema document from a JsonLite object tree /// </summary> /// <param name="schema"></param> /// <param name="doc"></param> /// <returns></returns> public Subschema From(object json, MPSchema doc) { var map = json as KeyValuePair <string, object>[]; if (map == null || map.Length == 0) { if (map != null) { return(SchemaConstants.Everything); } if (json is bool b) { return(b ? SchemaConstants.Everything : SchemaConstants.Nothing); } return(null); } var subschema = new Subschema { data = new object[(int)Keyword.MAX_STORED], Flags = SchemaFlags.TypeAll | SchemaFlags.Uncompressed }; bool exclusiveMinimum = false; bool exclusiveMaximum = false; bool isNothing = false; List <Pair> metadata = null; foreach (var kv in map) { var k = KeywordUtils.ParseKeyword(kv.Key); object v = kv.Value; Subschema child; Subschema[] list; string name; object[] array; string[] stringList; double d; switch (k) { case Keyword.AllOf: list = ReadSchemaList(v, doc); if (list == null || list.Length < 1) { return(null); } subschema.AllOf = list; break; case Keyword.AnyOf: list = ReadSchemaList(v, doc); if (list == null || list.Length < 1) { return(null); } subschema.AnyOf = list; break; case Keyword.OneOf: list = ReadSchemaList(v, doc); if (list == null || list.Length < 1) { return(null); } subschema.OneOf = list; break; case Keyword.Const: if (!doc.LimitVersion(SchemaVersion.Draft6, true)) { return(null); } subschema.Const = MPJson.From(v); break; case Keyword.Enum: array = v as object[]; if (array == null || array.Length < 1) { return(null); } subschema.Enum = MPJson.From(array); break; case Keyword.Not: child = From(v, doc); if (child == null) { return(null); } subschema.Not = child; if (map.Length == 1) { if (child.Type == TypeFlags.None) { return(SchemaConstants.Everything); } if (child == SchemaConstants.Everything) { return(SchemaConstants.Nothing); } } break; case Keyword.Type: var flags = subschema.Flags & ~SchemaFlags.TypeAll; if (v is string) { flags = ToTypeFlag((string)v); if (flags == 0) { return(null); } } else { stringList = ReadStringList(v); if (stringList == null) { return(null); } foreach (string type in stringList) { var flag = ToTypeFlag(type); if (flag == 0) { return(null); } flags |= flag; } } subschema.Flags = (subschema.Flags & ~SchemaFlags.TypeAll) | flags; break; case Keyword.If: if (!doc.LimitVersion(SchemaVersion.Draft7, true)) { return(null); } child = From(v, doc); if (child == null) { return(null); } subschema.If = child; break; case Keyword.Then: if (!doc.LimitVersion(SchemaVersion.Draft7, true)) { return(null); } child = From(v, doc); if (child == null) { return(null); } subschema.Then = child; break; case Keyword.Else: if (!doc.LimitVersion(SchemaVersion.Draft7, true)) { return(null); } child = From(v, doc); if (child == null) { return(null); } subschema.Else = child; break; // Number attributes case Keyword.ExclusiveMaximum: if (v is double && doc.LimitVersion(SchemaVersion.Draft6, true)) { subschema.ExclusiveMaximum = (double)v; } else if (v is bool && doc.LimitVersion(SchemaVersion.Draft4, false)) { exclusiveMaximum = (bool)v; } else { return(null); } break; case Keyword.ExclusiveMinimum: if (v is double && doc.LimitVersion(SchemaVersion.Draft6, true)) { subschema.ExclusiveMinimum = (double)v; } else if (v is bool && doc.LimitVersion(SchemaVersion.Draft4, false)) { exclusiveMinimum = (bool)v; } else { return(null); } break; case Keyword.Maximum: if (!(v is double)) { return(null); } if (exclusiveMaximum) { subschema.ExclusiveMaximum = (double)v; } else { subschema.Maximum = (double)v; } break; case Keyword.Minimum: if (!(v is double)) { return(null); } if (exclusiveMinimum) { subschema.ExclusiveMinimum = (double)v; } else { subschema.Minimum = (double)v; } break; case Keyword.MultipleOf: if (!(v is double) || (d = (double)v) <= 0) { return(null); } subschema.MultipleOf = d; break; // String attributes case Keyword.Format: name = v as string; if (name == null) { return(null); } subschema.Format = name; break; case Keyword.MaxLength: if (!IsNonnegativeInteger(v)) { return(null); } subschema.MaxLength = (double)v; break; case Keyword.MinLength: if (!IsNonnegativeInteger(v)) { return(null); } subschema.MinLength = (double)v; break; case Keyword.Pattern: name = v as string; var regex = SchemaRegexCache.Lookup(name); if (regex == null) { return(null); } subschema.Pattern = regex; break; // Object attributes case Keyword.AdditionalProperties: child = From(v, doc); if (child == null) { return(null); } subschema.AdditionalProperties = child; break; case Keyword.Dependencies: KeyValuePair <string, string[]>[] depRequired; KeyValuePair <string, Subschema>[] schemas; if (!ReadDependencies(v, doc, out schemas, out depRequired)) { return(null); } if (depRequired != null) { subschema.DependentRequired = depRequired; } if (schemas != null) { subschema.DependentSchemas = schemas; } break; case Keyword.DependentRequired: if (!doc.LimitVersion(SchemaVersion.Draft201909, true)) { return(null); } if (!ReadDependencies(v, doc, out schemas, out depRequired)) { return(null); } if (schemas != null) { return(null); } subschema.DependentRequired = depRequired; break; case Keyword.DependentSchemas: if (!doc.LimitVersion(SchemaVersion.Draft201909, true)) { return(null); } schemas = ReadPropertySchemas(v, doc); if (schemas == null) { return(null); } subschema.DependentSchemas = schemas; break; case Keyword.PatternProperties: var patternSchemas = ReadPatternSchemas(v, doc); if (patternSchemas == null) { return(null); } subschema.PatternProperties = patternSchemas; break; case Keyword.Properties: schemas = ReadPropertySchemas(v, doc); if (schemas == null) { return(null); } subschema.Properties = schemas; break; case Keyword.MaxProperties: if (!IsNonnegativeInteger(v)) { return(null); } subschema.MaxProperties = (double)v; break; case Keyword.MinProperties: if (!IsNonnegativeInteger(v)) { return(null); } subschema.MinProperties = (double)v; break; case Keyword.PropertyNames: if (!doc.LimitVersion(SchemaVersion.Draft6, true)) { return(null); } child = From(v, doc); if (child == null) { return(null); } subschema.PropertyNames = child; break; case Keyword.Required: stringList = ReadStringList(v); if (stringList == null) { return(null); } subschema.Required = stringList; break; // Array attributes case Keyword.AdditionalItems: child = From(v, doc); if (child == null) { return(null); } subschema.AdditionalItems = child; break; case Keyword.Contains: if (!doc.LimitVersion(SchemaVersion.Draft6, true)) { return(null); } child = From(v, doc); if (child == null) { return(null); } subschema.Contains = child; break; case Keyword.Items: object result = v is object[]? ReadSchemaList(v, doc) : (object)From(v, doc); if (result == null) { return(null); } subschema.Items = result; break; case Keyword.MaxContains: if (!IsNonnegativeInteger(v)) { return(null); } subschema.MaxContains = (double)v; break; case Keyword.MinContains: if (!IsNonnegativeInteger(v)) { return(null); } subschema.MinContains = (double)v; break; case Keyword.MaxItems: if (!IsNonnegativeInteger(v)) { return(null); } subschema.MaxItems = (double)v; break; case Keyword.MinItems: if (!IsNonnegativeInteger(v)) { return(null); } subschema.MinItems = (double)v; break; case Keyword.UniqueItems: if (!(v is bool)) { return(null); } if ((bool)v) { subschema.UniqueItems = true; } break; // MORE case Keyword._Ref: name = v as string; if (name == null) { return(null); } string reference = doc.ConvertReference(name); var r = subschema.Ref = new SchemaReference(reference); break; case Keyword.Id: if (!doc.LimitVersion(SchemaVersion.Draft4, false)) { return(null); } goto DoId; case Keyword._Id: if (!doc.LimitVersion(SchemaVersion.Draft6, true)) { return(null); } DoId: name = v as string; if (name == null) { return(null); } AddMetadata(ref metadata, kv.Key, v); break; case Keyword.ContentSchema: case Keyword.Default: AddMetadata(ref metadata, kv.Key, v); break; case Keyword.Title: case Keyword.Description: case Keyword._Schema: name = v as string; if (name == null) { return(null); } AddMetadata(ref metadata, kv.Key, v); break; case Keyword.Examples: if (!(v is object[])) { return(null); } AddMetadata(ref metadata, kv.Key, v); break; case Keyword._Comment: case Keyword.ContentEncoding: case Keyword.ContentMediaType: if (!doc.LimitVersion(SchemaVersion.Draft7, true)) { return(null); } name = v as string; if (name == null) { return(null); } AddMetadata(ref metadata, kv.Key, v); break; case Keyword.Deprecated: case Keyword.ReadOnly: case Keyword.WriteOnly: if (!doc.LimitVersion(SchemaVersion.Draft7, true)) { return(null); } if (!(v is bool)) { return(null); } if ((bool)v) { AddMetadata(ref metadata, kv.Key, v); } break; case Keyword._Defs: if (!doc.LimitVersion(SchemaVersion.Draft201909, true)) { return(null); } goto case Keyword.Definitions; case Keyword.Definitions: schemas = ReadPropertySchemas(v, doc); if (schemas == null) { return(null); } AddMetadata(ref metadata, kv.Key, schemas); break; } } // Ignore all other keywords if ref is present // Draft8 might have changed this, // but it is not reflected in the standard test suite if ((subschema.Flags & SchemaFlags._Ref) != 0) { subschema.Flags &= ~SchemaFlags.StoredProperties; subschema.Flags |= SchemaFlags.TypeAll | SchemaFlags._Ref; } if (metadata != null) { AssignMetadata(subschema, metadata); } CompressData(subschema); return(subschema); }