Skip to content

Commit b38fc79

Browse files
Merge branch 'main' into fix/revert-to-IDictionary-and-allow-sorting
2 parents 54fed37 + 7ea0e9d commit b38fc79

File tree

8 files changed

+158
-64
lines changed

8 files changed

+158
-64
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "2.0.0-preview.19"
2+
".": "2.0.0-preview.20"
33
}

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## [2.0.0-preview.20](https://github.com/microsoft/OpenAPI.NET/compare/v2.0.0-preview.19...v2.0.0-preview.20) (2025-05-20)
4+
5+
6+
### Bug Fixes
7+
8+
* nullable and type ordering should be maintain to ease up migration work ([6c82aa6](https://github.com/microsoft/OpenAPI.NET/commit/6c82aa6b2d6e4f39af3c594edd8590a0cf749530))
9+
* nullable should not be inserted as an attempt to normalize the document ([6c82aa6](https://github.com/microsoft/OpenAPI.NET/commit/6c82aa6b2d6e4f39af3c594edd8590a0cf749530))
10+
* refactor to avoid adding duplicate entries ([41fd508](https://github.com/microsoft/OpenAPI.NET/commit/41fd508074f1b70415026df3aa878cd5f5e7b1ee))
11+
* refactor to avoid adding duplicate entries ([#2359](https://github.com/microsoft/OpenAPI.NET/issues/2359)) ([9791eb6](https://github.com/microsoft/OpenAPI.NET/commit/9791eb684a0f040feeb8c58701fd4f3577e73e2c))
12+
* tree node has the wrong structure because of trailing slashes ([2ffb273](https://github.com/microsoft/OpenAPI.NET/commit/2ffb2735aa3718370d6094186142f9cf50b194fa))
13+
* tree node has the wrong structure because of trailing slashes ([4439340](https://github.com/microsoft/OpenAPI.NET/commit/443934060e1e446de726addde69a2de955b95a7b))
14+
* wrong link to json schema spec in schema doc comments ([d9b0c90](https://github.com/microsoft/OpenAPI.NET/commit/d9b0c906f7173b81fea15001d588edcbc3eed8f1))
15+
* wrong link to json schema spec in schema doc comments ([9a73ec6](https://github.com/microsoft/OpenAPI.NET/commit/9a73ec6e5486d84b6a30a5fa0ac5961b381fc3d3))
16+
317
## [2.0.0-preview.19](https://github.com/microsoft/OpenAPI.NET/compare/v2.0.0-preview.18...v2.0.0-preview.19) (2025-05-16)
418

519

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<PackageProjectUrl>https://github.com/Microsoft/OpenAPI.NET</PackageProjectUrl>
1313
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
1414
<PackageTags>OpenAPI .NET</PackageTags>
15-
<Version>2.0.0-preview.19</Version>
15+
<Version>2.0.0-preview.20</Version>
1616
</PropertyGroup>
1717
<!-- https://github.com/clairernovotny/DeterministicBuilds#deterministic-builds -->
1818
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">

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

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,17 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiReadOnlyExte
5555
public IDictionary<string, IOpenApiSchema>? Definitions { get; }
5656

5757
/// <summary>
58-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
58+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
5959
/// </summary>
6060
public string? ExclusiveMaximum { get; }
6161

6262
/// <summary>
63-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
63+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
6464
/// </summary>
6565
public string? ExclusiveMinimum { get; }
6666

6767
/// <summary>
68-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
68+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
6969
/// Value MUST be a string in V2 and V3.
7070
/// </summary>
7171
public JsonSchemaType? Type { get; }
@@ -76,45 +76,45 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiReadOnlyExte
7676
public string? Const { get; }
7777

7878
/// <summary>
79-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
79+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
8080
/// While relying on JSON Schema's defined formats,
8181
/// the OAS offers a few additional predefined formats.
8282
/// </summary>
8383
public string? Format { get; }
8484

8585
/// <summary>
86-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
86+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
8787
/// </summary>
8888
public string? Maximum { get; }
8989

9090
/// <summary>
91-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
91+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
9292
/// </summary>
9393
public string? Minimum { get; }
9494

9595
/// <summary>
96-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
96+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
9797
/// </summary>
9898
public int? MaxLength { get; }
9999

100100
/// <summary>
101-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
101+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
102102
/// </summary>
103103
public int? MinLength { get; }
104104

105105
/// <summary>
106-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
106+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
107107
/// This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect
108108
/// </summary>
109109
public string? Pattern { get; }
110110

111111
/// <summary>
112-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
112+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
113113
/// </summary>
114114
public decimal? MultipleOf { get; }
115115

116116
/// <summary>
117-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
117+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
118118
/// The default value represents what would be assumed by the consumer of the input as the value of the schema if one is not provided.
119119
/// Unlike JSON Schema, the value MUST conform to the defined type for the Schema Object defined at the same level.
120120
/// For example, if type is string, then default can be "foo" but cannot be 1.
@@ -142,64 +142,64 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiReadOnlyExte
142142
public bool WriteOnly { get; }
143143

144144
/// <summary>
145-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
145+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
146146
/// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
147147
/// </summary>
148148
public List<IOpenApiSchema>? AllOf { get; }
149149

150150
/// <summary>
151-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
151+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
152152
/// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
153153
/// </summary>
154154
public List<IOpenApiSchema>? OneOf { get; }
155155

156156
/// <summary>
157-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
157+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
158158
/// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
159159
/// </summary>
160160
public List<IOpenApiSchema>? AnyOf { get; }
161161

162162
/// <summary>
163-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
163+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
164164
/// Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
165165
/// </summary>
166166
public IOpenApiSchema? Not { get; }
167167

168168
/// <summary>
169-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
169+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
170170
/// </summary>
171171
public HashSet<string>? Required { get; }
172172

173173
/// <summary>
174-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
174+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
175175
/// Value MUST be an object and not an array. Inline or referenced schema MUST be of a Schema Object
176176
/// and not a standard JSON Schema. items MUST be present if the type is array.
177177
/// </summary>
178178
public IOpenApiSchema? Items { get; }
179179

180180
/// <summary>
181-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
181+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
182182
/// </summary>
183183
public int? MaxItems { get; }
184184

185185
/// <summary>
186-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
186+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
187187
/// </summary>
188188
public int? MinItems { get; }
189189

190190
/// <summary>
191-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
191+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
192192
/// </summary>
193193
public bool? UniqueItems { get; }
194194

195195
/// <summary>
196-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
196+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
197197
/// Property definitions MUST be a Schema Object and not a standard JSON Schema (inline or referenced).
198198
/// </summary>
199199
public IDictionary<string, IOpenApiSchema>? Properties { get; }
200200

201201
/// <summary>
202-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
202+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
203203
/// PatternProperty definitions MUST be a Schema Object and not a standard JSON Schema (inline or referenced)
204204
/// Each property name of this object SHOULD be a valid regular expression according to the ECMA 262 r
205205
/// egular expression dialect. Each property value of this object MUST be an object, and each object MUST
@@ -208,12 +208,12 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiReadOnlyExte
208208
public IDictionary<string, IOpenApiSchema>? PatternProperties { get; }
209209

210210
/// <summary>
211-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
211+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
212212
/// </summary>
213213
public int? MaxProperties { get; }
214214

215215
/// <summary>
216-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
216+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
217217
/// </summary>
218218
public int? MinProperties { get; }
219219

@@ -223,7 +223,7 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiReadOnlyExte
223223
public bool AdditionalPropertiesAllowed { get; }
224224

225225
/// <summary>
226-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
226+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
227227
/// Value can be boolean or object. Inline or referenced schema
228228
/// MUST be of a Schema Object and not a standard JSON Schema.
229229
/// </summary>
@@ -250,12 +250,12 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiReadOnlyExte
250250
public List<JsonNode>? Examples { get; }
251251

252252
/// <summary>
253-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
253+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
254254
/// </summary>
255255
public List<JsonNode>? Enum { get; }
256256

257257
/// <summary>
258-
/// Follow JSON Schema definition: https://tools.ietf.org/html/draft-fge-json-schema-validation-00
258+
/// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation
259259
/// </summary>
260260
public bool UnevaluatedProperties { get; }
261261

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -665,52 +665,70 @@ public bool AddComponent<T>(string id, T componentToRegister)
665665
Utils.CheckArgumentNull(componentToRegister);
666666
Utils.CheckArgumentNullOrEmpty(id);
667667
Components ??= new();
668+
669+
static bool AddToDictionary<TValue>(Dictionary<string, TValue> dict, string key, TValue value)
670+
{
671+
#if NET5_0_OR_GREATER
672+
return dict.TryAdd(key, value);
673+
#else
674+
if (!dict.ContainsKey(key))
675+
{
676+
dict.Add(key, value);
677+
return true;
678+
}
679+
return false;
680+
#endif
681+
}
682+
683+
bool added = false;
668684
switch (componentToRegister)
669685
{
670686
case IOpenApiSchema openApiSchema:
671-
Components.Schemas ??= new Dictionary<string, IOpenApiSchema>();
672-
Components.Schemas.Add(id, openApiSchema);
687+
Components.Schemas ??= [];
688+
added = AddToDictionary(Components.Schemas, id, openApiSchema);
673689
break;
674690
case IOpenApiParameter openApiParameter:
675-
Components.Parameters ??= new Dictionary<string, IOpenApiParameter>();
676-
Components.Parameters.Add(id, openApiParameter);
691+
Components.Parameters ??= [];
692+
added = AddToDictionary(Components.Parameters, id, openApiParameter);
677693
break;
678694
case IOpenApiResponse openApiResponse:
679-
Components.Responses ??= new Dictionary<string, IOpenApiResponse>();
680-
Components.Responses.Add(id, openApiResponse);
695+
Components.Responses ??= [];
696+
added = AddToDictionary(Components.Responses, id, openApiResponse);
681697
break;
682698
case IOpenApiRequestBody openApiRequestBody:
683-
Components.RequestBodies ??= new Dictionary<string, IOpenApiRequestBody>();
684-
Components.RequestBodies.Add(id, openApiRequestBody);
699+
Components.RequestBodies ??= [];
700+
added = AddToDictionary(Components.RequestBodies, id, openApiRequestBody);
685701
break;
686702
case IOpenApiLink openApiLink:
687-
Components.Links ??= new Dictionary<string, IOpenApiLink>();
688-
Components.Links.Add(id, openApiLink);
703+
Components.Links ??= [];
704+
added = AddToDictionary(Components.Links, id, openApiLink);
689705
break;
690706
case IOpenApiCallback openApiCallback:
691-
Components.Callbacks ??= new Dictionary<string, IOpenApiCallback>();
692-
Components.Callbacks.Add(id, openApiCallback);
707+
Components.Callbacks ??= [];
708+
added = AddToDictionary(Components.Callbacks, id, openApiCallback);
693709
break;
694710
case IOpenApiPathItem openApiPathItem:
695-
Components.PathItems ??= new Dictionary<string, IOpenApiPathItem>();
696-
Components.PathItems.Add(id, openApiPathItem);
711+
Components.PathItems ??= [];
712+
added = AddToDictionary(Components.PathItems, id, openApiPathItem);
697713
break;
698714
case IOpenApiExample openApiExample:
699-
Components.Examples ??= new Dictionary<string, IOpenApiExample>();
700-
Components.Examples.Add(id, openApiExample);
715+
Components.Examples ??= [];
716+
added = AddToDictionary(Components.Examples, id, openApiExample);
701717
break;
702718
case IOpenApiHeader openApiHeader:
703-
Components.Headers ??= new Dictionary<string, IOpenApiHeader>();
704-
Components.Headers.Add(id, openApiHeader);
719+
Components.Headers ??= [];
720+
added = AddToDictionary(Components.Headers, id, openApiHeader);
705721
break;
706722
case IOpenApiSecurityScheme openApiSecurityScheme:
707-
Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();
708-
Components.SecuritySchemes.Add(id, openApiSecurityScheme);
723+
Components.SecuritySchemes ??= [];
724+
added = AddToDictionary(Components.SecuritySchemes, id, openApiSecurityScheme);
709725
break;
710726
default:
711727
throw new ArgumentException($"Component type {componentToRegister!.GetType().Name} is not supported.");
712728
}
713-
return Workspace?.RegisterComponentForDocument(this, componentToRegister, id) ?? false;
729+
730+
// Register only if it was actually added to the collection
731+
return added && (Workspace?.RegisterComponentForDocument(this, componentToRegister, id) ?? false);
714732
}
715733
}
716734

src/Microsoft.OpenApi/Services/OpenApiUrlTreeNode.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public bool HasOperations(string label)
5959
{
6060
Utils.CheckArgumentNullOrEmpty(label);
6161

62-
return PathItems is not null && PathItems.TryGetValue(label, out var item) && item.Operations is not null && item.Operations.Any();
62+
return PathItems is not null && PathItems.TryGetValue(label, out var item) && item.Operations is { Count : > 0 };
6363
}
6464

6565
/// <summary>
@@ -151,13 +151,6 @@ public OpenApiUrlTreeNode Attach(string path,
151151
}
152152

153153
var segments = path.Split('/');
154-
if (path.EndsWith("/", StringComparison.OrdinalIgnoreCase))
155-
{
156-
// Remove the last element, which is empty, and append the trailing slash to the new last element
157-
// This is to support URLs with trailing slashes
158-
Array.Resize(ref segments, segments.Length - 1);
159-
segments[segments.Length - 1] += @"\";
160-
}
161154

162155
return Attach(segments: segments,
163156
pathItem: pathItem,
@@ -179,7 +172,8 @@ private OpenApiUrlTreeNode Attach(IEnumerable<string> segments,
179172
string currentPath)
180173
{
181174
var segment = segments.FirstOrDefault();
182-
if (string.IsNullOrEmpty(segment))
175+
// empty segment is significant
176+
if (segment is null)
183177
{
184178
if (PathItems.ContainsKey(label))
185179
{
@@ -190,6 +184,13 @@ private OpenApiUrlTreeNode Attach(IEnumerable<string> segments,
190184
PathItems.Add(label, pathItem);
191185
return this;
192186
}
187+
// special casing for '/' (root node)
188+
if (segment.Length == 0 && currentPath.Length == 0)
189+
{
190+
Path = currentPath;
191+
PathItems.Add(label, pathItem);
192+
return this;
193+
}
193194

194195
// If the child segment has already been defined, then insert into it
195196
if (Children.TryGetValue(segment, out var child))

test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,7 @@ public async Task SerializesDoubleHopeReferences()
12701270
Description = "A reference to a pet"
12711271
};
12721272
document.AddComponent("PetReference", petSchemaReference);
1273+
document.AddComponent("Pet", petSchema); // does not add duplicate keys
12731274
document.Paths.Add("/pets", new OpenApiPathItem
12741275
{
12751276
Operations = new()

0 commit comments

Comments
 (0)