Skip to content

Commit a83461d

Browse files
committed
Refactor code to cater for non-seekable streams
1 parent 8b07082 commit a83461d

File tree

1 file changed

+69
-44
lines changed

1 file changed

+69
-44
lines changed

src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,10 @@ public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings
5151
public static async Task<ReadResult> LoadAsync(Stream input, string format = null, OpenApiReaderSettings settings = null, CancellationToken cancellationToken = default)
5252
{
5353
settings ??= new OpenApiReaderSettings();
54-
format ??= InspectStreamFormat(input);
5554

56-
Stream preparedStream;
57-
58-
// Avoid buffering for JSON documents
59-
if (input is MemoryStream || format.Equals(OpenApiConstants.Json, StringComparison.OrdinalIgnoreCase))
60-
{
61-
preparedStream = input;
62-
}
63-
else
64-
{
65-
// Buffer stream for non-JSON formats (e.g., YAML) since they require synchronous reading
66-
preparedStream = new MemoryStream();
67-
await input.CopyToAsync(preparedStream, 81920, cancellationToken);
68-
preparedStream.Position = 0;
69-
}
55+
// Prepare the stream based on seekability and format
56+
var (preparedStream, detectedFormat) = await PrepareStreamForReadingAsync(input, format, cancellationToken);
57+
format ??= detectedFormat;
7058

7159
// Use StreamReader to process the prepared stream (buffered for YAML, direct for JSON)
7260
using var reader = new StreamReader(preparedStream, default, true, -1, settings.LeaveStreamOpen);
@@ -231,49 +219,86 @@ public static async Task<string> GetFormatAsync(string url)
231219
return null;
232220
}
233221

234-
private static string InspectStreamFormat(Stream stream)
222+
private static async Task<(Stream preparedStream, string format)> PrepareStreamForReadingAsync(Stream input, string format, CancellationToken token = default)
235223
{
236-
try
224+
Stream preparedStream = input;
225+
226+
if (!input.CanSeek)
237227
{
238-
if (stream == null) throw new ArgumentNullException(nameof(stream));
239-
if (!stream.CanSeek) throw new InvalidOperationException("Stream must support seeking."); // ensure stream supports seeking to reset position
228+
// Use a temporary buffer to read a small portion for format detection
229+
using var bufferStream = new MemoryStream();
230+
await input.CopyToAsync(bufferStream, 1024, token);
231+
bufferStream.Position = 0;
240232

241-
long initialPosition = stream.Position;
242-
int firstByte = stream.ReadByte();
233+
// Inspect the format from the buffered portion
234+
format ??= InspectStreamFormat(bufferStream);
243235

244-
// Check if stream is empty or contains only whitespace
245-
if (firstByte == -1)
236+
// If format is JSON, no need to buffer further — use the original stream.
237+
if (format.Equals(OpenApiConstants.Json, StringComparison.OrdinalIgnoreCase))
246238
{
247-
stream.Position = initialPosition;
248-
throw new InvalidOperationException("Stream is empty or contains only whitespace.");
239+
preparedStream = input;
249240
}
250-
251-
// Skip whitespace if present and read the next non-whitespace byte
252-
if (char.IsWhiteSpace((char)firstByte))
241+
else
253242
{
254-
firstByte = stream.ReadByte();
243+
// YAML or other non-JSON format; copy remaining input to a new stream.
244+
preparedStream = new MemoryStream();
245+
bufferStream.Position = 0;
246+
await bufferStream.CopyToAsync(preparedStream, 81920, token); // Copy buffered portion
247+
await input.CopyToAsync(preparedStream, 81920, token); // Copy remaining data
248+
preparedStream.Position = 0;
249+
}
250+
}
251+
else
252+
{
253+
format ??= InspectStreamFormat(input);
255254

256-
// If still whitespace or end of stream, throw an error
257-
if (firstByte == -1 || char.IsWhiteSpace((char)firstByte))
258-
{
259-
stream.Position = initialPosition;
260-
throw new InvalidOperationException("Stream is empty or contains only whitespace.");
261-
}
255+
if (!format.Equals(OpenApiConstants.Json, StringComparison.OrdinalIgnoreCase))
256+
{
257+
// Buffer stream for non-JSON formats (e.g., YAML) since they require synchronous reading
258+
preparedStream = new MemoryStream();
259+
await input.CopyToAsync(preparedStream, 81920, token);
260+
preparedStream.Position = 0;
262261
}
262+
}
263+
264+
return(preparedStream, format);
265+
}
266+
267+
private static string InspectStreamFormat(Stream stream)
268+
{
269+
if (stream == null) throw new ArgumentNullException(nameof(stream));
270+
271+
long initialPosition = stream.Position;
272+
int firstByte = stream.ReadByte();
263273

264-
stream.Position = initialPosition; // Reset the stream position to the beginning
274+
// Check if stream is empty or contains only whitespace
275+
if (firstByte == -1)
276+
{
277+
stream.Position = initialPosition;
278+
throw new InvalidOperationException("Stream is empty or contains only whitespace.");
279+
}
265280

266-
char firstChar = (char)firstByte;
267-
return firstChar switch
281+
// Skip whitespace if present and read the next non-whitespace byte
282+
if (char.IsWhiteSpace((char)firstByte))
283+
{
284+
firstByte = stream.ReadByte();
285+
286+
// If still whitespace or end of stream, throw an error
287+
if (firstByte == -1 || char.IsWhiteSpace((char)firstByte))
268288
{
269-
'{' or '[' => OpenApiConstants.Json, // If the first character is '{' or '[', assume JSON
270-
_ => OpenApiConstants.Yaml // Otherwise assume YAML
271-
};
289+
stream.Position = initialPosition;
290+
throw new InvalidOperationException("Stream is empty or contains only whitespace.");
291+
}
272292
}
273-
catch(Exception ex)
293+
294+
stream.Position = initialPosition; // Reset the stream position to the beginning
295+
296+
char firstChar = (char)firstByte;
297+
return firstChar switch
274298
{
275-
throw new OpenApiException(ex.Message);
276-
}
299+
'{' or '[' => OpenApiConstants.Json, // If the first character is '{' or '[', assume JSON
300+
_ => OpenApiConstants.Yaml // Otherwise assume YAML
301+
};
277302
}
278303

279304
private static string InspectTextReaderFormat(TextReader reader)

0 commit comments

Comments
 (0)