Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
14 changes: 7 additions & 7 deletions src/lib/BinkyLabs.OpenApi.Overlays.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="JsonPath.Net" Version="2.1.1"/>
<PackageReference Include="JsonPointer.Net" Version="5.3.1"/>
<PackageReference Include="JsonPath.Net" Version="2.1.1" />
<PackageReference Include="JsonPointer.Net" Version="5.3.1" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand All @@ -46,14 +46,14 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.OpenApi" Version="2.3.7"/>
<None Include="..\..\README.md" Pack="true" PackagePath="README.md"/>
<None Include="..\Assets\logo-binkylabs.png" Pack="true" PackagePath="logo-binkylabs.png"/>
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.7"/>
<PackageReference Include="Microsoft.OpenApi" Version="2.3.7" />
<None Include="..\..\README.md" Pack="true" PackagePath="README.md" />
<None Include="..\Assets\logo-binkylabs.png" Pack="true" PackagePath="logo-binkylabs.png" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.7" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.14.15">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="SharpYaml" Version="2.1.3"/>
<PackageReference Include="SharpYaml" Version="2.1.4" />
</ItemGroup>
</Project>
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