Skip to content
Draft
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
Prev Previous commit
Next Next commit
fix(reader): document parser-level exceptions
Co-authored-by: baywet <[email protected]>
  • Loading branch information
Copilot and baywet authored Sep 10, 2026
commit 3b67916f28950423e7335a1fa02f3c7f7ca3a525
24 changes: 18 additions & 6 deletions src/Microsoft.OpenApi.YamlReader/OpenApiYamlReader.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
using System.IO;
using System.Text.Json.Nodes;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.OpenApi.Reader;
using SharpYaml;
using System;
using System.Text;

namespace Microsoft.OpenApi.YamlReader
{
Expand Down Expand Up @@ -58,6 +58,10 @@ public OpenApiYamlReader(OpenApiYamlReaderSettings settings)
}

/// <inheritdoc/>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
public async Task<ReadResult> ReadAsync(Stream input,
Uri location,
OpenApiReaderSettings settings,
Expand All @@ -68,8 +72,8 @@ public async Task<ReadResult> ReadAsync(Stream input,
if (input is MemoryStream memoryStream)
{
return ReadCore(memoryStream, location, settings, cancellationToken);
}
else
}
else
{
using var preparedStream = new MemoryStream();
try
Expand All @@ -95,6 +99,10 @@ await CopyToMemoryStreamAsync(
}

/// <inheritdoc/>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
public ReadResult Read(MemoryStream input,
Uri location,
OpenApiReaderSettings settings)
Expand All @@ -118,7 +126,7 @@ private ReadResult ReadCore(MemoryStream input,
// this represents net core, net5 and up
using var stream = new StreamReader(input, default, true, -1, settings.LeaveStreamOpen);
#else
// the implementation differs and results in a null reference exception in NETFX
// the implementation differs and results in a null reference exception in NETFX
using var stream = new StreamReader(input, Encoding.UTF8, true, 4096, settings.LeaveStreamOpen);
#endif
jsonNode = LoadJsonNodesFromYamlDocument(stream, cancellationToken);
Expand Down Expand Up @@ -194,6 +202,10 @@ public static ReadResult Read(JsonNode jsonNode, Uri location, OpenApiReaderSett
}

/// <inheritdoc/>
/// <remarks>
/// OpenAPI semantic errors are returned in the <paramref name="diagnostic"/>. Syntax-level YAML
/// errors can throw before a fragment is created.
/// </remarks>
public T? ReadFragment<T>(MemoryStream input,
OpenApiSpecVersion version,
OpenApiDocument openApiDocument,
Expand Down
12 changes: 12 additions & 0 deletions src/Microsoft.OpenApi/Interfaces/IOpenApiReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public interface IOpenApiReader
/// <param name="settings"> The OpenApi reader settings.</param>
/// <param name="cancellationToken">Propagates notification that an operation should be cancelled.</param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
Comment thread
baywet marked this conversation as resolved.
Comment thread
baywet marked this conversation as resolved.
Task<ReadResult> ReadAsync(Stream input, Uri location, OpenApiReaderSettings settings, CancellationToken cancellationToken = default);

/// <summary>
Expand All @@ -31,6 +35,10 @@ public interface IOpenApiReader
/// <param name="location">Location of where the document that is getting loaded is saved</param>
/// <param name="settings"></param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
ReadResult Read(MemoryStream input, Uri location, OpenApiReaderSettings settings);

/// <summary>
Expand All @@ -42,6 +50,10 @@ public interface IOpenApiReader
/// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing.</param>
/// <param name="settings">The OpenApiReader settings.</param>
/// <returns>Instance of newly created IOpenApiElement.</returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <paramref name="diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a fragment is created.
/// </remarks>
T? ReadFragment<T>(MemoryStream input, OpenApiSpecVersion version, OpenApiDocument openApiDocument, out OpenApiDiagnostic diagnostic, OpenApiReaderSettings? settings = null) where T : IOpenApiElement;
}
}
32 changes: 24 additions & 8 deletions src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ public void RegisterComponents()
/// <summary>
/// A list of tags used by the specification with additional metadata.
/// </summary>
public ISet<OpenApiTag>? Tags
{
public ISet<OpenApiTag>? Tags
{
get
{
return _tags;
Expand Down Expand Up @@ -125,14 +125,14 @@ public ISet<OpenApiTag>? Tags
/// <summary>
/// Parameter-less constructor
/// </summary>
public OpenApiDocument()
public OpenApiDocument()
{
Workspace = new OpenApiWorkspace();
BaseUri = new(OpenApiConstants.BaseRegistryUri + Guid.NewGuid());
Info = new OpenApiInfo();
Paths = new OpenApiPaths();
}

/// <summary>
/// Initializes a copy of an an <see cref="OpenApiDocument"/> object
/// </summary>
Expand Down Expand Up @@ -527,14 +527,14 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList<OpenApiServer>?
.ToList();

// schemes
writer.WriteOptionalCollection(OpenApiConstants.Schemes, schemes, (w, s) =>
writer.WriteOptionalCollection(OpenApiConstants.Schemes, schemes, (w, s) =>
{
if(!string.IsNullOrEmpty(s) && s is not null)
if (!string.IsNullOrEmpty(s) && s is not null)
Comment thread
baywet marked this conversation as resolved.
Dismissed
Comment thread
baywet marked this conversation as resolved.
Dismissed
{
w.WriteValue(s);
}
});
}
}
}

/// <summary>
Expand Down Expand Up @@ -732,6 +732,10 @@ private static bool TryGetPlainNameFragment(string reference, out string fragmen
/// <param name="format">The OpenAPI format to use during parsing.</param>
/// <param name="settings">The OpenApi reader settings.</param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
Comment thread
baywet marked this conversation as resolved.
public static ReadResult Load(MemoryStream stream,
string? format = null,
OpenApiReaderSettings? settings = null)
Expand All @@ -746,6 +750,10 @@ public static ReadResult Load(MemoryStream stream,
/// <param name="settings">The OpenApi reader settings.</param>
/// <param name="token">The cancellation token</param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings? settings = null, CancellationToken token = default)
{
return await OpenApiModelFactory.LoadAsync(url, settings, token).ConfigureAwait(false);
Expand All @@ -759,6 +767,10 @@ public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings
/// <param name="settings">The OpenApi reader settings.</param>
/// <param name="cancellationToken">Propagates information about operation cancelling.</param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
Comment thread
baywet marked this conversation as resolved.
public static async Task<ReadResult> LoadAsync(Stream stream, string? format = null, OpenApiReaderSettings? settings = null, CancellationToken cancellationToken = default)
{
return await OpenApiModelFactory.LoadAsync(stream, format, settings, cancellationToken).ConfigureAwait(false);
Expand All @@ -772,6 +784,10 @@ public static async Task<ReadResult> LoadAsync(Stream stream, string? format = n
/// <param name="format"></param>
/// <param name="settings"></param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
public static ReadResult Parse(string input,
string? format = null,
OpenApiReaderSettings? settings = null)
Expand Down Expand Up @@ -930,7 +946,7 @@ public override void Visit(IOpenApiSchema schema)
{
Schemas.Add(id, schema);
}
}
}
base.Visit(schema);
}
}
Expand Down
26 changes: 21 additions & 5 deletions src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
using System.IO;
using System.Text.Json.Nodes;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System;

namespace Microsoft.OpenApi.Reader
{
Expand All @@ -23,6 +23,10 @@ public class OpenApiJsonReader : IOpenApiReader
/// <param name="location">Location of where the document that is getting loaded is saved</param>
/// <param name="settings">The Reader settings to be used during parsing.</param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
Comment thread
baywet marked this conversation as resolved.
public ReadResult Read(MemoryStream input,
Uri location,
OpenApiReaderSettings settings)
Expand Down Expand Up @@ -60,6 +64,10 @@ public ReadResult Read(MemoryStream input,
/// <param name="location">Location of where the document that is getting loaded is saved</param>
/// <param name="settings">The Reader settings to be used during parsing.</param>
/// <returns></returns>
/// <remarks>
/// Use this overload when JSON has already been parsed into a <see cref="JsonNode"/>. OpenAPI semantic
/// errors are returned in the <see cref="ReadResult.Diagnostic"/>.
/// </remarks>
public ReadResult Read(JsonNode jsonNode,
Uri location,
OpenApiReaderSettings settings)
Expand Down Expand Up @@ -91,7 +99,7 @@ public ReadResult Read(JsonNode jsonNode,
if (document is not null && settings.RuleSet is not null && settings.RuleSet.Rules.Any())
{
var openApiErrors = document.Validate(settings.RuleSet);
if(openApiErrors is not null)
if (openApiErrors is not null)
{
foreach (var item in openApiErrors.OfType<OpenApiValidatorError>())
{
Expand All @@ -101,7 +109,7 @@ public ReadResult Read(JsonNode jsonNode,
{
diagnostic.Warnings.Add(item);
}
}
}
}
diagnostic.Format = OpenApiConstants.Json;
return new()
Expand All @@ -119,6 +127,10 @@ public ReadResult Read(JsonNode jsonNode,
/// <param name="settings">The Reader settings to be used during parsing.</param>
/// <param name="cancellationToken">Propagates notifications that operations should be cancelled.</param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
public async Task<ReadResult> ReadAsync(Stream input,
Uri location,
OpenApiReaderSettings settings,
Expand Down Expand Up @@ -151,6 +163,10 @@ public async Task<ReadResult> ReadAsync(Stream input,
}

/// <inheritdoc/>
/// <remarks>
/// OpenAPI semantic errors are returned in the <paramref name="diagnostic"/>. Syntax-level JSON
/// errors can throw before a fragment is created.
/// </remarks>
Comment thread
baywet marked this conversation as resolved.
public T? ReadFragment<T>(MemoryStream input,
OpenApiSpecVersion version,
OpenApiDocument openApiDocument,
Expand Down
34 changes: 33 additions & 1 deletion src/Microsoft.OpenApi/Reader/OpenApiModelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ public static class OpenApiModelFactory
/// <param name="settings"> The OpenApi reader settings.</param>
/// <param name="format">The OpenAPI format.</param>
/// <returns>An OpenAPI document instance.</returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
Comment thread
baywet marked this conversation as resolved.
public static ReadResult Load(MemoryStream stream,
string? format = null,
OpenApiReaderSettings? settings = null)
Expand Down Expand Up @@ -59,6 +63,10 @@ public static ReadResult Load(MemoryStream stream,
/// <param name="settings">The OpenApiReader settings.</param>
/// <returns>Instance of newly created IOpenApiElement.</returns>
/// <returns>The OpenAPI element.</returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <paramref name="diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a fragment is created.
/// </remarks>
Comment thread
baywet marked this conversation as resolved.
public static T? Load<T>(MemoryStream input, OpenApiSpecVersion version, string? format, OpenApiDocument openApiDocument, out OpenApiDiagnostic diagnostic, OpenApiReaderSettings? settings = null) where T : IOpenApiElement
{
format ??= InspectStreamFormat(input);
Expand All @@ -73,6 +81,10 @@ public static ReadResult Load(MemoryStream stream,
/// <param name="settings"> The OpenApi reader settings.</param>
/// <param name="token">The cancellation token</param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings? settings = null, CancellationToken token = default)
{
settings ??= DefaultReaderSettings.Value;
Expand All @@ -94,6 +106,10 @@ public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings
/// <param name="token"></param>
/// <returns>Instance of newly created IOpenApiElement.</returns>
/// <returns>The OpenAPI element.</returns>
/// <remarks>
/// OpenAPI semantic errors are returned by the reader diagnostic. Syntax-level JSON or YAML errors can throw
/// before a fragment is created.
/// </remarks>
public static async Task<T?> LoadAsync<T>(string url, OpenApiSpecVersion version, OpenApiDocument openApiDocument, OpenApiReaderSettings? settings = null, CancellationToken token = default) where T : IOpenApiElement
{
settings ??= DefaultReaderSettings.Value;
Expand All @@ -112,6 +128,10 @@ public static async Task<ReadResult> LoadAsync(string url, OpenApiReaderSettings
/// <param name="cancellationToken">Propagates notification that operations should be cancelled.</param>
/// <param name="format">The Open API format</param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
Comment thread
baywet marked this conversation as resolved.
public static async Task<ReadResult> LoadAsync(Stream input, string? format = null, OpenApiReaderSettings? settings = null, CancellationToken cancellationToken = default)
{
#if NET6_0_OR_GREATER
Expand Down Expand Up @@ -159,6 +179,10 @@ public static async Task<ReadResult> LoadAsync(Stream input, string? format = nu
/// <param name="settings"></param>
/// <param name="token"></param>
/// <returns></returns>
/// <remarks>
/// OpenAPI semantic errors are returned by the reader diagnostic. Syntax-level JSON or YAML errors can throw
/// before a fragment is created.
/// </remarks>
public static async Task<T?> LoadAsync<T>(Stream input,
OpenApiSpecVersion version,
OpenApiDocument openApiDocument,
Expand Down Expand Up @@ -192,6 +216,10 @@ public static async Task<ReadResult> LoadAsync(Stream input, string? format = nu
/// <param name="format">The Open API format</param>
/// <param name="settings">The OpenApi reader settings.</param>
/// <returns>An OpenAPI document instance.</returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <see cref="ReadResult.Diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a <see cref="ReadResult"/> is created.
/// </remarks>
Comment thread
Copilot marked this conversation as resolved.
public static ReadResult Parse(string input,
string? format = null,
OpenApiReaderSettings? settings = null)
Expand Down Expand Up @@ -220,6 +248,10 @@ public static ReadResult Parse(string input,
/// <param name="format">The Open API format</param>
/// <param name="settings">The OpenApi reader settings.</param>
/// <returns>An OpenAPI document instance.</returns>
/// <remarks>
/// OpenAPI semantic errors are returned in the <paramref name="diagnostic"/>. Syntax-level JSON or YAML
/// errors can throw before a fragment is created.
/// </remarks>
public static T? Parse<T>(string input,
OpenApiSpecVersion version,
OpenApiDocument openApiDocument,
Expand Down Expand Up @@ -437,7 +469,7 @@ private static async Task<MemoryStream> CopyToMemoryStreamAsync(Stream input, Ca
#endif
bufferStream.Position = 0;
return bufferStream;
}
}

private static async Task<(Stream, string)> PrepareStreamForReadingAsync(Stream input, string? format, CancellationToken token = default)
{
Expand Down