Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,20 +403,12 @@
// Inspect the format from the buffered portion
format ??= InspectStreamFormat(bufferStream);

// If format is JSON, no need to buffer further — use the original stream.
if (format.Equals(OpenApiConstants.Json, StringComparison.OrdinalIgnoreCase))
{
preparedStream = input;
}
else
{
// YAML or other non-JSON format; copy remaining input to a new stream.
preparedStream = new MemoryStream();
bufferStream.Position = 0;
await bufferStream.CopyToAsync(preparedStream, 81920, token).ConfigureAwait(false); // Copy buffered portion
await input.CopyToAsync(preparedStream, 81920, token).ConfigureAwait(false); // Copy remaining data
preparedStream.Position = 0;
}
// we need to copy the stream to memory string we've already started reading it and can't reposition it
preparedStream = new MemoryStream();
bufferStream.Position = 0;
await bufferStream.CopyToAsync(preparedStream, 81920, token).ConfigureAwait(false); // Copy buffered portion
await input.CopyToAsync(preparedStream, 81920, token).ConfigureAwait(false); // Copy remaining data
preparedStream.Position = 0;
}
else
{
Expand Down
166 changes: 166 additions & 0 deletions test/Microsoft.OpenApi.Tests/Reader/OpenApiModelFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using System.IO;
using System;
using System.Threading;

namespace Microsoft.OpenApi.Tests.Reader;

Expand Down Expand Up @@ -119,4 +120,169 @@ await File.WriteAllTextAsync(tempFilePathReferrer,
Assert.NotNull(readResult.Document.Components);
Assert.Equal(baseUri, readResult.Document.BaseUri);
}
[Fact]
public async Task CanLoadANonSeekableStream()
{
// Given
var documentJson =
"""
{
"openapi": "3.1.0",
"info": {
"title": "Sample API",
"version": "1.0.0"
},
"paths": {}
}
""";
using var memoryStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(documentJson));
using var nonSeekableStream = new NonSeekableStream(memoryStream);

// When
var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream);

// Then
Assert.NotNull(document);
Assert.Equal("Sample API", document.Info.Title);
}

public sealed class NonSeekableStream : Stream
{
private readonly Stream _innerStream;
public NonSeekableStream(Stream stream) : base()
{
_innerStream = stream;
}
public override bool CanSeek => false;

public override long Position { get => _innerStream.Position; set => throw new InvalidOperationException("Seeking is not supported."); }

public override bool CanRead => _innerStream.CanRead;

public override bool CanWrite => _innerStream.CanWrite;

public override long Length => _innerStream.Length;
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _innerStream.BeginRead(buffer, offset, count, callback, state);
}

public override void Flush()
{
_innerStream.Flush();
}

public override int Read(byte[] buffer, int offset, int count)
{
return _innerStream.Read(buffer, offset, count);
}

public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException("Seeking is not supported.");
}

public override void SetLength(long value)
{
_innerStream.SetLength(value);
}

public override void Write(byte[] buffer, int offset, int count)
{
_innerStream.Write(buffer, offset, count);
}
protected override void Dispose(bool disposing)
{
_innerStream.Dispose();
base.Dispose(disposing);
}

public override async ValueTask DisposeAsync()
{
await _innerStream.DisposeAsync();
await base.DisposeAsync();
}

public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken);
}

public override bool CanTimeout => _innerStream.CanTimeout;

public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _innerStream.BeginWrite(buffer, offset, count, callback, state);
}

public override void CopyTo(Stream destination, int bufferSize)
{
_innerStream.CopyTo(destination, bufferSize);
}

public override void Close()
{
_innerStream.Close();
}

public override int EndRead(IAsyncResult asyncResult)
{
return _innerStream.EndRead(asyncResult);
}

public override void EndWrite(IAsyncResult asyncResult)
{
_innerStream.EndWrite(asyncResult);
}

public override int ReadByte()
{
return _innerStream.ReadByte();
}

public override void WriteByte(byte value)
{
_innerStream.WriteByte(value);
}

public override Task FlushAsync(CancellationToken cancellationToken)
{
return _innerStream.FlushAsync(cancellationToken);
}

public override int Read(Span<byte> buffer)
{
return _innerStream.Read(buffer);
}

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _innerStream.ReadAsync(buffer, offset, count, cancellationToken);
}

public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
return _innerStream.ReadAsync(buffer, cancellationToken);
}

public override int ReadTimeout { get => _innerStream.ReadTimeout; set => _innerStream.ReadTimeout = value; }

public override void Write(ReadOnlySpan<byte> buffer)
{
_innerStream.Write(buffer);
}

public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
}

public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
return _innerStream.WriteAsync(buffer, cancellationToken);
}

public override int WriteTimeout { get => _innerStream.WriteTimeout; set => _innerStream.WriteTimeout = value; }

}
}
Loading