예제 #1
0
 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);
 }
예제 #2
0
        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());
        }
예제 #3
0
        /// <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);
        }
예제 #4
0
        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);
        }
예제 #5
0
        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);
        }
예제 #6
0
        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);
        }
예제 #7
0
        /// <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);
        }
예제 #8
0
        /// <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);
        }