< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 218
Coverable lines: 218
Total lines: 353
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 95
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
CreateFalseSchema()100%110%
CreateTrueSchema()100%110%
.ctor()100%110%
.ctor(...)100%110%
MakeNullable()0%440%
ToJsonNode(...)0%52520%
EnsureMutable(...)0%440%
VerifyMutable()0%220%
Throw()100%110%
MapSchemaType(...)0%880%
ToIdentifier(System.Text.Json.Schema.JsonSchemaType)0%15150%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Text.Json.Nodes;
 7
 8namespace System.Text.Json.Schema
 9{
 10    internal sealed class JsonSchema
 11    {
 12        internal const string RefPropertyName = "$ref";
 13        internal const string CommentPropertyName = "$comment";
 14        internal const string TypePropertyName = "type";
 15        internal const string FormatPropertyName = "format";
 16        internal const string PatternPropertyName = "pattern";
 17        internal const string PropertiesPropertyName = "properties";
 18        internal const string RequiredPropertyName = "required";
 19        internal const string ItemsPropertyName = "items";
 20        internal const string AdditionalPropertiesPropertyName = "additionalProperties";
 21        internal const string EnumPropertyName = "enum";
 22        internal const string NotPropertyName = "not";
 23        internal const string AnyOfPropertyName = "anyOf";
 24        internal const string ConstPropertyName = "const";
 25        internal const string DefaultPropertyName = "default";
 26        internal const string ContentEncodingPropertyName = "contentEncoding";
 27        internal const string MinLengthPropertyName = "minLength";
 28        internal const string MaxLengthPropertyName = "maxLength";
 29        internal const string DeprecatedPropertyName = "deprecated";
 30
 031        public static JsonSchema CreateFalseSchema() => new(false);
 032        public static JsonSchema CreateTrueSchema() => new(true);
 33
 034        public JsonSchema() { }
 035        private JsonSchema(bool trueOrFalse) { _trueOrFalse = trueOrFalse; }
 36
 037        public bool IsTrue => _trueOrFalse is true;
 38        public bool IsFalse => _trueOrFalse is false;
 39
 40        /// <summary>
 41        /// Per the JSON schema core specification section 4.3
 42        /// (https://json-schema.org/draft/2020-12/json-schema-core#name-json-schema-documents)
 43        /// A JSON schema must either be an object or a boolean.
 44        /// We represent false and true schemas using this flag.
 45        /// It is not possible to specify keywords in boolean schemas.
 46        /// </summary>
 47        private readonly bool? _trueOrFalse;
 48
 049        public string? Ref { get; set { VerifyMutable(); field = value; } }
 50
 051        public string? Comment { get; set { VerifyMutable(); field = value; } }
 52
 053        public JsonSchemaType Type { get; set { VerifyMutable(); field = value; } } = JsonSchemaType.Any;
 54
 055        public string? Format { get; set { VerifyMutable(); field = value; } }
 56
 057        public string? Pattern { get; set { VerifyMutable(); field = value; } }
 58
 059        public JsonNode? Constant { get; set { VerifyMutable(); field = value; } }
 60
 061        public List<KeyValuePair<string, JsonSchema>>? Properties { get; set { VerifyMutable(); field = value; } }
 62
 063        public List<string>? Required { get; set { VerifyMutable(); field = value; } }
 64
 065        public JsonSchema? Items { get; set { VerifyMutable(); field = value; } }
 66
 067        public JsonSchema? AdditionalProperties { get; set { VerifyMutable(); field = value; } }
 68
 069        public JsonArray? Enum { get; set { VerifyMutable(); field = value; } }
 70
 071        public JsonSchema? Not { get; set { VerifyMutable(); field = value; } }
 72
 073        public List<JsonSchema>? AnyOf { get; set { VerifyMutable(); field = value; } }
 74
 075        public bool HasDefaultValue { get; set { VerifyMutable(); field = value; } }
 76
 077        public JsonNode? DefaultValue { get; set { VerifyMutable(); field = value; } }
 78
 079        public int? MinLength { get; set { VerifyMutable(); field = value; } }
 80
 081        public int? MaxLength { get; set { VerifyMutable(); field = value; } }
 82
 083        public bool? Deprecated { get; set { VerifyMutable(); field = value; } }
 84
 085        public string? ContentEncoding { get; set { VerifyMutable(); field = value; } }
 86
 087        public JsonSchemaExporterContext? ExporterContext { get; set; }
 88
 89        public int KeywordCount
 90        {
 91            get
 092            {
 093                if (_trueOrFalse is not null)
 094                {
 95                    // Boolean schemas admit no keywords
 096                    return 0;
 97                }
 98
 099                int count = 0;
 0100                Count(Ref is not null);
 0101                Count(Comment is not null);
 0102                Count(Type != JsonSchemaType.Any);
 0103                Count(Format is not null);
 0104                Count(Pattern is not null);
 0105                Count(Constant is not null);
 0106                Count(Properties is not null);
 0107                Count(Required is not null);
 0108                Count(Items is not null);
 0109                Count(AdditionalProperties is not null);
 0110                Count(Enum is not null);
 0111                Count(Not is not null);
 0112                Count(AnyOf is not null);
 0113                Count(HasDefaultValue);
 0114                Count(MinLength is not null);
 0115                Count(MaxLength is not null);
 0116                Count(Deprecated is not null);
 0117                Count(ContentEncoding is not null);
 118
 0119                return count;
 120
 121                void Count(bool isKeywordSpecified)
 0122                {
 0123                    count += isKeywordSpecified ? 1 : 0;
 0124                }
 0125            }
 126        }
 127
 128        public void MakeNullable()
 0129        {
 0130            if (_trueOrFalse is not null)
 0131            {
 132                // boolean schemas do not admit type keywords.
 0133                return;
 134            }
 135
 0136            if (Type != JsonSchemaType.Any)
 0137            {
 0138                Type |= JsonSchemaType.Null;
 0139            }
 0140        }
 141
 142        public JsonNode ToJsonNode(JsonSchemaExporterOptions options)
 0143        {
 0144            if (_trueOrFalse is { } boolSchema)
 0145            {
 0146                return CompleteSchema((JsonNode)boolSchema);
 147            }
 148
 0149            var objSchema = new JsonObject();
 150
 0151            if (Ref is not null)
 0152            {
 0153                objSchema.Add(RefPropertyName, Ref);
 0154            }
 155
 0156            if (Comment is not null)
 0157            {
 0158                objSchema.Add(CommentPropertyName, Comment);
 0159            }
 160
 0161            if (MapSchemaType(Type) is JsonNode type)
 0162            {
 0163                objSchema.Add(TypePropertyName, type);
 0164            }
 165
 0166            if (Format is not null)
 0167            {
 0168                objSchema.Add(FormatPropertyName, Format);
 0169            }
 170
 0171            if (Pattern is not null)
 0172            {
 0173                objSchema.Add(PatternPropertyName, Pattern);
 0174            }
 175
 0176            if (Constant is not null)
 0177            {
 0178                objSchema.Add(ConstPropertyName, Constant);
 0179            }
 180
 0181            if (Properties is not null)
 0182            {
 0183                var properties = new JsonObject();
 0184                foreach (KeyValuePair<string, JsonSchema> property in Properties)
 0185                {
 0186                    properties.Add(property.Key, property.Value.ToJsonNode(options));
 0187                }
 188
 0189                objSchema.Add(PropertiesPropertyName, properties);
 0190            }
 191
 0192            if (Required is not null)
 0193            {
 0194                var requiredArray = new JsonArray();
 0195                foreach (string requiredProperty in Required)
 0196                {
 0197                    requiredArray.Add((JsonNode)requiredProperty);
 0198                }
 199
 0200                objSchema.Add(RequiredPropertyName, requiredArray);
 0201            }
 202
 0203            if (Items is not null)
 0204            {
 0205                objSchema.Add(ItemsPropertyName, Items.ToJsonNode(options));
 0206            }
 207
 0208            if (AdditionalProperties is not null)
 0209            {
 0210                objSchema.Add(AdditionalPropertiesPropertyName, AdditionalProperties.ToJsonNode(options));
 0211            }
 212
 0213            if (Enum is not null)
 0214            {
 0215                objSchema.Add(EnumPropertyName, Enum);
 0216            }
 217
 0218            if (Not is not null)
 0219            {
 0220                objSchema.Add(NotPropertyName, Not.ToJsonNode(options));
 0221            }
 222
 0223            if (AnyOf is not null)
 0224            {
 0225                JsonArray anyOfArray = [];
 0226                foreach (JsonSchema schema in AnyOf)
 0227                {
 0228                    anyOfArray.Add(schema.ToJsonNode(options));
 0229                }
 230
 0231                objSchema.Add(AnyOfPropertyName, anyOfArray);
 0232            }
 233
 0234            if (HasDefaultValue)
 0235            {
 0236                objSchema.Add(DefaultPropertyName, DefaultValue);
 0237            }
 238
 0239            if (MinLength is int minLength)
 0240            {
 0241                objSchema.Add(MinLengthPropertyName, (JsonNode)minLength);
 0242            }
 243
 0244            if (MaxLength is int maxLength)
 0245            {
 0246                objSchema.Add(MaxLengthPropertyName, (JsonNode)maxLength);
 0247            }
 248
 0249            if (Deprecated is { } deprecated)
 0250            {
 0251                objSchema.Add(DeprecatedPropertyName, (JsonNode)deprecated);
 0252            }
 253
 0254            if (ContentEncoding is not null)
 0255            {
 0256                objSchema.Add(ContentEncodingPropertyName, ContentEncoding);
 0257            }
 258
 0259            return CompleteSchema(objSchema);
 260
 261            JsonNode CompleteSchema(JsonNode schema)
 0262            {
 0263                if (ExporterContext is { } context)
 0264                {
 0265                    Debug.Assert(options.TransformSchemaNode is not null, "context should only be populated if a callbac
 266                    // Apply any user-defined transformations to the schema.
 0267                    return options.TransformSchemaNode(context, schema);
 268                }
 269
 0270                return schema;
 0271            }
 0272        }
 273
 274        /// <summary>
 275        /// If the schema is boolean, replaces it with a semantically
 276        /// equivalent object schema that allows appending keywords.
 277        /// </summary>
 278        public static void EnsureMutable(ref JsonSchema schema)
 0279        {
 0280            switch (schema._trueOrFalse)
 281            {
 282                case false:
 0283                    schema = new JsonSchema { Not = CreateTrueSchema() };
 0284                    break;
 285                case true:
 0286                    schema = new JsonSchema();
 0287                    break;
 288            }
 0289        }
 290
 291        private static ReadOnlySpan<JsonSchemaType> s_schemaValues =>
 0292        [
 0293            // NB the order of these values influences order of types in the rendered schema
 0294            JsonSchemaType.String,
 0295            JsonSchemaType.Integer,
 0296            JsonSchemaType.Number,
 0297            JsonSchemaType.Boolean,
 0298            JsonSchemaType.Array,
 0299            JsonSchemaType.Object,
 0300            JsonSchemaType.Null,
 0301        ];
 302
 303        private void VerifyMutable()
 0304        {
 0305            Debug.Assert(_trueOrFalse is null, "Schema is not mutable");
 0306            if (_trueOrFalse is not null)
 0307            {
 0308                Throw();
 0309                static void Throw() => throw new InvalidOperationException();
 0310            }
 0311        }
 312
 313        public static JsonNode? MapSchemaType(JsonSchemaType schemaType)
 0314        {
 0315            if (schemaType is JsonSchemaType.Any)
 0316            {
 0317                return null;
 318            }
 319
 0320            if (ToIdentifier(schemaType) is string identifier)
 0321            {
 0322                return identifier;
 323            }
 324
 0325            var array = new JsonArray();
 0326            foreach (JsonSchemaType type in s_schemaValues)
 0327            {
 0328                if ((schemaType & type) != 0)
 0329                {
 0330                    array.Add((JsonNode)ToIdentifier(type)!);
 0331                }
 0332            }
 333
 0334            return array;
 335
 336            static string? ToIdentifier(JsonSchemaType schemaType)
 0337            {
 0338                return schemaType switch
 0339                {
 0340                    JsonSchemaType.Null => "null",
 0341                    JsonSchemaType.Boolean => "boolean",
 0342                    JsonSchemaType.Integer => "integer",
 0343                    JsonSchemaType.Number => "number",
 0344                    JsonSchemaType.String => "string",
 0345                    JsonSchemaType.Array => "array",
 0346                    JsonSchemaType.Object => "object",
 0347                    _ => null,
 0348                };
 0349            }
 0350        }
 351    }
 352}
 353