diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index c629f78be..8877faac8 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -50,6 +50,11 @@ public static class OpenApiConstants /// public const string Title = "title"; + /// + /// Field: Const + /// + public const string Const = "const"; + /// /// Field: Type /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index c2456286c..0215ac522 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -83,6 +83,11 @@ public class OpenApiSchema : IOpenApiAnnotatable, IOpenApiExtensible, IOpenApiRe /// public virtual JsonSchemaType? Type { get; set; } + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public virtual string Const { get; set; } + /// /// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00 /// While relying on JSON Schema's defined formats, @@ -347,6 +352,7 @@ public OpenApiSchema(OpenApiSchema schema) { Title = schema?.Title ?? Title; Id = schema?.Id ?? Id; + Const = schema?.Const ?? Const; Schema = schema?.Schema ?? Schema; Comment = schema?.Comment ?? Comment; Vocabulary = schema?.Vocabulary != null ? new Dictionary(schema.Vocabulary) : null; @@ -563,6 +569,7 @@ internal void WriteV31Properties(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Id, Id); writer.WriteProperty(OpenApiConstants.DollarSchema, Schema); writer.WriteProperty(OpenApiConstants.Comment, Comment); + writer.WriteProperty(OpenApiConstants.Const, Const); writer.WriteOptionalMap(OpenApiConstants.Vocabulary, Vocabulary, (w, s) => w.WriteValue(s)); writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV31(w)); writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef); diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs index a7b55e109..011e0b930 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -17,6 +17,9 @@ public class OpenApiSchemaReference : OpenApiSchema internal OpenApiSchema _target; private readonly OpenApiReference _reference; private string _description; + private JsonNode _default; + private JsonNode _example; + private IList _examples; private OpenApiSchema Target { @@ -90,6 +93,8 @@ internal OpenApiSchemaReference(OpenApiSchema target, string referenceId) /// public override JsonSchemaType? Type { get => Target.Type; set => Target.Type = value; } /// + public override string Const { get => Target.Const; set => Target.Const = value; } + /// public override string Format { get => Target.Format; set => Target.Format = value; } /// public override string Description @@ -114,7 +119,11 @@ public override string Description /// public override decimal? MultipleOf { get => Target.MultipleOf; set => Target.MultipleOf = value; } /// - public override JsonNode Default { get => Target.Default; set => Target.Default = value; } + public override JsonNode Default + { + get => _default ??= Target.Default; + set => _default = value; + } /// public override bool ReadOnly { get => Target.ReadOnly; set => Target.ReadOnly = value; } /// @@ -152,9 +161,17 @@ public override string Description /// public override OpenApiDiscriminator Discriminator { get => Target.Discriminator; set => Target.Discriminator = value; } /// - public override JsonNode Example { get => Target.Example; set => Target.Example = value; } + public override JsonNode Example + { + get => _example ??= Target.Example; + set => _example = value; + } /// - public override IList Examples { get => Target.Examples; set => Target.Examples = value; } + public override IList Examples + { + get => _examples ??= Target.Examples; + set => Target.Examples = value; + } /// public override IList Enum { get => Target.Enum; set => Target.Enum = value; } /// diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index 5dc76b7fb..9c035da0d 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -131,6 +131,10 @@ internal static partial class OpenApiV31Deserializer } } }, + { + "const", + (o, n, _) => o.Const = n.GetScalarValue() + }, { "allOf", (o, n, t) => o.AllOf = n.CreateList(LoadSchema, t) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs index 967bb0f3e..5f149b021 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -452,5 +452,48 @@ public void SerializeSchemaWithJsonSchemaKeywordsWorks() schema.Vocabulary.Keys.Count.Should().Be(5); schemaString.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); } + + [Fact] + public void ParseSchemaWithConstWorks() + { + var expected = @"{ + ""$schema"": ""https://json-schema.org/draft/2020-12/schema"", + ""required"": [ + ""status"" + ], + ""type"": ""object"", + ""properties"": { + ""status"": { + ""const"": ""active"", + ""type"": ""string"" + }, + ""user"": { + ""required"": [ + ""role"" + ], + ""type"": ""object"", + ""properties"": { + ""role"": { + ""const"": ""admin"", + ""type"": ""string"" + } + } + } + } +}"; + + var path = Path.Combine(SampleFolderPath, "schemaWithConst.json"); + + // Act + var schema = OpenApiModelFactory.Load(path, OpenApiSpecVersion.OpenApi3_1, out _); + schema.Properties["status"].Const.Should().Be("active"); + schema.Properties["user"].Properties["role"].Const.Should().Be("admin"); + + // serialization + var writer = new StringWriter(); + schema.SerializeAsV31(new OpenApiJsonWriter(writer)); + var schemaString = writer.ToString(); + schemaString.MakeLineBreaksEnvironmentNeutral().Should().Be(expected.MakeLineBreaksEnvironmentNeutral()); + } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithConst.json b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithConst.json new file mode 100644 index 000000000..ec0a0c794 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/Samples/OpenApiSchema/schemaWithConst.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "active" + }, + "user": { + "type": "object", + "properties": { + "role": { + "type": "string", + "const": "admin" + } + }, + "required": [ "role" ] + } + }, + "required": [ "status" ] +} diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 3fe034011..8f9f8ed41 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -398,6 +398,7 @@ namespace Microsoft.OpenApi.Models public const string Comment = "$comment"; public const string Components = "components"; public const string ComponentsSegment = "/components/"; + public const string Const = "const"; public const string Consumes = "consumes"; public const string Contact = "contact"; public const string Content = "content"; @@ -882,6 +883,7 @@ namespace Microsoft.OpenApi.Models public virtual System.Collections.Generic.IList AllOf { get; set; } public virtual System.Collections.Generic.IList AnyOf { get; set; } public virtual string Comment { get; set; } + public virtual string Const { get; set; } public virtual System.Text.Json.Nodes.JsonNode Default { get; set; } public virtual System.Collections.Generic.IDictionary Definitions { get; set; } public virtual bool Deprecated { get; set; } @@ -1222,6 +1224,7 @@ namespace Microsoft.OpenApi.Models.References public override System.Collections.Generic.IList AllOf { get; set; } public override System.Collections.Generic.IList AnyOf { get; set; } public override string Comment { get; set; } + public override string Const { get; set; } public override System.Text.Json.Nodes.JsonNode Default { get; set; } public override System.Collections.Generic.IDictionary Definitions { get; set; } public override bool Deprecated { get; set; }