diff --git a/install-tool.ps1 b/install-tool.ps1 new file mode 100644 index 000000000..0e6521110 --- /dev/null +++ b/install-tool.ps1 @@ -0,0 +1,7 @@ +$latest = Get-ChildItem .\artifacts\ Microsoft.OpenApi.Tool* | select-object -Last 1 +$version = $latest.Name.Split(".")[3..5] | join-string -Separator "." + +if (Test-Path -Path ./artifacts/openapi-parser.exe) { + dotnet tool uninstall --tool-path artifacts Microsoft.OpenApi.Tool +} +dotnet tool install --tool-path artifacts --add-source .\artifacts\ --version $version Microsoft.OpenApi.Tool \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Tool/Microsoft.OpenApi.Tool.csproj b/src/Microsoft.OpenApi.Tool/Microsoft.OpenApi.Tool.csproj index 5845ce4f9..40e46f1a4 100644 --- a/src/Microsoft.OpenApi.Tool/Microsoft.OpenApi.Tool.csproj +++ b/src/Microsoft.OpenApi.Tool/Microsoft.OpenApi.Tool.csproj @@ -4,13 +4,13 @@ Exe netcoreapp3.1 true - openapi + openapi-parser ./../../artifacts 1.3.0-preview - + @@ -19,7 +19,7 @@ - + diff --git a/src/Microsoft.OpenApi.Tool/OpenApiService.cs b/src/Microsoft.OpenApi.Tool/OpenApiService.cs index fd42da1a1..c52c08941 100644 --- a/src/Microsoft.OpenApi.Tool/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Tool/OpenApiService.cs @@ -2,10 +2,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; +using System.Net.Http; using System.Text; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations; using Microsoft.OpenApi.Writers; @@ -14,48 +17,57 @@ namespace Microsoft.OpenApi.Tool static class OpenApiService { public static void ProcessOpenApiDocument( - FileInfo input, + string input, FileInfo output, OpenApiSpecVersion version, OpenApiFormat format, bool inline, bool resolveExternal) { + if (input == null) + { + throw new ArgumentNullException("input"); + } + + var stream = GetStream(input); + OpenApiDocument document; - using (Stream stream = input.OpenRead()) + + var result = new OpenApiStreamReader(new OpenApiReaderSettings + { + ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, + RuleSet = ValidationRuleSet.GetDefaultRuleSet() + } + ).ReadAsync(stream).GetAwaiter().GetResult(); + + document = result.OpenApiDocument; + var context = result.OpenApiDiagnostic; + + if (context.Errors.Count != 0) { + var errorReport = new StringBuilder(); - document = new OpenApiStreamReader(new OpenApiReaderSettings + foreach (var error in context.Errors) { - ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, - RuleSet = ValidationRuleSet.GetDefaultRuleSet() + errorReport.AppendLine(error.ToString()); } - ).Read(stream, out var context); - if (context.Errors.Count != 0) - { - var errorReport = new StringBuilder(); - - foreach (var error in context.Errors) - { - errorReport.AppendLine(error.ToString()); - } - throw new ArgumentException(String.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())); - } + throw new ArgumentException(String.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())); } using (var outputStream = output?.Create()) { TextWriter textWriter; - if (outputStream!=null) + if (outputStream != null) { textWriter = new StreamWriter(outputStream); - } else + } + else { textWriter = Console.Out; } - + var settings = new OpenApiWriterSettings() { ReferenceInline = inline == true ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences @@ -72,11 +84,67 @@ public static void ProcessOpenApiDocument( default: throw new ArgumentException("Unknown format"); } - - document.Serialize(writer,version ); + + document.Serialize(writer, version); textWriter.Flush(); } } -} + + private static Stream GetStream(string input) + { + Stream stream; + if (input.StartsWith("http")) + { + var httpClient = new HttpClient(new HttpClientHandler() + { + SslProtocols = System.Security.Authentication.SslProtocols.Tls12, + }) + { + DefaultRequestVersion = HttpVersion.Version20 + }; + stream = httpClient.GetStreamAsync(input).Result; + } + else + { + var fileInput = new FileInfo(input); + stream = fileInput.OpenRead(); + } + + return stream; + } + + internal static void ValidateOpenApiDocument(string input) + { + if (input == null) + { + throw new ArgumentNullException("input"); + } + + var stream = GetStream(input); + + OpenApiDocument document; + + document = new OpenApiStreamReader(new OpenApiReaderSettings + { + //ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, + RuleSet = ValidationRuleSet.GetDefaultRuleSet() + } + ).Read(stream, out var context); + + if (context.Errors.Count != 0) + { + foreach (var error in context.Errors) + { + Console.WriteLine(error.ToString()); + } + } + + var statsVisitor = new StatsVisitor(); + var walker = new OpenApiWalker(statsVisitor); + walker.Walk(document); + + Console.WriteLine(statsVisitor.GetStatisticsReport()); + } + } } diff --git a/src/Microsoft.OpenApi.Tool/Program.cs b/src/Microsoft.OpenApi.Tool/Program.cs index 2c95b954f..446e2829a 100644 --- a/src/Microsoft.OpenApi.Tool/Program.cs +++ b/src/Microsoft.OpenApi.Tool/Program.cs @@ -9,23 +9,54 @@ namespace Microsoft.OpenApi.Tool { class Program { - static async Task Main(string[] args) + static async Task OldMain(string[] args) { + var command = new RootCommand { - new Option("--input") { Argument = new Argument() }, - new Option("--output") { Argument = new Argument() }, - new Option("--version") { Argument = new Argument() }, - new Option("--format") { Argument = new Argument() }, - new Option("--inline") { Argument = new Argument() }, - new Option("--resolveExternal") { Argument = new Argument() } + new Option("--input", "Input OpenAPI description file path or URL", typeof(string) ), + new Option("--output","Output OpenAPI description file", typeof(FileInfo), arity: ArgumentArity.ZeroOrOne), + new Option("--version", "OpenAPI specification version", typeof(OpenApiSpecVersion)), + new Option("--format", "File format",typeof(OpenApiFormat) ), + new Option("--inline", "Inline $ref instances", typeof(bool) ), + new Option("--resolveExternal","Resolve external $refs", typeof(bool)) }; - command.Handler = CommandHandler.Create( + command.Handler = CommandHandler.Create( OpenApiService.ProcessOpenApiDocument); // Parse the incoming args and invoke the handler return await command.InvokeAsync(args); } + + static async Task Main(string[] args) + { + var rootCommand = new RootCommand() { + }; + + var validateCommand = new Command("validate") + { + new Option("--input", "Input OpenAPI description file path or URL", typeof(string) ) + }; + validateCommand.Handler = CommandHandler.Create(OpenApiService.ValidateOpenApiDocument); + + var transformCommand = new Command("transform") + { + new Option("--input", "Input OpenAPI description file path or URL", typeof(string) ), + new Option("--output","Output OpenAPI description file", typeof(FileInfo), arity: ArgumentArity.ZeroOrOne), + new Option("--version", "OpenAPI specification version", typeof(OpenApiSpecVersion)), + new Option("--format", "File format",typeof(OpenApiFormat) ), + new Option("--inline", "Inline $ref instances", typeof(bool) ), + new Option("--resolveExternal","Resolve external $refs", typeof(bool)) + }; + transformCommand.Handler = CommandHandler.Create( + OpenApiService.ProcessOpenApiDocument); + + rootCommand.Add(transformCommand); + rootCommand.Add(validateCommand); + + // Parse the incoming args and invoke the handler + return await rootCommand.InvokeAsync(args); + } } } diff --git a/src/Microsoft.OpenApi.Tool/StatsVisitor.cs b/src/Microsoft.OpenApi.Tool/StatsVisitor.cs new file mode 100644 index 000000000..3c633d860 --- /dev/null +++ b/src/Microsoft.OpenApi.Tool/StatsVisitor.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; + +namespace Microsoft.OpenApi.Tool +{ + internal class StatsVisitor : OpenApiVisitorBase + { + public int ParameterCount { get; set; } = 0; + + public override void Visit(OpenApiParameter parameter) + { + ParameterCount++; + } + + public int SchemaCount { get; set; } = 0; + + public override void Visit(OpenApiSchema schema) + { + SchemaCount++; + } + + public int HeaderCount { get; set; } = 0; + + public override void Visit(IDictionary headers) + { + HeaderCount++; + } + + public int PathItemCount { get; set; } = 0; + + public override void Visit(OpenApiPathItem pathItem) + { + PathItemCount++; + } + + public int RequestBodyCount { get; set; } = 0; + + public override void Visit(OpenApiRequestBody requestBody) + { + RequestBodyCount++; + } + + public int ResponseCount { get; set; } = 0; + + public override void Visit(OpenApiResponses response) + { + ResponseCount++; + } + + public int OperationCount { get; set; } = 0; + + public override void Visit(OpenApiOperation operation) + { + OperationCount++; + } + + public int LinkCount { get; set; } = 0; + + public override void Visit(OpenApiLink operation) + { + LinkCount++; + } + + public int CallbackCount { get; set; } = 0; + + public override void Visit(OpenApiCallback callback) + { + CallbackCount++; + } + + public string GetStatisticsReport() + { + return $"Path Items: {PathItemCount}" + Environment.NewLine + + $"Operations: {OperationCount}" + Environment.NewLine + + $"Parameters: {ParameterCount}" + Environment.NewLine + + $"Request Bodies: {RequestBodyCount}" + Environment.NewLine + + $"Responses: {ResponseCount}" + Environment.NewLine + + $"Links: {LinkCount}" + Environment.NewLine + + $"Callbacks: {CallbackCount}" + Environment.NewLine + + $"Schemas: {SchemaCount}" + Environment.NewLine; + } + } +}