Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ dotnet add <pathToCsProj> package BinkyLabs.OpenApi.Overlays
The following example illustrates how you can load or parse an Overlay document from JSON or YAML.

```csharp
var overlayDocument = await OverlayDocument.LoadFromUrlAsync("https://source/overlay.json");
var (overlayDocument) = await OverlayDocument.LoadFromUrlAsync("https://source/overlay.json");
```

### Applying an Overlay document to an OpenAPI document

The following example illustrates how you can apply an Overlay document to an OpenAPI document.

```csharp
var resultOpenApiDocument = await overlayDocument.ApplyToDocumentAsync("https://source/openapi.json");
var (resultOpenApiDocument) = await overlayDocument.ApplyToDocumentAndLoadAsync("https://source/openapi.json");
```

### Applying multiple Overlay documents to an OpenAPI document
Expand All @@ -39,7 +39,7 @@ The following example illustrates how you can apply multiple Overlay documents t
```csharp
var combinedOverlay = overlayDocument1.CombineWith(overlayDocument2);
// order matters during the combination, the actions will be appended
var resultOpenApiDocument = await combinedOverlay.ApplyToDocumentAsync("https://source/openapi.json");
var (resultOpenApiDocument) = await combinedOverlay.ApplyToDocumentAndLoadAsync("https://source/openapi.json");
```

### Serializing an Overlay document
Expand Down
92 changes: 76 additions & 16 deletions src/lib/Models/OverlayDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ internal bool ApplyToDocument(JsonNode jsonNode, OverlayDiagnostic overlayDiagno
/// <param name="readerSettings">Settings to use when reading the document.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The OpenAPI document after applying the action.</returns>
public async Task<OverlayApplicationResult> ApplyToExtendedDocumentAsync(string? format = default, OverlayReaderSettings? readerSettings = default, CancellationToken cancellationToken = default)
public async Task<OverlayApplicationResultOfJsonNode> ApplyToExtendedDocumentAsync(string? format = default, OverlayReaderSettings? readerSettings = default, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(Extends))
{
Expand All @@ -135,6 +135,24 @@ public async Task<OverlayApplicationResult> ApplyToExtendedDocumentAsync(string?
return await ApplyToDocumentAsync(Extends, format, readerSettings, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Applies the action to an OpenAPI document loaded from the extends property.
/// The document is read in the specified format (e.g., JSON or YAML).
/// </summary>
/// <param name="format">The format of the document (e.g., JSON or YAML).</param>
/// <param name="readerSettings">Settings to use when reading the document.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The OpenAPI document after applying the action.</returns>
public async Task<OverlayApplicationResultOfOpenApiDocument> ApplyToExtendedDocumentAndLoadAsync(string? format = default, OverlayReaderSettings? readerSettings = default, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(Extends))
{
throw new InvalidOperationException("The 'extends' property must be set to apply the overlay to an extended document.");
}
var jsonResult = await ApplyToExtendedDocumentAsync(format, readerSettings, cancellationToken).ConfigureAwait(false);
return LoadDocument(jsonResult, new Uri(Extends), format ?? string.Empty, readerSettings);
}

/// <summary>
/// Applies the action to an OpenAPI document loaded from a specified path or URI.
/// The document is read in the specified format (e.g., JSON or YAML).
Expand All @@ -144,7 +162,7 @@ public async Task<OverlayApplicationResult> ApplyToExtendedDocumentAsync(string?
/// <param name="readerSettings">Settings to use when reading the document.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The OpenAPI document after applying the action.</returns>
public async Task<OverlayApplicationResult> ApplyToDocumentAsync(string documentPathOrUri, string? format = default, OverlayReaderSettings? readerSettings = default, CancellationToken cancellationToken = default)
public async Task<OverlayApplicationResultOfJsonNode> ApplyToDocumentAsync(string documentPathOrUri, string? format = default, OverlayReaderSettings? readerSettings = default, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(documentPathOrUri);
readerSettings ??= new OverlayReaderSettings();
Expand All @@ -164,6 +182,23 @@ public async Task<OverlayApplicationResult> ApplyToDocumentAsync(string document
using var fileStream = new FileStream(documentPathOrUri, FileMode.Open, FileAccess.Read);
await fileStream.CopyToAsync(input, cancellationToken).ConfigureAwait(false);
}
var result = await ApplyToDocumentStreamAsync(input, format, readerSettings, cancellationToken).ConfigureAwait(false);
await input.DisposeAsync().ConfigureAwait(false);
return result;
}

/// <summary>
/// Applies the action to an OpenAPI document loaded from a specified path or URI.
/// The document is read in the specified format (e.g., JSON or YAML).
/// </summary>
/// <param name="documentPathOrUri">Path or URI to the OpenAPI document.</param>
/// <param name="format">The format of the document (e.g., JSON or YAML).</param>
/// <param name="readerSettings">Settings to use when reading the document.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The OpenAPI document after applying the action.</returns>
public async Task<OverlayApplicationResultOfOpenApiDocument> ApplyToDocumentAndLoadAsync(string documentPathOrUri, string? format = default, OverlayReaderSettings? readerSettings = default, CancellationToken cancellationToken = default)
{
var jsonResult = await ApplyToDocumentAsync(documentPathOrUri, format, readerSettings, cancellationToken).ConfigureAwait(false);

// Convert file paths to absolute paths before creating URI to handle relative paths correctly
Uri uri;
Expand All @@ -178,22 +213,20 @@ public async Task<OverlayApplicationResult> ApplyToDocumentAsync(string document
var absolutePath = Path.GetFullPath(documentPathOrUri);
uri = new Uri(absolutePath, UriKind.Absolute);
}
var result = await ApplyToDocumentStreamAsync(input, uri, format, readerSettings, cancellationToken).ConfigureAwait(false);
await input.DisposeAsync().ConfigureAwait(false);
return result;

return LoadDocument(jsonResult, uri, format ?? string.Empty, readerSettings);
}

/// <summary>
/// Applies the action to an OpenAPI document loaded from a specified path or URI.
/// The document is read in the specified format (e.g., JSON or YAML).
/// </summary>
/// <param name="input">A stream containing the OpenAPI document.</param>
/// <param name="location">The URI location of the document, used for to load external references.</param>
/// <param name="format">The format of the document (e.g., JSON or YAML).</param>
/// <param name="readerSettings">Settings to use when reading the document.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The OpenAPI document after applying the action.</returns>
public async Task<OverlayApplicationResult> ApplyToDocumentStreamAsync(Stream input, Uri location, string? format = default, OverlayReaderSettings? readerSettings = default, CancellationToken cancellationToken = default)
public async Task<OverlayApplicationResultOfJsonNode> ApplyToDocumentStreamAsync(Stream input, string? format = default, OverlayReaderSettings? readerSettings = default, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(input);
readerSettings ??= new OverlayReaderSettings();
Expand All @@ -213,21 +246,48 @@ public async Task<OverlayApplicationResult> ApplyToDocumentStreamAsync(Stream in
throw new InvalidOperationException("Failed to parse the OpenAPI document.");
var overlayDiagnostic = new OverlayDiagnostic();
var result = ApplyToDocument(jsonNode, overlayDiagnostic);
var openAPIJsonReader = new OpenApiJsonReader();
var (openAPIDocument, openApiDiagnostic) = openAPIJsonReader.Read(jsonNode, location, readerSettings.OpenApiSettings);
if (openApiDiagnostic is not null)
return new OverlayApplicationResultOfJsonNode
{
openApiDiagnostic.Format = format;
}
return new OverlayApplicationResult
{
Document = openAPIDocument,
Document = jsonNode,
Diagnostic = overlayDiagnostic,
OpenApiDiagnostic = openApiDiagnostic,
IsSuccessful = result,
OpenApiDiagnostic = new OpenApiDiagnostic()
{
Format = format
}
};
}
/// <summary>
/// Applies the action to an OpenAPI document loaded from a specified path or URI.
/// The document is read in the specified format (e.g., JSON or YAML).
/// </summary>
/// <param name="input">A stream containing the OpenAPI document.</param>
/// <param name="location">The URI location of the document, used for to load external references.</param>
/// <param name="format">The format of the document (e.g., JSON or YAML).</param>
/// <param name="readerSettings">Settings to use when reading the document.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The OpenAPI document after applying the action.</returns>
public async Task<OverlayApplicationResultOfOpenApiDocument> ApplyToDocumentStreamAndLoadAsync(Stream input, Uri location, string? format = default, OverlayReaderSettings? readerSettings = default, CancellationToken cancellationToken = default)
{
var jsonResult = await ApplyToDocumentStreamAsync(input, format, readerSettings, cancellationToken).ConfigureAwait(false);
return LoadDocument(jsonResult, location, format ?? string.Empty, readerSettings);
}
internal static OverlayApplicationResultOfOpenApiDocument LoadDocument(OverlayApplicationResultOfJsonNode jsonResult, Uri location, string format, OverlayReaderSettings? readerSettings)
{
readerSettings ??= new OverlayReaderSettings();
var openAPIJsonReader = new OpenApiJsonReader();
if (jsonResult.Document is null)
{
return OverlayApplicationResultOfOpenApiDocument.FromJsonResultWithFailedLoad(jsonResult);
}
var (openAPIDocument, openApiDiagnostic) = openAPIJsonReader.Read(jsonResult.Document, location, readerSettings.OpenApiSettings);
if (openApiDiagnostic is not null && !string.IsNullOrEmpty(format))
{
openApiDiagnostic.Format = format;
}
return OverlayApplicationResultOfOpenApiDocument.FromJsonResult(jsonResult, openAPIDocument, openApiDiagnostic);
}
/// <summary>
/// Combines this overlay document with another overlay document.
/// The returned document will be a new document, and its metadata (info, etc.) will be the one from the other document.
/// The actions from both documents will be merged. The current document actions will be first, and the ones from the other document will be next.
Expand Down
52 changes: 46 additions & 6 deletions src/lib/OverlayApplicationResult.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json.Nodes;

using BinkyLabs.OpenApi.Overlays.Reader;

using Microsoft.OpenApi;
Expand All @@ -8,12 +10,50 @@ namespace BinkyLabs.OpenApi.Overlays;
/// <summary>
/// Result of applying overlays to an OpenAPI document
/// </summary>
public class OverlayApplicationResult
public class OverlayApplicationResultOfOpenApiDocument : OverlayApplicationResult<OpenApiDocument>
{
internal static OverlayApplicationResultOfOpenApiDocument FromJsonResultWithFailedLoad(OverlayApplicationResultOfJsonNode jsonResult)
{
ArgumentNullException.ThrowIfNull(jsonResult);
return new OverlayApplicationResultOfOpenApiDocument
{
Document = null,
Diagnostic = jsonResult.Diagnostic,
// maintains source format information
OpenApiDiagnostic = jsonResult.OpenApiDiagnostic,
IsSuccessful = false,
};
}
internal static OverlayApplicationResultOfOpenApiDocument FromJsonResult(OverlayApplicationResultOfJsonNode jsonResult, OpenApiDocument? document, OpenApiDiagnostic? openApiDiagnostic)
{
ArgumentNullException.ThrowIfNull(jsonResult);
return new OverlayApplicationResultOfOpenApiDocument
{
Document = document,
Diagnostic = jsonResult.Diagnostic,
// maintains source format information
OpenApiDiagnostic = openApiDiagnostic ?? jsonResult.OpenApiDiagnostic,
IsSuccessful = jsonResult.IsSuccessful,
};
}
}

/// <summary>
/// Result of applying overlays to an OpenAPI document
/// </summary>
public class OverlayApplicationResultOfJsonNode : OverlayApplicationResult<JsonNode>
{
}

/// <summary>
/// Result of applying overlays to an OpenAPI document
/// </summary>
public class OverlayApplicationResult<T>
{
/// <summary>
/// The resulting OpenAPI document after applying overlays, or null if application failed
/// </summary>
public OpenApiDocument? Document { get; init; }
public T? Document { get; init; }
/// <summary>
/// Diagnostics from applying the overlays
/// </summary>
Expand All @@ -35,7 +75,7 @@ public class OverlayApplicationResult
/// <param name="diagnostic">Diagnostics from applying the overlays</param>
/// <param name="openApiDiagnostic">Diagnostics from reading the updated OpenAPI document</param>
/// <param name="isSuccessful">Indicates whether the overlay application was successful</param>
public void Deconstruct(out OpenApiDocument? document, out OverlayDiagnostic diagnostic, out OpenApiDiagnostic? openApiDiagnostic, out bool isSuccessful)
public void Deconstruct(out T? document, out OverlayDiagnostic diagnostic, out OpenApiDiagnostic? openApiDiagnostic, out bool isSuccessful)
{
document = Document;
diagnostic = Diagnostic;
Expand All @@ -48,7 +88,7 @@ public void Deconstruct(out OpenApiDocument? document, out OverlayDiagnostic dia
/// <param name="document">The resulting OpenAPI document after applying overlays, or null if application failed</param>
/// <param name="diagnostic">Diagnostics from applying the overlays</param>
/// <param name="openApiDiagnostic">Diagnostics from reading the updated OpenAPI document</param>
public void Deconstruct(out OpenApiDocument? document, out OverlayDiagnostic diagnostic, out OpenApiDiagnostic? openApiDiagnostic)
public void Deconstruct(out T? document, out OverlayDiagnostic diagnostic, out OpenApiDiagnostic? openApiDiagnostic)
{
Deconstruct(out document, out diagnostic, out openApiDiagnostic, out _);
}
Expand All @@ -57,15 +97,15 @@ public void Deconstruct(out OpenApiDocument? document, out OverlayDiagnostic dia
/// </summary>
/// <param name="document">The resulting OpenAPI document after applying overlays, or null if application failed</param>
/// <param name="diagnostic">Diagnostics from applying the overlays</param>
public void Deconstruct(out OpenApiDocument? document, out OverlayDiagnostic diagnostic)
public void Deconstruct(out T? document, out OverlayDiagnostic diagnostic)
{
Deconstruct(out document, out diagnostic, out _);
}
/// <summary>
/// Deconstructs the OverlayApplicationResult into its components
/// </summary>
/// <param name="document">The resulting OpenAPI document after applying overlays, or null if application failed</param>
public void Deconstruct(out OpenApiDocument? document)
public void Deconstruct(out T? document)
{
Deconstruct(out document, out _);
}
Expand Down
Loading