Skip to content

Commit 08414a1

Browse files
authored
Merge pull request #2172 from microsoft/fix/exclusive
fix: deduplicates exclusive min/max properties in the object model
2 parents 88ea12c + 0d5b471 commit 08414a1

File tree

12 files changed

+174
-121
lines changed

12 files changed

+174
-121
lines changed

src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs

+2-12
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiSerializable
5656
/// <summary>
5757
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
5858
/// </summary>
59-
public decimal? V31ExclusiveMaximum { get; }
59+
public decimal? ExclusiveMaximum { get; }
6060

6161
/// <summary>
6262
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
6363
/// </summary>
64-
public decimal? V31ExclusiveMinimum { get; }
64+
public decimal? ExclusiveMinimum { get; }
6565

6666
/// <summary>
6767
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
@@ -91,21 +91,11 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiSerializable
9191
/// </summary>
9292
public decimal? Maximum { get; }
9393

94-
/// <summary>
95-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
96-
/// </summary>
97-
public bool? ExclusiveMaximum { get; }
98-
9994
/// <summary>
10095
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
10196
/// </summary>
10297
public decimal? Minimum { get; }
10398

104-
/// <summary>
105-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
106-
/// </summary>
107-
public bool? ExclusiveMinimum { get; }
108-
10999
/// <summary>
110100
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
111101
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

+137-33
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,63 @@ public class OpenApiSchema : IOpenApiReferenceable, IOpenApiExtensible, IOpenApi
4444
/// <inheritdoc />
4545
public IDictionary<string, IOpenApiSchema> Definitions { get; set; }
4646

47+
private decimal? _exclusiveMaximum;
4748
/// <inheritdoc />
48-
public decimal? V31ExclusiveMaximum { get; set; }
49+
public decimal? ExclusiveMaximum
50+
{
51+
get
52+
{
53+
if (_exclusiveMaximum.HasValue)
54+
{
55+
return _exclusiveMaximum;
56+
}
57+
if (IsExclusiveMaximum == true && _maximum.HasValue)
58+
{
59+
return _maximum;
60+
}
61+
return null;
62+
}
63+
set
64+
{
65+
_exclusiveMaximum = value;
66+
IsExclusiveMaximum = value != null;
67+
}
68+
}
4969

70+
/// <summary>
71+
/// Compatibility property for OpenAPI 3.0 or earlier serialization of the exclusive maximum value.
72+
/// </summary>
73+
/// DO NOT CHANGE THE VISIBILITY OF THIS PROPERTY TO PUBLIC
74+
internal bool? IsExclusiveMaximum { get; set; }
75+
76+
private decimal? _exclusiveMinimum;
5077
/// <inheritdoc />
51-
public decimal? V31ExclusiveMinimum { get; set; }
78+
public decimal? ExclusiveMinimum
79+
{
80+
get
81+
{
82+
if (_exclusiveMinimum.HasValue)
83+
{
84+
return _exclusiveMinimum;
85+
}
86+
if (IsExclusiveMinimum == true && _minimum.HasValue)
87+
{
88+
return _minimum;
89+
}
90+
return null;
91+
}
92+
set
93+
{
94+
_exclusiveMinimum = value;
95+
IsExclusiveMinimum = value != null;
96+
}
97+
}
98+
99+
/// <summary>
100+
/// Compatibility property for OpenAPI 3.0 or earlier serialization of the exclusive minimum value.
101+
/// </summary>
102+
/// DO NOT CHANGE THE VISIBILITY OF THIS PROPERTY TO PUBLIC
103+
internal bool? IsExclusiveMinimum { get; set; }
52104

53105
/// <inheritdoc />
54106
public bool UnEvaluatedProperties { get; set; }
@@ -65,17 +117,42 @@ public class OpenApiSchema : IOpenApiReferenceable, IOpenApiExtensible, IOpenApi
65117
/// <inheritdoc />
66118
public string Description { get; set; }
67119

120+
private decimal? _maximum;
68121
/// <inheritdoc />
69-
public decimal? Maximum { get; set; }
70-
71-
/// <inheritdoc />
72-
public bool? ExclusiveMaximum { get; set; }
122+
public decimal? Maximum
123+
{
124+
get
125+
{
126+
if (IsExclusiveMaximum == true)
127+
{
128+
return null;
129+
}
130+
return _maximum;
131+
}
132+
set
133+
{
134+
_maximum = value;
135+
}
136+
}
73137

74-
/// <inheritdoc />
75-
public decimal? Minimum { get; set; }
138+
private decimal? _minimum;
76139

77140
/// <inheritdoc />
78-
public bool? ExclusiveMinimum { get; set; }
141+
public decimal? Minimum
142+
{
143+
get
144+
{
145+
if (IsExclusiveMinimum == true)
146+
{
147+
return null;
148+
}
149+
return _minimum;
150+
}
151+
set
152+
{
153+
_minimum = value;
154+
}
155+
}
79156

80157
/// <inheritdoc />
81158
public int? MaxLength { get; set; }
@@ -201,15 +278,18 @@ internal OpenApiSchema(IOpenApiSchema schema)
201278
DynamicRef = schema.DynamicRef ?? DynamicRef;
202279
Definitions = schema.Definitions != null ? new Dictionary<string, IOpenApiSchema>(schema.Definitions) : null;
203280
UnevaluatedProperties = schema.UnevaluatedProperties;
204-
V31ExclusiveMaximum = schema.V31ExclusiveMaximum ?? V31ExclusiveMaximum;
205-
V31ExclusiveMinimum = schema.V31ExclusiveMinimum ?? V31ExclusiveMinimum;
281+
ExclusiveMaximum = schema.ExclusiveMaximum ?? ExclusiveMaximum;
282+
ExclusiveMinimum = schema.ExclusiveMinimum ?? ExclusiveMinimum;
283+
if (schema is OpenApiSchema eMSchema)
284+
{
285+
IsExclusiveMaximum = eMSchema.IsExclusiveMaximum;
286+
IsExclusiveMinimum = eMSchema.IsExclusiveMinimum;
287+
}
206288
Type = schema.Type ?? Type;
207289
Format = schema.Format ?? Format;
208290
Description = schema.Description ?? Description;
209291
Maximum = schema.Maximum ?? Maximum;
210-
ExclusiveMaximum = schema.ExclusiveMaximum ?? ExclusiveMaximum;
211292
Minimum = schema.Minimum ?? Minimum;
212-
ExclusiveMinimum = schema.ExclusiveMinimum ?? ExclusiveMinimum;
213293
MaxLength = schema.MaxLength ?? MaxLength;
214294
MinLength = schema.MinLength ?? MinLength;
215295
Pattern = schema.Pattern ?? Pattern;
@@ -257,6 +337,44 @@ public void SerializeAsV3(IOpenApiWriter writer)
257337
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer));
258338
}
259339

340+
private static void SerializeBounds(IOpenApiWriter writer, OpenApiSpecVersion version, string propertyName, string exclusivePropertyName, string isExclusivePropertyName, decimal? value, decimal? exclusiveValue, bool? isExclusiveValue)
341+
{
342+
if (version >= OpenApiSpecVersion.OpenApi3_1)
343+
{
344+
if (exclusiveValue.HasValue)
345+
{
346+
// was explicitly set in the document or object model
347+
writer.WriteProperty(exclusivePropertyName, exclusiveValue.Value);
348+
}
349+
else if (isExclusiveValue == true && value.HasValue)
350+
{
351+
// came from parsing an old document
352+
writer.WriteProperty(exclusivePropertyName, value);
353+
}
354+
else if (value.HasValue)
355+
{
356+
// was explicitly set in the document or object model
357+
writer.WriteProperty(propertyName, value);
358+
}
359+
}
360+
else
361+
{
362+
if (exclusiveValue.HasValue)
363+
{
364+
// was explicitly set in a new document being downcast or object model
365+
writer.WriteProperty(propertyName, exclusiveValue.Value);
366+
writer.WriteProperty(isExclusivePropertyName, true);
367+
}
368+
else if (value.HasValue)
369+
{
370+
// came from parsing an old document, we're just mirroring the information
371+
writer.WriteProperty(propertyName, value);
372+
if (isExclusiveValue.HasValue)
373+
writer.WriteProperty(isExclusivePropertyName, isExclusiveValue.Value);
374+
}
375+
}
376+
}
377+
260378
private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
261379
Action<IOpenApiWriter, IOpenApiSerializable> callback)
262380
{
@@ -274,16 +392,12 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
274392
writer.WriteProperty(OpenApiConstants.MultipleOf, MultipleOf);
275393

276394
// maximum
277-
writer.WriteProperty(OpenApiConstants.Maximum, Maximum);
278-
279395
// exclusiveMaximum
280-
writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, ExclusiveMaximum);
396+
SerializeBounds(writer, version, OpenApiConstants.Maximum, OpenApiConstants.ExclusiveMaximum, OpenApiConstants.V31ExclusiveMaximum, Maximum, ExclusiveMaximum, IsExclusiveMaximum);
281397

282398
// minimum
283-
writer.WriteProperty(OpenApiConstants.Minimum, Minimum);
284-
285399
// exclusiveMinimum
286-
writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, ExclusiveMinimum);
400+
SerializeBounds(writer, version, OpenApiConstants.Minimum, OpenApiConstants.ExclusiveMinimum, OpenApiConstants.V31ExclusiveMinimum, Minimum, ExclusiveMinimum, IsExclusiveMinimum);
287401

288402
// maxLength
289403
writer.WriteProperty(OpenApiConstants.MaxLength, MaxLength);
@@ -407,8 +521,6 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer)
407521
writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV31(w));
408522
writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef);
409523
writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor);
410-
writer.WriteProperty(OpenApiConstants.V31ExclusiveMaximum, V31ExclusiveMaximum);
411-
writer.WriteProperty(OpenApiConstants.V31ExclusiveMinimum, V31ExclusiveMinimum);
412524
writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties, false);
413525
writer.WriteOptionalCollection(OpenApiConstants.Examples, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s));
414526
writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w));
@@ -438,16 +550,12 @@ internal void WriteAsItemsProperties(IOpenApiWriter writer)
438550
writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d));
439551

440552
// maximum
441-
writer.WriteProperty(OpenApiConstants.Maximum, Maximum);
442-
443553
// exclusiveMaximum
444-
writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, ExclusiveMaximum);
554+
SerializeBounds(writer, OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Maximum, OpenApiConstants.ExclusiveMaximum, OpenApiConstants.V31ExclusiveMaximum, Maximum, ExclusiveMaximum, IsExclusiveMaximum);
445555

446556
// minimum
447-
writer.WriteProperty(OpenApiConstants.Minimum, Minimum);
448-
449557
// exclusiveMinimum
450-
writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, ExclusiveMinimum);
558+
SerializeBounds(writer, OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Minimum, OpenApiConstants.ExclusiveMinimum, OpenApiConstants.V31ExclusiveMinimum, Minimum, ExclusiveMinimum, IsExclusiveMinimum);
451559

452560
// maxLength
453561
writer.WriteProperty(OpenApiConstants.MaxLength, MaxLength);
@@ -522,16 +630,12 @@ private void SerializeAsV2(
522630
writer.WriteProperty(OpenApiConstants.MultipleOf, MultipleOf);
523631

524632
// maximum
525-
writer.WriteProperty(OpenApiConstants.Maximum, Maximum);
526-
527633
// exclusiveMaximum
528-
writer.WriteProperty(OpenApiConstants.ExclusiveMaximum, ExclusiveMaximum);
634+
SerializeBounds(writer, OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Maximum, OpenApiConstants.ExclusiveMaximum, OpenApiConstants.V31ExclusiveMaximum, Maximum, ExclusiveMaximum, IsExclusiveMaximum);
529635

530636
// minimum
531-
writer.WriteProperty(OpenApiConstants.Minimum, Minimum);
532-
533637
// exclusiveMinimum
534-
writer.WriteProperty(OpenApiConstants.ExclusiveMinimum, ExclusiveMinimum);
638+
SerializeBounds(writer, OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Minimum, OpenApiConstants.ExclusiveMinimum, OpenApiConstants.V31ExclusiveMinimum, Minimum, ExclusiveMinimum, IsExclusiveMinimum);
535639

536640
// maxLength
537641
writer.WriteProperty(OpenApiConstants.MaxLength, MaxLength);

src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs

+2-6
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ public string Description
6666
/// <inheritdoc/>
6767
public IDictionary<string, IOpenApiSchema> Definitions { get => Target?.Definitions; }
6868
/// <inheritdoc/>
69-
public decimal? V31ExclusiveMaximum { get => Target?.V31ExclusiveMaximum; }
69+
public decimal? ExclusiveMaximum { get => Target?.ExclusiveMaximum; }
7070
/// <inheritdoc/>
71-
public decimal? V31ExclusiveMinimum { get => Target?.V31ExclusiveMinimum; }
71+
public decimal? ExclusiveMinimum { get => Target?.ExclusiveMinimum; }
7272
/// <inheritdoc/>
7373
public bool UnEvaluatedProperties { get => Target?.UnEvaluatedProperties ?? false; }
7474
/// <inheritdoc/>
@@ -80,12 +80,8 @@ public string Description
8080
/// <inheritdoc/>
8181
public decimal? Maximum { get => Target?.Maximum; }
8282
/// <inheritdoc/>
83-
public bool? ExclusiveMaximum { get => Target?.ExclusiveMaximum; }
84-
/// <inheritdoc/>
8583
public decimal? Minimum { get => Target?.Minimum; }
8684
/// <inheritdoc/>
87-
public bool? ExclusiveMinimum { get => Target?.ExclusiveMinimum; }
88-
/// <inheritdoc/>
8985
public int? MaxLength { get => Target?.MaxLength; }
9086
/// <inheritdoc/>
9187
public int? MinLength { get => Target?.MinLength; }

src/Microsoft.OpenApi/Reader/V2/OpenApiHeaderDeserializer.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ internal static partial class OpenApiV2Deserializer
4949
},
5050
{
5151
"exclusiveMaximum",
52-
(o, n, _) => GetOrCreateSchema(o).ExclusiveMaximum = bool.Parse(n.GetScalarValue())
52+
(o, n, _) => GetOrCreateSchema(o).IsExclusiveMaximum = bool.Parse(n.GetScalarValue())
5353
},
5454
{
5555
"minimum",
5656
(o, n, _) => GetOrCreateSchema(o).Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue)
5757
},
5858
{
5959
"exclusiveMinimum",
60-
(o, n, _) => GetOrCreateSchema(o).ExclusiveMinimum = bool.Parse(n.GetScalarValue())
60+
(o, n, _) => GetOrCreateSchema(o).IsExclusiveMinimum = bool.Parse(n.GetScalarValue())
6161
},
6262
{
6363
"maxLength",

src/Microsoft.OpenApi/Reader/V2/OpenApiSchemaDeserializer.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ internal static partial class OpenApiV2Deserializer
3434
},
3535
{
3636
"exclusiveMaximum",
37-
(o, n, _) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue())
37+
(o, n, _) => o.IsExclusiveMaximum = bool.Parse(n.GetScalarValue())
3838
},
3939
{
4040
"minimum",
4141
(o, n, _) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue)
4242
},
4343
{
4444
"exclusiveMinimum",
45-
(o, n, _) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue())
45+
(o, n, _) => o.IsExclusiveMinimum = bool.Parse(n.GetScalarValue())
4646
},
4747
{
4848
"maxLength",

src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ internal static partial class OpenApiV3Deserializer
3434
},
3535
{
3636
"exclusiveMaximum",
37-
(o, n, _) => o.ExclusiveMaximum = bool.Parse(n.GetScalarValue())
37+
(o, n, _) => o.IsExclusiveMaximum = bool.Parse(n.GetScalarValue())
3838
},
3939
{
4040
"minimum",
4141
(o, n, _) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue)
4242
},
4343
{
4444
"exclusiveMinimum",
45-
(o, n, _) => o.ExclusiveMinimum = bool.Parse(n.GetScalarValue())
45+
(o, n, _) => o.IsExclusiveMinimum = bool.Parse(n.GetScalarValue())
4646
},
4747
{
4848
"maxLength",

src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ internal static partial class OpenApiV31Deserializer
5959
},
6060
{
6161
"exclusiveMaximum",
62-
(o, n, _) => o.V31ExclusiveMaximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue)
62+
(o, n, _) => o.ExclusiveMaximum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue)
6363
},
6464
{
6565
"minimum",
6666
(o, n, _) => o.Minimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MinValue)
6767
},
6868
{
6969
"exclusiveMinimum",
70-
(o, n, _) => o.V31ExclusiveMinimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue)
70+
(o, n, _) => o.ExclusiveMinimum = ParserHelper.ParseDecimalWithFallbackOnOverflow(n.GetScalarValue(), decimal.MaxValue)
7171
},
7272
{
7373
"maxLength",

0 commit comments

Comments
 (0)