Skip to content

Commit 3e159d2

Browse files
committed
Serialize JsonSchema
1 parent ffc87ca commit 3e159d2

File tree

5 files changed

+281
-14
lines changed

5 files changed

+281
-14
lines changed

src/Microsoft.OpenApi.Readers/V31/JsonSchemaDeserializer.cs

Lines changed: 263 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

4-
using System.Text.Json;
4+
using System.Collections.Generic;
5+
using System.Globalization;
6+
using System.Text.Json.Nodes;
57
using Json.Schema;
8+
using Json.Schema.OpenApi;
69
using Microsoft.OpenApi.Extensions;
10+
using Microsoft.OpenApi.Interfaces;
711
using Microsoft.OpenApi.Models;
812
using Microsoft.OpenApi.Readers.ParseNodes;
913
using JsonSchema = Json.Schema.JsonSchema;
@@ -15,20 +19,260 @@ namespace Microsoft.OpenApi.Readers.V31
1519
/// runtime Open API object model.
1620
/// </summary>
1721
internal static partial class OpenApiV31Deserializer
18-
{
22+
{
23+
private static readonly FixedFieldMap<JsonSchemaBuilder> _schemaFixedFields = new()
24+
{
25+
{
26+
"title", (o, n) =>
27+
{
28+
o.Title(n.GetScalarValue());
29+
}
30+
},
31+
{
32+
"multipleOf", (o, n) =>
33+
{
34+
o.MultipleOf(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture));
35+
}
36+
},
37+
{
38+
"maximum", (o, n) =>
39+
{
40+
o.Maximum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture));
41+
}
42+
},
43+
{
44+
"exclusiveMaximum", (o, n) =>
45+
{
46+
o.ExclusiveMaximum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture));
47+
}
48+
},
49+
{
50+
"minimum", (o, n) =>
51+
{
52+
o.Minimum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture));
53+
}
54+
},
55+
{
56+
"exclusiveMinimum", (o, n) =>
57+
{
58+
o.ExclusiveMinimum(decimal.Parse(n.GetScalarValue(), NumberStyles.Float, CultureInfo.InvariantCulture));
59+
}
60+
},
61+
{
62+
"maxLength", (o, n) =>
63+
{
64+
o.MaxLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture));
65+
}
66+
},
67+
{
68+
"minLength", (o, n) =>
69+
{
70+
o.MinLength(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture));
71+
}
72+
},
73+
{
74+
"pattern", (o, n) =>
75+
{
76+
o.Pattern(n.GetScalarValue());
77+
}
78+
},
79+
{
80+
"maxItems", (o, n) =>
81+
{
82+
o.MaxItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture));
83+
}
84+
},
85+
{
86+
"minItems", (o, n) =>
87+
{
88+
o.MinItems(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture));
89+
}
90+
},
91+
{
92+
"uniqueItems", (o, n) =>
93+
{
94+
o.UniqueItems(bool.Parse(n.GetScalarValue()));
95+
}
96+
},
97+
{
98+
"maxProperties", (o, n) =>
99+
{
100+
o.MaxProperties(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture));
101+
}
102+
},
103+
{
104+
"minProperties", (o, n) =>
105+
{
106+
o.MinProperties(uint.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture));
107+
}
108+
},
109+
{
110+
"required", (o, n) =>
111+
{
112+
o.Required(new HashSet<string>(n.CreateSimpleList(n2 => n2.GetScalarValue())));
113+
}
114+
},
115+
{
116+
"enum", (o, n) =>
117+
{
118+
o.Enum(n.CreateListOfAny());
119+
}
120+
},
121+
{
122+
"type", (o, n) =>
123+
{
124+
if(n is ListNode)
125+
{
126+
o.Type(n.CreateSimpleList(s => SchemaTypeConverter.ConvertToSchemaValueType(s.GetScalarValue())));
127+
}
128+
else
129+
{
130+
o.Type(SchemaTypeConverter.ConvertToSchemaValueType(n.GetScalarValue()));
131+
}
132+
}
133+
},
134+
{
135+
"allOf", (o, n) =>
136+
{
137+
o.AllOf(n.CreateList(LoadSchema));
138+
}
139+
},
140+
{
141+
"oneOf", (o, n) =>
142+
{
143+
o.OneOf(n.CreateList(LoadSchema));
144+
}
145+
},
146+
{
147+
"anyOf", (o, n) =>
148+
{
149+
o.AnyOf(n.CreateList(LoadSchema));
150+
}
151+
},
152+
{
153+
"not", (o, n) =>
154+
{
155+
o.Not(LoadSchema(n));
156+
}
157+
},
158+
{
159+
"items", (o, n) =>
160+
{
161+
o.Items(LoadSchema(n));
162+
}
163+
},
164+
{
165+
"properties", (o, n) =>
166+
{
167+
o.Properties(n.CreateMap(LoadSchema));
168+
}
169+
},
170+
{
171+
"additionalProperties", (o, n) =>
172+
{
173+
if (n is ValueNode)
174+
{
175+
o.AdditionalPropertiesAllowed(bool.Parse(n.GetScalarValue()));
176+
}
177+
else
178+
{
179+
o.AdditionalProperties(LoadSchema(n));
180+
}
181+
}
182+
},
183+
{
184+
"description", (o, n) =>
185+
{
186+
o.Description(n.GetScalarValue());
187+
}
188+
},
189+
{
190+
"format", (o, n) =>
191+
{
192+
o.Format(n.GetScalarValue());
193+
}
194+
},
195+
{
196+
"default", (o, n) =>
197+
{
198+
o.Default(n.CreateAny().Node);
199+
}
200+
},
201+
{
202+
"discriminator", (o, n) =>
203+
{
204+
var discriminator = LoadDiscriminator(n);
205+
o.Discriminator(discriminator);
206+
}
207+
},
208+
{
209+
"readOnly", (o, n) =>
210+
{
211+
o.ReadOnly(bool.Parse(n.GetScalarValue()));
212+
}
213+
},
214+
{
215+
"writeOnly", (o, n) =>
216+
{
217+
o.WriteOnly(bool.Parse(n.GetScalarValue()));
218+
}
219+
},
220+
{
221+
"xml", (o, n) =>
222+
{
223+
var xml = LoadXml(n);
224+
o.Xml(xml.Namespace, xml.Name, xml.Prefix, xml.Attribute, xml.Wrapped,
225+
(IReadOnlyDictionary<string, JsonNode>)xml.Extensions);
226+
}
227+
},
228+
{
229+
"externalDocs", (o, n) =>
230+
{
231+
var externalDocs = LoadExternalDocs(n);
232+
o.ExternalDocs(externalDocs.Url, externalDocs.Description,
233+
(IReadOnlyDictionary<string, JsonNode>)externalDocs.Extensions);
234+
}
235+
},
236+
{
237+
"example", (o, n) =>
238+
{
239+
if(n is ListNode)
240+
{
241+
o.Examples(n.CreateSimpleList(s => (JsonNode)s.GetScalarValue()));
242+
}
243+
else
244+
{
245+
o.Example(n.CreateAny().Node);
246+
}
247+
}
248+
},
249+
{
250+
"deprecated", (o, n) =>
251+
{
252+
o.Deprecated(bool.Parse(n.GetScalarValue()));
253+
}
254+
},
255+
};
256+
257+
private static readonly PatternFieldMap<JsonSchemaBuilder> _schemaPatternFields = new PatternFieldMap<JsonSchemaBuilder>
258+
{
259+
{s => s.StartsWith("x-"), (o, p, n) => o.Extensions(LoadExtensions(p, LoadExtension(p, n)))}
260+
};
261+
19262
public static JsonSchema LoadSchema(ParseNode node)
20263
{
21264
var mapNode = node.CheckMapNode(OpenApiConstants.Schema);
22265
var builder = new JsonSchemaBuilder();
23266

24267
// check for a $ref and if present, add it to the builder as a Ref keyword
25-
if (mapNode.GetReferencePointer() is {} pointer)
268+
var pointer = mapNode.GetReferencePointer();
269+
if (pointer != null)
26270
{
27271
builder = builder.Ref(pointer);
28272

29273
// Check for summary and description and append to builder
30274
var summary = mapNode.GetSummaryValue();
31-
var description = mapNode.GetDescriptionValue();
275+
var description = mapNode.GetDescriptionValue();
32276
if (!string.IsNullOrEmpty(summary))
33277
{
34278
builder.Summary(summary);
@@ -40,10 +284,23 @@ public static JsonSchema LoadSchema(ParseNode node)
40284

41285
return builder.Build();
42286
}
43-
else
287+
288+
foreach (var propertyNode in mapNode)
44289
{
45-
return node.JsonNode.Deserialize<JsonSchema>();
290+
propertyNode.ParseField(builder, _schemaFixedFields, _schemaPatternFields);
46291
}
292+
293+
var schema = builder.Build();
294+
return schema;
295+
}
296+
297+
private static Dictionary<string, IOpenApiExtension> LoadExtensions(string value, IOpenApiExtension extension)
298+
{
299+
var extensions = new Dictionary<string, IOpenApiExtension>
300+
{
301+
{ value, extension }
302+
};
303+
return extensions;
47304
}
48305
}
49306

src/Microsoft.OpenApi/Extensions/JsonSchemaBuilderExtensions.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ namespace Microsoft.OpenApi.Extensions
1515
/// </summary>
1616
public static class JsonSchemaBuilderExtensions
1717
{
18-
private static readonly Dictionary<string, IJsonSchemaKeyword> _keywords = new Dictionary<string, IJsonSchemaKeyword>();
19-
2018
/// <summary>
2119
/// Custom extensions in the schema
2220
/// </summary>
@@ -154,8 +152,7 @@ public static JsonSchemaBuilder Remove(this JsonSchemaBuilder builder, string ke
154152
schemaBuilder.Add(item);
155153
}
156154
}
157-
158-
//_keywords.Remove(keyword);
155+
159156
return schemaBuilder;
160157
}
161158
}

src/Microsoft.OpenApi/Validations/Rules/JsonSchemaRules.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ public static class JsonSchemaRules
4848

4949
}
5050

51+
context.Exit();
52+
53+
// example
54+
context.Enter("example");
55+
56+
if (jsonSchema.GetExample() != null)
57+
{
58+
RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), jsonSchema.GetExample(), jsonSchema);
59+
}
60+
5161
context.Exit();
5262

5363
// enum

src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,10 @@ public void WriteJsonSchemaWithoutReference(IOpenApiWriter writer, JsonSchema sc
574574

575575
// externalDocs
576576
writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, schema.GetExternalDocs(), (w, s) => JsonSerializer.Serialize(s));
577-
577+
578+
// example
579+
writer.WriteOptionalObject(OpenApiConstants.Example, schema.GetExample(), (w, s) => w.WriteAny(new OpenApiAny(s)));
580+
578581
// examples
579582
writer.WriteOptionalCollection(OpenApiConstants.Examples, schema.GetExamples(), (n, e) => n.WriteAny(new OpenApiAny(e)));
580583

test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System.Collections.Generic;
@@ -52,8 +52,8 @@ public void RuleSetConstructorsReturnsTheCorrectRules()
5252
Assert.Empty(ruleSet_4.Rules);
5353

5454
// Update the number if you add new default rule(s).
55-
Assert.Equal(22, ruleSet_1.Rules.Count);
56-
Assert.Equal(22, ruleSet_2.Rules.Count);
55+
Assert.Equal(23, ruleSet_1.Rules.Count);
56+
Assert.Equal(23, ruleSet_2.Rules.Count);
5757
Assert.Equal(3, ruleSet_3.Rules.Count);
5858
}
5959

0 commit comments

Comments
 (0)