diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml
index 4ce92b8e9..244797588 100644
--- a/.azure-pipelines/ci-build.yml
+++ b/.azure-pipelines/ci-build.yml
@@ -216,7 +216,7 @@ stages:
displayName: publish Hidi as executable
inputs:
command: 'publish'
- arguments: -c Release --runtime win-x64 /p:PublishSingleFile=true --self-contained --output $(Build.ArtifactStagingDirectory)/Microsoft.OpenApi.Hidi-v$(hidiversion) -p:PublishTrimmed=true
+ arguments: -c Release --runtime win-x64 /p:PublishSingleFile=true /p:PackAsTool=false --self-contained --output $(Build.ArtifactStagingDirectory)/Microsoft.OpenApi.Hidi-v$(hidiversion)
projects: 'src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj'
publishWebProjects: False
zipAfterPublish: false
diff --git a/.github/workflows/auto-merge-dependabot.yml b/.github/workflows/auto-merge-dependabot.yml
new file mode 100644
index 000000000..6e5953f56
--- /dev/null
+++ b/.github/workflows/auto-merge-dependabot.yml
@@ -0,0 +1,32 @@
+name: Auto-merge dependabot updates
+
+on:
+ pull_request:
+ branches: [ main ]
+
+permissions:
+ pull-requests: write
+ contents: write
+
+jobs:
+
+ dependabot-merge:
+
+ runs-on: ubuntu-latest
+
+ if: ${{ github.actor == 'dependabot[bot]' }}
+
+ steps:
+ - name: Dependabot metadata
+ id: metadata
+ uses: dependabot/fetch-metadata@v1.6.0
+ with:
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+
+ - name: Enable auto-merge for Dependabot PRs
+ # Only if version bump is not a major version change
+ if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}}
+ run: gh pr merge --auto --merge "$PR_URL"
+ env:
+ PR_URL: ${{github.event.pull_request.html_url}}
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index c9094388f..5ca7ee1ea 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -17,7 +17,7 @@ jobs:
- name: Check out the repo
uses: actions/checkout@v4
- name: Login to GitHub package feed
- uses: docker/login-action@v2.2.0
+ uses: docker/login-action@v3.0.0
with:
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
@@ -30,13 +30,13 @@ jobs:
id: getversion
- name: Push to GitHub Packages - Nightly
if: ${{ github.ref == 'refs/heads/vnext' }}
- uses: docker/build-push-action@v4.1.1
+ uses: docker/build-push-action@v5.0.0
with:
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly
- name: Push to GitHub Packages - Release
if: ${{ github.ref == 'refs/heads/master' }}
- uses: docker/build-push-action@v4.1.1
+ uses: docker/build-push-action@v5.0.0
with:
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.getversion.outputs.version }}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 0313280bf..186b10bea 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,5 +3,9 @@
"activityBar.background": "#03323C",
"titleBar.activeBackground": "#054754",
"titleBar.activeForeground": "#F0FCFE"
- }
+ },
+ "cSpell.words": [
+ "csdl",
+ "Hidi"
+ ]
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 60cc5e2f7..358d0a686 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ var document = new OpenApiDocument
};
```
-Reading and writing a OpenAPI description
+Reading and writing an OpenAPI description
```C#
var httpClient = new HttpClient
diff --git a/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs b/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs
index 194d122b8..faf03c3f0 100644
--- a/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs
+++ b/src/Microsoft.OpenApi.Hidi/Extensions/OpenApiExtensibleExtensions.cs
@@ -12,13 +12,13 @@ internal static class OpenApiExtensibleExtensions
/// A dictionary of .
/// The key corresponding to the .
/// A value matching the provided extensionKey. Return null when extensionKey is not found.
- public static string GetExtension(this IDictionary extensions, string extensionKey)
+ internal static string GetExtension(this IDictionary extensions, string extensionKey)
{
if (extensions.TryGetValue(extensionKey, out var value) && value is OpenApiString castValue)
{
return castValue.Value;
}
- return default;
+ return string.Empty;
}
}
}
diff --git a/src/Microsoft.OpenApi.Hidi/Extensions/StringExtensions.cs b/src/Microsoft.OpenApi.Hidi/Extensions/StringExtensions.cs
index e5c4b81c4..99208a1d4 100644
--- a/src/Microsoft.OpenApi.Hidi/Extensions/StringExtensions.cs
+++ b/src/Microsoft.OpenApi.Hidi/Extensions/StringExtensions.cs
@@ -12,13 +12,16 @@ internal static class StringExtensions
///
/// Checks if the specified searchValue is equal to the target string based on the specified .
///
- /// The target string to commpare to.
+ /// The target string to compare to.
/// The search string to seek.
/// The to use. This defaults to .
/// true if the searchValue parameter occurs within this string; otherwise, false.
- public static bool IsEquals(this string target, string searchValue, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
+ public static bool IsEquals(this string? target, string? searchValue, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
{
- if (string.IsNullOrWhiteSpace(target) || string.IsNullOrWhiteSpace(searchValue))
+ if (string.IsNullOrWhiteSpace(target) && string.IsNullOrWhiteSpace(searchValue))
+ {
+ return true;
+ } else if (string.IsNullOrWhiteSpace(target))
{
return false;
}
diff --git a/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs
index 02a2e1947..450a05d21 100644
--- a/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs
+++ b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs
@@ -88,7 +88,7 @@ public override void Visit(OpenApiOperation operation)
private static string ResolveVerbSegmentInOpertationId(string operationId)
{
var charPos = operationId.LastIndexOf('.', operationId.Length - 1);
- if (operationId.Contains('_') || charPos < 0)
+ if (operationId.Contains('_', StringComparison.OrdinalIgnoreCase) || charPos < 0)
return operationId;
var newOperationId = new StringBuilder(operationId);
newOperationId[charPos] = '_';
@@ -99,7 +99,7 @@ private static string ResolveVerbSegmentInOpertationId(string operationId)
private static string ResolvePutOperationId(string operationId)
{
return operationId.Contains(DefaultPutPrefix, StringComparison.OrdinalIgnoreCase) ?
- operationId.Replace(DefaultPutPrefix, PowerShellPutPrefix) : operationId;
+ operationId.Replace(DefaultPutPrefix, PowerShellPutPrefix, StringComparison.Ordinal) : operationId;
}
private static string ResolveByRefOperationId(string operationId)
@@ -191,9 +191,8 @@ private void AddAdditionalPropertiesToSchema(OpenApiSchema schema)
private static void ResolveOneOfSchema(OpenApiSchema schema)
{
- if (schema.OneOf?.Any() ?? false)
+ if (schema.OneOf?.FirstOrDefault() is {} newSchema)
{
- var newSchema = schema.OneOf.FirstOrDefault();
schema.OneOf = null;
FlattenSchema(schema, newSchema);
}
@@ -201,9 +200,8 @@ private static void ResolveOneOfSchema(OpenApiSchema schema)
private static void ResolveAnyOfSchema(OpenApiSchema schema)
{
- if (schema.AnyOf?.Any() ?? false)
+ if (schema.AnyOf?.FirstOrDefault() is {} newSchema)
{
- var newSchema = schema.AnyOf.FirstOrDefault();
schema.AnyOf = null;
FlattenSchema(schema, newSchema);
}
diff --git a/src/Microsoft.OpenApi.Hidi/Handlers/PluginCommandHandler.cs b/src/Microsoft.OpenApi.Hidi/Handlers/PluginCommandHandler.cs
index aae0285f0..2c7e921bb 100644
--- a/src/Microsoft.OpenApi.Hidi/Handlers/PluginCommandHandler.cs
+++ b/src/Microsoft.OpenApi.Hidi/Handlers/PluginCommandHandler.cs
@@ -5,6 +5,7 @@
using System.CommandLine.Invocation;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Hidi.Options;
@@ -24,26 +25,34 @@ public int Invoke(InvocationContext context)
public async Task InvokeAsync(InvocationContext context)
{
HidiOptions hidiOptions = new HidiOptions(context.ParseResult, CommandOptions);
- CancellationToken cancellationToken = (CancellationToken)context.BindingContext.GetService(typeof(CancellationToken));
+ CancellationToken cancellationToken = (CancellationToken)context.BindingContext.GetRequiredService(typeof(CancellationToken));
using var loggerFactory = Logger.ConfigureLogger(hidiOptions.LogLevel);
var logger = loggerFactory.CreateLogger();
try
{
- await OpenApiService.PluginManifest(hidiOptions, logger, cancellationToken);
+ await OpenApiService.PluginManifest(hidiOptions, logger, cancellationToken).ConfigureAwait(false);
return 0;
}
+#if RELEASE
+#pragma warning disable CA1031 // Do not catch general exception types
+#endif
catch (Exception ex)
{
#if DEBUG
logger.LogCritical(ex, "Command failed");
throw; // so debug tools go straight to the source of the exception when attached
#else
+#pragma warning disable CA2254
logger.LogCritical(ex.Message);
+#pragma warning restore CA2254
return 1;
#endif
}
+#if RELEASE
+#pragma warning restore CA1031 // Do not catch general exception types
+#endif
}
}
}
diff --git a/src/Microsoft.OpenApi.Hidi/Handlers/ShowCommandHandler.cs b/src/Microsoft.OpenApi.Hidi/Handlers/ShowCommandHandler.cs
index eea087e36..054912302 100644
--- a/src/Microsoft.OpenApi.Hidi/Handlers/ShowCommandHandler.cs
+++ b/src/Microsoft.OpenApi.Hidi/Handlers/ShowCommandHandler.cs
@@ -5,6 +5,7 @@
using System.CommandLine.Invocation;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Hidi.Options;
@@ -24,26 +25,34 @@ public int Invoke(InvocationContext context)
public async Task InvokeAsync(InvocationContext context)
{
HidiOptions hidiOptions = new HidiOptions(context.ParseResult, CommandOptions);
- CancellationToken cancellationToken = (CancellationToken)context.BindingContext.GetService(typeof(CancellationToken));
+ CancellationToken cancellationToken = (CancellationToken)context.BindingContext.GetRequiredService(typeof(CancellationToken));
using var loggerFactory = Logger.ConfigureLogger(hidiOptions.LogLevel);
var logger = loggerFactory.CreateLogger();
try
{
- await OpenApiService.ShowOpenApiDocument(hidiOptions, logger, cancellationToken);
+ await OpenApiService.ShowOpenApiDocument(hidiOptions, logger, cancellationToken).ConfigureAwait(false);
return 0;
}
+#if RELEASE
+#pragma warning disable CA1031 // Do not catch general exception types
+#endif
catch (Exception ex)
{
#if DEBUG
logger.LogCritical(ex, "Command failed");
throw; // so debug tools go straight to the source of the exception when attached
#else
+#pragma warning disable CA2254
logger.LogCritical( ex.Message);
+#pragma warning restore CA2254
return 1;
#endif
}
+#if RELEASE
+#pragma warning restore CA1031 // Do not catch general exception types
+#endif
}
}
}
diff --git a/src/Microsoft.OpenApi.Hidi/Handlers/TransformCommandHandler.cs b/src/Microsoft.OpenApi.Hidi/Handlers/TransformCommandHandler.cs
index 85e9c05f6..293fefec9 100644
--- a/src/Microsoft.OpenApi.Hidi/Handlers/TransformCommandHandler.cs
+++ b/src/Microsoft.OpenApi.Hidi/Handlers/TransformCommandHandler.cs
@@ -5,6 +5,7 @@
using System.CommandLine.Invocation;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Hidi.Options;
@@ -24,26 +25,34 @@ public int Invoke(InvocationContext context)
public async Task InvokeAsync(InvocationContext context)
{
HidiOptions hidiOptions = new HidiOptions(context.ParseResult, CommandOptions);
- CancellationToken cancellationToken = (CancellationToken)context.BindingContext.GetService(typeof(CancellationToken));
+ CancellationToken cancellationToken = (CancellationToken)context.BindingContext.GetRequiredService(typeof(CancellationToken));
using var loggerFactory = Logger.ConfigureLogger(hidiOptions.LogLevel);
var logger = loggerFactory.CreateLogger();
try
{
- await OpenApiService.TransformOpenApiDocument(hidiOptions, logger, cancellationToken);
+ await OpenApiService.TransformOpenApiDocument(hidiOptions, logger, cancellationToken).ConfigureAwait(false);
return 0;
}
+#if RELEASE
+#pragma warning disable CA1031 // Do not catch general exception types
+#endif
catch (Exception ex)
{
#if DEBUG
logger.LogCritical(ex, "Command failed");
throw; // so debug tools go straight to the source of the exception when attached
#else
+#pragma warning disable CA2254
logger.LogCritical( ex.Message);
+#pragma warning restore CA2254
return 1;
#endif
}
+#if RELEASE
+#pragma warning restore CA1031 // Do not catch general exception types
+#endif
}
}
}
diff --git a/src/Microsoft.OpenApi.Hidi/Handlers/ValidateCommandHandler.cs b/src/Microsoft.OpenApi.Hidi/Handlers/ValidateCommandHandler.cs
index 29e0a9518..4351a04cb 100644
--- a/src/Microsoft.OpenApi.Hidi/Handlers/ValidateCommandHandler.cs
+++ b/src/Microsoft.OpenApi.Hidi/Handlers/ValidateCommandHandler.cs
@@ -5,6 +5,7 @@
using System.CommandLine.Invocation;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Hidi.Options;
@@ -26,24 +27,33 @@ public int Invoke(InvocationContext context)
public async Task InvokeAsync(InvocationContext context)
{
HidiOptions hidiOptions = new HidiOptions(context.ParseResult, CommandOptions);
- CancellationToken cancellationToken = (CancellationToken)context.BindingContext.GetService(typeof(CancellationToken));
+ CancellationToken cancellationToken = (CancellationToken)context.BindingContext.GetRequiredService(typeof(CancellationToken));
using var loggerFactory = Logger.ConfigureLogger(hidiOptions.LogLevel);
var logger = loggerFactory.CreateLogger();
try
{
- await OpenApiService.ValidateOpenApiDocument(hidiOptions.OpenApi, logger, cancellationToken);
+ if (hidiOptions.OpenApi is null) throw new InvalidOperationException("OpenApi file is required");
+ await OpenApiService.ValidateOpenApiDocument(hidiOptions.OpenApi, logger, cancellationToken).ConfigureAwait(false);
return 0;
}
+#if RELEASE
+#pragma warning disable CA1031 // Do not catch general exception types
+#endif
catch (Exception ex)
{
#if DEBUG
logger.LogCritical(ex, "Command failed");
throw; // so debug tools go straight to the source of the exception when attached
#else
- logger.LogCritical( ex.Message);
+#pragma warning disable CA2254
+ logger.LogCritical(ex.Message);
+#pragma warning restore CA2254
return 1;
#endif
}
+#if RELEASE
+#pragma warning restore CA1031 // Do not catch general exception types
+#endif
}
}
}
diff --git a/src/Microsoft.OpenApi.Hidi/Logger.cs b/src/Microsoft.OpenApi.Hidi/Logger.cs
index 2b02e9600..717ca1a41 100644
--- a/src/Microsoft.OpenApi.Hidi/Logger.cs
+++ b/src/Microsoft.OpenApi.Hidi/Logger.cs
@@ -5,7 +5,7 @@
namespace Microsoft.OpenApi.Hidi
{
- public class Logger
+ public static class Logger
{
public static ILoggerFactory ConfigureLogger(LogLevel logLevel)
{
diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
index fd0d9325e..ced9d9c3c 100644
--- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
+++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
@@ -3,8 +3,10 @@
Exe
net7.0
- 9.0
+ latest
+ true
true
+ enable
http://go.microsoft.com/fwlink/?LinkID=288890
https://github.com/Microsoft/OpenAPI.NET
MIT
@@ -15,7 +17,7 @@
Microsoft.OpenApi.Hidi
hidi
./../../artifacts
- 1.2.9
+ 1.3.0
OpenAPI.NET CLI tool for slicing OpenAPI documents
© Microsoft Corporation. All rights reserved.
OpenAPI .NET
@@ -26,6 +28,10 @@
true
true
+ $(NoWarn);NU5048;NU5104;CA1848;
+ true
+ readme.md
+ All
@@ -62,4 +68,8 @@
+
+
+
+
diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
index 78f48cd4c..809e3ecf9 100644
--- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
+++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
@@ -23,6 +23,7 @@
using Microsoft.OpenApi.ApiManifest;
using Microsoft.OpenApi.ApiManifest.OpenAI;
using Microsoft.OpenApi.Extensions;
+using Microsoft.OpenApi.Hidi.Extensions;
using Microsoft.OpenApi.Hidi.Formatters;
using Microsoft.OpenApi.Hidi.Options;
using Microsoft.OpenApi.Hidi.Utilities;
@@ -69,24 +70,22 @@ public static async Task TransformOpenApiDocument(HidiOptions options, ILogger l
OpenApiSpecVersion openApiVersion = options.Version != null ? TryParseOpenApiSpecVersion(options.Version) : OpenApiSpecVersion.OpenApi3_0;
// If ApiManifest is provided, set the referenced OpenAPI document
- var apiDependency = await FindApiDependency(options.FilterOptions?.FilterByApiManifest, logger, cancellationToken);
+ var apiDependency = await FindApiDependency(options.FilterOptions.FilterByApiManifest, logger, cancellationToken).ConfigureAwait(false);
if (apiDependency != null)
{
options.OpenApi = apiDependency.ApiDescripionUrl;
}
// If Postman Collection is provided, load it
- JsonDocument postmanCollection = null;
- if (!String.IsNullOrEmpty(options.FilterOptions?.FilterByCollection))
+ JsonDocument? postmanCollection = null;
+ if (!string.IsNullOrEmpty(options.FilterOptions?.FilterByCollection))
{
- using (var collectionStream = await GetStream(options.FilterOptions.FilterByCollection, logger, cancellationToken))
- {
- postmanCollection = JsonDocument.Parse(collectionStream);
- }
+ using var collectionStream = await GetStream(options.FilterOptions.FilterByCollection, logger, cancellationToken).ConfigureAwait(false);
+ postmanCollection = JsonDocument.Parse(collectionStream);
}
// Load OpenAPI document
- OpenApiDocument document = await GetOpenApi(options, logger, cancellationToken, options.MetadataVersion);
+ OpenApiDocument document = await GetOpenApi(options, logger, cancellationToken, options.MetadataVersion).ConfigureAwait(false);
if (options.FilterOptions != null)
{
@@ -94,7 +93,7 @@ public static async Task TransformOpenApiDocument(HidiOptions options, ILogger l
}
var languageFormat = options.SettingsConfig?.GetSection("LanguageFormat")?.Value;
- if (Extensions.StringExtensions.IsEquals(languageFormat, "PowerShell"))
+ if ("PowerShell".IsEquals(languageFormat))
{
// PowerShell Walker.
var powerShellFormatter = new PowerShellFormatter();
@@ -105,7 +104,7 @@ public static async Task TransformOpenApiDocument(HidiOptions options, ILogger l
}
catch (TaskCanceledException)
{
- Console.Error.WriteLine("CTRL+C pressed, aborting the operation.");
+ await Console.Error.WriteLineAsync("CTRL+C pressed, aborting the operation.").ConfigureAwait(false);
}
catch (IOException)
{
@@ -117,43 +116,48 @@ public static async Task TransformOpenApiDocument(HidiOptions options, ILogger l
}
}
- private static async Task FindApiDependency(string apiManifestPath, ILogger logger, CancellationToken cancellationToken)
+ private static async Task FindApiDependency(string? apiManifestPath, ILogger logger, CancellationToken cancellationToken)
{
- ApiDependency apiDependency = null;
+ ApiDependency? apiDependency = null;
// If API Manifest is provided, load it, use it get the OpenAPI path
- ApiManifestDocument apiManifest = null;
+ ApiManifestDocument? apiManifest = null;
if (!string.IsNullOrEmpty(apiManifestPath))
{
// Extract fragment identifier if passed as the name of the ApiDependency
var apiManifestRef = apiManifestPath.Split('#');
- string apiDependencyName = null;
+ var apiDependencyName = string.Empty;
if (apiManifestRef.Length > 1)
{
apiDependencyName = apiManifestRef[1];
}
- using (var fileStream = await GetStream(apiManifestRef[0], logger, cancellationToken))
+ using (var fileStream = await GetStream(apiManifestRef[0], logger, cancellationToken).ConfigureAwait(false))
{
apiManifest = ApiManifestDocument.Load(JsonDocument.Parse(fileStream).RootElement);
}
- apiDependency = apiDependencyName != null ? apiManifest.ApiDependencies[apiDependencyName] : apiManifest.ApiDependencies.First().Value;
+ apiDependency = !string.IsNullOrEmpty(apiDependencyName) && apiManifest.ApiDependencies.TryGetValue(apiDependencyName, out var dependency) ? dependency : apiManifest.ApiDependencies.First().Value;
}
return apiDependency;
}
- private static OpenApiDocument ApplyFilters(HidiOptions options, ILogger logger, ApiDependency apiDependency, JsonDocument postmanCollection, OpenApiDocument document)
+ private static OpenApiDocument ApplyFilters(HidiOptions options, ILogger logger, ApiDependency? apiDependency, JsonDocument? postmanCollection, OpenApiDocument document)
{
- Dictionary> requestUrls = null;
+ Dictionary> requestUrls;
if (apiDependency != null)
{
requestUrls = GetRequestUrlsFromManifest(apiDependency);
}
else if (postmanCollection != null)
{
- requestUrls = EnumerateJsonDocument(postmanCollection.RootElement, requestUrls);
+ requestUrls = EnumerateJsonDocument(postmanCollection.RootElement, new());
logger.LogTrace("Finished fetching the list of paths and Http methods defined in the Postman collection.");
}
+ else
+ {
+ requestUrls = new();
+ logger.LogTrace("No filter options provided.");
+ }
logger.LogTrace("Creating predicate from filter options.");
var predicate = FilterOpenApiDocument(options.FilterOptions.FilterByOperationIds,
@@ -167,7 +171,7 @@ private static OpenApiDocument ApplyFilters(HidiOptions options, ILogger logger,
stopwatch.Start();
document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
stopwatch.Stop();
- logger.LogTrace("{timestamp}ms: Creating filtered OpenApi document with {paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
+ logger.LogTrace("{Timestamp}ms: Creating filtered OpenApi document with {Paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
}
return document;
@@ -177,8 +181,9 @@ private static void WriteOpenApi(HidiOptions options, OpenApiFormat openApiForma
{
using (logger.BeginScope("Output"))
{
+ if (options.Output is null) throw new InvalidOperationException("Output file path is null");
using var outputStream = options.Output.Create();
- var textWriter = new StreamWriter(outputStream);
+ using var textWriter = new StreamWriter(outputStream);
var settings = new OpenApiWriterSettings()
{
@@ -200,13 +205,13 @@ private static void WriteOpenApi(HidiOptions options, OpenApiFormat openApiForma
document.Serialize(writer, openApiVersion);
stopwatch.Stop();
- logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms");
+ logger.LogTrace("Finished serializing in {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
textWriter.Flush();
}
}
// Get OpenAPI document either from OpenAPI or CSDL
- private static async Task GetOpenApi(HidiOptions options, ILogger logger, CancellationToken cancellationToken, string metadataVersion = null)
+ private static async Task GetOpenApi(HidiOptions options, ILogger logger, CancellationToken cancellationToken, string? metadataVersion = null)
{
OpenApiDocument document;
@@ -215,59 +220,59 @@ private static async Task GetOpenApi(HidiOptions options, ILogg
if (!string.IsNullOrEmpty(options.Csdl))
{
var stopwatch = new Stopwatch();
- using (logger.BeginScope("Convert CSDL: {csdl}", options.Csdl))
+ using (logger.BeginScope("Convert CSDL: {Csdl}", options.Csdl))
{
stopwatch.Start();
- stream = await GetStream(options.Csdl, logger, cancellationToken);
- Stream filteredStream = null;
+ stream = await GetStream(options.Csdl, logger, cancellationToken).ConfigureAwait(false);
+ Stream? filteredStream = null;
if (!string.IsNullOrEmpty(options.CsdlFilter))
{
XslCompiledTransform transform = GetFilterTransform();
filteredStream = ApplyFilterToCsdl(stream, options.CsdlFilter, transform);
filteredStream.Position = 0;
- stream.Dispose();
- stream = null;
+ await stream.DisposeAsync().ConfigureAwait(false);
}
- document = await ConvertCsdlToOpenApi(filteredStream ?? stream, metadataVersion, options.SettingsConfig, cancellationToken);
+ document = await ConvertCsdlToOpenApi(filteredStream ?? stream, metadataVersion, options.SettingsConfig, cancellationToken).ConfigureAwait(false);
stopwatch.Stop();
- logger.LogTrace("{timestamp}ms: Generated OpenAPI with {paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
+ logger.LogTrace("{Timestamp}ms: Generated OpenAPI with {Paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
}
}
- else
+ else if (!string.IsNullOrEmpty(options.OpenApi))
{
- stream = await GetStream(options.OpenApi, logger, cancellationToken);
- var result = await ParseOpenApi(options.OpenApi, options.InlineExternal, logger, stream, cancellationToken);
+ stream = await GetStream(options.OpenApi, logger, cancellationToken).ConfigureAwait(false);
+ var result = await ParseOpenApi(options.OpenApi, options.InlineExternal, logger, stream, cancellationToken).ConfigureAwait(false);
document = result.OpenApiDocument;
}
+ else throw new InvalidOperationException("No input file path or URL provided");
return document;
}
- private static Func FilterOpenApiDocument(string filterbyoperationids, string filterbytags, Dictionary> requestUrls, OpenApiDocument document, ILogger logger)
+ private static Func? FilterOpenApiDocument(string? filterByOperationIds, string? filterByTags, Dictionary> requestUrls, OpenApiDocument document, ILogger logger)
{
- Func predicate = null;
+ Func? predicate = null;
using (logger.BeginScope("Create Filter"))
{
// Check if filter options are provided, then slice the OpenAPI document
- if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags))
+ if (!string.IsNullOrEmpty(filterByOperationIds) && !string.IsNullOrEmpty(filterByTags))
{
throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time.");
}
- if (!string.IsNullOrEmpty(filterbyoperationids))
+ if (!string.IsNullOrEmpty(filterByOperationIds))
{
logger.LogTrace("Creating predicate based on the operationIds supplied.");
- predicate = OpenApiFilterService.CreatePredicate(tags: filterbyoperationids);
+ predicate = OpenApiFilterService.CreatePredicate(tags: filterByOperationIds);
}
- if (!string.IsNullOrEmpty(filterbytags))
+ if (!string.IsNullOrEmpty(filterByTags))
{
logger.LogTrace("Creating predicate based on the tags supplied.");
- predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags);
+ predicate = OpenApiFilterService.CreatePredicate(tags: filterByTags);
}
- if (requestUrls != null)
+ if (requestUrls.Any())
{
logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection.");
predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source: document);
@@ -281,13 +286,13 @@ private static Dictionary> GetRequestUrlsFromManifest(ApiDe
{
// Get the request URLs from the API Dependencies in the API manifest
var requests = apiDependency
- .Requests.Where(static r => !r.Exclude)
- .Select(static r => new { UriTemplate = r.UriTemplate, Method = r.Method })
+ .Requests.Where(static r => !r.Exclude && !string.IsNullOrEmpty(r.UriTemplate) && !string.IsNullOrEmpty(r.Method))
+ .Select(static r => new { UriTemplate = r.UriTemplate!, Method = r.Method! })
.GroupBy(static r => r.UriTemplate)
.ToDictionary(static g => g.Key, static g => g.Select(static r => r.Method).ToList());
// This makes the assumption that the UriTemplate in the ApiManifest matches exactly the UriTemplate in the OpenAPI document
// This does not need to be the case. The URI template in the API manifest could map to a set of OpenAPI paths.
- // Additional logic will be required to handle this scenario. I sugggest we build this into the OpenAPI.Net library at some point.
+ // Additional logic will be required to handle this scenario. I suggest we build this into the OpenAPI.Net library at some point.
return requests;
}
@@ -295,43 +300,43 @@ private static XslCompiledTransform GetFilterTransform()
{
XslCompiledTransform transform = new();
Assembly assembly = typeof(OpenApiService).GetTypeInfo().Assembly;
- Stream xslt = assembly.GetManifestResourceStream("Microsoft.OpenApi.Hidi.CsdlFilter.xslt");
- transform.Load(new XmlTextReader(new StreamReader(xslt)));
+ using var xslt = assembly.GetManifestResourceStream("Microsoft.OpenApi.Hidi.CsdlFilter.xslt") ?? throw new InvalidOperationException("Could not find the Microsoft.OpenApi.Hidi.CsdlFilter.xslt file in the assembly. Check build configuration.");
+ using var streamReader = new StreamReader(xslt);
+ using var textReader = new XmlTextReader(streamReader);
+ transform.Load(textReader);
return transform;
}
private static Stream ApplyFilterToCsdl(Stream csdlStream, string entitySetOrSingleton, XslCompiledTransform transform)
{
- Stream stream;
using StreamReader inputReader = new(csdlStream, leaveOpen: true);
- XmlReader inputXmlReader = XmlReader.Create(inputReader);
+ using XmlReader inputXmlReader = XmlReader.Create(inputReader);
MemoryStream filteredStream = new();
- StreamWriter writer = new(filteredStream);
+ using StreamWriter writer = new(filteredStream, leaveOpen: true);
XsltArgumentList args = new();
args.AddParam("entitySetOrSingleton", "", entitySetOrSingleton);
transform.Transform(inputXmlReader, args, writer);
- stream = filteredStream;
- return stream;
+ return filteredStream;
}
///
/// Implementation of the validate command
///
public static async Task ValidateOpenApiDocument(
- string openapi,
+ string openApi,
ILogger logger,
CancellationToken cancellationToken)
{
- if (string.IsNullOrEmpty(openapi))
+ if (string.IsNullOrEmpty(openApi))
{
- throw new ArgumentNullException(nameof(openapi));
+ throw new ArgumentNullException(nameof(openApi));
}
try
{
- using var stream = await GetStream(openapi, logger, cancellationToken);
+ using var stream = await GetStream(openApi, logger, cancellationToken).ConfigureAwait(false);
- var result = await ParseOpenApi(openapi, false, logger, stream, cancellationToken);
+ var result = await ParseOpenApi(openApi, false, logger, stream, cancellationToken).ConfigureAwait(false);
using (logger.BeginScope("Calculating statistics"))
{
@@ -340,12 +345,14 @@ public static async Task ValidateOpenApiDocument(
walker.Walk(result.OpenApiDocument);
logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report..");
+ #pragma warning disable CA2254
logger.LogInformation(statsVisitor.GetStatisticsReport());
+ #pragma warning restore CA2254
}
}
catch (TaskCanceledException)
{
- Console.Error.WriteLine("CTRL+C pressed, aborting the operation.");
+ await Console.Error.WriteLineAsync("CTRL+C pressed, aborting the operation.").ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -357,7 +364,7 @@ private static async Task ParseOpenApi(string openApiFile, bool inli
{
ReadResult result;
Stopwatch stopwatch = Stopwatch.StartNew();
- using (logger.BeginScope("Parsing OpenAPI: {openApiFile}", openApiFile))
+ using (logger.BeginScope("Parsing OpenAPI: {OpenApiFile}", openApiFile))
{
stopwatch.Start();
@@ -368,9 +375,9 @@ private static async Task ParseOpenApi(string openApiFile, bool inli
new Uri(openApiFile) :
new Uri("file://" + new FileInfo(openApiFile).DirectoryName + Path.DirectorySeparatorChar)
}
- ).ReadAsync(stream, cancellationToken);
+ ).ReadAsync(stream, cancellationToken).ConfigureAwait(false);
- logger.LogTrace("{timestamp}ms: Completed parsing.", stopwatch.ElapsedMilliseconds);
+ logger.LogTrace("{Timestamp}ms: Completed parsing.", stopwatch.ElapsedMilliseconds);
LogErrors(logger, result);
stopwatch.Stop();
@@ -384,10 +391,10 @@ private static async Task ParseOpenApi(string openApiFile, bool inli
///
/// The CSDL stream.
/// An OpenAPI document.
- public static async Task ConvertCsdlToOpenApi(Stream csdl, string metadataVersion = null, IConfiguration settings = null, CancellationToken token = default)
+ public static async Task ConvertCsdlToOpenApi(Stream csdl, string? metadataVersion = null, IConfiguration? settings = null, CancellationToken token = default)
{
using var reader = new StreamReader(csdl);
- var csdlText = await reader.ReadToEndAsync(token);
+ var csdlText = await reader.ReadToEndAsync(token).ConfigureAwait(false);
var edmModel = CsdlReader.Parse(XElement.Parse(csdlText).CreateReader());
settings ??= SettingsUtilities.GetConfiguration();
@@ -480,19 +487,15 @@ private static async Task GetStream(string input, ILogger logger, Cancel
var stopwatch = new Stopwatch();
stopwatch.Start();
- if (input.StartsWith("http"))
+ if (input.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
try
{
- var httpClientHandler = new HttpClientHandler()
- {
- SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
- };
- using var httpClient = new HttpClient(httpClientHandler)
+ using var httpClient = new HttpClient
{
DefaultRequestVersion = HttpVersion.Version20
};
- stream = await httpClient.GetStreamAsync(input, cancellationToken);
+ stream = await httpClient.GetStreamAsync(new Uri(input), cancellationToken).ConfigureAwait(false);
}
catch (HttpRequestException ex)
{
@@ -518,7 +521,7 @@ ex is SecurityException ||
}
}
stopwatch.Stop();
- logger.LogTrace("{timestamp}ms: Read file {input}", stopwatch.ElapsedMilliseconds, input);
+ logger.LogTrace("{Timestamp}ms: Read file {Input}", stopwatch.ElapsedMilliseconds, input);
}
return stream;
}
@@ -532,12 +535,12 @@ ex is SecurityException ||
private static OpenApiFormat GetOpenApiFormat(string input, ILogger logger)
{
logger.LogTrace("Getting the OpenApi format");
- return !input.StartsWith("http") && Path.GetExtension(input) == ".json" ? OpenApiFormat.Json : OpenApiFormat.Yaml;
+ return !input.StartsWith("http", StringComparison.OrdinalIgnoreCase) && Path.GetExtension(input) == ".json" ? OpenApiFormat.Json : OpenApiFormat.Yaml;
}
- private static string GetInputPathExtension(string openapi = null, string csdl = null)
+ private static string GetInputPathExtension(string? openapi = null, string? csdl = null)
{
- var extension = String.Empty;
+ var extension = string.Empty;
if (!string.IsNullOrEmpty(openapi))
{
extension = Path.GetExtension(openapi);
@@ -550,7 +553,7 @@ private static string GetInputPathExtension(string openapi = null, string csdl =
return extension;
}
- internal static async Task ShowOpenApiDocument(HidiOptions options, ILogger logger, CancellationToken cancellationToken)
+ internal static async Task ShowOpenApiDocument(HidiOptions options, ILogger logger, CancellationToken cancellationToken)
{
try
{
@@ -559,11 +562,16 @@ internal static async Task ShowOpenApiDocument(HidiOptions options, ILog
throw new ArgumentException("Please input a file path or URL");
}
- var document = await GetOpenApi(options, logger, cancellationToken);
+ var document = await GetOpenApi(options, logger, cancellationToken).ConfigureAwait(false);
using (logger.BeginScope("Creating diagram"))
{
// If output is null, create a HTML file in the user's temporary directory
+ var sourceUrl = (string.IsNullOrEmpty(options.OpenApi), string.IsNullOrEmpty(options.Csdl)) switch {
+ (false, _) => options.OpenApi!,
+ (_, false) => options.Csdl!,
+ _ => throw new InvalidOperationException("No input file path or URL provided")
+ };
if (options.Output == null)
{
var tempPath = Path.GetTempPath() + "/hidi/";
@@ -578,7 +586,7 @@ internal static async Task ShowOpenApiDocument(HidiOptions options, ILog
using (var file = new FileStream(output.FullName, FileMode.Create))
{
using var writer = new StreamWriter(file);
- WriteTreeDocumentAsHtml(options.OpenApi ?? options.Csdl, document, writer);
+ WriteTreeDocumentAsHtml(sourceUrl, document, writer);
}
logger.LogTrace("Created Html document with diagram ");
@@ -595,7 +603,7 @@ internal static async Task ShowOpenApiDocument(HidiOptions options, ILog
using (var file = new FileStream(options.Output.FullName, FileMode.Create))
{
using var writer = new StreamWriter(file);
- WriteTreeDocumentAsMarkdown(options.OpenApi ?? options.Csdl, document, writer);
+ WriteTreeDocumentAsMarkdown(sourceUrl, document, writer);
}
logger.LogTrace("Created markdown document with diagram ");
return options.Output.FullName;
@@ -604,7 +612,7 @@ internal static async Task ShowOpenApiDocument(HidiOptions options, ILog
}
catch (TaskCanceledException)
{
- Console.Error.WriteLine("CTRL+C pressed, aborting the operation.");
+ await Console.Error.WriteLineAsync("CTRL+C pressed, aborting the operation.").ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -622,7 +630,7 @@ private static void LogErrors(ILogger logger, ReadResult result)
{
foreach (var error in context.Errors)
{
- logger.LogError("Detected error during parsing: {error}", error.ToString());
+ logger.LogError("Detected error during parsing: {Error}", error.ToString());
}
}
}
@@ -640,7 +648,7 @@ internal static void WriteTreeDocumentAsMarkdown(string openapiUrl, OpenApiDocum
// write a span for each mermaidcolorscheme
foreach (var style in OpenApiUrlTreeNode.MermaidNodeStyles)
{
- writer.WriteLine($"{style.Key.Replace("_", " ")}");
+ writer.WriteLine($"{style.Key.Replace("_", " ", StringComparison.OrdinalIgnoreCase)}");
}
writer.WriteLine("");
writer.WriteLine();
@@ -673,7 +681,7 @@ internal static void WriteTreeDocumentAsHtml(string sourceUrl, OpenApiDocument d
// write a span for each mermaidcolorscheme
foreach (var style in OpenApiUrlTreeNode.MermaidNodeStyles)
{
- writer.WriteLine($"{style.Key.Replace("_", " ")}");
+ writer.WriteLine($"{style.Key.Replace("_", " ", StringComparison.OrdinalIgnoreCase)}");
}
writer.WriteLine("");
writer.WriteLine("
");
@@ -698,17 +706,17 @@ internal static void WriteTreeDocumentAsHtml(string sourceUrl, OpenApiDocument d
writer.WriteLine("