Skip to content

Commit ecdf133

Browse files
committed
Make format optional; infer it from the stream or textReader input instead
1 parent 17f36c6 commit ecdf133

File tree

1 file changed

+61
-6
lines changed

1 file changed

+61
-6
lines changed

src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs

Lines changed: 61 additions & 6 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;
@@ -101,10 +101,10 @@ public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings
101101
/// <param name="cancellationToken">Propagates notification that operations should be cancelled.</param>
102102
/// <param name="format">The Open API format</param>
103103
/// <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)
105105
{
106-
Utils.CheckArgumentNull(format, nameof(format));
107106
settings ??= new OpenApiReaderSettings();
107+
format ??= InspectStreamFormat(input);
108108

109109
Stream preparedStream;
110110

@@ -137,7 +137,7 @@ public static async Task<ReadResult> LoadAsync(Stream input, string format, Open
137137
/// <returns></returns>
138138
public static async Task<ReadResult> LoadAsync(TextReader input, string format, OpenApiReaderSettings settings = null, CancellationToken cancellationToken = default)
139139
{
140-
Utils.CheckArgumentNull(format, nameof(format));
140+
format ??= InspectTextReaderFormat(input);
141141
var reader = OpenApiReaderRegistry.GetReader(format);
142142
return await reader.ReadAsync(input, settings, cancellationToken);
143143
}
@@ -175,6 +175,9 @@ public static async Task<ReadResult> ParseAsync(string input,
175175
string format = null,
176176
OpenApiReaderSettings settings = null)
177177
{
178+
var format = input.StartsWith("{") || input.StartsWith("[") ? OpenApiConstants.Json : OpenApiConstants.Yaml;
179+
settings ??= new OpenApiReaderSettings();
180+
using var reader = new StringReader(input);
178181
return await LoadAsync(reader, format, settings);
179182
}
180183

@@ -252,8 +255,8 @@ public static T Load<T>(Stream input, OpenApiSpecVersion version, string format,
252255
/// <returns>The OpenAPI element.</returns>
253256
public static T Load<T>(TextReader input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic, string format, OpenApiReaderSettings settings = null) where T : IOpenApiElement
254257
{
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);
257260
}
258261

259262

@@ -297,6 +300,58 @@ public static string GetFormat(string url)
297300
return null;
298301
}
299302

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+
300355
private static async Task<Stream> GetStreamAsync(string url)
301356
{
302357
Stream stream;

0 commit comments

Comments
 (0)