|
1 | | -// Copyright (c) Microsoft Corporation. All rights reserved. |
| 1 | +// Copyright (c) Microsoft Corporation. All rights reserved. |
2 | 2 | // Licensed under the MIT license. |
3 | 3 |
|
4 | 4 | using System; |
@@ -101,10 +101,10 @@ public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings |
101 | 101 | /// <param name="cancellationToken">Propagates notification that operations should be cancelled.</param> |
102 | 102 | /// <param name="format">The Open API format</param> |
103 | 103 | /// <returns></returns> |
104 | | - public static async Task<ReadResult> LoadAsync(Stream input, string format, OpenApiReaderSettings settings = null, CancellationToken cancellationToken = default) |
| 104 | + public static async Task<ReadResult> LoadAsync(Stream input, string format = null, OpenApiReaderSettings settings = null, CancellationToken cancellationToken = default) |
105 | 105 | { |
106 | | - Utils.CheckArgumentNull(format, nameof(format)); |
107 | 106 | settings ??= new OpenApiReaderSettings(); |
| 107 | + format ??= InspectStreamFormat(input); |
108 | 108 |
|
109 | 109 | Stream preparedStream; |
110 | 110 |
|
@@ -137,7 +137,7 @@ public static async Task<ReadResult> LoadAsync(Stream input, string format, Open |
137 | 137 | /// <returns></returns> |
138 | 138 | public static async Task<ReadResult> LoadAsync(TextReader input, string format, OpenApiReaderSettings settings = null, CancellationToken cancellationToken = default) |
139 | 139 | { |
140 | | - Utils.CheckArgumentNull(format, nameof(format)); |
| 140 | + format ??= InspectTextReaderFormat(input); |
141 | 141 | var reader = OpenApiReaderRegistry.GetReader(format); |
142 | 142 | return await reader.ReadAsync(input, settings, cancellationToken); |
143 | 143 | } |
@@ -175,6 +175,9 @@ public static async Task<ReadResult> ParseAsync(string input, |
175 | 175 | string format = null, |
176 | 176 | OpenApiReaderSettings settings = null) |
177 | 177 | { |
| 178 | + var format = input.StartsWith("{") || input.StartsWith("[") ? OpenApiConstants.Json : OpenApiConstants.Yaml; |
| 179 | + settings ??= new OpenApiReaderSettings(); |
| 180 | + using var reader = new StringReader(input); |
178 | 181 | return await LoadAsync(reader, format, settings); |
179 | 182 | } |
180 | 183 |
|
@@ -252,8 +255,8 @@ public static T Load<T>(Stream input, OpenApiSpecVersion version, string format, |
252 | 255 | /// <returns>The OpenAPI element.</returns> |
253 | 256 | public static T Load<T>(TextReader input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic, string format, OpenApiReaderSettings settings = null) where T : IOpenApiElement |
254 | 257 | { |
255 | | - format ??= OpenApiConstants.Json; |
256 | | - return OpenApiReaderRegistry.GetReader(format).ReadFragment<T>(input, version, out diagnostic, settings); |
| 258 | + format ??= InspectTextReaderFormat(input); |
| 259 | + return await OpenApiReaderRegistry.GetReader(format).ReadFragmentAsync<T>(input, version, settings); |
257 | 260 | } |
258 | 261 |
|
259 | 262 |
|
@@ -297,6 +300,58 @@ public static string GetFormat(string url) |
297 | 300 | return null; |
298 | 301 | } |
299 | 302 |
|
| 303 | + private static string InspectStreamFormat(Stream stream) |
| 304 | + { |
| 305 | + try |
| 306 | + { |
| 307 | + if (stream == null) throw new ArgumentNullException(nameof(stream)); |
| 308 | + if (!stream.CanSeek) throw new InvalidOperationException("Stream must support seeking."); // ensure stream supports seeking to reset position |
| 309 | + |
| 310 | + long initialPosition = stream.Position; |
| 311 | + int firstByte = stream.ReadByte(); |
| 312 | + |
| 313 | + // Check if stream is empty or contains only whitespace |
| 314 | + if (firstByte == -1) |
| 315 | + { |
| 316 | + stream.Position = initialPosition; |
| 317 | + throw new InvalidOperationException("Stream is empty or contains only whitespace."); |
| 318 | + } |
| 319 | + |
| 320 | + // Skip whitespace if present and read the next non-whitespace byte |
| 321 | + if (char.IsWhiteSpace((char)firstByte)) |
| 322 | + { |
| 323 | + firstByte = stream.ReadByte(); |
| 324 | + |
| 325 | + // If still whitespace or end of stream, throw an error |
| 326 | + if (firstByte == -1 || char.IsWhiteSpace((char)firstByte)) |
| 327 | + { |
| 328 | + stream.Position = initialPosition; |
| 329 | + throw new InvalidOperationException("Stream is empty or contains only whitespace."); |
| 330 | + } |
| 331 | + } |
| 332 | + |
| 333 | + stream.Position = initialPosition; // Reset the stream position to the beginning |
| 334 | + |
| 335 | + char firstChar = (char)firstByte; |
| 336 | + return firstChar switch |
| 337 | + { |
| 338 | + '{' or '[' => OpenApiConstants.Json, // If the first character is '{' or '[', assume JSON |
| 339 | + _ => OpenApiConstants.Yaml // Otherwise assume YAML |
| 340 | + }; |
| 341 | + } |
| 342 | + catch(Exception ex) |
| 343 | + { |
| 344 | + throw new OpenApiException(ex.Message); |
| 345 | + } |
| 346 | + } |
| 347 | + |
| 348 | + private static string InspectTextReaderFormat(TextReader reader) |
| 349 | + { |
| 350 | + // Read the first line or a few characters from the input |
| 351 | + var input = reader.ReadLine().Trim(); |
| 352 | + return input.StartsWith("{") || input.StartsWith("[") ? OpenApiConstants.Json : OpenApiConstants.Yaml; |
| 353 | + } |
| 354 | + |
300 | 355 | private static async Task<Stream> GetStreamAsync(string url) |
301 | 356 | { |
302 | 357 | Stream stream; |
|
0 commit comments