From 316c365db351f3100b6dd5a2e8a3da91fe8f8b14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 21:57:18 +0000 Subject: [PATCH 01/50] Bump Verify.Xunit from 19.13.1 to 19.14.1 Bumps [Verify.Xunit](https://github.com/VerifyTests/Verify) from 19.13.1 to 19.14.1. - [Release notes](https://github.com/VerifyTests/Verify/releases) - [Commits](https://github.com/VerifyTests/Verify/compare/19.13.1...19.14.1) --- updated-dependencies: - dependency-name: Verify.Xunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 2dcf347ba..36369938b 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -28,7 +28,7 @@ - + all From 8b7d1c8e3870234e4e8bf6ba2557260e53005255 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 3 May 2023 13:22:35 +0300 Subject: [PATCH 02/50] Update README.md with guidelines on using workbench tool --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 0190e572d..baf262999 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ |--|--| |Models and Writers|[![nuget](https://img.shields.io/nuget/v/Microsoft.OpenApi.svg)](https://www.nuget.org/packages/Microsoft.OpenApi/) | |Readers | [![nuget](https://img.shields.io/nuget/v/Microsoft.OpenApi.Readers.svg)](https://www.nuget.org/packages/Microsoft.OpenApi.Readers/) | +|Hidi|[![nuget](https://img.shields.io/nuget/v/Microsoft.OpenApi.Hidi.svg)](https://www.nuget.org/packages/Microsoft.OpenApi.Hidi/) The **OpenAPI.NET** SDK contains a useful object model for OpenAPI documents in .NET along with common serializers to extract raw OpenAPI JSON and YAML documents from the model. @@ -90,6 +91,28 @@ var outputString = openApiDocument.Serialize(OpenApiSpecVersion.OpenApi2_0, Open ``` +# Validating/Testing OpenApi descriptions +In order to test the validity of an OpenApi document, we avail the following tools: +- [Microsoft.OpenApi.Hidi](https://www.nuget.org/packages/Microsoft.OpenApi.Hidi) + + A commandline tool for validating and transforming OpenApi descriptions. [Installation guidelines and documentation](https://github.com/microsoft/OpenAPI.NET/blob/vnext/src/Microsoft.OpenApi.Hidi/readme.md) + +- Microsoft.OpenApi.Workbench + + A workbench tool consisting of a GUI where you can test and convert OpenApi descriptions in both Json and Yaml from v2-->v3 and vice versa. + + #### Installation guidelines: + 1. Clone the repo locally by running this command: + `git clone https://github.com/microsoft/OpenAPI.NET.git` + 2. Open the solution file `(.sln)` in the root of the project with Visual Studio + 3. Navigate to the `src/Microsoft.OpenApi.Workbench` directory and set it as the startup project + 4. Run the project and you'll see a GUI pop up resembling the one below: + + + + + 5. Copy paste your OpenApi descriptions or paste the path to the descriptions file and click on `convert` to render the results. + # Build Status |**master**| From f80d52e599d3e713a6f08b23ba53d85560abc8b6 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 3 May 2023 15:44:25 +0300 Subject: [PATCH 03/50] Update README.md Co-authored-by: Irvine Sunday <40403681+irvinesunday@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index baf262999..07f4553c9 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ In order to test the validity of an OpenApi document, we avail the following too - 5. Copy paste your OpenApi descriptions or paste the path to the descriptions file and click on `convert` to render the results. + 5. Copy and paste your OpenApi descriptions in the **Input Content** window or paste the path to the descriptions file in the **Input File** textbox and click on `convert` to render the results. # Build Status From 998590255ae8c178834ca65eb2516a6cc118ca17 Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Mon, 8 May 2023 16:20:24 -0700 Subject: [PATCH 04/50] Add OpenAPI formatter for PS --- .../Formatters/PowerShellFormatter.cs | 210 ++++++++++++++++++ .../Handlers/TransformCommandHandler.cs | 4 +- .../Microsoft.OpenApi.Hidi.csproj | 1 + src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 69 +++--- src/Microsoft.OpenApi.Hidi/Program.cs | 14 +- .../Services/OpenApiServiceTests.cs | 26 +-- 6 files changed, 272 insertions(+), 52 deletions(-) create mode 100644 src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs diff --git a/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs new file mode 100644 index 000000000..df8fbb948 --- /dev/null +++ b/src/Microsoft.OpenApi.Hidi/Formatters/PowerShellFormatter.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Humanizer; +using Humanizer.Inflections; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; + +namespace Microsoft.OpenApi.Hidi.Formatters +{ + internal class PowerShellFormatter : OpenApiVisitorBase + { + private const string DefaultPutPrefix = ".Update"; + private const string PowerShellPutPrefix = ".Set"; + private readonly Stack _schemaLoop = new(); + private static readonly Regex s_oDataCastRegex = new("(.*(?<=[a-z]))\\.(As(?=[A-Z]).*)", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); + private static readonly Regex s_hashSuffixRegex = new(@"^[^-]+", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); + private static readonly Regex s_oDataRefRegex = new("(?<=[a-z])Ref(?=[A-Z])", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); + + static PowerShellFormatter() + { + // Add singularization exclusions. + // TODO: Read exclusions from a user provided file. + Vocabularies.Default.AddSingular("(drive)s$", "$1"); // drives does not properly singularize to drive. + Vocabularies.Default.AddSingular("(data)$", "$1"); // exclude the following from singularization. + Vocabularies.Default.AddSingular("(delta)$", "$1"); + Vocabularies.Default.AddSingular("(quota)$", "$1"); + Vocabularies.Default.AddSingular("(statistics)$", "$1"); + } + + //TODO: FHL for PS + // Fixes (Order matters): + // 1. Singularize operationId operationIdSegments. + // 2. Add '_' to verb in an operationId. + // 3. Fix odata cast operationIds. + // 4. Fix hash suffix in operationIds. + // 5. Fix Put operation id should have -> {xxx}_Set{Yyy} + // 5. Fix anyOf and oneOf schema. + // 6. Add AdditionalProperties to object schemas. + + public override void Visit(OpenApiSchema schema) + { + AddAddtionalPropertiesToSchema(schema); + ResolveAnyOfSchema(schema); + ResolveOneOfSchema(schema); + + base.Visit(schema); + } + + public override void Visit(OpenApiPathItem pathItem) + { + if (pathItem.Operations.ContainsKey(OperationType.Put)) + { + var operationId = pathItem.Operations[OperationType.Put].OperationId; + pathItem.Operations[OperationType.Put].OperationId = ResolvePutOperationId(operationId); + } + + base.Visit(pathItem); + } + + public override void Visit(OpenApiOperation operation) + { + if (operation.OperationId == null) + throw new ArgumentNullException(nameof(operation.OperationId), $"OperationId is required {PathString}"); + + var operationId = operation.OperationId; + + operationId = RemoveHashSuffix(operationId); + operationId = ResolveODataCastOperationId(operationId); + operationId = ResolveByRefOperationId(operationId); + + + var operationIdSegments = operationId.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + operationId = SingularizeAndDeduplicateOperationId(operationIdSegments); + + operation.OperationId = operationId; + base.Visit(operation); + } + + private void AddAddtionalPropertiesToSchema(OpenApiSchema schema) + { + if (schema != null && !_schemaLoop.Contains(schema) && "object".Equals(schema?.Type, StringComparison.OrdinalIgnoreCase)) + { + schema.AdditionalProperties = new OpenApiSchema() { Type = "object" }; + + /* Because 'additionalProperties' are now being walked, + * we need a way to keep track of visited schemas to avoid + * endlessly creating and walking them in an infinite recursion. + */ + _schemaLoop.Push(schema.AdditionalProperties); + } + } + + private static void ResolveOneOfSchema(OpenApiSchema schema) + { + if (schema.OneOf?.Any() ?? false) + { + var newSchema = schema.OneOf.FirstOrDefault(); + schema.OneOf = null; + FlattenSchema(schema, newSchema); + } + } + + private static void ResolveAnyOfSchema(OpenApiSchema schema) + { + if (schema.AnyOf?.Any() ?? false) + { + var newSchema = schema.AnyOf.FirstOrDefault(); + schema.AnyOf = null; + FlattenSchema(schema, newSchema); + } + } + + private static string ResolvePutOperationId(string operationId) + { + return operationId.Contains(DefaultPutPrefix) ? + operationId.Replace(DefaultPutPrefix, PowerShellPutPrefix) : operationId; + } + + private static string ResolveByRefOperationId(string operationId) + { + // Update $ref path operationId name + // Ref key word is enclosed between lower-cased and upper-cased letters + // Ex.: applications_GetRefCreatedOnBehalfOf to applications_GetCreatedOnBehalfOfByRef + return s_oDataRefRegex.Match(operationId).Success ? $"{s_oDataRefRegex.Replace(operationId, string.Empty)}ByRef" : operationId; + } + + private static string ResolveODataCastOperationId(string operationId) + { + var match = s_oDataCastRegex.Match(operationId); + return match.Success ? $"{match.Groups[1]}{match.Groups[2]}" : operationId; + } + + private static string SingularizeAndDeduplicateOperationId(IList operationIdSegments) + { + var segmentsCount = operationIdSegments.Count; + var lastSegmentIndex = segmentsCount - 1; + var singularizedSegments = new List(); + + for (int x = 0; x < segmentsCount; x++) + { + var segment = operationIdSegments[x].Singularize(inputIsKnownToBePlural: false); + + // If a segment name is contained in the previous segment, the latter is considered a duplicate. + // The last segment is ignored as a rule. + if ((x > 0 && x < lastSegmentIndex) && singularizedSegments.Last().Equals(segment, StringComparison.OrdinalIgnoreCase)) + continue; + + singularizedSegments.Add(segment); + } + return string.Join(".", singularizedSegments); + } + + private static string RemoveHashSuffix(string operationId) + { + // Remove hash suffix values from OperationIds. + return s_hashSuffixRegex.Match(operationId).Value; + } + + private static void FlattenSchema(OpenApiSchema schema, OpenApiSchema newSchema) + { + if (newSchema != null) + { + if (newSchema.Reference != null) + { + schema.Reference = newSchema.Reference; + schema.UnresolvedReference = true; + } + else + { + // Copies schema properties based on https://github.com/microsoft/OpenAPI.NET.OData/pull/264. + CopySchema(schema, newSchema); + } + } + } + + private static void CopySchema(OpenApiSchema schema, OpenApiSchema newSchema) + { + schema.Title ??= newSchema.Title; + schema.Type ??= newSchema.Type; + schema.Format ??= newSchema.Format; + schema.Description ??= newSchema.Description; + schema.Maximum ??= newSchema.Maximum; + schema.ExclusiveMaximum ??= newSchema.ExclusiveMaximum; + schema.Minimum ??= newSchema.Minimum; + schema.ExclusiveMinimum ??= newSchema.ExclusiveMinimum; + schema.MaxLength ??= newSchema.MaxLength; + schema.MinLength ??= newSchema.MinLength; + schema.Pattern ??= newSchema.Pattern; + schema.MultipleOf ??= newSchema.MultipleOf; + schema.Not ??= newSchema.Not; + schema.Required ??= newSchema.Required; + schema.Items ??= newSchema.Items; + schema.MaxItems ??= newSchema.MaxItems; + schema.MinItems ??= newSchema.MinItems; + schema.UniqueItems ??= newSchema.UniqueItems; + schema.Properties ??= newSchema.Properties; + schema.MaxProperties ??= newSchema.MaxProperties; + schema.MinProperties ??= newSchema.MinProperties; + schema.Discriminator ??= newSchema.Discriminator; + schema.ExternalDocs ??= newSchema.ExternalDocs; + schema.Enum ??= newSchema.Enum; + schema.ReadOnly = !schema.ReadOnly ? newSchema.ReadOnly : schema.ReadOnly; + schema.WriteOnly = !schema.WriteOnly ? newSchema.WriteOnly : schema.WriteOnly; + schema.Nullable = !schema.Nullable ? newSchema.Nullable : schema.Nullable; + schema.Deprecated = !schema.Deprecated ? newSchema.Deprecated : schema.Deprecated; + } + } +} diff --git a/src/Microsoft.OpenApi.Hidi/Handlers/TransformCommandHandler.cs b/src/Microsoft.OpenApi.Hidi/Handlers/TransformCommandHandler.cs index e00cd7efa..1c4262ba5 100644 --- a/src/Microsoft.OpenApi.Hidi/Handlers/TransformCommandHandler.cs +++ b/src/Microsoft.OpenApi.Hidi/Handlers/TransformCommandHandler.cs @@ -29,6 +29,7 @@ internal class TransformCommandHandler : ICommandHandler public Option FilterByCollectionOption { get; set; } public Option InlineLocalOption { get; set; } public Option InlineExternalOption { get; set; } + public Option LanguageFormatOption { get; set; } public int Invoke(InvocationContext context) { @@ -49,6 +50,7 @@ public async Task InvokeAsync(InvocationContext context) LogLevel logLevel = context.ParseResult.GetValueForOption(LogLevelOption); bool inlineLocal = context.ParseResult.GetValueForOption(InlineLocalOption); bool inlineExternal = context.ParseResult.GetValueForOption(InlineExternalOption); + string? languageFormatOption = context.ParseResult.GetValueForOption(LanguageFormatOption); string filterbyoperationids = context.ParseResult.GetValueForOption(FilterByOperationIdsOption); string filterbytags = context.ParseResult.GetValueForOption(FilterByTagsOption); string filterbycollection = context.ParseResult.GetValueForOption(FilterByCollectionOption); @@ -59,7 +61,7 @@ public async Task InvokeAsync(InvocationContext context) var logger = loggerFactory.CreateLogger(); try { - await OpenApiService.TransformOpenApiDocument(openapi, csdl, csdlFilter, output, cleanOutput, version, metadataVersion, format, terseOutput, settingsFile, inlineLocal, inlineExternal, filterbyoperationids, filterbytags, filterbycollection, logger, cancellationToken); + await OpenApiService.TransformOpenApiDocument(openapi, csdl, csdlFilter, output, cleanOutput, version, metadataVersion, format, terseOutput, settingsFile, inlineLocal, inlineExternal, languageFormatOption, filterbyoperationids, filterbytags, filterbycollection, logger, cancellationToken); return 0; } diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index e370c84bb..2cc6ceae6 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 5d5ec95d4..73d12806c 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -26,6 +26,7 @@ using System.Xml; using System.Reflection; using Microsoft.Extensions.Configuration; +using Microsoft.OpenApi.Hidi.Formatters; namespace Microsoft.OpenApi.Hidi { @@ -47,6 +48,7 @@ public static async Task TransformOpenApiDocument( string settingsFile, bool inlineLocal, bool inlineExternal, + string? languageFormatOption, string filterbyoperationids, string filterbytags, string filterbycollection, @@ -82,6 +84,13 @@ CancellationToken cancellationToken OpenApiDocument document = await GetOpenApi(openapi, csdl, csdlFilter, settingsFile, inlineExternal, logger, cancellationToken, metadataVersion); document = await FilterOpenApiDocument(filterbyoperationids, filterbytags, filterbycollection, document, logger, cancellationToken); + if (!string.IsNullOrWhiteSpace(languageFormatOption) && languageFormatOption.Equals("PowerShell", StringComparison.InvariantCultureIgnoreCase)) + { + // PowerShell Walker. + var powerShellFormatter = new PowerShellFormatter(); + var walker = new OpenApiWalker(powerShellFormatter); + walker.Walk(document); + } WriteOpenApi(output, terseOutput, inlineLocal, inlineExternal, openApiFormat, openApiVersion, document, logger); } catch (TaskCanceledException) @@ -98,7 +107,7 @@ CancellationToken cancellationToken } } - private static void WriteOpenApi(FileInfo output, bool terseOutput, bool inlineLocal, bool inlineExternal, OpenApiFormat openApiFormat, OpenApiSpecVersion openApiVersion, OpenApiDocument document, ILogger logger) + private static void WriteOpenApi(FileInfo output, bool terseOutput, bool inlineLocal, bool inlineExternal, OpenApiFormat openApiFormat, OpenApiSpecVersion openApiVersion, OpenApiDocument document, ILogger logger) { using (logger.BeginScope("Output")) { @@ -135,7 +144,7 @@ private static async Task GetOpenApi(string openapi, string csd { OpenApiDocument document; Stream stream; - + if (!string.IsNullOrEmpty(csdl)) { var stopwatch = new Stopwatch(); @@ -168,7 +177,7 @@ private static async Task GetOpenApi(string openapi, string csd return document; } - private static async Task FilterOpenApiDocument(string filterbyoperationids, string filterbytags, string filterbycollection, OpenApiDocument document, ILogger logger, CancellationToken cancellationToken) + private static async Task FilterOpenApiDocument(string filterbyoperationids, string filterbytags, string filterbycollection, OpenApiDocument document, ILogger logger, CancellationToken cancellationToken) { using (logger.BeginScope("Filter")) { @@ -239,8 +248,8 @@ private static Stream ApplyFilterToCsdl(Stream csdlStream, string entitySetOrSin /// Implementation of the validate command /// public static async Task ValidateOpenApiDocument( - string openapi, - ILogger logger, + string openapi, + ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(openapi)) @@ -285,7 +294,7 @@ private static async Task ParseOpenApi(string openApiFile, bool inli result = await new OpenApiStreamReader(new OpenApiReaderSettings { LoadExternalRefs = inlineExternal, - BaseUrl = openApiFile.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? + BaseUrl = openApiFile.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? new Uri(openApiFile) : new Uri("file://" + new FileInfo(openApiFile).DirectoryName + Path.DirectorySeparatorChar) } @@ -296,7 +305,7 @@ private static async Task ParseOpenApi(string openApiFile, bool inli LogErrors(logger, result); stopwatch.Stop(); } - + return result; } @@ -310,7 +319,7 @@ internal static IConfiguration GetConfiguration(string settingsFile) return config; } - + /// /// Converts CSDL to OpenAPI /// @@ -329,7 +338,7 @@ public static async Task ConvertCsdlToOpenApi(Stream csdl, stri { settings.SemVerVersion = metadataVersion; } - + config.GetSection("OpenApiConvertSettings").Bind(settings); OpenApiDocument document = edmModel.ConvertToOpenApi(settings); @@ -354,7 +363,7 @@ public static OpenApiDocument FixReferences(OpenApiDocument document) return doc; } - + /// /// Takes in a file stream, parses the stream into a JsonDocument and gets a list of paths and Http methods /// @@ -377,13 +386,13 @@ public static Dictionary> ParseJsonCollectionFile(Stream st private static Dictionary> EnumerateJsonDocument(JsonElement itemElement, Dictionary> paths) { var itemsArray = itemElement.GetProperty("item"); - + foreach (var item in itemsArray.EnumerateArray()) { - if(item.ValueKind == JsonValueKind.Object) + if (item.ValueKind == JsonValueKind.Object) { - if(item.TryGetProperty("request", out var request)) - { + if (item.TryGetProperty("request", out var request)) + { // Fetch list of methods and urls from collection, store them in a dictionary var path = request.GetProperty("url").GetProperty("raw").ToString(); var method = request.GetProperty("method").ToString(); @@ -395,11 +404,11 @@ private static Dictionary> EnumerateJsonDocument(JsonElemen { paths[path].Add(method); } - } - else - { + } + else + { EnumerateJsonDocument(item, paths); - } + } } else { @@ -508,11 +517,11 @@ internal static async Task ShowOpenApiDocument(string openapi, string cs if (output == null) { var tempPath = Path.GetTempPath() + "/hidi/"; - if(!File.Exists(tempPath)) + if (!File.Exists(tempPath)) { Directory.CreateDirectory(tempPath); - } - + } + var fileName = Path.GetRandomFileName(); output = new FileInfo(Path.Combine(tempPath, fileName + ".html")); @@ -528,7 +537,7 @@ internal static async Task ShowOpenApiDocument(string openapi, string cs process.StartInfo.FileName = output.FullName; process.StartInfo.UseShellExecute = true; process.Start(); - + return output.FullName; } else // Write diagram as Markdown document to output file @@ -540,7 +549,7 @@ internal static async Task ShowOpenApiDocument(string openapi, string cs } logger.LogTrace("Created markdown document with diagram "); return output.FullName; - } + } } } catch (TaskCanceledException) @@ -563,7 +572,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()); } } } @@ -581,7 +590,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("_", " ")}"); } writer.WriteLine(""); writer.WriteLine(); @@ -609,7 +618,7 @@ internal static void WriteTreeDocumentAsHtml(string sourceUrl, OpenApiDocument d writer.WriteLine("

" + document.Info.Title + "

"); writer.WriteLine(); writer.WriteLine($"

API Description: {sourceUrl}

"); - + writer.WriteLine(@"
"); // write a span for each mermaidcolorscheme foreach (var style in OpenApiUrlTreeNode.MermaidNodeStyles) @@ -622,8 +631,8 @@ internal static void WriteTreeDocumentAsHtml(string sourceUrl, OpenApiDocument d rootNode.WriteMermaid(writer); writer.WriteLine(""); - // Write script tag to include JS library for rendering markdown - writer.WriteLine(@""); - // Write script tag to include JS library for rendering mermaid - writer.WriteLine("("--format", "File format"); formatOption.AddAlias("-f"); - + var terseOutputOption = new Option("--terse-output", "Produce terse json output"); terseOutputOption.AddAlias("--to"); @@ -76,6 +73,9 @@ internal static RootCommand CreateRootCommand() var inlineExternalOption = new Option("--inline-external", "Inline external $ref instances"); inlineExternalOption.AddAlias("--ie"); + // TODO: Move to settings file (--settings-path). + var languageFormatOption = new Option("--language-style", "Language to format the OpenAPI document. e.g. powershell"); + var validateCommand = new Command("validate") { descriptionOption, @@ -105,7 +105,8 @@ internal static RootCommand CreateRootCommand() filterByTagsOption, filterByCollectionOption, inlineLocalOption, - inlineExternalOption + inlineExternalOption, + languageFormatOption }; transformCommand.Handler = new TransformCommandHandler @@ -125,7 +126,8 @@ internal static RootCommand CreateRootCommand() FilterByTagsOption = filterByTagsOption, FilterByCollectionOption = filterByCollectionOption, InlineLocalOption = inlineLocalOption, - InlineExternalOption = inlineExternalOption + InlineExternalOption = inlineExternalOption, + LanguageFormatOption = languageFormatOption }; var showCommand = new Command("show") diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs index 9081c49f5..50a85fb15 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs @@ -3,16 +3,12 @@ using System.CommandLine; using System.CommandLine.Invocation; -using System.Text; -using Castle.Core.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Hidi; -using Microsoft.OpenApi.Hidi.Handlers; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData; using Microsoft.OpenApi.Services; -using Microsoft.VisualStudio.TestPlatform.Utilities; using Xunit; namespace Microsoft.OpenApi.Tests.Services @@ -36,7 +32,7 @@ public async Task ReturnConvertedCSDLFile() Assert.NotEmpty(openApiDoc.Paths); Assert.Equal(expectedPathCount, openApiDoc.Paths.Count); } - + [Theory] [InlineData("Todos.Todo.UpdateTodo", null, 1)] [InlineData("Todos.Todo.ListTodo", null, 1)] @@ -47,7 +43,7 @@ public async Task ReturnFilteredOpenApiDocBasedOnOperationIdsAndInputCsdlDocumen var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles\\Todo.xml"); var fileInput = new FileInfo(filePath); var csdlStream = fileInput.OpenRead(); - + // Act var openApiDoc = await OpenApiService.ConvertCsdlToOpenApi(csdlStream); var predicate = OpenApiFilterService.CreatePredicate(operationIds, tags); @@ -58,7 +54,7 @@ public async Task ReturnFilteredOpenApiDocBasedOnOperationIdsAndInputCsdlDocumen Assert.NotEmpty(subsetOpenApiDocument.Paths); Assert.Equal(expectedPathCount, subsetOpenApiDocument.Paths.Count); } - + [Theory] [InlineData("UtilityFiles/appsettingstest.json")] [InlineData(null)] @@ -122,7 +118,7 @@ public void ShowCommandGeneratesMermaidDiagramAsHtml() var output = reader.ReadToEnd(); Assert.Contains("graph LR", output); } - + [Fact] public async Task ShowCommandGeneratesMermaidMarkdownFileWithMermaidDiagram() @@ -190,18 +186,18 @@ public async Task TransformCommandConvertsOpenApi() { var fileinfo = new FileInfo("sample.json"); // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocument("UtilityFiles\\SampleOpenApi.yml",null, null, fileinfo, true, null, null, null,false,null,false,false,null,null,null,new Logger(new LoggerFactory()), new CancellationToken()); + await OpenApiService.TransformOpenApiDocument("UtilityFiles\\SampleOpenApi.yml", null, null, fileinfo, true, null, null, null, false, null, false, false, null, null, null, null, new Logger(new LoggerFactory()), new CancellationToken()); var output = File.ReadAllText("sample.json"); Assert.NotEmpty(output); } - + [Fact] public async Task TransformCommandConvertsOpenApiWithDefaultOutputname() { // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocument("UtilityFiles\\SampleOpenApi.yml", null, null, null, true, null, null, null, false, null, false, false, null, null, null, new Logger(new LoggerFactory()), new CancellationToken()); + await OpenApiService.TransformOpenApiDocument("UtilityFiles\\SampleOpenApi.yml", null, null, null, true, null, null, null, false, null, false, false, null, null, null, null, new Logger(new LoggerFactory()), new CancellationToken()); var output = File.ReadAllText("output.yml"); Assert.NotEmpty(output); @@ -211,7 +207,7 @@ public async Task TransformCommandConvertsOpenApiWithDefaultOutputname() public async Task TransformCommandConvertsCsdlWithDefaultOutputname() { // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocument(null, "UtilityFiles\\Todo.xml", null, null, true, null, null, null, false, null, false, false, null, null, null, new Logger(new LoggerFactory()), new CancellationToken()); + await OpenApiService.TransformOpenApiDocument(null, "UtilityFiles\\Todo.xml", null, null, true, null, null, null, false, null, false, false, null, null, null, null, new Logger(new LoggerFactory()), new CancellationToken()); var output = File.ReadAllText("output.yml"); Assert.NotEmpty(output); @@ -221,7 +217,7 @@ public async Task TransformCommandConvertsCsdlWithDefaultOutputname() public async Task TransformCommandConvertsOpenApiWithDefaultOutputnameAndSwitchFormat() { // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocument("UtilityFiles\\SampleOpenApi.yml", null, null, null, true, "3.0", null, OpenApiFormat.Yaml, false, null, false, false, null, null, null, new Logger(new LoggerFactory()), new CancellationToken()); + await OpenApiService.TransformOpenApiDocument("UtilityFiles\\SampleOpenApi.yml", null, null, null, true, "3.0", null, OpenApiFormat.Yaml, false, null, false, false, null, null, null, null, new Logger(new LoggerFactory()), new CancellationToken()); var output = File.ReadAllText("output.yml"); Assert.NotEmpty(output); @@ -231,7 +227,7 @@ public async Task TransformCommandConvertsOpenApiWithDefaultOutputnameAndSwitchF public async Task ThrowTransformCommandIfOpenApiAndCsdlAreEmpty() { await Assert.ThrowsAsync(async () => - await OpenApiService.TransformOpenApiDocument(null, null, null, null, true, null, null, null, false, null, false, false, null, null, null, new Logger(new LoggerFactory()), new CancellationToken())); + await OpenApiService.TransformOpenApiDocument(null, null, null, null, true, null, null, null, false, null, false, false, null, null, null, null, new Logger(new LoggerFactory()), new CancellationToken())); } @@ -239,7 +235,7 @@ await Assert.ThrowsAsync(async () => public void InvokeTransformCommand() { var rootCommand = Program.CreateRootCommand(); - var args = new string[] { "transform", "-d", ".\\UtilityFiles\\SampleOpenApi.yml", "-o", "sample.json","--co" }; + var args = new string[] { "transform", "-d", ".\\UtilityFiles\\SampleOpenApi.yml", "-o", "sample.json", "--co" }; var parseResult = rootCommand.Parse(args); var handler = rootCommand.Subcommands.Where(c => c.Name == "transform").First().Handler; var context = new InvocationContext(parseResult); From faf29de50d861f33c297e1f78229d454b3554bca Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 9 May 2023 15:02:49 +0300 Subject: [PATCH 05/50] Clean up Readme file --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 07f4553c9..60cc5e2f7 100644 --- a/README.md +++ b/README.md @@ -91,15 +91,15 @@ var outputString = openApiDocument.Serialize(OpenApiSpecVersion.OpenApi2_0, Open ``` -# Validating/Testing OpenApi descriptions +# Validating/Testing OpenAPI descriptions In order to test the validity of an OpenApi document, we avail the following tools: - [Microsoft.OpenApi.Hidi](https://www.nuget.org/packages/Microsoft.OpenApi.Hidi) - A commandline tool for validating and transforming OpenApi descriptions. [Installation guidelines and documentation](https://github.com/microsoft/OpenAPI.NET/blob/vnext/src/Microsoft.OpenApi.Hidi/readme.md) + A commandline tool for validating and transforming OpenAPI descriptions. [Installation guidelines and documentation](https://github.com/microsoft/OpenAPI.NET/blob/vnext/src/Microsoft.OpenApi.Hidi/readme.md) - Microsoft.OpenApi.Workbench - A workbench tool consisting of a GUI where you can test and convert OpenApi descriptions in both Json and Yaml from v2-->v3 and vice versa. + A workbench tool consisting of a GUI where you can test and convert OpenAPI descriptions in both JSON and YAML from v2-->v3 and vice versa. #### Installation guidelines: 1. Clone the repo locally by running this command: @@ -111,7 +111,7 @@ In order to test the validity of an OpenApi document, we avail the following too - 5. Copy and paste your OpenApi descriptions in the **Input Content** window or paste the path to the descriptions file in the **Input File** textbox and click on `convert` to render the results. + 5. Copy and paste your OpenAPI descriptions in the **Input Content** window or paste the path to the descriptions file in the **Input File** textbox and click on `Convert` to render the results. # Build Status From f4dee067824b3b8a18c526bf1e09f20fdd2bb548 Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Tue, 9 May 2023 17:01:26 -0700 Subject: [PATCH 06/50] Use strongly typed config and options --- .../Extensions/CommandExtensions.cs | 19 +++ .../Handlers/ShowCommandHandler.cs | 25 ++-- .../Handlers/TransformCommandHandler.cs | 49 ++----- .../Handlers/ValidateCommandHandler.cs | 19 +-- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 88 ++++-------- .../Options/CommandOptions.cs | 93 ++++++++++++ .../Options/FilterOptions.cs | 12 ++ .../Options/HidiOptions.cs | 62 ++++++++ src/Microsoft.OpenApi.Hidi/Program.cs | 134 ++---------------- .../Utilities/SettingsUtilities.cs | 32 +++++ .../Services/OpenApiServiceTests.cs | 58 ++++++-- 11 files changed, 335 insertions(+), 256 deletions(-) create mode 100644 src/Microsoft.OpenApi.Hidi/Extensions/CommandExtensions.cs create mode 100644 src/Microsoft.OpenApi.Hidi/Options/CommandOptions.cs create mode 100644 src/Microsoft.OpenApi.Hidi/Options/FilterOptions.cs create mode 100644 src/Microsoft.OpenApi.Hidi/Options/HidiOptions.cs create mode 100644 src/Microsoft.OpenApi.Hidi/Utilities/SettingsUtilities.cs diff --git a/src/Microsoft.OpenApi.Hidi/Extensions/CommandExtensions.cs b/src/Microsoft.OpenApi.Hidi/Extensions/CommandExtensions.cs new file mode 100644 index 000000000..9d5077432 --- /dev/null +++ b/src/Microsoft.OpenApi.Hidi/Extensions/CommandExtensions.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.CommandLine; + +namespace Microsoft.OpenApi.Hidi.Extensions +{ + internal static class CommandExtensions + { + public static void AddOptions(this Command command, IReadOnlyList