Skip to content

Commit 63e4b27

Browse files
committed
feat: change all collections to ordered collections
1 parent 3f8b2b9 commit 63e4b27

File tree

146 files changed

+1251
-719
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

146 files changed

+1251
-719
lines changed

docs/upgrade-guide-2.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,57 @@ var outputString = openApiDocument.Serialize(OpenApiSpecVersion.OpenApi2_0, Open
504504
// After (2.0)
505505
var outputString = openApiDocument.Serialize(OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Json);
506506
```
507+
### Use OrderedDictionary string Instead of Dictionary
508+
509+
Changed all collections to ordered collections.
510+
511+
**Example:**
512+
513+
```csharp
514+
// Before (1.6)
515+
new OpenApiSchema
516+
{
517+
Required = new HashSet<string> { "id", "name" },
518+
Properties = new Dictionary<string, IOpenApiSchema>
519+
{
520+
["id"] = new OpenApiSchema
521+
{
522+
Type = JsonSchemaType.Integer,
523+
Format = "int64"
524+
},
525+
["name"] = new OpenApiSchema
526+
{
527+
Type = JsonSchemaType.String
528+
},
529+
["tag"] = new OpenApiSchema
530+
{
531+
Type = JsonSchemaType.String
532+
}
533+
}
534+
}
535+
536+
// After (2.0)
537+
new OpenApiSchema
538+
{
539+
Required = new HashSet<string> { "id", "name" },
540+
Properties = new OrderedDictionary<string, IOpenApiSchema>
541+
{
542+
["id"] = new OpenApiSchema
543+
{
544+
Type = JsonSchemaType.Integer,
545+
Format = "int64"
546+
},
547+
["name"] = new OpenApiSchema
548+
{
549+
Type = JsonSchemaType.String
550+
},
551+
["tag"] = new OpenApiSchema
552+
{
553+
Type = JsonSchemaType.String
554+
}
555+
}
556+
}
557+
```
507558

508559
### Bug Fixes
509560

src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ namespace Microsoft.OpenApi.Hidi.Extensions
88
internal static class OpenApiExtensibleExtensions
99
{
1010
/// <summary>
11-
/// Gets an extension value from the extensions dictionary.
11+
/// Gets an extension value from the extensions OrderedDictionary.
1212
/// </summary>
13-
/// <param name="extensions">A dictionary of <see cref="IOpenApiExtension"/>.</param>
13+
/// <param name="extensions">A OrderedDictionary of <see cref="IOpenApiExtension"/>.</param>
1414
/// <param name="extensionKey">The key corresponding to the <see cref="IOpenApiExtension"/>.</param>
1515
/// <returns>A <see cref="string"/> value matching the provided extensionKey. Return null when extensionKey is not found. </returns>
16-
internal static string GetExtension(this Dictionary<string, IOpenApiExtension> extensions, string extensionKey)
16+
internal static string GetExtension(this OrderedDictionary<string, IOpenApiExtension> extensions, string extensionKey)
1717
{
1818
if (extensions.TryGetValue(extensionKey, out var value) && value is OpenApiAny { Node: JsonValue castValue } && castValue.TryGetValue<string>(out var stringValue))
1919
{

src/Microsoft.OpenApi.Hidi/OpenApiService.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public static async Task TransformOpenApiDocumentAsync(HidiOptions options, ILog
155155

156156
private static OpenApiDocument ApplyFilters(HidiOptions options, ILogger logger, ApiDependency? apiDependency, JsonDocument? postmanCollection, OpenApiDocument document)
157157
{
158-
Dictionary<string, List<string>> requestUrls;
158+
OrderedDictionary<string, List<string>> requestUrls;
159159
if (apiDependency != null)
160160
{
161161
requestUrls = GetRequestUrlsFromManifest(apiDependency);
@@ -262,7 +262,7 @@ private static async Task WriteOpenApiAsync(HidiOptions options, string openApiF
262262
return document;
263263
}
264264

265-
private static Func<string, HttpMethod, OpenApiOperation, bool>? FilterOpenApiDocument(string? filterByOperationIds, string? filterByTags, Dictionary<string, List<string>> requestUrls, OpenApiDocument document, ILogger logger)
265+
private static Func<string, HttpMethod, OpenApiOperation, bool>? FilterOpenApiDocument(string? filterByOperationIds, string? filterByTags, OrderedDictionary<string, List<string>> requestUrls, OpenApiDocument document, ILogger logger)
266266
{
267267
Func<string, HttpMethod, OpenApiOperation, bool>? predicate = null;
268268

@@ -295,18 +295,18 @@ private static async Task WriteOpenApiAsync(HidiOptions options, string openApiF
295295
return predicate;
296296
}
297297

298-
private static Dictionary<string, List<string>> GetRequestUrlsFromManifest(ApiDependency apiDependency)
298+
private static OrderedDictionary<string, List<string>> GetRequestUrlsFromManifest(ApiDependency apiDependency)
299299
{
300300
// Get the request URLs from the API Dependencies in the API manifest
301301
var requests = apiDependency
302302
.Requests.Where(static r => !r.Exclude && !string.IsNullOrEmpty(r.UriTemplate) && !string.IsNullOrEmpty(r.Method))
303303
.Select(static r => new { UriTemplate = r.UriTemplate!, Method = r.Method! })
304304
.GroupBy(static r => r.UriTemplate)
305-
.ToDictionary(static g => g.Key, static g => g.Select(static r => r.Method).ToList());
305+
.ToOrderedDictionary(static g => g.Key, static g => g.Select(static r => r.Method).ToList());
306306
// This makes the assumption that the UriTemplate in the ApiManifest matches exactly the UriTemplate in the OpenAPI document
307307
// This does not need to be the case. The URI template in the API manifest could map to a set of OpenAPI paths.
308308
// Additional logic will be required to handle this scenario. I suggest we build this into the OpenAPI.Net library at some point.
309-
return requests;
309+
return new OrderedDictionary<string, List<string>>(requests);
310310
}
311311

312312
private static XslCompiledTransform GetFilterTransform()
@@ -451,10 +451,10 @@ private static async Task<ReadResult> ParseOpenApiAsync(string openApiFile, bool
451451
/// Takes in a file stream, parses the stream into a JsonDocument and gets a list of paths and Http methods
452452
/// </summary>
453453
/// <param name="stream"> A file stream.</param>
454-
/// <returns> A dictionary of request urls and http methods from a collection.</returns>
455-
public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream stream, ILogger logger)
454+
/// <returns> A OrderedDictionary of request urls and http methods from a collection.</returns>
455+
public static OrderedDictionary<string, List<string>> ParseJsonCollectionFile(Stream stream, ILogger logger)
456456
{
457-
var requestUrls = new Dictionary<string, List<string>>();
457+
var requestUrls = new OrderedDictionary<string, List<string>>();
458458

459459
logger.LogTrace("Parsing the json collection file into a JsonDocument");
460460
using var document = JsonDocument.Parse(stream);
@@ -466,7 +466,7 @@ public static Dictionary<string, List<string>> ParseJsonCollectionFile(Stream st
466466
return requestUrls;
467467
}
468468

469-
private static Dictionary<string, List<string>> EnumerateJsonDocument(JsonElement itemElement, Dictionary<string, List<string>> paths)
469+
private static OrderedDictionary<string, List<string>> EnumerateJsonDocument(JsonElement itemElement, OrderedDictionary<string, List<string>> paths)
470470
{
471471
var itemsArray = itemElement.GetProperty("item");
472472

@@ -476,7 +476,7 @@ private static Dictionary<string, List<string>> EnumerateJsonDocument(JsonElemen
476476
{
477477
if (item.TryGetProperty("request", out var request))
478478
{
479-
// Fetch list of methods and urls from collection, store them in a dictionary
479+
// Fetch list of methods and urls from collection, store them in a OrderedDictionary
480480
var path = request.GetProperty("url").GetProperty("raw").ToString();
481481
var method = request.GetProperty("method").ToString();
482482
if (paths.TryGetValue(path, out var value))

src/Microsoft.OpenApi.Hidi/StatsVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public override void Visit(IOpenApiSchema schema)
2727

2828
public int HeaderCount { get; set; }
2929

30-
public override void Visit(Dictionary<string, IOpenApiHeader> headers)
30+
public override void Visit(OrderedDictionary<string, IOpenApiHeader> headers)
3131
{
3232
HeaderCount++;
3333
}

src/Microsoft.OpenApi.Workbench/StatsVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public override void Visit(IOpenApiSchema schema)
2727

2828
public int HeaderCount { get; set; }
2929

30-
public override void Visit(Dictionary<string, IOpenApiHeader> headers)
30+
public override void Visit(OrderedDictionary<string, IOpenApiHeader> headers)
3131
{
3232
HeaderCount++;
3333
}

src/Microsoft.OpenApi/Extensions/OpenApiServerExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ public static class OpenApiServerExtensions
1818
/// <returns>A URL with the provided variables substituted.</returns>
1919
/// <exception cref="ArgumentException">
2020
/// Thrown when:
21-
/// 1. A substitution has no valid value in both the supplied dictionary and the default
21+
/// 1. A substitution has no valid value in both the supplied OrderedDictionary and the default
2222
/// 2. A substitution's value is not available in the enum provided
2323
/// </exception>
24-
public static string? ReplaceServerUrlVariables(this OpenApiServer server, Dictionary<string, string>? values = null)
24+
public static string? ReplaceServerUrlVariables(this OpenApiServer server, OrderedDictionary<string, string>? values = null)
2525
{
2626
var parsedUrl = server.Url;
2727
if (server.Variables is not null && parsedUrl is not null)

src/Microsoft.OpenApi/Extensions/OpenApiTypeMapper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static string[] ToIdentifiers(this JsonSchemaType schemaType)
3838
return schemaType.ToIdentifiersInternal().ToArray();
3939
}
4040

41-
private static readonly Dictionary<JsonSchemaType, string> allSchemaTypes = new()
41+
private static readonly OrderedDictionary<JsonSchemaType, string> allSchemaTypes = new()
4242
{
4343
{ JsonSchemaType.Boolean, "boolean" },
4444
{ JsonSchemaType.Integer, "integer" },
@@ -115,7 +115,7 @@ public static JsonSchemaType ToJsonSchemaType(this string identifier)
115115
return type;
116116
}
117117

118-
private static readonly Dictionary<Type, Func<OpenApiSchema>> _simpleTypeToOpenApiSchema = new()
118+
private static readonly OrderedDictionary<Type, Func<OpenApiSchema>> _simpleTypeToOpenApiSchema = new()
119119
{
120120
[typeof(bool)] = () => new() { Type = JsonSchemaType.Boolean },
121121
[typeof(byte)] = () => new() { Type = JsonSchemaType.String, Format = "byte" },

src/Microsoft.OpenApi/Extensions/StringExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ internal static class StringExtensions
4747
}
4848
private static ReadOnlyDictionary<string, object> GetEnumValues<T>([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type enumType) where T : Enum
4949
{
50-
var result = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
50+
var result = new OrderedDictionary<string, object>(StringComparer.OrdinalIgnoreCase);
5151
foreach (var field in enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
5252
{
5353
if (field.GetCustomAttribute<DisplayAttribute>() is { } displayAttribute

src/Microsoft.OpenApi/HashSet.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
5+
namespace Microsoft.OpenApi
6+
{
7+
/// <summary>
8+
/// Represents a collection of unique elements that preserves insertion order.
9+
/// </summary>
10+
/// <typeparam name="T">The type of elements in the set.</typeparam>
11+
public class HashSet<T> : ICollection<T>
12+
where T : notnull
13+
{
14+
private readonly OrderedDictionary<T, bool> _dict;
15+
16+
/// <summary>
17+
/// Gets the equality comparer used to determine element equality.
18+
/// </summary>
19+
public IEqualityComparer<T> Comparer { get; }
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="HashSet{T}"/> class that is empty
23+
/// and uses the default equality comparer for the type.
24+
/// </summary>
25+
public HashSet() : this(EqualityComparer<T>.Default) { }
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="HashSet{T}"/> class that is empty
29+
/// and uses the specified equality comparer.
30+
/// </summary>
31+
/// <param name="comparer">The comparer to use when determining equality of elements.</param>
32+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="comparer"/> is null.</exception>
33+
public HashSet(IEqualityComparer<T> comparer)
34+
{
35+
Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
36+
_dict = new OrderedDictionary<T, bool>(comparer);
37+
}
38+
39+
/// <summary>
40+
/// Initializes a new instance of the <see cref="HashSet{T}"/> class that contains elements copied
41+
/// from the specified collection and uses the specified equality comparer.
42+
/// </summary>
43+
/// <param name="collection">The collection whose elements are copied to the new set.</param>
44+
/// <param name="comparer">The comparer to use when determining equality of elements.</param>
45+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="collection"/> or <paramref name="comparer"/> is null.</exception>
46+
public HashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
47+
: this(comparer)
48+
{
49+
if (collection is null) throw new ArgumentNullException(nameof(collection));
50+
51+
foreach (var item in collection)
52+
{
53+
Add(item);
54+
}
55+
}
56+
57+
/// <summary>
58+
/// Initializes a new instance of the <see cref="HashSet{T}"/> class that contains elements copied
59+
/// from the specified collection and uses the default equality comparer.
60+
/// </summary>
61+
/// <param name="collection">The collection whose elements are copied to the new set.</param>
62+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="collection"/> is null.</exception>
63+
public HashSet(IEnumerable<T> collection)
64+
: this(collection, EqualityComparer<T>.Default)
65+
{
66+
}
67+
68+
/// <inheritdoc />
69+
public int Count => _dict.Count;
70+
71+
/// <inheritdoc />
72+
public bool IsReadOnly => false;
73+
74+
/// <summary>
75+
/// Attempts to add the specified element to the set.
76+
/// </summary>
77+
/// <param name="item">The element to add.</param>
78+
/// <returns><c>true</c> if the element was added; <c>false</c> if it already exists in the set.</returns>
79+
public bool Add(T item) => _dict.TryAdd(item, true);
80+
81+
void ICollection<T>.Add(T item) => Add(item);
82+
83+
/// <summary>
84+
/// Removes all elements from the set.
85+
/// </summary>
86+
public void Clear() => _dict.Clear();
87+
88+
/// <summary>
89+
/// Determines whether the set contains a specific element.
90+
/// </summary>
91+
/// <param name="item">The element to locate.</param>
92+
/// <returns><c>true</c> if the set contains the element; otherwise, <c>false</c>.</returns>
93+
public bool Contains(T item) => _dict.ContainsKey(item);
94+
95+
/// <summary>
96+
/// Copies the elements of the set to an array, starting at a particular array index.
97+
/// </summary>
98+
/// <param name="array">The destination array.</param>
99+
/// <param name="arrayIndex">The zero-based index in the array at which copying begins.</param>
100+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="array"/> is null.</exception>
101+
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="arrayIndex"/> is less than 0.</exception>
102+
/// <exception cref="ArgumentException">Thrown if the number of elements in the source set is greater than the available space in the destination array.</exception>
103+
public void CopyTo(T[] array, int arrayIndex)
104+
{
105+
if (array == null) throw new ArgumentNullException(nameof(array));
106+
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
107+
if (array.Length - arrayIndex < Count) throw new ArgumentException("The array is too small.");
108+
109+
foreach (var key in _dict.Keys)
110+
{
111+
array[arrayIndex++] = key;
112+
}
113+
}
114+
115+
/// <summary>
116+
/// Removes the specified element from the set.
117+
/// </summary>
118+
/// <param name="item">The element to remove.</param>
119+
/// <returns><c>true</c> if the element was successfully removed; <c>false</c> if it was not found.</returns>
120+
public bool Remove(T item) => _dict.Remove(item);
121+
122+
/// <summary>
123+
/// Returns an enumerator that iterates through the set in insertion order.
124+
/// </summary>
125+
/// <returns>An enumerator for the set.</returns>
126+
public IEnumerator<T> GetEnumerator() => _dict.Keys.GetEnumerator();
127+
128+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
129+
}
130+
}

src/Microsoft.OpenApi/Interfaces/IMetadataContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ public interface IMetadataContainer
1414
/// A collection of properties associated with the current OpenAPI element to be used by the application.
1515
/// Metadata are NOT (de)serialized with the schema and can be used for custom properties.
1616
/// </summary>
17-
Dictionary<string, object>? Metadata { get; set; }
17+
OrderedDictionary<string, object>? Metadata { get; set; }
1818
}

0 commit comments

Comments
 (0)