From 45968f3263f39206bc31dbf9f8488e1991a380cf Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 15 Aug 2021 13:34:03 -0400 Subject: [PATCH 01/49] Can validate external references now --- .../OpenApiYamlDocumentReader.cs | 6 +- .../Services/DefaultStreamLoader.cs | 21 ++++-- .../V3/OpenApiV3VersionService.cs | 20 +++++- src/Microsoft.OpenApi.Tool/OpenApiService.cs | 71 ++++++++++++++----- src/Microsoft.OpenApi.Tool/Program.cs | 5 +- .../Models/OpenApiReference.cs | 18 +++-- .../OpenApiWorkspaceStreamTests.cs | 5 +- .../Models/OpenApiReferenceTests.cs | 54 ++++++++++---- 8 files changed, 150 insertions(+), 50 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs index bb00fb370..63b78ccba 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs @@ -88,7 +88,7 @@ public async Task ReadAsync(YamlDocument input) // Parse the OpenAPI Document document = context.Parse(input); - await ResolveReferencesAsync(diagnostic, document); + await ResolveReferencesAsync(diagnostic, document, _settings.BaseUrl); } catch (OpenApiException ex) { @@ -133,7 +133,7 @@ private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument doc } } - private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiDocument document) + private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiDocument document, Uri baseUrl) { List errors = new List(); @@ -146,7 +146,7 @@ private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiD var openApiWorkSpace = new OpenApiWorkspace(); // Load this root document into the workspace - var streamLoader = new DefaultStreamLoader(); + var streamLoader = new DefaultStreamLoader(baseUrl); var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings); await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document); diff --git a/src/Microsoft.OpenApi.Readers/Services/DefaultStreamLoader.cs b/src/Microsoft.OpenApi.Readers/Services/DefaultStreamLoader.cs index 4659db711..09f16632b 100644 --- a/src/Microsoft.OpenApi.Readers/Services/DefaultStreamLoader.cs +++ b/src/Microsoft.OpenApi.Readers/Services/DefaultStreamLoader.cs @@ -14,18 +14,25 @@ namespace Microsoft.OpenApi.Readers.Services /// internal class DefaultStreamLoader : IStreamLoader { + private readonly Uri baseUrl; private HttpClient _httpClient = new HttpClient(); + + public DefaultStreamLoader(Uri baseUrl) + { + this.baseUrl = baseUrl; + } + public Stream Load(Uri uri) { + var absoluteUri = new Uri(baseUrl, uri); switch (uri.Scheme) { case "file": - return File.OpenRead(uri.AbsolutePath); + return File.OpenRead(absoluteUri.AbsolutePath); case "http": case "https": - return _httpClient.GetStreamAsync(uri).GetAwaiter().GetResult(); - + return _httpClient.GetStreamAsync(absoluteUri).GetAwaiter().GetResult(); default: throw new ArgumentException("Unsupported scheme"); } @@ -33,13 +40,15 @@ public Stream Load(Uri uri) public async Task LoadAsync(Uri uri) { - switch (uri.Scheme) + var absoluteUri = new Uri(baseUrl, uri); + + switch (absoluteUri.Scheme) { case "file": - return File.OpenRead(uri.AbsolutePath); + return File.OpenRead(absoluteUri.AbsolutePath); case "http": case "https": - return await _httpClient.GetStreamAsync(uri); + return await _httpClient.GetStreamAsync(absoluteUri); default: throw new ArgumentException("Unsupported scheme"); } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs index 2663b6a3b..1a0b1bae2 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs @@ -53,6 +53,8 @@ internal class OpenApiV3VersionService : IOpenApiVersionService /// /// Parse the string to a object. /// + /// The URL of the reference + /// The type of object refefenced based on the context of the reference public OpenApiReference ConvertToOpenApiReference( string reference, ReferenceType? type) @@ -95,8 +97,22 @@ public OpenApiReference ConvertToOpenApiReference( // $ref: externalSource.yaml#/Pet if (id.StartsWith("/components/")) { - id = segments[1].Split('/')[3]; - } + var localSegments = segments[1].Split('/'); + var referencedType = localSegments[2].GetEnumFromDisplayName(); + if (type == null) + { + type = referencedType; + } + else + { + if (type != referencedType) + { + throw new OpenApiException("Referenced type mismatch"); + } + } + id = localSegments[3]; + } + return new OpenApiReference { ExternalResource = segments[0], diff --git a/src/Microsoft.OpenApi.Tool/OpenApiService.cs b/src/Microsoft.OpenApi.Tool/OpenApiService.cs index c52c08941..3b3afcbd0 100644 --- a/src/Microsoft.OpenApi.Tool/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Tool/OpenApiService.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Text; +using System.Threading.Tasks; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; @@ -29,14 +30,16 @@ public static void ProcessOpenApiDocument( throw new ArgumentNullException("input"); } - var stream = GetStream(input); + var inputUrl = GetInputUrl(input); + var stream = GetStream(inputUrl); OpenApiDocument document; var result = new OpenApiStreamReader(new OpenApiReaderSettings { ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, - RuleSet = ValidationRuleSet.GetDefaultRuleSet() + RuleSet = ValidationRuleSet.GetDefaultRuleSet(), + BaseUrl = new Uri(inputUrl.AbsoluteUri) } ).ReadAsync(stream).GetAwaiter().GetResult(); @@ -91,10 +94,22 @@ public static void ProcessOpenApiDocument( } } - private static Stream GetStream(string input) + private static Uri GetInputUrl(string input) { - Stream stream; if (input.StartsWith("http")) + { + return new Uri(input); + } + else + { + return new Uri("file://" + Path.GetFullPath(input)); + } + } + + private static Stream GetStream(Uri input) + { + Stream stream; + if (input.Scheme == "http" || input.Scheme == "https") { var httpClient = new HttpClient(new HttpClientHandler() { @@ -105,32 +120,40 @@ private static Stream GetStream(string input) }; stream = httpClient.GetStreamAsync(input).Result; } - else + else if (input.Scheme == "file") { - var fileInput = new FileInfo(input); + var fileInput = new FileInfo(input.AbsolutePath); stream = fileInput.OpenRead(); + } + else + { + throw new ArgumentException("Unrecognized exception"); } return stream; } - internal static void ValidateOpenApiDocument(string input) + internal static async Task ValidateOpenApiDocument(string input, bool resolveExternal) { if (input == null) { throw new ArgumentNullException("input"); } - - var stream = GetStream(input); + var inputUrl = GetInputUrl(input); + var stream = GetStream(GetInputUrl(input)); OpenApiDocument document; - document = new OpenApiStreamReader(new OpenApiReaderSettings + var result = await new OpenApiStreamReader(new OpenApiReaderSettings { - //ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, - RuleSet = ValidationRuleSet.GetDefaultRuleSet() + ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, + RuleSet = ValidationRuleSet.GetDefaultRuleSet(), + BaseUrl = new Uri(inputUrl.AbsoluteUri) } - ).Read(stream, out var context); + ).ReadAsync(stream); + + document = result.OpenApiDocument; + var context = result.OpenApiDiagnostic; if (context.Errors.Count != 0) { @@ -140,11 +163,25 @@ internal static void ValidateOpenApiDocument(string input) } } - var statsVisitor = new StatsVisitor(); - var walker = new OpenApiWalker(statsVisitor); - walker.Walk(document); + if (document.Workspace == null) { + var statsVisitor = new StatsVisitor(); + var walker = new OpenApiWalker(statsVisitor); + walker.Walk(document); + Console.WriteLine(statsVisitor.GetStatisticsReport()); + } + else + { + foreach (var memberDocument in document.Workspace.Documents) + { + Console.WriteLine("Stats for " + memberDocument.Info.Title); + var statsVisitor = new StatsVisitor(); + var walker = new OpenApiWalker(statsVisitor); + walker.Walk(memberDocument); + Console.WriteLine(statsVisitor.GetStatisticsReport()); + } + } - Console.WriteLine(statsVisitor.GetStatisticsReport()); + } } } diff --git a/src/Microsoft.OpenApi.Tool/Program.cs b/src/Microsoft.OpenApi.Tool/Program.cs index 446e2829a..0ae4cb782 100644 --- a/src/Microsoft.OpenApi.Tool/Program.cs +++ b/src/Microsoft.OpenApi.Tool/Program.cs @@ -36,9 +36,10 @@ static async Task Main(string[] args) var validateCommand = new Command("validate") { - new Option("--input", "Input OpenAPI description file path or URL", typeof(string) ) + new Option("--input", "Input OpenAPI description file path or URL", typeof(string) ), + new Option("--resolveExternal","Resolve external $refs", typeof(bool)) }; - validateCommand.Handler = CommandHandler.Create(OpenApiService.ValidateOpenApiDocument); + validateCommand.Handler = CommandHandler.Create(OpenApiService.ValidateOpenApiDocument); var transformCommand = new Command("transform") { diff --git a/src/Microsoft.OpenApi/Models/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs index 2c8f738ca..cbe64d3e6 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReference.cs @@ -54,7 +54,7 @@ public string ReferenceV3 { if (IsExternal) { - return GetExternalReference(); + return GetExternalReferenceV3(); } if (!Type.HasValue) @@ -85,7 +85,7 @@ public string ReferenceV2 { if (IsExternal) { - return GetExternalReference(); + return GetExternalReferenceV2(); } if (!Type.HasValue) @@ -171,11 +171,21 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteEndObject(); } - private string GetExternalReference() + private string GetExternalReferenceV3() { if (Id != null) { - return ExternalResource + "#/" + Id; + return ExternalResource + "#/components/" + Type.GetDisplayName() + "/"+ Id; + } + + return ExternalResource; + } + + private string GetExternalReferenceV2() + { + if (Id != null) + { + return ExternalResource + "#/" + GetReferenceTypeNameAsV2((ReferenceType)Type) + "/" + Id; } return ExternalResource; diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index d684144cb..4a35301c6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -19,7 +19,7 @@ public class OpenApiWorkspaceStreamTests // Use OpenApiWorkspace to load a document and a referenced document [Fact] - public async Task LoadDocumentIntoWorkspace() + public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoWorkspace() { // Create a reader that will resolve all references var reader = new OpenApiStreamReader(new OpenApiReaderSettings() @@ -48,7 +48,7 @@ public async Task LoadDocumentIntoWorkspace() [Fact] - public async Task LoadTodoDocumentIntoWorkspace() + public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWorkspace() { // Create a reader that will resolve all references var reader = new OpenApiStreamReader(new OpenApiReaderSettings() @@ -65,6 +65,7 @@ public async Task LoadTodoDocumentIntoWorkspace() Assert.NotNull(result.OpenApiDocument.Workspace); Assert.True(result.OpenApiDocument.Workspace.Contains("TodoComponents.yaml")); + var referencedSchema = result.OpenApiDocument .Paths["/todos"] .Operations[OperationType.Get] diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs index c251814db..b9edd2a32 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs @@ -37,30 +37,54 @@ public void SettingInternalReferenceForComponentsStyleReferenceShouldSucceed( } [Theory] - [InlineData("Pet.json", "Pet.json", null)] - [InlineData("Pet.yaml", "Pet.yaml", null)] - [InlineData("abc", "abc", null)] - [InlineData("Pet.json#/Pet", "Pet.json", "Pet")] - [InlineData("Pet.yaml#/Pet", "Pet.yaml", "Pet")] - [InlineData("abc#/Pet", "abc", "Pet")] - public void SettingExternalReferenceShouldSucceed(string expected, string externalResource, string id) + [InlineData("Pet.json", "Pet.json", null, null)] + [InlineData("Pet.yaml", "Pet.yaml", null, null)] + [InlineData("abc", "abc", null, null)] + [InlineData("Pet.json#/components/schemas/Pet", "Pet.json", "Pet", ReferenceType.Schema)] + [InlineData("Pet.yaml#/components/schemas/Pet", "Pet.yaml", "Pet", ReferenceType.Schema)] + [InlineData("abc#/components/schemas/Pet", "abc", "Pet", ReferenceType.Schema)] + public void SettingExternalReferenceV3ShouldSucceed(string expected, string externalResource, string id, ReferenceType? type) { // Arrange & Act var reference = new OpenApiReference { ExternalResource = externalResource, + Type = type, Id = id }; // Assert reference.ExternalResource.Should().Be(externalResource); - reference.Type.Should().BeNull(); reference.Id.Should().Be(id); reference.ReferenceV3.Should().Be(expected); + } + + [Theory] + [InlineData("Pet.json", "Pet.json", null, null)] + [InlineData("Pet.yaml", "Pet.yaml", null, null)] + [InlineData("abc", "abc", null, null)] + [InlineData("Pet.json#/definitions/Pet", "Pet.json", "Pet", ReferenceType.Schema)] + [InlineData("Pet.yaml#/definitions/Pet", "Pet.yaml", "Pet", ReferenceType.Schema)] + [InlineData("abc#/definitions/Pet", "abc", "Pet", ReferenceType.Schema)] + public void SettingExternalReferenceV2ShouldSucceed(string expected, string externalResource, string id, ReferenceType? type) + { + // Arrange & Act + var reference = new OpenApiReference + { + ExternalResource = externalResource, + Type = type, + Id = id + }; + + // Assert + reference.ExternalResource.Should().Be(externalResource); + reference.Id.Should().Be(id); + reference.ReferenceV2.Should().Be(expected); } + [Fact] public void SerializeSchemaReferenceAsJsonV3Works() { @@ -144,11 +168,12 @@ public void SerializeExternalReferenceAsJsonV2Works() var reference = new OpenApiReference { ExternalResource = "main.json", + Type= ReferenceType.Schema, Id = "Pets" }; var expected = @"{ - ""$ref"": ""main.json#/Pets"" + ""$ref"": ""main.json#/definitions/Pets"" }"; // Act @@ -167,9 +192,10 @@ public void SerializeExternalReferenceAsYamlV2Works() var reference = new OpenApiReference { ExternalResource = "main.json", + Type = ReferenceType.Schema, Id = "Pets" }; - var expected = @"$ref: main.json#/Pets"; + var expected = @"$ref: main.json#/definitions/Pets"; // Act var actual = reference.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); @@ -182,10 +208,10 @@ public void SerializeExternalReferenceAsYamlV2Works() public void SerializeExternalReferenceAsJsonV3Works() { // Arrange - var reference = new OpenApiReference { ExternalResource = "main.json", Id = "Pets" }; + var reference = new OpenApiReference { ExternalResource = "main.json", Type = ReferenceType.Schema,Id = "Pets" }; var expected = @"{ - ""$ref"": ""main.json#/Pets"" + ""$ref"": ""main.json#/components/schemas/Pets"" }"; // Act @@ -201,8 +227,8 @@ public void SerializeExternalReferenceAsJsonV3Works() public void SerializeExternalReferenceAsYamlV3Works() { // Arrange - var reference = new OpenApiReference { ExternalResource = "main.json", Id = "Pets" }; - var expected = @"$ref: main.json#/Pets"; + var reference = new OpenApiReference { ExternalResource = "main.json", Type = ReferenceType.Schema, Id = "Pets" }; + var expected = @"$ref: main.json#/components/schemas/Pets"; // Act var actual = reference.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); From 89cc609f419df85a48008391e0f1dbc671967fed Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 31 Oct 2021 21:30:09 -0400 Subject: [PATCH 02/49] Fixed inlining settings --- .../OpenApiStreamReader.cs | 7 ++ .../EnumBindingSourceExtension.cs | 53 ++++++++++ src/Microsoft.OpenApi.Workbench/MainModel.cs | 98 +++++++++++++++---- .../MainWindow.xaml | 5 +- .../MainWindow.xaml.cs | 11 ++- .../Microsoft.OpenApi.Workbench.csproj | 1 + .../Models/OpenApiCallback.cs | 2 +- .../Models/OpenApiComponents.cs | 2 +- .../Models/OpenApiDocument.cs | 2 +- .../Models/OpenApiExample.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiHeader.cs | 4 +- src/Microsoft.OpenApi/Models/OpenApiLink.cs | 2 +- .../Models/OpenApiParameter.cs | 4 +- .../Models/OpenApiPathItem.cs | 4 +- .../Models/OpenApiResponse.cs | 4 +- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 4 +- .../Writers/OpenApiWriterSettings.cs | 54 +++++++++- .../OpenApiWorkspaceStreamTests.cs | 6 +- .../PublicApi/PublicApi.approved.txt | 4 + .../Writers/OpenApiYamlWriterTests.cs | 8 +- 20 files changed, 229 insertions(+), 48 deletions(-) create mode 100644 src/Microsoft.OpenApi.Workbench/EnumBindingSourceExtension.cs diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index cab5d1a83..7f721cadd 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.IO; using System.Threading.Tasks; using Microsoft.OpenApi.Interfaces; @@ -23,6 +24,12 @@ public class OpenApiStreamReader : IOpenApiReader public OpenApiStreamReader(OpenApiReaderSettings settings = null) { _settings = settings ?? new OpenApiReaderSettings(); + + if(_settings.ReferenceResolution == ReferenceResolutionSetting.ResolveAllReferences + && _settings.BaseUrl == null) + { + throw new ArgumentException("BaseUrl must be provided to resolve external references."); + } } /// diff --git a/src/Microsoft.OpenApi.Workbench/EnumBindingSourceExtension.cs b/src/Microsoft.OpenApi.Workbench/EnumBindingSourceExtension.cs new file mode 100644 index 000000000..d1f8bd798 --- /dev/null +++ b/src/Microsoft.OpenApi.Workbench/EnumBindingSourceExtension.cs @@ -0,0 +1,53 @@ +// Thanks Brian! https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/ +using System; +using System.Windows.Markup; + +namespace Microsoft.OpenApi.Workbench +{ + public class EnumBindingSourceExtension : MarkupExtension + { + private Type _enumType; + public Type EnumType + { + get { return this._enumType; } + set + { + if (value != this._enumType) + { + if (null != value) + { + Type enumType = Nullable.GetUnderlyingType(value) ?? value; + if (!enumType.IsEnum) + throw new ArgumentException("Type must be for an Enum."); + } + + this._enumType = value; + } + } + } + + public EnumBindingSourceExtension() { } + + public EnumBindingSourceExtension(Type enumType) + { + this.EnumType = enumType; + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + if (null == this._enumType) + throw new InvalidOperationException("The EnumType must be specified."); + + Type actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType; + Array enumValues = Enum.GetValues(actualEnumType); + + if (actualEnumType == this._enumType) + return enumValues; + + Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1); + enumValues.CopyTo(tempArray, 1); + return tempArray; + } + } + +} diff --git a/src/Microsoft.OpenApi.Workbench/MainModel.cs b/src/Microsoft.OpenApi.Workbench/MainModel.cs index 6304b7f23..70074736b 100644 --- a/src/Microsoft.OpenApi.Workbench/MainModel.cs +++ b/src/Microsoft.OpenApi.Workbench/MainModel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -6,7 +6,9 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Net.Http; using System.Text; +using System.Threading.Tasks; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; @@ -23,6 +25,11 @@ public class MainModel : INotifyPropertyChanged { private string _input; + private bool _inlineLocal = false; + private bool _inlineExternal = false; + + private bool _resolveExternal = false; + private string _inputFile; private string _output; @@ -33,11 +40,7 @@ public class MainModel : INotifyPropertyChanged private string _renderTime; - /// - /// Default format. - /// - private bool _Inline = false; - + /// /// Default format. /// @@ -48,6 +51,9 @@ public class MainModel : INotifyPropertyChanged /// private OpenApiSpecVersion _version = OpenApiSpecVersion.OpenApi3_0; + + private HttpClient _httpClient = new HttpClient(); + public string Input { get => _input; @@ -58,6 +64,16 @@ public string Input } } + public bool ResolveExternal + { + get => _resolveExternal; + set + { + _resolveExternal = value; + OnPropertyChanged(nameof(ResolveExternal)); + } + } + public string InputFile { get => _inputFile; @@ -67,7 +83,6 @@ public string InputFile OnPropertyChanged(nameof(InputFile)); } } - public string Output { get => _output; @@ -119,13 +134,23 @@ public OpenApiFormat Format } } - public bool Inline + public bool InlineLocal + { + get => _inlineLocal; + set + { + _inlineLocal = value; + OnPropertyChanged(nameof(InlineLocal)); + } + } + + public bool InlineExternal { - get => _Inline; + get => _inlineExternal; set { - _Inline = value; - OnPropertyChanged(nameof(Inline)); + _inlineExternal = value; + OnPropertyChanged(nameof(InlineExternal)); } } @@ -180,17 +205,28 @@ protected void OnPropertyChanged(string propertyName) /// The core method of the class. /// Runs the parsing and serializing. /// - internal void ParseDocument() + internal async Task ParseDocument() { + Stream stream = null; try { - Stream stream; - if (!String.IsNullOrWhiteSpace(_inputFile)) + if (!string.IsNullOrWhiteSpace(_inputFile)) { - stream = new FileStream(_inputFile, FileMode.Open); + if (_inputFile.StartsWith("http")) + { + stream = await _httpClient.GetStreamAsync(_inputFile); + } + else + { + stream = new FileStream(_inputFile, FileMode.Open); + } } else { + if (ResolveExternal) + { + throw new ArgumentException("Input file must be used to resolve external references"); + } stream = CreateStream(_input); } @@ -198,12 +234,27 @@ internal void ParseDocument() var stopwatch = new Stopwatch(); stopwatch.Start(); - var document = new OpenApiStreamReader(new OpenApiReaderSettings + var settings = new OpenApiReaderSettings { - ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences, + ReferenceResolution = ResolveExternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, RuleSet = ValidationRuleSet.GetDefaultRuleSet() + }; + if (ResolveExternal) + { + if (_inputFile.StartsWith("http")) + { + settings.BaseUrl = new Uri(_inputFile); + } + else + { + settings.BaseUrl = new Uri("file://" + Path.GetDirectoryName(_inputFile) + "/"); + } } - ).Read(stream, out var context); + var readResult = await new OpenApiStreamReader(settings + ).ReadAsync(stream); + var document = readResult.OpenApiDocument; + var context = readResult.OpenApiDiagnostic; + stopwatch.Stop(); ParseTime = $"{stopwatch.ElapsedMilliseconds} ms"; @@ -241,6 +292,14 @@ internal void ParseDocument() Output = string.Empty; Errors = "Failed to parse input: " + ex.Message; } + finally { + if (stream != null) + { + stream.Close(); + stream.Dispose(); + } + + } } /// @@ -255,7 +314,8 @@ private string WriteContents(OpenApiDocument document) Version, Format, new OpenApiWriterSettings() { - ReferenceInline = this.Inline == true ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences + InlineLocalReferences = InlineLocal, + InlineExternalReferences = InlineExternal }); outputStream.Position = 0; diff --git a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml index daf8a2209..41a4f2543 100644 --- a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml +++ b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml @@ -4,6 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Microsoft.OpenApi.Workbench" + xmlns:writers="clr-namespace:Microsoft.OpenApi.Writers;assembly=Microsoft.OpenApi" mc:Ignorable="d" Title="OpenAPI Workbench" Height="600" Width="800"> @@ -42,7 +43,9 @@ - + + + diff --git a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs index f33132359..08bbb177d 100644 --- a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs +++ b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Windows; namespace Microsoft.OpenApi.Workbench @@ -18,9 +19,15 @@ public MainWindow() DataContext = _mainModel; } - private void Button_Click(object sender, RoutedEventArgs e) + private async void Button_Click(object sender, RoutedEventArgs e) { - _mainModel.ParseDocument(); + try + { + await _mainModel.ParseDocument(); + } catch (Exception ex) + { + _mainModel.Errors = ex.Message; + } } } } diff --git a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj index 47a9d6ffe..8d8239222 100644 --- a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj +++ b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj @@ -58,6 +58,7 @@ MSBuild:Compile Designer + MSBuild:Compile diff --git a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs index 4f685d2de..644334ab4 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs @@ -70,7 +70,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV3(writer); return; diff --git a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs index 08b8bd020..cd8cdae74 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs @@ -80,7 +80,7 @@ public void SerializeAsV3(IOpenApiWriter writer) // If references have been inlined we don't need the to render the components section // however if they have cycles, then we will need a component rendered - if (writer.GetSettings().ReferenceInline != ReferenceInlineSetting.DoNotInlineReferences) + if (writer.GetSettings().InlineLocalReferences) { var loops = writer.GetSettings().LoopDetector.Loops; writer.WriteStartObject(); diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 0db9b2391..4f4e673af 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -135,7 +135,7 @@ public void SerializeAsV2(IOpenApiWriter writer) // If references have been inlined we don't need the to render the components section // however if they have cycles, then we will need a component rendered - if (writer.GetSettings().ReferenceInline != ReferenceInlineSetting.DoNotInlineReferences) + if (writer.GetSettings().InlineLocalReferences) { var loops = writer.GetSettings().LoopDetector.Loops; diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index d9bc08e30..787c2972e 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -64,7 +64,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV3(writer); return; diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index eb6736183..d94681a1c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -96,7 +96,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV3(writer); return; @@ -161,7 +161,7 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV2(writer); return; diff --git a/src/Microsoft.OpenApi/Models/OpenApiLink.cs b/src/Microsoft.OpenApi/Models/OpenApiLink.cs index fb7396db2..82502f14a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLink.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLink.cs @@ -71,7 +71,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV3(writer); return; diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 03b465109..50d78ae00 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -145,7 +145,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV3(writer); return; @@ -216,7 +216,7 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV2(writer); return; diff --git a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs index b222ba34f..78e97ec18 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs @@ -75,7 +75,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineAllReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV3(writer); return; @@ -95,7 +95,7 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineAllReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV2(writer); return; diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index bc2e4e525..ef30602b9 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -61,7 +61,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV3(writer); return; @@ -105,7 +105,7 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) { Reference.SerializeAsV2(writer); return; diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 60e314fcf..1d579a89a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -255,7 +255,7 @@ public void SerializeAsV3(IOpenApiWriter writer) if (Reference != null) { - if (settings.ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (!settings.ShouldInlineReference(Reference)) { Reference.SerializeAsV3(writer); return; @@ -445,7 +445,7 @@ internal void SerializeAsV2( if (Reference != null) { var settings = writer.GetSettings(); - if (settings.ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + if (!settings.ShouldInlineReference(Reference)) { Reference.SerializeAsV2(writer); return; diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs index 45eedc831..458d8f4a3 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs @@ -1,36 +1,80 @@  +using System; +using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; namespace Microsoft.OpenApi.Writers { /// - /// Indicates if and when the reader should convert references into complete object renderings + /// Indicates if and when the writer should convert references into complete object renderings /// + [Obsolete("Use InlineLocalReference and InlineExternalReference settings instead")] public enum ReferenceInlineSetting { /// - /// Create placeholder objects with an OpenApiReference instance and UnresolvedReference set to true. + /// Render all references as $ref. /// DoNotInlineReferences, /// - /// Convert local references to references of valid domain objects. + /// Render all local references as inline objects /// InlineLocalReferences, /// - /// Convert all references to references of valid domain objects. + /// Render all references as inline objects. /// InlineAllReferences } + /// /// Configuration settings to control how OpenAPI documents are written /// public class OpenApiWriterSettings { + private ReferenceInlineSetting referenceInline = ReferenceInlineSetting.DoNotInlineReferences; + internal LoopDetector LoopDetector { get; } = new LoopDetector(); /// /// Indicates how references in the source document should be handled. /// - public ReferenceInlineSetting ReferenceInline { get; set; } = ReferenceInlineSetting.DoNotInlineReferences; + [Obsolete("Use InlineLocalReference and InlineExternalReference settings instead")] + public ReferenceInlineSetting ReferenceInline { + get { return referenceInline; } + set { + referenceInline = value; + switch(referenceInline) + { + case ReferenceInlineSetting.DoNotInlineReferences: + InlineLocalReferences = false; + InlineExternalReferences = false; + break; + case ReferenceInlineSetting.InlineLocalReferences: + InlineLocalReferences = true; + InlineExternalReferences = false; + break; + case ReferenceInlineSetting.InlineAllReferences: + InlineLocalReferences = true; + InlineExternalReferences = true; + break; + } + } + } + /// + /// Indicates if local references should be rendered as an inline object + /// + public bool InlineLocalReferences { get; set; } = false; + + /// + /// Indicates if external references should be rendered as an inline object + /// + public bool InlineExternalReferences { get; set; } = false; + + + internal bool ShouldInlineReference(OpenApiReference reference) + { + return (reference.IsLocal && InlineLocalReferences) + || (reference.IsExternal && InlineExternalReferences); + } + } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index 4a35301c6..b3cbb8c6d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -25,7 +25,8 @@ public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoW var reader = new OpenApiStreamReader(new OpenApiReaderSettings() { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - CustomExternalLoader = new MockLoader() + CustomExternalLoader = new MockLoader(), + BaseUrl = new Uri("file://c:\\") }); // Todo: this should be ReadAsync @@ -54,7 +55,8 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo var reader = new OpenApiStreamReader(new OpenApiReaderSettings() { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, - CustomExternalLoader = new ResourceLoader() + CustomExternalLoader = new ResourceLoader(), + BaseUrl = new Uri("fie://c:\\") }); ReadResult result; diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index d5a89e586..e4c3f4282 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1323,6 +1323,9 @@ namespace Microsoft.OpenApi.Writers public class OpenApiWriterSettings { public OpenApiWriterSettings() { } + public bool InlineExternalReferences { get; set; } + public bool InlineLocalReferences { get; set; } + [System.Obsolete("Use InlineLocalReference and InlineExternalReference settings instead")] public Microsoft.OpenApi.Writers.ReferenceInlineSetting ReferenceInline { get; set; } } public class OpenApiYamlWriter : Microsoft.OpenApi.Writers.OpenApiWriterBase @@ -1341,6 +1344,7 @@ namespace Microsoft.OpenApi.Writers public override void WriteValue(string value) { } protected override void WriteValueSeparator() { } } + [System.Obsolete("Use InlineLocalReference and InlineExternalReference settings instead")] public enum ReferenceInlineSetting { DoNotInlineReferences = 0, diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs index a73fdbb9b..29e8c7676 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs @@ -373,7 +373,7 @@ public void WriteInlineSchema() components: { }"; var outputString = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences}); + var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { InlineLocalReferences = true } ); // Act doc.SerializeAsV3(writer); @@ -409,7 +409,7 @@ public void WriteInlineSchemaV2() type: object"; var outputString = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences }); + var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { InlineLocalReferences = true }); // Act doc.SerializeAsV2(writer); @@ -515,7 +515,7 @@ public void WriteInlineRecursiveSchema() // Component schemas that are there due to cycles are still inlined because the items they reference may not exist in the components because they don't have cycles. var outputString = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences }); + var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { InlineLocalReferences = true }); // Act doc.SerializeAsV3(writer); @@ -629,7 +629,7 @@ public void WriteInlineRecursiveSchemav2() // Component schemas that are there due to cycles are still inlined because the items they reference may not exist in the components because they don't have cycles. var outputString = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences }); + var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { InlineLocalReferences = true }); // Act doc.SerializeAsV2(writer); From a4519b4627c3d056e7f062740ab57e08437cd192 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sat, 29 Jan 2022 17:12:59 -0500 Subject: [PATCH 03/49] Updated hidi parameters to control both local and remote inlining independently --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 12 +++++++----- src/Microsoft.OpenApi.Hidi/Program.cs | 6 +++--- .../OpenApiReaderSettings.cs | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index e381dd64f..05b44c9c6 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -26,11 +26,12 @@ public static void ProcessOpenApiDocument( FileInfo output, OpenApiSpecVersion? version, OpenApiFormat? format, + bool inlineExternal, + bool inlineLocal, string filterByOperationIds, string filterByTags, - string filterByCollection, - bool inline, - bool resolveExternal) + string filterByCollection + ) { if (string.IsNullOrEmpty(input)) { @@ -52,7 +53,7 @@ public static void ProcessOpenApiDocument( var result = new OpenApiStreamReader(new OpenApiReaderSettings { - ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, + ReferenceResolution = inlineExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, RuleSet = ValidationRuleSet.GetDefaultRuleSet(), BaseUrl = new Uri(inputUrl.AbsoluteUri) } @@ -105,7 +106,8 @@ public static void ProcessOpenApiDocument( var settings = new OpenApiWriterSettings() { - ReferenceInline = inline ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences + InlineLocalReferences = inlineLocal, + InlineExternalReferences = inlineExternal }; var openApiFormat = format ?? GetOpenApiFormat(input); diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index 8bc54e2fc..c6f7eff44 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -28,13 +28,13 @@ static async Task Main(string[] args) 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)), + new Option("--inlineExternal", "Inline external $ref instances", typeof(bool) ), + new Option("--inlineLocal", "Inline local $ref instances", typeof(bool) ), new Option("--filterByOperationIds", "Filters OpenApiDocument by OperationId(s) provided", typeof(string)), new Option("--filterByTags", "Filters OpenApiDocument by Tag(s) provided", typeof(string)), new Option("--filterByCollection", "Filters OpenApiDocument by Postman collection provided", typeof(string)) }; - transformCommand.Handler = CommandHandler.Create( + transformCommand.Handler = CommandHandler.Create( OpenApiService.ProcessOpenApiDocument); rootCommand.Add(transformCommand); diff --git a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs index 732708459..2e8d50adb 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs @@ -30,7 +30,7 @@ public enum ReferenceResolutionSetting /// ResolveLocalReferences, /// - /// Convert all references to references of valid domain objects. + /// Not used anymore. Will be removed in v2. Convert all references to references of valid domain objects. /// ResolveAllReferences } From 4c26dbb5727b2b8412a380e7dd7ae61df96cf780 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sat, 19 Feb 2022 12:38:48 -0500 Subject: [PATCH 04/49] Added hostdocument to OpenApiReference --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 2 +- .../OpenApiReaderSettings.cs | 13 +- .../OpenApiStreamReader.cs | 2 +- .../OpenApiYamlDocumentReader.cs | 56 ++++----- .../Interfaces/IEffective.cs | 23 ++++ .../Interfaces/IOpenApiReferenceable.cs | 1 + .../Models/OpenApiDocument.cs | 34 ++++-- .../Models/OpenApiParameter.cs | 14 ++- .../Models/OpenApiReference.cs | 5 + src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 24 +++- .../Models/OpenApiSecurityScheme.cs | 4 +- .../Services/OpenApiReferenceResolver.cs | 43 ++++--- .../OpenApiWorkspaceStreamTests.cs | 13 +- .../TryLoadReferenceV2Tests.cs | 13 +- .../V2Tests/OpenApiDocumentTests.cs | 28 +++-- .../V3Tests/OpenApiCallbackTests.cs | 2 + .../V3Tests/OpenApiDocumentTests.cs | 115 +++++++++++++----- .../V3Tests/OpenApiSchemaTests.cs | 28 +++-- .../Workspaces/OpenApiWorkspaceTests.cs | 5 +- .../Writers/OpenApiYamlWriterTests.cs | 13 +- 20 files changed, 287 insertions(+), 151 deletions(-) create mode 100644 src/Microsoft.OpenApi/Interfaces/IEffective.cs diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 05b44c9c6..890327ddc 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -53,7 +53,7 @@ string filterByCollection var result = new OpenApiStreamReader(new OpenApiReaderSettings { - ReferenceResolution = inlineExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, + LoadExternalRefs = inlineExternal, RuleSet = ValidationRuleSet.GetDefaultRuleSet(), BaseUrl = new Uri(inputUrl.AbsoluteUri) } diff --git a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs index 2e8d50adb..12ccdb681 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs @@ -4,15 +4,10 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Readers.Interface; -using Microsoft.OpenApi.Readers.ParseNodes; -using Microsoft.OpenApi.Readers.Services; using Microsoft.OpenApi.Validations; using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.OpenApi.Readers { @@ -30,7 +25,7 @@ public enum ReferenceResolutionSetting /// ResolveLocalReferences, /// - /// Not used anymore. Will be removed in v2. Convert all references to references of valid domain objects. + /// ResolveAllReferences effectively means load external references. Will be removed in v2. External references are never "resolved". /// ResolveAllReferences } @@ -43,8 +38,14 @@ public class OpenApiReaderSettings /// /// Indicates how references in the source document should be handled. /// + /// This setting will be going away in the next major version of this library. Use GetEffective on model objects to get resolved references. public ReferenceResolutionSetting ReferenceResolution { get; set; } = ReferenceResolutionSetting.ResolveLocalReferences; + /// + /// When external references are found, load them into a shared workspace + /// + public bool LoadExternalRefs { get; set; } = false; + /// /// Dictionary of parsers for converting extensions into strongly typed classes /// diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index 987e79ceb..13bdbdef8 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -25,7 +25,7 @@ public OpenApiStreamReader(OpenApiReaderSettings settings = null) { _settings = settings ?? new OpenApiReaderSettings(); - if(_settings.ReferenceResolution == ReferenceResolutionSetting.ResolveAllReferences + if((_settings.ReferenceResolution == ReferenceResolutionSetting.ResolveAllReferences || _settings.LoadExternalRefs) && _settings.BaseUrl == null) { throw new ArgumentException("BaseUrl must be provided to resolve external references."); diff --git a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs index 63b78ccba..6cf64a5bb 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs @@ -53,6 +53,11 @@ public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic // Parse the OpenAPI Document document = context.Parse(input); + if (_settings.LoadExternalRefs) + { + throw new InvalidOperationException("Cannot load external refs using the synchronous Read, use ReadAsync instead."); + } + ResolveReferences(diagnostic, document); } catch (OpenApiException ex) @@ -88,7 +93,12 @@ public async Task ReadAsync(YamlDocument input) // Parse the OpenAPI Document document = context.Parse(input); - await ResolveReferencesAsync(diagnostic, document, _settings.BaseUrl); + if (_settings.LoadExternalRefs) + { + await LoadExternalRefs(document); + } + + ResolveReferences(diagnostic, document); } catch (OpenApiException ex) { @@ -112,28 +122,18 @@ public async Task ReadAsync(YamlDocument input) }; } - - private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document) + private async Task LoadExternalRefs(OpenApiDocument document) { - // Resolve References if requested - switch (_settings.ReferenceResolution) - { - case ReferenceResolutionSetting.ResolveAllReferences: - throw new ArgumentException("Cannot resolve all references via a synchronous call. Use ReadAsync."); - case ReferenceResolutionSetting.ResolveLocalReferences: - var errors = document.ResolveReferences(false); + // Create workspace for all documents to live in. + var openApiWorkSpace = new OpenApiWorkspace(); - foreach (var item in errors) - { - diagnostic.Errors.Add(item); - } - break; - case ReferenceResolutionSetting.DoNotResolveReferences: - break; - } + // Load this root document into the workspace + var streamLoader = new DefaultStreamLoader(_settings.BaseUrl); + var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings); + await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document); } - private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiDocument document, Uri baseUrl) + private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document) { List errors = new List(); @@ -141,23 +141,9 @@ private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiD switch (_settings.ReferenceResolution) { case ReferenceResolutionSetting.ResolveAllReferences: - - // Create workspace for all documents to live in. - var openApiWorkSpace = new OpenApiWorkspace(); - - // Load this root document into the workspace - var streamLoader = new DefaultStreamLoader(baseUrl); - var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings); - await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document); - - // Resolve all references in all the documents loaded into the OpenApiWorkspace - foreach (var doc in openApiWorkSpace.Documents) - { - errors.AddRange(doc.ResolveReferences(true)); - } - break; + throw new ArgumentException("Resolving external references is not supported"); case ReferenceResolutionSetting.ResolveLocalReferences: - errors.AddRange(document.ResolveReferences(false)); + errors.AddRange(document.ResolveReferences()); break; case ReferenceResolutionSetting.DoNotResolveReferences: break; diff --git a/src/Microsoft.OpenApi/Interfaces/IEffective.cs b/src/Microsoft.OpenApi/Interfaces/IEffective.cs new file mode 100644 index 000000000..b62ec12ab --- /dev/null +++ b/src/Microsoft.OpenApi/Interfaces/IEffective.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Interfaces +{ + /// + /// OpenApiElements that implement IEffective indicate that their description is not self-contained. + /// External elements affect the effective description. + /// + /// Currently this will only be used for accessing external references. + /// In the next major version, this will be the approach accessing all referenced elements. + /// This will enable us to support merging properties that are peers of the $ref + /// Type of OpenApi Element that is being referenced. + public interface IEffective where T : class,IOpenApiElement + { + /// + /// Returns a calculated and cloned version of the element. + /// + T GetEffective(OpenApiDocument document); + } +} diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs index eb47c64bc..c790e1fda 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs @@ -31,5 +31,6 @@ public interface IOpenApiReferenceable : IOpenApiSerializable /// Serialize to OpenAPI V2 document without using reference. /// void SerializeAsV2WithoutReference(IOpenApiWriter writer); + } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 4f4e673af..6ffc260d1 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -321,27 +321,37 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList /// /// Walk the OpenApiDocument and resolve unresolved references /// - /// Indicates if external references should be resolved. Document needs to reference a workspace for this to be possible. - public IEnumerable ResolveReferences(bool useExternal = false) + /// + /// This method will be replaced by a LoadExternalReferences in the next major update to this library. + /// Resolving references at load time is going to go away. + /// + public IEnumerable ResolveReferences() { - var resolver = new OpenApiReferenceResolver(this, useExternal); + var resolver = new OpenApiReferenceResolver(this, false); var walker = new OpenApiWalker(resolver); walker.Walk(this); return resolver.Errors; } - /// - /// Load the referenced object from a object - /// - public IOpenApiReferenceable ResolveReference(OpenApiReference reference) + /// + /// Load the referenced object from a object + /// + internal T ResolveReferenceTo(OpenApiReference reference) where T : class, IOpenApiReferenceable + { + if (reference.IsExternal) { - return ResolveReference(reference, false); + return ResolveReference(reference, true) as T; } + else + { + return ResolveReference(reference, false) as T; + } + } - /// - /// Load the referenced object from a object - /// - public IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool useExternal) + /// + /// Load the referenced object from a object + /// + internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool useExternal) { if (reference == null) { diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 50d78ae00..ebc70465a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -12,7 +12,7 @@ namespace Microsoft.OpenApi.Models /// /// Parameter Object. /// - public class OpenApiParameter : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible + public class OpenApiParameter : IOpenApiSerializable, IOpenApiReferenceable, IEffective, IOpenApiExtensible { private bool? _explode; @@ -332,6 +332,18 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } + + public OpenApiParameter GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } + else + { + return this; + } + } } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs index cbe64d3e6..3f1370800 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReference.cs @@ -45,6 +45,11 @@ public class OpenApiReference : IOpenApiSerializable /// public bool IsLocal => ExternalResource == null; + /// + /// The OpenApiDocument that is hosting the OpenApiReference instance. This is used to enable dereferencing the reference. + /// + public OpenApiDocument HostDocument { get; set; } = null; + /// /// Gets the full reference string for v3.0. /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 1d579a89a..c98c32c17 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -11,7 +11,7 @@ namespace Microsoft.OpenApi.Models /// /// Schema Object. /// - public class OpenApiSchema : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible + public class OpenApiSchema : IOpenApiSerializable, IOpenApiReferenceable, IEffective, IOpenApiExtensible { /// /// Follow JSON Schema definition. Short text providing information about the data. @@ -252,6 +252,7 @@ public void SerializeAsV3(IOpenApiWriter writer) } var settings = writer.GetSettings(); + var target = this; if (Reference != null) { @@ -259,6 +260,13 @@ public void SerializeAsV3(IOpenApiWriter writer) { Reference.SerializeAsV3(writer); return; + } + else + { + if (Reference.IsExternal) // Temporary until v2 + { + target = this.GetEffective(Reference.HostDocument); + } } // If Loop is detected then just Serialize as a reference. @@ -270,7 +278,7 @@ public void SerializeAsV3(IOpenApiWriter writer) } } - SerializeAsV3WithoutReference(writer); + target.SerializeAsV3WithoutReference(writer); if (Reference != null) { @@ -283,6 +291,7 @@ public void SerializeAsV3(IOpenApiWriter writer) /// public void SerializeAsV3WithoutReference(IOpenApiWriter writer) { + writer.WriteStartObject(); // title @@ -666,5 +675,16 @@ internal void WriteAsSchemaProperties( // extensions writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); } + + public OpenApiSchema GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } else + { + return this; + } + } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs index 7694c5fd4..902ce19bc 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; @@ -83,7 +84,8 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + + if (Reference != null) { Reference.SerializeAsV3(writer); return; diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs index 6755883b6..840f9c660 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs @@ -42,6 +42,13 @@ public override void Visit(OpenApiDocument doc) } } + public override void Visit(IOpenApiReferenceable referenceable) + { + if (referenceable.Reference != null) + { + referenceable.Reference.HostDocument = _currentDocument; + } + } public override void Visit(OpenApiComponents components) { ResolveMap(components.Parameters); @@ -237,7 +244,7 @@ private void ResolveTags(IList tags) { try { - return _currentDocument.ResolveReference(reference) as T; + return _currentDocument.ResolveReference(reference, false) as T; } catch (OpenApiException ex) { @@ -245,24 +252,26 @@ private void ResolveTags(IList tags) return null; } } - else if (_resolveRemoteReferences == true) - { - if (_currentDocument.Workspace == null) - { - _errors.Add(new OpenApiReferenceError(reference,"Cannot resolve external references for documents not in workspaces.")); - // Leave as unresolved reference - return new T() - { - UnresolvedReference = true, - Reference = reference - }; - } - var target = _currentDocument.Workspace.ResolveReference(reference); + // The concept of merging references with their target at load time is going away in the next major version + // External references will not support this approach. + //else if (_resolveRemoteReferences == true) + //{ + // if (_currentDocument.Workspace == null) + // { + // _errors.Add(new OpenApiReferenceError(reference,"Cannot resolve external references for documents not in workspaces.")); + // // Leave as unresolved reference + // return new T() + // { + // UnresolvedReference = true, + // Reference = reference + // }; + // } + // var target = _currentDocument.Workspace.ResolveReference(reference); - // TODO: If it is a document fragment, then we should resolve it within the current context + // // TODO: If it is a document fragment, then we should resolve it within the current context - return target as T; - } + // return target as T; + //} else { // Leave as unresolved reference diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index b3cbb8c6d..4a2c2cafe 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -1,13 +1,9 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.Interface; -using Microsoft.OpenApi.Readers.Services; -using Microsoft.OpenApi.Services; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.OpenApiWorkspaceTests @@ -24,7 +20,7 @@ public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoW // Create a reader that will resolve all references var reader = new OpenApiStreamReader(new OpenApiReaderSettings() { - ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, + LoadExternalRefs = true, CustomExternalLoader = new MockLoader(), BaseUrl = new Uri("file://c:\\") }); @@ -54,7 +50,7 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo // Create a reader that will resolve all references var reader = new OpenApiStreamReader(new OpenApiReaderSettings() { - ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences, + LoadExternalRefs = true, CustomExternalLoader = new ResourceLoader(), BaseUrl = new Uri("fie://c:\\") }); @@ -73,7 +69,7 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo .Operations[OperationType.Get] .Responses["200"] .Content["application/json"] - .Schema; + .Schema.GetEffective(result.OpenApiDocument); Assert.Equal("object", referencedSchema.Type); Assert.Equal("string", referencedSchema.Properties["subject"].Type); Assert.False(referencedSchema.UnresolvedReference); @@ -81,8 +77,9 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo var referencedParameter = result.OpenApiDocument .Paths["/todos"] .Operations[OperationType.Get] - .Parameters + .Parameters.Select(p => p.GetEffective(result.OpenApiDocument)) .Where(p => p.Name == "filter").FirstOrDefault(); + Assert.Equal("string", referencedParameter.Schema.Type); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index d7f110b10..a641b7d6f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs @@ -38,7 +38,7 @@ public void LoadSchemaReference() }; // Act - var referencedObject = document.ResolveReference(reference); + var referencedObject = document.ResolveReferenceTo(reference); // Assert referencedObject.Should().BeEquivalentTo( @@ -93,7 +93,7 @@ public void LoadParameterReference() }; // Act - var referencedObject = document.ResolveReference(reference); + var referencedObject = document.ResolveReferenceTo(reference); // Assert referencedObject.Should().BeEquivalentTo( @@ -136,7 +136,7 @@ public void LoadSecuritySchemeReference() }; // Act - var referencedObject = document.ResolveReference(reference); + var referencedObject = document.ResolveReferenceTo(reference); // Assert referencedObject.Should().BeEquivalentTo( @@ -173,7 +173,7 @@ public void LoadResponseReference() }; // Act - var referencedObject = document.ResolveReference(reference); + var referencedObject = document.ResolveReferenceTo(reference); // Assert referencedObject.Should().BeEquivalentTo( @@ -212,7 +212,7 @@ public void LoadResponseAndSchemaReference() }; // Act - var referencedObject = document.ResolveReference(reference); + var referencedObject = document.ResolveReferenceTo(reference); // Assert referencedObject.Should().BeEquivalentTo( @@ -241,7 +241,8 @@ public void LoadResponseAndSchemaReference() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "SampleObject2" + Id = "SampleObject2", + HostDocument = document } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index ad8e7e445..57593a79e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -8,15 +8,23 @@ using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Writers; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests { + + public class OpenApiDocumentTests { private const string SampleFolderPath = "V2Tests/Samples/"; + + + [Fact] public void ShouldThrowWhenReferenceTypeIsInvalid() { @@ -161,7 +169,8 @@ public void ShouldParseProducesInAnyOrder() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "Item" + Id = "Item", + HostDocument = doc }, Items = new OpenApiSchema() { @@ -177,7 +186,8 @@ public void ShouldParseProducesInAnyOrder() Reference = new OpenApiReference() { Type = ReferenceType.Schema, - Id = "Item" + Id = "Item", + HostDocument = doc } } }; @@ -187,7 +197,8 @@ public void ShouldParseProducesInAnyOrder() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "Item" + Id = "Item", + HostDocument = doc }, Properties = new Dictionary() { @@ -205,7 +216,8 @@ public void ShouldParseProducesInAnyOrder() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "Error" + Id = "Error", + HostDocument= doc }, Properties = new Dictionary() { @@ -375,7 +387,8 @@ public void ShouldAssignSchemaToAllResponses() Reference = new OpenApiReference { Id = "Item", - Type = ReferenceType.Schema + Type = ReferenceType.Schema, + HostDocument = document } } }; @@ -402,7 +415,8 @@ public void ShouldAssignSchemaToAllResponses() Reference = new OpenApiReference { Id = "Error", - Type = ReferenceType.Schema + Type = ReferenceType.Schema, + HostDocument= document } }; var responses = document.Paths["/items"].Operations[OperationType.Get].Responses; @@ -430,7 +444,7 @@ public void ShouldAllowComponentsThatJustContainAReference() OpenApiDocument doc = reader.Read(stream, out OpenApiDiagnostic diags); OpenApiSchema schema1 = doc.Components.Schemas["AllPets"]; Assert.False(schema1.UnresolvedReference); - OpenApiSchema schema2 = (OpenApiSchema)doc.ResolveReference(schema1.Reference); + OpenApiSchema schema2 = doc.ResolveReferenceTo(schema1.Reference); if (schema2.UnresolvedReference && schema1.Reference.Id == schema2.Reference.Id) { // detected a cycle - this code gets triggered diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs index f23bee9f9..320f01fae 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs @@ -127,6 +127,7 @@ public void ParseCallbackWithReferenceShouldSucceed() { Type = ReferenceType.Callback, Id = "simpleHook", + HostDocument = openApiDoc } }); } @@ -185,6 +186,7 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() { Type = ReferenceType.Callback, Id = "simpleHook", + HostDocument = openApiDoc } }); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 93d3c1a1b..f1d8b805f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -9,9 +9,12 @@ using System.Threading; using FluentAssertions; using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Validations; using Microsoft.OpenApi.Validations.Rules; +using Microsoft.OpenApi.Writers; using Newtonsoft.Json; using Xunit; using Xunit.Abstractions; @@ -25,6 +28,49 @@ public class OpenApiDocumentTests private readonly ITestOutputHelper _output; + public T Clone(T element) where T : IOpenApiSerializable + { + using (var stream = new MemoryStream()) + { + IOpenApiWriter writer; + var streamWriter = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture); + writer = new OpenApiJsonWriter(streamWriter, new OpenApiJsonWriterSettings() { + InlineLocalReferences = true}); + element.SerializeAsV3(writer); + writer.Flush(); + stream.Position = 0; + + using (var streamReader = new StreamReader(stream)) + { + var result = streamReader.ReadToEnd(); + return new OpenApiStringReader().ReadFragment(result, OpenApiSpecVersion.OpenApi3_0, out OpenApiDiagnostic diagnostic4); + } + } + } + + public OpenApiSecurityScheme CloneSecurityScheme(OpenApiSecurityScheme element) + { + using (var stream = new MemoryStream()) + { + IOpenApiWriter writer; + var streamWriter = new FormattingStreamWriter(stream, CultureInfo.InvariantCulture); + writer = new OpenApiJsonWriter(streamWriter, new OpenApiJsonWriterSettings() + { + InlineLocalReferences = true + }); + element.SerializeAsV3WithoutReference(writer); + writer.Flush(); + stream.Position = 0; + + using (var streamReader = new StreamReader(stream)) + { + var result = streamReader.ReadToEnd(); + return new OpenApiStringReader().ReadFragment(result, OpenApiSpecVersion.OpenApi3_0, out OpenApiDiagnostic diagnostic4); + } + } + } + + public OpenApiDocumentTests(ITestOutputHelper output) { _output = output; @@ -256,7 +302,8 @@ public void ParseStandardPetStoreDocumentShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "pet" + Id = "pet", + HostDocument = actual } }, ["newPet"] = new OpenApiSchema @@ -285,7 +332,8 @@ public void ParseStandardPetStoreDocumentShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "newPet" + Id = "newPet", + HostDocument = actual } }, ["errorModel"] = new OpenApiSchema @@ -311,38 +359,39 @@ public void ParseStandardPetStoreDocumentShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "errorModel" + Id = "errorModel", + HostDocument = actual } }, } }; // Create a clone of the schema to avoid modifying things in components. - var petSchema = - JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(components.Schemas["pet"])); + var petSchema = Clone(components.Schemas["pet"]); + petSchema.Reference = new OpenApiReference { Id = "pet", - Type = ReferenceType.Schema + Type = ReferenceType.Schema, + HostDocument = actual }; - var newPetSchema = - JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(components.Schemas["newPet"])); + var newPetSchema = Clone(components.Schemas["newPet"]); + newPetSchema.Reference = new OpenApiReference { Id = "newPet", - Type = ReferenceType.Schema + Type = ReferenceType.Schema, + HostDocument = actual }; - var errorModelSchema = - JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(components.Schemas["errorModel"])); + var errorModelSchema = Clone(components.Schemas["errorModel"]); + errorModelSchema.Reference = new OpenApiReference { Id = "errorModel", - Type = ReferenceType.Schema + Type = ReferenceType.Schema, + HostDocument = actual }; var expected = new OpenApiDocument @@ -683,7 +732,8 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "pet" + Id = "pet", + HostDocument = actual } }, ["newPet"] = new OpenApiSchema @@ -712,7 +762,8 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "newPet" + Id = "newPet", + HostDocument = actual } }, ["errorModel"] = new OpenApiSchema @@ -752,7 +803,8 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Reference = new OpenApiReference { Id = "securitySchemeName1", - Type = ReferenceType.SecurityScheme + Type = ReferenceType.SecurityScheme, + HostDocument = actual } }, @@ -763,34 +815,31 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Reference = new OpenApiReference { Id = "securitySchemeName2", - Type = ReferenceType.SecurityScheme + Type = ReferenceType.SecurityScheme, + HostDocument = actual } } } }; // Create a clone of the schema to avoid modifying things in components. - var petSchema = - JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(components.Schemas["pet"])); + var petSchema = Clone(components.Schemas["pet"]); petSchema.Reference = new OpenApiReference { Id = "pet", Type = ReferenceType.Schema }; - var newPetSchema = - JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(components.Schemas["newPet"])); + var newPetSchema = Clone(components.Schemas["newPet"]); + newPetSchema.Reference = new OpenApiReference { Id = "newPet", Type = ReferenceType.Schema }; - var errorModelSchema = - JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(components.Schemas["errorModel"])); + var errorModelSchema = Clone(components.Schemas["errorModel"]); + errorModelSchema.Reference = new OpenApiReference { Id = "errorModel", @@ -814,16 +863,16 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Name = "tagName2" }; - var securityScheme1 = JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(components.SecuritySchemes["securitySchemeName1"])); + var securityScheme1 = CloneSecurityScheme(components.SecuritySchemes["securitySchemeName1"]); + securityScheme1.Reference = new OpenApiReference { Id = "securitySchemeName1", Type = ReferenceType.SecurityScheme }; - var securityScheme2 = JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(components.SecuritySchemes["securitySchemeName2"])); + var securityScheme2 = CloneSecurityScheme(components.SecuritySchemes["securitySchemeName2"]); + securityScheme2.Reference = new OpenApiReference { Id = "securitySchemeName2", @@ -1170,7 +1219,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }; - actual.Should().BeEquivalentTo(expected); + actual.Should().BeEquivalentTo(expected, options => options.Excluding(m => m.Name == "HostDocument")); } context.Should().BeEquivalentTo( diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index dbf0cf3f6..9bdafeba6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -359,7 +359,8 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "ErrorModel" + Id = "ErrorModel", + HostDocument = openApiDoc }, Required = { @@ -372,7 +373,8 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "ExtendedErrorModel" + Id = "ExtendedErrorModel", + HostDocument = openApiDoc }, AllOf = { @@ -381,7 +383,8 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "ErrorModel" + Id = "ErrorModel", + HostDocument = openApiDoc }, // Schema should be dereferenced in our model, so all the properties // from the ErrorModel above should be propagated here. @@ -420,7 +423,7 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() } } } - }); + },options => options.Excluding(m => m.Name == "HostDocument")); } } @@ -469,7 +472,8 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() Reference = new OpenApiReference() { Id= "Pet", - Type = ReferenceType.Schema + Type = ReferenceType.Schema, + HostDocument = openApiDoc } }, ["Cat"] = new OpenApiSchema @@ -482,7 +486,8 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "Pet" + Id = "Pet", + HostDocument = openApiDoc }, // Schema should be dereferenced in our model, so all the properties // from the Pet above should be propagated here. @@ -532,7 +537,8 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() Reference = new OpenApiReference() { Id= "Cat", - Type = ReferenceType.Schema + Type = ReferenceType.Schema, + HostDocument = openApiDoc } }, ["Dog"] = new OpenApiSchema @@ -545,7 +551,8 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() Reference = new OpenApiReference { Type = ReferenceType.Schema, - Id = "Pet" + Id = "Pet", + HostDocument = openApiDoc }, // Schema should be dereferenced in our model, so all the properties // from the Pet above should be propagated here. @@ -591,11 +598,12 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() Reference = new OpenApiReference() { Id= "Dog", - Type = ReferenceType.Schema + Type = ReferenceType.Schema, + HostDocument = openApiDoc } } } - }); + }, options => options.Excluding(m => m.Name == "HostDocument")); } } diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs index bee746eae..63045847b 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -126,11 +126,12 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() workspace.AddDocument("root", doc); workspace.AddDocument("common", CreateCommonDocument()); - var errors = doc.ResolveReferences(true); + var errors = doc.ResolveReferences(); Assert.Empty(errors); var schema = doc.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; - Assert.False(schema.UnresolvedReference); + var effectiveSchema = schema.GetEffective(doc); + Assert.False(effectiveSchema.UnresolvedReference); } [Fact] diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs index 29e8c7676..bfaa3da51 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs @@ -468,6 +468,8 @@ private static OpenApiDocument CreateDocWithSimpleSchemaToInline() ["thing"] = thingSchema} } }; + thingSchema.Reference.HostDocument = doc; + return doc; } @@ -544,12 +546,6 @@ private static OpenApiDocument CreateDocWithRecursiveSchemaReference() var relatedSchema = new OpenApiSchema() { Type = "integer", - UnresolvedReference = false, - Reference = new OpenApiReference - { - Id = "related", - Type = ReferenceType.Schema - } }; thingSchema.Properties["related"] = relatedSchema; @@ -587,6 +583,7 @@ private static OpenApiDocument CreateDocWithRecursiveSchemaReference() ["thing"] = thingSchema} } }; + thingSchema.Reference.HostDocument = doc; return doc; } @@ -623,9 +620,7 @@ public void WriteInlineRecursiveSchemav2() children: $ref: '#/definitions/thing' related: - $ref: '#/definitions/related' - related: - type: integer"; + type: integer"; // Component schemas that are there due to cycles are still inlined because the items they reference may not exist in the components because they don't have cycles. var outputString = new StringWriter(CultureInfo.InvariantCulture); From d41cf71d7c65c3adae3486003a73e43c79644a84 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sat, 19 Feb 2022 15:01:59 -0500 Subject: [PATCH 05/49] Missed these files --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 103 +------------------ src/Microsoft.OpenApi.Hidi/Program.cs | 22 ---- 2 files changed, 4 insertions(+), 121 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 7f51960d3..632042f38 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -34,14 +34,6 @@ public static async void ProcessOpenApiDocument( FileInfo output, OpenApiSpecVersion? version, OpenApiFormat? format, -<<<<<<< HEAD - bool inlineExternal, - bool inlineLocal, - string filterByOperationIds, - string filterByTags, - string filterByCollection - ) -======= LogLevel loglevel, bool inline, bool resolveexternal, @@ -49,7 +41,6 @@ string filterByCollection string filterbytags, string filterbycollection ) ->>>>>>> origin/vnext { var logger = ConfigureLoggerInstance(loglevel); @@ -95,18 +86,6 @@ string filterbycollection OpenApiFormat openApiFormat; var stopwatch = new Stopwatch(); -<<<<<<< HEAD - var inputUrl = GetInputUrl(input); - var stream = GetStream(inputUrl); - - OpenApiDocument document; - - var result = new OpenApiStreamReader(new OpenApiReaderSettings - { - LoadExternalRefs = inlineExternal, - RuleSet = ValidationRuleSet.GetDefaultRuleSet(), - BaseUrl = new Uri(inputUrl.AbsoluteUri) -======= if (!string.IsNullOrEmpty(csdl)) { // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion @@ -151,13 +130,8 @@ string filterbycollection openApiFormat = format ?? GetOpenApiFormat(openapi, logger); version ??= result.OpenApiDiagnostic.SpecificationVersion; ->>>>>>> origin/vnext } -<<<<<<< HEAD - document = result.OpenApiDocument; -======= ->>>>>>> origin/vnext Func predicate; // Check if filter options are provided, then slice the OpenAPI document @@ -178,15 +152,7 @@ string filterbycollection logger.LogTrace("Creating predicate based on the tags supplied."); predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags); -<<<<<<< HEAD - if (!string.IsNullOrEmpty(filterByCollection)) - { - var fileStream = GetStream(GetInputUrl(filterByCollection)); - var requestUrls = ParseJsonCollectionFile(fileStream); - predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source:document); -======= logger.LogTrace("Creating subset OpenApi document."); ->>>>>>> origin/vnext document = OpenApiFilterService.CreateFilteredDocument(document, predicate); } if (!string.IsNullOrEmpty(filterbycollection)) @@ -229,26 +195,6 @@ string filterbycollection textWriter.Flush(); } -<<<<<<< HEAD - private static Uri GetInputUrl(string input) - { - if (input.StartsWith("http")) - { - return new Uri(input); - } - else - { - return new Uri("file://" + Path.GetFullPath(input)); - } - } - - private static Stream GetStream(Uri input) - { - Stream stream; - if (input.Scheme == "http" || input.Scheme == "https") - { - var httpClient = new HttpClient(new HttpClientHandler() -======= /// /// Converts CSDL to OpenAPI /// @@ -303,10 +249,9 @@ private static async Task GetStream(string input, ILogger logger) stopwatch.Start(); Stream stream; - if (input.StartsWith("http")) + if (input.Scheme == "http" || input.Scheme == "https") { try ->>>>>>> origin/vnext { var httpClientHandler = new HttpClientHandler() { @@ -326,14 +271,6 @@ private static async Task GetStream(string input, ILogger logger) } else if (input.Scheme == "file") { -<<<<<<< HEAD - var fileInput = new FileInfo(input.AbsolutePath); - stream = fileInput.OpenRead(); - } - else - { - throw new ArgumentException("Unrecognized exception"); -======= try { var fileInput = new FileInfo(input); @@ -350,7 +287,6 @@ ex is SecurityException || logger.LogError($"Could not open the file at {input}, reason: {ex.Message}"); return null; } ->>>>>>> origin/vnext } stopwatch.Stop(); logger.LogTrace("{timestamp}ms: Read file {input}", stopwatch.ElapsedMilliseconds, input); @@ -389,31 +325,18 @@ public static Dictionary> ParseJsonCollectionFile(Stream st return requestUrls; } -<<<<<<< HEAD - internal static async Task ValidateOpenApiDocument(string input, bool resolveExternal) -======= internal static async Task ValidateOpenApiDocument(string openapi, LogLevel loglevel) ->>>>>>> origin/vnext { if (string.IsNullOrEmpty(openapi)) { throw new ArgumentNullException(nameof(openapi)); } -<<<<<<< HEAD - var inputUrl = GetInputUrl(input); - var stream = GetStream(GetInputUrl(input)); - - OpenApiDocument document; - - var result = await new OpenApiStreamReader(new OpenApiReaderSettings -======= var logger = ConfigureLoggerInstance(loglevel); var stream = await GetStream(openapi, logger); OpenApiDocument document; logger.LogTrace("Parsing the OpenApi file"); document = new OpenApiStreamReader(new OpenApiReaderSettings ->>>>>>> origin/vnext { ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, RuleSet = ValidationRuleSet.GetDefaultRuleSet(), @@ -432,30 +355,12 @@ internal static async Task ValidateOpenApiDocument(string openapi, LogLevel logl } } - if (document.Workspace == null) { - var statsVisitor = new StatsVisitor(); - var walker = new OpenApiWalker(statsVisitor); - walker.Walk(document); - Console.WriteLine(statsVisitor.GetStatisticsReport()); - } - else - { - foreach (var memberDocument in document.Workspace.Documents) - { - Console.WriteLine("Stats for " + memberDocument.Info.Title); - var statsVisitor = new StatsVisitor(); - var walker = new OpenApiWalker(statsVisitor); - walker.Walk(memberDocument); - Console.WriteLine(statsVisitor.GetStatisticsReport()); - } - } + var statsVisitor = new StatsVisitor(); + var walker = new OpenApiWalker(statsVisitor); + walker.Walk(document); -<<<<<<< HEAD - -======= logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); Console.WriteLine(statsVisitor.GetStatisticsReport()); ->>>>>>> origin/vnext } private static OpenApiFormat GetOpenApiFormat(string input, ILogger logger) diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index c078d7ba6..95e6f63f2 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -51,27 +51,6 @@ static async Task Main(string[] args) var validateCommand = new Command("validate") { -<<<<<<< HEAD - new Option("--input", "Input OpenAPI description file path or URL", typeof(string) ), - new Option("--resolveExternal","Resolve external $refs", typeof(bool)) - }; - 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("--inlineExternal", "Inline external $ref instances", typeof(bool) ), - new Option("--inlineLocal", "Inline local $ref instances", typeof(bool) ), - new Option("--filterByOperationIds", "Filters OpenApiDocument by OperationId(s) provided", typeof(string)), - new Option("--filterByTags", "Filters OpenApiDocument by Tag(s) provided", typeof(string)), - new Option("--filterByCollection", "Filters OpenApiDocument by Postman collection provided", typeof(string)) - }; - transformCommand.Handler = CommandHandler.Create( - OpenApiService.ProcessOpenApiDocument); -======= descriptionOption, logLevelOption }; @@ -95,7 +74,6 @@ static async Task Main(string[] args) transformCommand.SetHandler ( OpenApiService.ProcessOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); ->>>>>>> origin/vnext rootCommand.Add(transformCommand); rootCommand.Add(validateCommand); From 403fdbc6ab8bbd0352e07067853e1e6ca085d8cb Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Mon, 21 Feb 2022 17:45:43 -0500 Subject: [PATCH 06/49] Updated referencable items to use GetEffective when inlining --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 20 +++--- src/Microsoft.OpenApi.Hidi/Program.cs | 14 ++--- .../Models/OpenApiCallback.cs | 34 ++++++++-- .../Models/OpenApiExample.cs | 33 ++++++++-- src/Microsoft.OpenApi/Models/OpenApiHeader.cs | 53 +++++++++++++--- src/Microsoft.OpenApi/Models/OpenApiLink.cs | 35 +++++++++-- .../Models/OpenApiParameter.cs | 62 +++++++++++++------ .../Models/OpenApiPathItem.cs | 50 ++++++++++++--- .../Models/OpenApiRequestBody.cs | 31 +++++++++- .../Models/OpenApiResponse.cs | 51 ++++++++++++--- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 18 +++++- 11 files changed, 321 insertions(+), 80 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 632042f38..e813e72a4 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -28,15 +28,15 @@ namespace Microsoft.OpenApi.Hidi { public class OpenApiService { - public static async void ProcessOpenApiDocument( + public static async Task ProcessOpenApiDocument( string openapi, string csdl, FileInfo output, OpenApiSpecVersion? version, OpenApiFormat? format, LogLevel loglevel, - bool inline, - bool resolveexternal, + bool inlineLocal, + bool inlineExternal, string filterbyoperationids, string filterbytags, string filterbycollection @@ -104,8 +104,9 @@ string filterbycollection logger.LogTrace("Parsing OpenApi file"); var result = new OpenApiStreamReader(new OpenApiReaderSettings { - ReferenceResolution = resolveexternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, - RuleSet = ValidationRuleSet.GetDefaultRuleSet() + RuleSet = ValidationRuleSet.GetDefaultRuleSet(), + LoadExternalRefs = inlineExternal, + BaseUrl = openapi.StartsWith("http") ? new Uri(openapi) : new Uri("file:" + new FileInfo(openapi).DirectoryName + "\\") } ).ReadAsync(stream).GetAwaiter().GetResult(); @@ -249,7 +250,7 @@ private static async Task GetStream(string input, ILogger logger) stopwatch.Start(); Stream stream; - if (input.Scheme == "http" || input.Scheme == "https") + if (input.StartsWith("http")) { try { @@ -269,7 +270,7 @@ private static async Task GetStream(string input, ILogger logger) return null; } } - else if (input.Scheme == "file") + else { try { @@ -336,11 +337,10 @@ internal static async Task ValidateOpenApiDocument(string openapi, LogLevel logl OpenApiDocument document; logger.LogTrace("Parsing the OpenApi file"); - document = new OpenApiStreamReader(new OpenApiReaderSettings + var result = await new OpenApiStreamReader(new OpenApiReaderSettings { - ReferenceResolution = resolveExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, RuleSet = ValidationRuleSet.GetDefaultRuleSet(), - BaseUrl = new Uri(inputUrl.AbsoluteUri) + BaseUrl = new Uri(openapi) } ).ReadAsync(stream); diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index 95e6f63f2..309fa5360 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -43,11 +43,11 @@ static async Task Main(string[] args) var filterByCollectionOption = new Option("--filter-by-collection", "Filters OpenApiDocument by Postman collection provided"); filterByCollectionOption.AddAlias("-c"); - var inlineOption = new Option("--inline", "Inline $ref instances"); - inlineOption.AddAlias("-i"); + var inlineLocalOption = new Option("--inlineLocal", "Inline local $ref instances"); + inlineLocalOption.AddAlias("-il"); - var resolveExternalOption = new Option("--resolve-external", "Resolve external $refs"); - resolveExternalOption.AddAlias("-ex"); + var inlineExternalOption = new Option("--inlineExternal", "Inline external $ref instances"); + inlineExternalOption.AddAlias("-ie"); var validateCommand = new Command("validate") { @@ -68,12 +68,12 @@ static async Task Main(string[] args) filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption, - inlineOption, - resolveExternalOption, + inlineLocalOption, + inlineExternalOption }; transformCommand.SetHandler ( - OpenApiService.ProcessOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); + OpenApiService.ProcessOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineLocalOption, inlineExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); rootCommand.Add(transformCommand); rootCommand.Add(validateCommand); diff --git a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs index 644334ab4..bd8bfce76 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs @@ -70,15 +70,41 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + + if (Reference != null) { - Reference.SerializeAsV3(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV3(writer); + return; + } + else + { + target = GetEffective(Reference.HostDocument); + } } + target.SerializeAsV3WithoutReference(writer); + } - SerializeAsV3WithoutReference(writer); + /// + /// Returns an effective OpenApiCallback object based on the presence of a $ref + /// + /// The host OpenApiDocument that contains the reference. + /// OpenApiCallback + public OpenApiCallback GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } + else + { + return this; + } } + /// /// Serialize to OpenAPI V3 document without using reference. /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index 787c2972e..f7371f55c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -64,13 +64,38 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + + if (Reference != null) { - Reference.SerializeAsV3(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV3(writer); + return; + } + else + { + target = GetEffective(Reference.HostDocument); + } } + target.SerializeAsV3WithoutReference(writer); + } - SerializeAsV3WithoutReference(writer); + /// + /// Returns an effective OpenApiExample object based on the presence of a $ref + /// + /// The host OpenApiDocument that contains the reference. + /// OpenApiExample + public OpenApiExample GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } + else + { + return this; + } } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index d94681a1c..791af1d70 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -96,15 +96,42 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + + if (Reference != null) { - Reference.SerializeAsV3(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV3(writer); + return; + } + else + { + target = GetEffective(Reference.HostDocument); + } } + target.SerializeAsV3WithoutReference(writer); - SerializeAsV3WithoutReference(writer); } + /// + /// Returns an effective OpenApiHeader object based on the presence of a $ref + /// + /// The host OpenApiDocument that contains the reference. + /// OpenApiHeader + public OpenApiHeader GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } + else + { + return this; + } + } + + /// /// Serialize to OpenAPI V3 document without using reference. /// @@ -161,13 +188,21 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + + if (Reference != null) { - Reference.SerializeAsV2(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV2(writer); + return; + } + else + { + target = GetEffective(Reference.HostDocument); + } } - - SerializeAsV2WithoutReference(writer); + target.SerializeAsV2WithoutReference(writer); } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiLink.cs b/src/Microsoft.OpenApi/Models/OpenApiLink.cs index 82502f14a..1d1488ca6 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLink.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLink.cs @@ -71,15 +71,42 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + + if (Reference != null) { - Reference.SerializeAsV3(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV3(writer); + return; + } + else + { + target = GetEffective(Reference.HostDocument); + } } + target.SerializeAsV3WithoutReference(writer); - SerializeAsV3WithoutReference(writer); } + /// + /// Returns an effective OpenApiLink object based on the presence of a $ref + /// + /// The host OpenApiDocument that contains the reference. + /// OpenApiLink + public OpenApiLink GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } + else + { + return this; + } + } + + /// /// Serialize to OpenAPI V3 document without using reference. /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index ebc70465a..12f37f61a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System.Collections.Generic; +using System.Runtime; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; @@ -145,13 +146,39 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + + if (Reference != null) { - Reference.SerializeAsV3(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV3(writer); + return; + } + else + { + target = this.GetEffective(Reference.HostDocument); + } } - SerializeAsV3WithoutReference(writer); + target.SerializeAsV3WithoutReference(writer); + } + + /// + /// Returns an effective OpenApiParameter object based on the presence of a $ref + /// + /// The host OpenApiDocument that contains the reference. + /// OpenApiParameter + public OpenApiParameter GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } + else + { + return this; + } } /// @@ -216,13 +243,21 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + if (Reference != null) { - Reference.SerializeAsV2(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV2(writer); + return; + } + else + { + target = this.GetEffective(Reference.HostDocument); + } } - SerializeAsV2WithoutReference(writer); + target.SerializeAsV2WithoutReference(writer); } /// @@ -333,17 +368,6 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } - public OpenApiParameter GetEffective(OpenApiDocument doc) - { - if (this.Reference != null) - { - return doc.ResolveReferenceTo(this.Reference); - } - else - { - return this; - } - } } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs index 78e97ec18..a8fc27c27 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs @@ -74,15 +74,38 @@ public void SerializeAsV3(IOpenApiWriter writer) { throw Error.ArgumentNull(nameof(writer)); } + var target = this; - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + if (Reference != null) { - Reference.SerializeAsV3(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV3(writer); + return; + } + else + { + target = GetEffective(Reference.HostDocument); + } } + target.SerializeAsV3WithoutReference(writer); + } - SerializeAsV3WithoutReference(writer); - + /// + /// Returns an effective OpenApiPathItem object based on the presence of a $ref + /// + /// The host OpenApiDocument that contains the reference. + /// OpenApiPathItem + public OpenApiPathItem GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } + else + { + return this; + } } /// @@ -95,13 +118,22 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + + if (Reference != null) { - Reference.SerializeAsV2(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV2(writer); + return; + } + else + { + target = this.GetEffective(Reference.HostDocument); + } } - SerializeAsV2WithoutReference(writer); + target.SerializeAsV2WithoutReference(writer); } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index d6308efcf..353e294f2 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -55,13 +55,38 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } + var target = this; + if (Reference != null) { - Reference.SerializeAsV3(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV3(writer); + return; + } + else + { + target = GetEffective(Reference.HostDocument); + } } + target.SerializeAsV3WithoutReference(writer); + } - SerializeAsV3WithoutReference(writer); + /// + /// Returns an effective OpenApiRequestBody object based on the presence of a $ref + /// + /// The host OpenApiDocument that contains the reference. + /// OpenApiRequestBody + public OpenApiRequestBody GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } + else + { + return this; + } } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index ef30602b9..faf279013 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -61,13 +61,38 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + + if (Reference != null) { - Reference.SerializeAsV3(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV3(writer); + return; + } + else + { + target = GetEffective(Reference.HostDocument); + } } + target.SerializeAsV3WithoutReference(writer); + } - SerializeAsV3WithoutReference(writer); + /// + /// Returns an effective OpenApiRequestBody object based on the presence of a $ref + /// + /// The host OpenApiDocument that contains the reference. + /// OpenApiResponse + public OpenApiResponse GetEffective(OpenApiDocument doc) + { + if (this.Reference != null) + { + return doc.ResolveReferenceTo(this.Reference); + } + else + { + return this; + } } /// @@ -105,13 +130,21 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null && !writer.GetSettings().ShouldInlineReference(Reference)) + var target = this; + + if (Reference != null) { - Reference.SerializeAsV2(writer); - return; + if (!writer.GetSettings().ShouldInlineReference(Reference)) + { + Reference.SerializeAsV2(writer); + return; + } + else + { + target = GetEffective(Reference.HostDocument); + } } - - SerializeAsV2WithoutReference(writer); + target.SerializeAsV2WithoutReference(writer); } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index c98c32c17..74aed7da1 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -451,14 +451,23 @@ internal void SerializeAsV2( throw Error.ArgumentNull(nameof(writer)); } + var settings = writer.GetSettings(); + var target = this; + if (Reference != null) { - var settings = writer.GetSettings(); if (!settings.ShouldInlineReference(Reference)) { Reference.SerializeAsV2(writer); return; } + else + { + if (Reference.IsExternal) // Temporary until v2 + { + target = this.GetEffective(Reference.HostDocument); + } + } // If Loop is detected then just Serialize as a reference. if (!settings.LoopDetector.PushLoop(this)) @@ -475,7 +484,7 @@ internal void SerializeAsV2( parentRequiredProperties = new HashSet(); } - SerializeAsV2WithoutReference(writer, parentRequiredProperties, propertyName); + target.SerializeAsV2WithoutReference(writer, parentRequiredProperties, propertyName); } /// @@ -676,6 +685,11 @@ internal void WriteAsSchemaProperties( writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); } + /// + /// Returns an effective OpenApiSchema object based on the presence of a $ref + /// + /// The host OpenApiDocument that contains the reference. + /// OpenApiSchema public OpenApiSchema GetEffective(OpenApiDocument doc) { if (this.Reference != null) From d7616ee3f5dc44f1dc5957748a96d52ebd6de836 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Mon, 21 Feb 2022 22:27:15 -0500 Subject: [PATCH 07/49] Added IEffective interfaces to models that have references --- .../Models/OpenApiCallback.cs | 2 +- .../Models/OpenApiExample.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiHeader.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiLink.cs | 2 +- .../Models/OpenApiPathItem.cs | 2 +- .../Models/OpenApiRequestBody.cs | 2 +- .../Models/OpenApiResponse.cs | 2 +- .../PublicApi/PublicApi.approved.txt | 39 ++++++++++++------- 8 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs index bd8bfce76..57aa3c888 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs @@ -12,7 +12,7 @@ namespace Microsoft.OpenApi.Models /// /// Callback Object: A map of possible out-of band callbacks related to the parent operation. /// - public class OpenApiCallback : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible + public class OpenApiCallback : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible, IEffective { /// /// A Path Item Object used to define a callback request and expected responses. diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index f7371f55c..d4c268584 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -11,7 +11,7 @@ namespace Microsoft.OpenApi.Models /// /// Example Object. /// - public class OpenApiExample : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible + public class OpenApiExample : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible, IEffective { /// /// Short description for the example. diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index 791af1d70..e8576a0ca 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -13,7 +13,7 @@ namespace Microsoft.OpenApi.Models /// Header Object. /// The Header Object follows the structure of the Parameter Object. /// - public class OpenApiHeader : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible + public class OpenApiHeader : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible, IEffective { /// /// Indicates if object is populated with data or is just a reference to the data diff --git a/src/Microsoft.OpenApi/Models/OpenApiLink.cs b/src/Microsoft.OpenApi/Models/OpenApiLink.cs index 1d1488ca6..f5acb0d3f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLink.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLink.cs @@ -11,7 +11,7 @@ namespace Microsoft.OpenApi.Models /// /// Link Object. /// - public class OpenApiLink : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible + public class OpenApiLink : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible, IEffective { /// /// A relative or absolute reference to an OAS operation. diff --git a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs index a8fc27c27..375f1f034 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs @@ -11,7 +11,7 @@ namespace Microsoft.OpenApi.Models /// /// Path Item Object: to describe the operations available on a single path. /// - public class OpenApiPathItem : IOpenApiSerializable, IOpenApiExtensible, IOpenApiReferenceable + public class OpenApiPathItem : IOpenApiSerializable, IOpenApiExtensible, IOpenApiReferenceable, IEffective { /// /// An optional, string summary, intended to apply to all operations in this path. diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index 353e294f2..8a65f1fde 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -11,7 +11,7 @@ namespace Microsoft.OpenApi.Models /// /// Request Body Object /// - public class OpenApiRequestBody : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible + public class OpenApiRequestBody : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible, IEffective { /// /// Indicates if object is populated with data or is just a reference to the data diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index faf279013..0a31ca0a4 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -11,7 +11,7 @@ namespace Microsoft.OpenApi.Models /// /// Response object. /// - public class OpenApiResponse : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible + public class OpenApiResponse : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible, IEffective { /// /// REQUIRED. A short description of the response. diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 484d64dba..43900109b 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -271,6 +271,11 @@ namespace Microsoft.OpenApi.Extensions } namespace Microsoft.OpenApi.Interfaces { + public interface IEffective + where T : class, Microsoft.OpenApi.Interfaces.IOpenApiElement + { + T GetEffective(Microsoft.OpenApi.Models.OpenApiDocument document); + } public interface IOpenApiElement { } public interface IOpenApiExtensible : Microsoft.OpenApi.Interfaces.IOpenApiElement { @@ -315,7 +320,7 @@ namespace Microsoft.OpenApi } namespace Microsoft.OpenApi.Models { - public class OpenApiCallback : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiCallback : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiCallback() { } public System.Collections.Generic.IDictionary Extensions { get; set; } @@ -323,6 +328,7 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } public bool UnresolvedReference { get; set; } public void AddPathItem(Microsoft.OpenApi.Expressions.RuntimeExpression expression, Microsoft.OpenApi.Models.OpenApiPathItem pathItem) { } + public Microsoft.OpenApi.Models.OpenApiCallback GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -500,9 +506,7 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IList Servers { get; set; } public System.Collections.Generic.IList Tags { get; set; } public Microsoft.OpenApi.Services.OpenApiWorkspace Workspace { get; set; } - public Microsoft.OpenApi.Interfaces.IOpenApiReferenceable ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } - public Microsoft.OpenApi.Interfaces.IOpenApiReferenceable ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference, bool useExternal) { } - public System.Collections.Generic.IEnumerable ResolveReferences(bool useExternal = false) { } + public System.Collections.Generic.IEnumerable ResolveReferences() { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } @@ -526,7 +530,7 @@ namespace Microsoft.OpenApi.Models public string Pointer { get; set; } public override string ToString() { } } - public class OpenApiExample : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiExample : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiExample() { } public string Description { get; set; } @@ -536,6 +540,7 @@ namespace Microsoft.OpenApi.Models public string Summary { get; set; } public bool UnresolvedReference { get; set; } public Microsoft.OpenApi.Any.IOpenApiAny Value { get; set; } + public Microsoft.OpenApi.Models.OpenApiExample GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -558,7 +563,7 @@ namespace Microsoft.OpenApi.Models public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiHeader : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiHeader : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiHeader() { } public bool AllowEmptyValue { get; set; } @@ -575,6 +580,7 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Models.OpenApiSchema Schema { get; set; } public Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } public bool UnresolvedReference { get; set; } + public Microsoft.OpenApi.Models.OpenApiHeader GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -602,7 +608,7 @@ namespace Microsoft.OpenApi.Models public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiLink : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiLink : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiLink() { } public string Description { get; set; } @@ -614,6 +620,7 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Models.RuntimeExpressionAnyWrapper RequestBody { get; set; } public Microsoft.OpenApi.Models.OpenApiServer Server { get; set; } public bool UnresolvedReference { get; set; } + public Microsoft.OpenApi.Models.OpenApiLink GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -672,7 +679,7 @@ namespace Microsoft.OpenApi.Models public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiParameter : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiParameter : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiParameter() { } public bool AllowEmptyValue { get; set; } @@ -691,12 +698,13 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Models.OpenApiSchema Schema { get; set; } public Microsoft.OpenApi.Models.ParameterStyle? Style { get; set; } public bool UnresolvedReference { get; set; } + public Microsoft.OpenApi.Models.OpenApiParameter GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiPathItem : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiPathItem : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiPathItem() { } public string Description { get; set; } @@ -708,6 +716,7 @@ namespace Microsoft.OpenApi.Models public string Summary { get; set; } public bool UnresolvedReference { get; set; } public void AddOperation(Microsoft.OpenApi.Models.OperationType operationType, Microsoft.OpenApi.Models.OpenApiOperation operation) { } + public Microsoft.OpenApi.Models.OpenApiPathItem GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -721,6 +730,7 @@ namespace Microsoft.OpenApi.Models { public OpenApiReference() { } public string ExternalResource { get; set; } + public Microsoft.OpenApi.Models.OpenApiDocument HostDocument { get; set; } public string Id { get; set; } public bool IsExternal { get; } public bool IsLocal { get; } @@ -730,7 +740,7 @@ namespace Microsoft.OpenApi.Models public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiRequestBody : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiRequestBody : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiRequestBody() { } public System.Collections.Generic.IDictionary Content { get; set; } @@ -739,12 +749,13 @@ namespace Microsoft.OpenApi.Models public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } public bool Required { get; set; } public bool UnresolvedReference { get; set; } + public Microsoft.OpenApi.Models.OpenApiRequestBody GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } } - public class OpenApiResponse : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiResponse : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiResponse() { } public System.Collections.Generic.IDictionary Content { get; set; } @@ -754,6 +765,7 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IDictionary Links { get; set; } public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } public bool UnresolvedReference { get; set; } + public Microsoft.OpenApi.Models.OpenApiResponse GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -763,7 +775,7 @@ namespace Microsoft.OpenApi.Models { public OpenApiResponses() { } } - public class OpenApiSchema : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable + public class OpenApiSchema : Microsoft.OpenApi.Interfaces.IEffective, Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiExtensible, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { public OpenApiSchema() { } public Microsoft.OpenApi.Models.OpenApiSchema AdditionalProperties { get; set; } @@ -805,6 +817,7 @@ namespace Microsoft.OpenApi.Models public bool UnresolvedReference { get; set; } public bool WriteOnly { get; set; } public Microsoft.OpenApi.Models.OpenApiXml Xml { get; set; } + public Microsoft.OpenApi.Models.OpenApiSchema GetEffective(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV2WithoutReference(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -1206,7 +1219,7 @@ namespace Microsoft.OpenApi.Validations.Rules public static Microsoft.OpenApi.Validations.ValidationRule ResponsesMustBeIdentifiedByDefaultOrStatusCode { get; } public static Microsoft.OpenApi.Validations.ValidationRule ResponsesMustContainAtLeastOneResponse { get; } } - [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.All, AllowMultiple=false, Inherited=false)] + [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=false, Inherited=false)] public class OpenApiRuleAttribute : System.Attribute { public OpenApiRuleAttribute() { } From e34a4f9851b0fdc6bd049d600a20ff5a15ebe30b Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Mon, 21 Feb 2022 22:34:19 -0500 Subject: [PATCH 08/49] Removed redundant using --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index e813e72a4..fb785f9e1 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -12,7 +12,6 @@ using System.Text; using System.Threading.Tasks; using System.Text.Json; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using System.Xml.Linq; using Microsoft.OData.Edm.Csdl; From 5034952d506ae08837ca75d73fb71940eda070b9 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Thu, 3 Mar 2022 18:58:39 +0300 Subject: [PATCH 09/49] Update cmd parameter to accept string values for OpenApi spec version --- src/Microsoft.OpenApi.Hidi/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index 95e6f63f2..4fed0cb44 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -25,7 +25,7 @@ static async Task Main(string[] args) var outputOption = new Option("--output", () => new FileInfo("./output"), "The output directory path for the generated file.") { Arity = ArgumentArity.ZeroOrOne }; outputOption.AddAlias("-o"); - var versionOption = new Option("--version", "OpenAPI specification version"); + var versionOption = new Option("--version", "OpenAPI specification version"); versionOption.AddAlias("-v"); var formatOption = new Option("--format", "File format"); @@ -72,7 +72,7 @@ static async Task Main(string[] args) resolveExternalOption, }; - transformCommand.SetHandler ( + transformCommand.SetHandler ( OpenApiService.ProcessOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); rootCommand.Add(transformCommand); From 5fbb5683195a2879374c8df0588fa3373b8d44bb Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Thu, 3 Mar 2022 19:00:54 +0300 Subject: [PATCH 10/49] Cast string to OpenApiSpecVersion enum value --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 12 ++++--- .../OpenApiSpecVersionExtension.cs | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionExtension.cs diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 1f86e3c06..77ddd08c0 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -22,6 +22,7 @@ using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations; using Microsoft.OpenApi.Writers; +using static Microsoft.OpenApi.Hidi.OpenApiSpecVersionExtension; namespace Microsoft.OpenApi.Hidi { @@ -31,7 +32,7 @@ public static async Task ProcessOpenApiDocument( string openapi, string csdl, FileInfo output, - OpenApiSpecVersion? version, + string? version, OpenApiFormat? format, LogLevel loglevel, bool inline, @@ -83,13 +84,14 @@ string filterbycollection Stream stream; OpenApiDocument document; OpenApiFormat openApiFormat; + OpenApiSpecVersion? openApiVersion = null; var stopwatch = new Stopwatch(); if (!string.IsNullOrEmpty(csdl)) { // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion openApiFormat = format ?? GetOpenApiFormat(csdl, logger); - version ??= OpenApiSpecVersion.OpenApi3_0; + openApiVersion = version.TryParseOpenApiSpecVersion(); stream = await GetStream(csdl, logger); document = await ConvertCsdlToOpenApi(stream); @@ -128,7 +130,7 @@ string filterbycollection } openApiFormat = format ?? GetOpenApiFormat(openapi, logger); - version ??= result.OpenApiDiagnostic.SpecificationVersion; + openApiVersion ??= result.OpenApiDiagnostic.SpecificationVersion; } Func predicate; @@ -185,14 +187,14 @@ string filterbycollection logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer"); stopwatch.Start(); - document.Serialize(writer, (OpenApiSpecVersion)version); + document.Serialize(writer, (OpenApiSpecVersion)openApiVersion); stopwatch.Stop(); logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms"); textWriter.Flush(); } - + /// /// Converts CSDL to OpenAPI /// diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionExtension.cs b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionExtension.cs new file mode 100644 index 000000000..9b877099e --- /dev/null +++ b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionExtension.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Linq; + +namespace Microsoft.OpenApi.Hidi +{ + public static class OpenApiSpecVersionExtension + { + public static OpenApiSpecVersion TryParseOpenApiSpecVersion(this string value) + { + if (string.IsNullOrEmpty(value)) + { + throw new InvalidOperationException("Please provide a version"); + } + var res = value.Split('.', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); + + if (int.TryParse(res, out int result)) + { + + if (result >= 2 || result <= 3) + { + return (OpenApiSpecVersion)result; + } + } + + return OpenApiSpecVersion.OpenApi3_0; // default + } + } +} From da118bf71cfe8899f22b9a771dcec15e26206a92 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Mon, 7 Mar 2022 12:06:25 +0300 Subject: [PATCH 11/49] Refactor logic to accept a string as a regular param and rename the extension class --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 4 ++-- ...rsionExtension.cs => OpenApiSpecVersionHelper.cs} | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) rename src/Microsoft.OpenApi.Hidi/{OpenApiSpecVersionExtension.cs => OpenApiSpecVersionHelper.cs} (76%) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 77ddd08c0..952dc0b94 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -22,7 +22,7 @@ using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations; using Microsoft.OpenApi.Writers; -using static Microsoft.OpenApi.Hidi.OpenApiSpecVersionExtension; +using static Microsoft.OpenApi.Hidi.OpenApiSpecVersionHelper; namespace Microsoft.OpenApi.Hidi { @@ -91,7 +91,7 @@ string filterbycollection { // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion openApiFormat = format ?? GetOpenApiFormat(csdl, logger); - openApiVersion = version.TryParseOpenApiSpecVersion(); + openApiVersion = TryParseOpenApiSpecVersion(version); stream = await GetStream(csdl, logger); document = await ConvertCsdlToOpenApi(stream); diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionExtension.cs b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs similarity index 76% rename from src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionExtension.cs rename to src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs index 9b877099e..a78255be2 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionExtension.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs @@ -6,24 +6,24 @@ namespace Microsoft.OpenApi.Hidi { - public static class OpenApiSpecVersionExtension + public static class OpenApiSpecVersionHelper { - public static OpenApiSpecVersion TryParseOpenApiSpecVersion(this string value) + public static OpenApiSpecVersion TryParseOpenApiSpecVersion(string value) { if (string.IsNullOrEmpty(value)) { throw new InvalidOperationException("Please provide a version"); } var res = value.Split('.', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); - + if (int.TryParse(res, out int result)) { - if (result >= 2 || result <= 3) + if (result >= 2 && result < 3) { - return (OpenApiSpecVersion)result; + return OpenApiSpecVersion.OpenApi2_0; } - } + } return OpenApiSpecVersion.OpenApi3_0; // default } From 24daf0ceab2bccb8017d2ef78a980f6a054df443 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 8 Mar 2022 14:55:49 +0300 Subject: [PATCH 12/49] Better error handling --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 58 ++++++++++++++------ src/Microsoft.OpenApi.Hidi/Program.cs | 5 +- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 1f86e3c06..def1afb60 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -22,6 +22,7 @@ using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations; using Microsoft.OpenApi.Writers; +using System.Threading; namespace Microsoft.OpenApi.Hidi { @@ -38,7 +39,8 @@ public static async Task ProcessOpenApiDocument( bool resolveexternal, string filterbyoperationids, string filterbytags, - string filterbycollection + string filterbycollection, + CancellationToken cancellationToken ) { var logger = ConfigureLoggerInstance(loglevel); @@ -52,7 +54,11 @@ string filterbycollection } catch (ArgumentNullException ex) { - logger.LogError(ex.Message); +#if DEBUG + logger.LogCritical(ex, ex.Message); +#else + logger.LogCritical(ex.Message); +#endif return; } try @@ -64,19 +70,27 @@ string filterbycollection } catch (ArgumentException ex) { - logger.LogError(ex.Message); +#if DEBUG + logger.LogCritical(ex, ex.Message); +#else + logger.LogCritical(ex.Message); +#endif return; } try { if (output.Exists) { - throw new IOException("The file you're writing to already exists. Please input a new file path."); + throw new IOException($"The file {output} already exists. Please input a new file path."); } } catch (IOException ex) { - logger.LogError(ex.Message); +#if DEBUG + logger.LogCritical(ex, ex.Message); +#else + logger.LogCritical(ex.Message); +#endif return; } @@ -91,12 +105,12 @@ string filterbycollection openApiFormat = format ?? GetOpenApiFormat(csdl, logger); version ??= OpenApiSpecVersion.OpenApi3_0; - stream = await GetStream(csdl, logger); + stream = await GetStream(csdl, logger, cancellationToken); document = await ConvertCsdlToOpenApi(stream); } else { - stream = await GetStream(openapi, logger); + stream = await GetStream(openapi, logger, cancellationToken); // Parsing OpenAPI file stopwatch.Start(); @@ -156,7 +170,7 @@ string filterbycollection } if (!string.IsNullOrEmpty(filterbycollection)) { - var fileStream = await GetStream(filterbycollection, logger); + var fileStream = await GetStream(filterbycollection, logger, cancellationToken); var requestUrls = ParseJsonCollectionFile(fileStream, logger); logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection."); @@ -245,7 +259,7 @@ public static OpenApiDocument FixReferences(OpenApiDocument document) return doc; } - private static async Task GetStream(string input, ILogger logger) + private static async Task GetStream(string input, ILogger logger, CancellationToken cancellationToken) { var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -263,11 +277,15 @@ private static async Task GetStream(string input, ILogger logger) { DefaultRequestVersion = HttpVersion.Version20 }; - stream = await httpClient.GetStreamAsync(input); + stream = await httpClient.GetStreamAsync(input, cancellationToken); } catch (HttpRequestException ex) { - logger.LogError($"Could not download the file at {input}, reason{ex}"); +#if DEBUG + logger.LogCritical(ex, $"Could not download the file at {input}, reason: {ex.Message}"); +#else + logger.LogCritical($"Could not download the file at {input}, reason: {ex.Message}", input, ex.Message); +#endif return null; } } @@ -286,7 +304,11 @@ ex is UnauthorizedAccessException || ex is SecurityException || ex is NotSupportedException) { - logger.LogError($"Could not open the file at {input}, reason: {ex.Message}"); +#if DEBUG + logger.LogCritical(ex, $"Could not open the file at {input}, reason: {ex.Message}"); +#else + logger.LogCritical($"Could not open the file at {input}, reason: {ex.Message}"); +#endif return null; } } @@ -327,14 +349,14 @@ public static Dictionary> ParseJsonCollectionFile(Stream st return requestUrls; } - internal static async Task ValidateOpenApiDocument(string openapi, LogLevel loglevel) + internal static async Task ValidateOpenApiDocument(string openapi, LogLevel loglevel, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(openapi)) { throw new ArgumentNullException(nameof(openapi)); } var logger = ConfigureLoggerInstance(loglevel); - var stream = await GetStream(openapi, logger); + var stream = await GetStream(openapi, logger, cancellationToken); OpenApiDocument document; logger.LogTrace("Parsing the OpenApi file"); @@ -369,16 +391,16 @@ private static OpenApiFormat GetOpenApiFormat(string input, ILogger logger) private static ILogger ConfigureLoggerInstance(LogLevel loglevel) { // Configure logger options - #if DEBUG +#if DEBUG loglevel = loglevel > LogLevel.Debug ? LogLevel.Debug : loglevel; - #endif +#endif var logger = LoggerFactory.Create((builder) => { builder .AddConsole() - #if DEBUG +#if DEBUG .AddDebug() - #endif +#endif .SetMinimumLevel(loglevel); }).CreateLogger(); diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index 95e6f63f2..f3d455e4a 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -3,6 +3,7 @@ using System.CommandLine; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -55,7 +56,7 @@ static async Task Main(string[] args) logLevelOption }; - validateCommand.SetHandler(OpenApiService.ValidateOpenApiDocument, descriptionOption, logLevelOption); + validateCommand.SetHandler(OpenApiService.ValidateOpenApiDocument, descriptionOption, logLevelOption); var transformCommand = new Command("transform") { @@ -72,7 +73,7 @@ static async Task Main(string[] args) resolveExternalOption, }; - transformCommand.SetHandler ( + transformCommand.SetHandler ( OpenApiService.ProcessOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); rootCommand.Add(transformCommand); From 0e39178616322b24862fd6d46debe5e37765f2f2 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 8 Mar 2022 16:43:02 +0300 Subject: [PATCH 13/49] Remove string interpolation --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index def1afb60..7b94c4997 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -128,10 +128,13 @@ CancellationToken cancellationToken var context = result.OpenApiDiagnostic; if (context.Errors.Count > 0) { + logger.LogTrace("{timestamp}ms: Parsed OpenAPI with errors. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count); + var errorReport = new StringBuilder(); foreach (var error in context.Errors) { + logger.LogError("OpenApi Parsing error: {message}", error.ToString()); errorReport.AppendLine(error.ToString()); } logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}"); @@ -282,9 +285,9 @@ private static async Task GetStream(string input, ILogger logger, Cancel catch (HttpRequestException ex) { #if DEBUG - logger.LogCritical(ex, $"Could not download the file at {input}, reason: {ex.Message}"); + logger.LogCritical(ex, "Could not download the file at {inputPath}, reason: {exMessage}", input, ex.Message); #else - logger.LogCritical($"Could not download the file at {input}, reason: {ex.Message}", input, ex.Message); + logger.LogCritical( "Could not download the file at {inputPath}, reason: {exMessage}", input, ex.Message); #endif return null; } @@ -305,9 +308,9 @@ ex is SecurityException || ex is NotSupportedException) { #if DEBUG - logger.LogCritical(ex, $"Could not open the file at {input}, reason: {ex.Message}"); + logger.LogCritical(ex, "Could not open the file at {inputPath}, reason: {exMessage}", input, ex.Message); #else - logger.LogCritical($"Could not open the file at {input}, reason: {ex.Message}"); + logger.LogCritical("Could not open the file at {inputPath}, reason: {exMessage}", input, ex.Message); #endif return null; } From 7128fb235107e1a589ff338c5fcdef11b6dfc7c2 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 8 Mar 2022 16:54:37 +0300 Subject: [PATCH 14/49] Add a try catch block to catch any exceptions thrown during document validation --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 56 ++++++++++++-------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 7b94c4997..64c228387 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -354,35 +354,49 @@ public static Dictionary> ParseJsonCollectionFile(Stream st internal static async Task ValidateOpenApiDocument(string openapi, LogLevel loglevel, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(openapi)) - { - throw new ArgumentNullException(nameof(openapi)); - } var logger = ConfigureLoggerInstance(loglevel); - var stream = await GetStream(openapi, logger, cancellationToken); - OpenApiDocument document; - logger.LogTrace("Parsing the OpenApi file"); - document = new OpenApiStreamReader(new OpenApiReaderSettings + try { - RuleSet = ValidationRuleSet.GetDefaultRuleSet() - } - ).Read(stream, out var context); + if (string.IsNullOrEmpty(openapi)) + { + throw new ArgumentNullException(nameof(openapi)); + } + var stream = await GetStream(openapi, logger, cancellationToken); - if (context.Errors.Count != 0) - { - foreach (var error in context.Errors) + OpenApiDocument document; + logger.LogTrace("Parsing the OpenApi file"); + document = new OpenApiStreamReader(new OpenApiReaderSettings { - Console.WriteLine(error.ToString()); + RuleSet = ValidationRuleSet.GetDefaultRuleSet() + } + ).Read(stream, out var context); + + if (context.Errors.Count != 0) + { + foreach (var error in context.Errors) + { + logger.LogError("OpenApi Parsing error: {message}", error.ToString()); + Console.WriteLine(error.ToString()); + } } - } - var statsVisitor = new StatsVisitor(); - var walker = new OpenApiWalker(statsVisitor); - walker.Walk(document); + var statsVisitor = new StatsVisitor(); + var walker = new OpenApiWalker(statsVisitor); + walker.Walk(document); + + logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); + Console.WriteLine(statsVisitor.GetStatisticsReport()); + } + catch(Exception ex) + { +#if DEBUG + logger.LogCritical(ex, ex.Message); +#else + logger.LogCritical(ex.Message); +#endif + } - logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); - Console.WriteLine(statsVisitor.GetStatisticsReport()); } private static OpenApiFormat GetOpenApiFormat(string input, ILogger logger) From 37ecce28f8459a5b25987d6a5f71c2ade9677030 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 8 Mar 2022 17:49:47 +0300 Subject: [PATCH 15/49] Clean up code --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 30 ++------------------ 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 64c228387..09ad21d90 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -49,42 +49,18 @@ CancellationToken cancellationToken { if (string.IsNullOrEmpty(openapi) && string.IsNullOrEmpty(csdl)) { - throw new ArgumentNullException("Please input a file path"); + throw new ArgumentException("Please input a file path"); } - } - catch (ArgumentNullException ex) - { -#if DEBUG - logger.LogCritical(ex, ex.Message); -#else - logger.LogCritical(ex.Message); -#endif - return; - } - try - { if(output == null) { - throw new ArgumentException(nameof(output)); + throw new ArgumentNullException(nameof(output)); } - } - catch (ArgumentException ex) - { -#if DEBUG - logger.LogCritical(ex, ex.Message); -#else - logger.LogCritical(ex.Message); -#endif - return; - } - try - { if (output.Exists) { throw new IOException($"The file {output} already exists. Please input a new file path."); } } - catch (IOException ex) + catch (Exception ex) { #if DEBUG logger.LogCritical(ex, ex.Message); From 7defeda67fa9bb2a2efb6bfe93fd4b1a8d0897b9 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 8 Mar 2022 20:47:58 +0300 Subject: [PATCH 16/49] Add a --clean-output parameter for overwriting existing files --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 5 +++++ src/Microsoft.OpenApi.Hidi/Program.cs | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 1f86e3c06..9e50debf2 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -31,6 +31,7 @@ public static async Task ProcessOpenApiDocument( string openapi, string csdl, FileInfo output, + bool cleanoutput, OpenApiSpecVersion? version, OpenApiFormat? format, LogLevel loglevel, @@ -69,6 +70,10 @@ string filterbycollection } try { + if (cleanoutput) + { + output.Delete(); + } if (output.Exists) { throw new IOException("The file you're writing to already exists. Please input a new file path."); diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index 95e6f63f2..960031ded 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -25,6 +25,9 @@ static async Task Main(string[] args) var outputOption = new Option("--output", () => new FileInfo("./output"), "The output directory path for the generated file.") { Arity = ArgumentArity.ZeroOrOne }; outputOption.AddAlias("-o"); + var cleanOutputOption = new Option("--clean-output", "Overwrite an existing file"); + cleanOutputOption.AddAlias("-co"); + var versionOption = new Option("--version", "OpenAPI specification version"); versionOption.AddAlias("-v"); @@ -62,6 +65,7 @@ static async Task Main(string[] args) descriptionOption, csdlOption, outputOption, + cleanOutputOption, versionOption, formatOption, logLevelOption, @@ -72,8 +76,8 @@ static async Task Main(string[] args) resolveExternalOption, }; - transformCommand.SetHandler ( - OpenApiService.ProcessOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); + transformCommand.SetHandler ( + OpenApiService.ProcessOpenApiDocument, descriptionOption, csdlOption, outputOption, cleanOutputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); rootCommand.Add(transformCommand); rootCommand.Add(validateCommand); From f683b7212d9a3a0fa6228c61b263eec1b9258ae9 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 8 Mar 2022 21:03:34 +0300 Subject: [PATCH 17/49] Add an exit statement and use logger to log errors to the console --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 09ad21d90..ec53b615f 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -353,7 +353,6 @@ internal static async Task ValidateOpenApiDocument(string openapi, LogLevel logl foreach (var error in context.Errors) { logger.LogError("OpenApi Parsing error: {message}", error.ToString()); - Console.WriteLine(error.ToString()); } } @@ -362,7 +361,7 @@ internal static async Task ValidateOpenApiDocument(string openapi, LogLevel logl walker.Walk(document); logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); - Console.WriteLine(statsVisitor.GetStatisticsReport()); + logger.LogInformation(statsVisitor.GetStatisticsReport()); } catch(Exception ex) { @@ -371,6 +370,7 @@ internal static async Task ValidateOpenApiDocument(string openapi, LogLevel logl #else logger.LogCritical(ex.Message); #endif + return; } } From abd6f508866c9250b292f3e51c681e5d39fdb996 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 8 Mar 2022 21:32:17 +0300 Subject: [PATCH 18/49] Clean up code to bubble up exceptions to the global catch block --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 216 +++++++++---------- 1 file changed, 103 insertions(+), 113 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index ec53b615f..11fe6decc 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -59,131 +59,131 @@ CancellationToken cancellationToken { throw new IOException($"The file {output} already exists. Please input a new file path."); } - } - catch (Exception ex) - { -#if DEBUG - logger.LogCritical(ex, ex.Message); -#else - logger.LogCritical(ex.Message); -#endif - return; - } - Stream stream; - OpenApiDocument document; - OpenApiFormat openApiFormat; - var stopwatch = new Stopwatch(); - - if (!string.IsNullOrEmpty(csdl)) - { - // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion - openApiFormat = format ?? GetOpenApiFormat(csdl, logger); - version ??= OpenApiSpecVersion.OpenApi3_0; - - stream = await GetStream(csdl, logger, cancellationToken); - document = await ConvertCsdlToOpenApi(stream); - } - else - { - stream = await GetStream(openapi, logger, cancellationToken); + Stream stream; + OpenApiDocument document; + OpenApiFormat openApiFormat; + var stopwatch = new Stopwatch(); - // Parsing OpenAPI file - stopwatch.Start(); - logger.LogTrace("Parsing OpenApi file"); - var result = new OpenApiStreamReader(new OpenApiReaderSettings + if (!string.IsNullOrEmpty(csdl)) { - ReferenceResolution = resolveexternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, - RuleSet = ValidationRuleSet.GetDefaultRuleSet() + // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion + openApiFormat = format ?? GetOpenApiFormat(csdl, logger); + version ??= OpenApiSpecVersion.OpenApi3_0; + + stream = await GetStream(csdl, logger, cancellationToken); + document = await ConvertCsdlToOpenApi(stream); } - ).ReadAsync(stream).GetAwaiter().GetResult(); + else + { + stream = await GetStream(openapi, logger, cancellationToken); - document = result.OpenApiDocument; - stopwatch.Stop(); + // Parsing OpenAPI file + stopwatch.Start(); + logger.LogTrace("Parsing OpenApi file"); + var result = new OpenApiStreamReader(new OpenApiReaderSettings + { + ReferenceResolution = resolveexternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, + RuleSet = ValidationRuleSet.GetDefaultRuleSet() + } + ).ReadAsync(stream).GetAwaiter().GetResult(); - var context = result.OpenApiDiagnostic; - if (context.Errors.Count > 0) - { - logger.LogTrace("{timestamp}ms: Parsed OpenAPI with errors. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count); + document = result.OpenApiDocument; + stopwatch.Stop(); - var errorReport = new StringBuilder(); + var context = result.OpenApiDiagnostic; + if (context.Errors.Count > 0) + { + logger.LogTrace("{timestamp}ms: Parsed OpenAPI with errors. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count); - foreach (var error in context.Errors) + var errorReport = new StringBuilder(); + + foreach (var error in context.Errors) + { + logger.LogError("OpenApi Parsing error: {message}", error.ToString()); + errorReport.AppendLine(error.ToString()); + } + logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}"); + } + else { - logger.LogError("OpenApi Parsing error: {message}", error.ToString()); - errorReport.AppendLine(error.ToString()); + logger.LogTrace("{timestamp}ms: Parsed OpenApi successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count); } - logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}"); + + openApiFormat = format ?? GetOpenApiFormat(openapi, logger); + version ??= result.OpenApiDiagnostic.SpecificationVersion; } - else + + Func predicate; + + // Check if filter options are provided, then slice the OpenAPI document + if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags)) { - logger.LogTrace("{timestamp}ms: Parsed OpenApi successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count); + throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time."); } + if (!string.IsNullOrEmpty(filterbyoperationids)) + { + logger.LogTrace("Creating predicate based on the operationIds supplied."); + predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids); - openApiFormat = format ?? GetOpenApiFormat(openapi, logger); - version ??= result.OpenApiDiagnostic.SpecificationVersion; - } - - Func predicate; + logger.LogTrace("Creating subset OpenApi document."); + document = OpenApiFilterService.CreateFilteredDocument(document, predicate); + } + if (!string.IsNullOrEmpty(filterbytags)) + { + logger.LogTrace("Creating predicate based on the tags supplied."); + predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags); - // Check if filter options are provided, then slice the OpenAPI document - if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags)) - { - throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time."); - } - if (!string.IsNullOrEmpty(filterbyoperationids)) - { - logger.LogTrace("Creating predicate based on the operationIds supplied."); - predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids); + logger.LogTrace("Creating subset OpenApi document."); + document = OpenApiFilterService.CreateFilteredDocument(document, predicate); + } + if (!string.IsNullOrEmpty(filterbycollection)) + { + var fileStream = await GetStream(filterbycollection, logger, cancellationToken); + var requestUrls = ParseJsonCollectionFile(fileStream, logger); - logger.LogTrace("Creating subset OpenApi document."); - document = OpenApiFilterService.CreateFilteredDocument(document, predicate); - } - if (!string.IsNullOrEmpty(filterbytags)) - { - logger.LogTrace("Creating predicate based on the tags supplied."); - predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags); + logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection."); + predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source: document); - logger.LogTrace("Creating subset OpenApi document."); - document = OpenApiFilterService.CreateFilteredDocument(document, predicate); - } - if (!string.IsNullOrEmpty(filterbycollection)) - { - var fileStream = await GetStream(filterbycollection, logger, cancellationToken); - var requestUrls = ParseJsonCollectionFile(fileStream, logger); + logger.LogTrace("Creating subset OpenApi document."); + document = OpenApiFilterService.CreateFilteredDocument(document, predicate); + } - logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection."); - predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source:document); + logger.LogTrace("Creating a new file"); + using var outputStream = output?.Create(); + var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out; - logger.LogTrace("Creating subset OpenApi document."); - document = OpenApiFilterService.CreateFilteredDocument(document, predicate); - } - - logger.LogTrace("Creating a new file"); - using var outputStream = output?.Create(); - var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out; + var settings = new OpenApiWriterSettings() + { + ReferenceInline = inline ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences + }; - var settings = new OpenApiWriterSettings() - { - ReferenceInline = inline ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences - }; + IOpenApiWriter writer = openApiFormat switch + { + OpenApiFormat.Json => new OpenApiJsonWriter(textWriter, settings), + OpenApiFormat.Yaml => new OpenApiYamlWriter(textWriter, settings), + _ => throw new ArgumentException("Unknown format"), + }; - IOpenApiWriter writer = openApiFormat switch - { - OpenApiFormat.Json => new OpenApiJsonWriter(textWriter, settings), - OpenApiFormat.Yaml => new OpenApiYamlWriter(textWriter, settings), - _ => throw new ArgumentException("Unknown format"), - }; + logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer"); - logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer"); - - stopwatch.Start(); - document.Serialize(writer, (OpenApiSpecVersion)version); - stopwatch.Stop(); + stopwatch.Start(); + document.Serialize(writer, (OpenApiSpecVersion)version); + stopwatch.Stop(); - logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms"); + logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms"); - textWriter.Flush(); + textWriter.Flush(); + } + catch (Exception ex) + { +#if DEBUG + logger.LogCritical(ex, ex.Message); +#else + logger.LogCritical(ex.Message); +#endif + return; + } } /// @@ -260,12 +260,7 @@ private static async Task GetStream(string input, ILogger logger, Cancel } catch (HttpRequestException ex) { -#if DEBUG - logger.LogCritical(ex, "Could not download the file at {inputPath}, reason: {exMessage}", input, ex.Message); -#else - logger.LogCritical( "Could not download the file at {inputPath}, reason: {exMessage}", input, ex.Message); -#endif - return null; + throw new InvalidOperationException($"Could not download the file at {input}", ex); } } else @@ -283,12 +278,7 @@ ex is UnauthorizedAccessException || ex is SecurityException || ex is NotSupportedException) { -#if DEBUG - logger.LogCritical(ex, "Could not open the file at {inputPath}, reason: {exMessage}", input, ex.Message); -#else - logger.LogCritical("Could not open the file at {inputPath}, reason: {exMessage}", input, ex.Message); -#endif - return null; + throw new InvalidOperationException($"Could not open the file at {input}", ex); } } stopwatch.Stop(); From 9544806f4dfde6bf97c5d165b8af92baaeac5bd0 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 8 Mar 2022 22:21:04 +0300 Subject: [PATCH 19/49] Add exit codes for process termination handling --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 11fe6decc..2cf1b01ad 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -28,7 +28,7 @@ namespace Microsoft.OpenApi.Hidi { public class OpenApiService { - public static async Task ProcessOpenApiDocument( + public static async Task ProcessOpenApiDocument( string openapi, string csdl, FileInfo output, @@ -174,6 +174,8 @@ CancellationToken cancellationToken logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms"); textWriter.Flush(); + + return 0; } catch (Exception ex) { @@ -182,7 +184,7 @@ CancellationToken cancellationToken #else logger.LogCritical(ex.Message); #endif - return; + return 1; } } @@ -318,7 +320,7 @@ public static Dictionary> ParseJsonCollectionFile(Stream st return requestUrls; } - internal static async Task ValidateOpenApiDocument(string openapi, LogLevel loglevel, CancellationToken cancellationToken) + internal static async Task ValidateOpenApiDocument(string openapi, LogLevel loglevel, CancellationToken cancellationToken) { var logger = ConfigureLoggerInstance(loglevel); @@ -352,6 +354,8 @@ internal static async Task ValidateOpenApiDocument(string openapi, LogLevel logl logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); logger.LogInformation(statsVisitor.GetStatisticsReport()); + + return 0; } catch(Exception ex) { @@ -360,7 +364,7 @@ internal static async Task ValidateOpenApiDocument(string openapi, LogLevel logl #else logger.LogCritical(ex.Message); #endif - return; + return 1; } } From be964247425e09a4f7fb7a4afca0bd8c1d3a8276 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Wed, 9 Mar 2022 00:16:49 -0500 Subject: [PATCH 20/49] f --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 8f1fa2c43..ba8b84e0e 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -64,14 +64,15 @@ CancellationToken cancellationToken Stream stream; OpenApiDocument document; OpenApiFormat openApiFormat; + OpenApiSpecVersion openApiVersion; var stopwatch = new Stopwatch(); if (!string.IsNullOrEmpty(csdl)) { // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion openApiFormat = format ?? GetOpenApiFormat(csdl, logger); - version ??= OpenApiSpecVersion.OpenApi3_0; - + openApiVersion = version == null ? OpenApiSpecVersion.OpenApi3_0 : TryParseOpenApiSpecVersion(version); + stream = await GetStream(csdl, logger, cancellationToken); document = await ConvertCsdlToOpenApi(stream); } @@ -112,7 +113,7 @@ CancellationToken cancellationToken } openApiFormat = format ?? GetOpenApiFormat(openapi, logger); - version ??= result.OpenApiDiagnostic.SpecificationVersion; + openApiVersion = version == null ? TryParseOpenApiSpecVersion(version) : result.OpenApiDiagnostic.SpecificationVersion; } Func predicate; @@ -127,14 +128,14 @@ CancellationToken cancellationToken logger.LogTrace("Creating predicate based on the operationIds supplied."); predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids); -\ logger.LogTrace("Creating subset OpenApi document."); + logger.LogTrace("Creating subset OpenApi document."); document = OpenApiFilterService.CreateFilteredDocument(document, predicate); } if (!string.IsNullOrEmpty(filterbytags)) { logger.LogTrace("Creating predicate based on the tags supplied."); predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags); -\ + logger.LogTrace("Creating subset OpenApi document."); document = OpenApiFilterService.CreateFilteredDocument(document, predicate); } @@ -169,7 +170,7 @@ CancellationToken cancellationToken logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer"); stopwatch.Start(); - document.Serialize(writer, (OpenApiSpecVersion)version); + document.Serialize(writer, openApiVersion); stopwatch.Stop(); logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms"); From b785cb9932c0e1cc27e3d8cd996ca83b36e263e9 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 9 Mar 2022 08:38:55 +0300 Subject: [PATCH 21/49] Add a condition for ensuring the output file path exists before cleaning it --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index a961a0e8f..e96317943 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -56,7 +56,7 @@ CancellationToken cancellationToken { throw new ArgumentNullException(nameof(output)); } - if (cleanoutput) + if (cleanoutput && output.Exists) { output.Delete(); } From 3be26f5f3010a282810178e814c2a0a0ce8a13c6 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 9 Mar 2022 09:07:50 +0300 Subject: [PATCH 22/49] Package updates --- .github/workflows/ci-cd.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .../Microsoft.OpenApi.Hidi.csproj | 2 +- .../Microsoft.OpenApi.Readers.csproj | 4 ++-- .../Microsoft.OpenApi.Readers.Tests.csproj | 2 +- .../Microsoft.OpenApi.Tests.csproj | 10 +++++----- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 6f619ca85..8e5cb1f51 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -41,7 +41,7 @@ jobs: - name: Checkout repository id: checkout_repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1d2d4106d..0adca3d2d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout repository id: checkout_repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v2 diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index e33f4777e..2e52659ea 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -32,7 +32,7 @@ - + diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index b4c41e6aa..77b9cad26 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -40,7 +40,7 @@ - + @@ -66,4 +66,4 @@ SRResource.Designer.cs - + \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 086d80d75..7d346009e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -247,7 +247,7 @@ - + diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 360eeea92..aa7f00a17 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -17,11 +17,11 @@ - + - - - + + + all @@ -30,7 +30,7 @@ - + From 6e5d120570c4d8f16a63f86c100c35cf9f80cd6f Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 9 Mar 2022 09:10:09 +0300 Subject: [PATCH 23/49] Bump up system.commandline --- src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 2e52659ea..e1809f275 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -36,7 +36,7 @@ - + From 53748ad035c57b6cb5bb2f49e1d72135929fe88e Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 9 Mar 2022 09:33:17 +0300 Subject: [PATCH 24/49] Code cleanup --- src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index e1809f275..d9a958db9 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -32,8 +32,8 @@ - - + + From 194576e7e3df29fb387b51fd51aa4c9bd33d99ae Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Thu, 10 Mar 2022 22:37:35 +0300 Subject: [PATCH 25/49] Fix exception thrown when OpenSpecVersion is null --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index ba8b84e0e..357152343 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -113,7 +113,7 @@ CancellationToken cancellationToken } openApiFormat = format ?? GetOpenApiFormat(openapi, logger); - openApiVersion = version == null ? TryParseOpenApiSpecVersion(version) : result.OpenApiDiagnostic.SpecificationVersion; + openApiVersion = version == null ? result.OpenApiDiagnostic.SpecificationVersion : TryParseOpenApiSpecVersion(version); } Func predicate; From af25fe44d1bcffc8a6e544d8d9c30017a0de3cc5 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Thu, 10 Mar 2022 22:38:24 +0300 Subject: [PATCH 26/49] Add recursive solution for nested collection item object --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 41 ++++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 357152343..129dfa54b 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -301,24 +301,41 @@ public static Dictionary> ParseJsonCollectionFile(Stream st logger.LogTrace("Parsing the json collection file into a JsonDocument"); using var document = JsonDocument.Parse(stream); var root = document.RootElement; - var itemElement = root.GetProperty("item"); - foreach (var requestObject in itemElement.EnumerateArray().Select(item => item.GetProperty("request"))) - { - // Fetch list of methods and urls from collection, store them in a dictionary - var path = requestObject.GetProperty("url").GetProperty("raw").ToString(); - var method = requestObject.GetProperty("method").ToString(); - if (!requestUrls.ContainsKey(path)) + requestUrls = Enumerate(root, requestUrls); + + logger.LogTrace("Finished fetching the list of paths and Http methods defined in the Postman collection."); + return requestUrls; + } + + private static Dictionary> Enumerate(JsonElement itemElement, Dictionary> paths) + { + var itemsArray = itemElement.GetProperty("item"); + + foreach (var item in itemsArray.EnumerateArray()) + { + if (item.TryGetProperty("request", out var request)) { - requestUrls.Add(path, new List { method }); + // 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(); + + if (!paths.ContainsKey(path)) + { + paths.Add(path, new List { method }); + } + else + { + paths[path].Add(method); + } } else { - requestUrls[path].Add(method); + Enumerate(item, paths); } } - logger.LogTrace("Finished fetching the list of paths and Http methods defined in the Postman collection."); - return requestUrls; + + return paths; } internal static async Task ValidateOpenApiDocument(string openapi, LogLevel loglevel, CancellationToken cancellationToken) From bc87d1ef003040168fb104c1344886df6aa5ebbc Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Thu, 10 Mar 2022 23:23:15 +0300 Subject: [PATCH 27/49] Code refactoring --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 32 ++++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 129dfa54b..eebc5b5dd 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -314,20 +314,26 @@ private static Dictionary> Enumerate(JsonElement itemElemen foreach (var item in itemsArray.EnumerateArray()) { - if (item.TryGetProperty("request", out var request)) + if(item.ValueKind == JsonValueKind.Object) { - // 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(); - - if (!paths.ContainsKey(path)) - { - paths.Add(path, new List { method }); - } - else - { - paths[path].Add(method); - } + 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(); + if (!paths.ContainsKey(path)) + { + paths.Add(path, new List { method }); + } + else + { + paths[path].Add(method); + } + } + else + { + Enumerate(item, paths); + } } else { From c666c4ae4b1034b73ef602152bbcb87263bd2099 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Thu, 10 Mar 2022 23:23:51 +0300 Subject: [PATCH 28/49] Add nested sample collection file and copy it to output directory --- .../Microsoft.OpenApi.Tests.csproj | 3 + .../UtilityFiles/postmanCollection_ver3.json | 1382 +++++++++++++++++ 2 files changed, 1385 insertions(+) create mode 100644 test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver3.json diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 360eeea92..eea157d2e 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -44,6 +44,9 @@ Always + + Always + Always diff --git a/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver3.json b/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver3.json new file mode 100644 index 000000000..2c7637ed7 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver3.json @@ -0,0 +1,1382 @@ +{ + "info": { + "_postman_id": "6281bdba-62b8-2276-a5d6-268e87f48c89", + "name": "Graph-Collection", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "admin", + "item": [ + { + "name": "/admin", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin" + ] + } + } + }, + { + "name": "/admin", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews", + "{serviceHealth-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews", + "{serviceHealth-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews", + "{serviceHealth-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews", + "{serviceHealth-id}", + "issues" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews", + "{serviceHealth-id}", + "issues" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews", + "{serviceHealth-id}", + "issues", + "{serviceHealthIssue-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews", + "{serviceHealth-id}", + "issues", + "{serviceHealthIssue-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews", + "{serviceHealth-id}", + "issues", + "{serviceHealthIssue-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}/microsoft.graph.incidentReport()", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}/microsoft.graph.incidentReport()", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "healthOverviews", + "{serviceHealth-id}", + "issues", + "{serviceHealthIssue-id}", + "microsoft.graph.incidentReport()" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/issues", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "issues" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/issues", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "issues" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "issues", + "{serviceHealthIssue-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "issues", + "{serviceHealthIssue-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "issues", + "{serviceHealthIssue-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}/microsoft.graph.incidentReport()", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}/microsoft.graph.incidentReport()", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "issues", + "{serviceHealthIssue-id}", + "microsoft.graph.incidentReport()" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/microsoft.graph.archive", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.archive", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "microsoft.graph.archive" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/microsoft.graph.favorite", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.favorite", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "microsoft.graph.favorite" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/microsoft.graph.markRead", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.markRead", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "microsoft.graph.markRead" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/microsoft.graph.markUnread", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.markUnread", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "microsoft.graph.markUnread" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/microsoft.graph.unarchive", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.unarchive", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "microsoft.graph.unarchive" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/microsoft.graph.unfavorite", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.unfavorite", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "microsoft.graph.unfavorite" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}", + "attachments" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}", + "attachments" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}", + "attachments", + "{serviceAnnouncementAttachment-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}", + "attachments", + "{serviceAnnouncementAttachment-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}", + "attachments", + "{serviceAnnouncementAttachment-id}" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}/content", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}/content", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}", + "attachments", + "{serviceAnnouncementAttachment-id}", + "content" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}/content", + "request": { + "method": "PUT", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}/content", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}", + "attachments", + "{serviceAnnouncementAttachment-id}", + "content" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachmentsArchive", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachmentsArchive", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}", + "attachmentsArchive" + ] + } + } + }, + { + "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachmentsArchive", + "request": { + "method": "PUT", + "url": { + "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachmentsArchive", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "admin", + "serviceAnnouncement", + "messages", + "{serviceUpdateMessage-id}", + "attachmentsArchive" + ] + } + } + } + ] + }, + { + "name": "agreementAcceptances", + "item": [ + { + "name": "/agreementAcceptances", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "agreementAcceptances" + ] + } + } + }, + { + "name": "/agreementAcceptances", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "agreementAcceptances" + ] + } + } + }, + { + "name": "/agreementAcceptances/{agreementAcceptance-id}", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances/{agreementAcceptance-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "agreementAcceptances", + "{agreementAcceptance-id}" + ] + } + } + }, + { + "name": "/agreementAcceptances/{agreementAcceptance-id}", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances/{agreementAcceptance-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "agreementAcceptances", + "{agreementAcceptance-id}" + ] + } + } + }, + { + "name": "/agreementAcceptances/{agreementAcceptance-id}", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances/{agreementAcceptance-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "agreementAcceptances", + "{agreementAcceptance-id}" + ] + } + } + } + ] + }, + { + "name": "appCatalogs", + "item": [ + { + "name": "/appCatalogs", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs" + ] + } + } + }, + { + "name": "/appCatalogs", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}", + "appDefinitions" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}", + "appDefinitions" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}", + "appDefinitions", + "{teamsAppDefinition-id}" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}", + "appDefinitions", + "{teamsAppDefinition-id}" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}", + "appDefinitions", + "{teamsAppDefinition-id}" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}", + "appDefinitions", + "{teamsAppDefinition-id}", + "bot" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}", + "appDefinitions", + "{teamsAppDefinition-id}", + "bot" + ] + } + } + }, + { + "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs", + "teamsApps", + "{teamsApp-id}", + "appDefinitions", + "{teamsAppDefinition-id}", + "bot" + ] + } + } + } + ] + } + ] +} \ No newline at end of file From 2a8996d414e3f546c5c885441174dc1a6b223e4f Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Thu, 10 Mar 2022 23:24:01 +0300 Subject: [PATCH 29/49] Add unit test --- .../Services/OpenApiFilterServiceTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs index 78f8ec048..28c259fc6 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs @@ -69,6 +69,23 @@ public void ReturnFilteredOpenApiDocumentBasedOnPostmanCollection() Assert.Equal(3, subsetOpenApiDocument.Paths.Count); } + [Fact] + public void ShouldParseNestedPostmanCollection() + { + // Arrange + var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles\\postmanCollection_ver3.json"); + var fileInput = new FileInfo(filePath); + var stream = fileInput.OpenRead(); + + // Act + var requestUrls = OpenApiService.ParseJsonCollectionFile(stream, _logger); + var pathCount = requestUrls.Count; + + // Assert + Assert.NotNull(requestUrls); + Assert.Equal(30, pathCount); + } + [Fact] public void ThrowsExceptionWhenUrlsInCollectionAreMissingFromSourceDocument() { From 7ac200e87446bed7a245429c0e0d2a3f22629e0c Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Fri, 11 Mar 2022 23:21:10 -0500 Subject: [PATCH 30/49] Fixed issues related to merge conflicts --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 21 ++++++++++++-------- src/Microsoft.OpenApi.Hidi/Program.cs | 10 +++++++--- src/Microsoft.OpenApi.Hidi/appsettings.json | 7 ------- 3 files changed, 20 insertions(+), 18 deletions(-) delete mode 100644 src/Microsoft.OpenApi.Hidi/appsettings.json diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index ba8b84e0e..3d38ea678 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -71,8 +71,8 @@ CancellationToken cancellationToken { // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion openApiFormat = format ?? GetOpenApiFormat(csdl, logger); - openApiVersion = version == null ? OpenApiSpecVersion.OpenApi3_0 : TryParseOpenApiSpecVersion(version); - + openApiVersion = version != null ? TryParseOpenApiSpecVersion(version) : OpenApiSpecVersion.OpenApi3_0; + stream = await GetStream(csdl, logger, cancellationToken); document = await ConvertCsdlToOpenApi(stream); } @@ -113,7 +113,7 @@ CancellationToken cancellationToken } openApiFormat = format ?? GetOpenApiFormat(openapi, logger); - openApiVersion = version == null ? TryParseOpenApiSpecVersion(version) : result.OpenApiDiagnostic.SpecificationVersion; + openApiVersion = version != null ? TryParseOpenApiSpecVersion(version) : result.OpenApiDiagnostic.SpecificationVersion; } Func predicate; @@ -181,9 +181,10 @@ CancellationToken cancellationToken catch (Exception ex) { #if DEBUG - logger.LogCritical(ex, ex.Message); + logger.LogCritical(ex, ex.Message); #else logger.LogCritical(ex.Message); + #endif return 1; } @@ -335,12 +336,14 @@ internal static async Task ValidateOpenApiDocument(string openapi, LogLevel OpenApiDocument document; logger.LogTrace("Parsing the OpenApi file"); - document = new OpenApiStreamReader(new OpenApiReaderSettings + var result = await new OpenApiStreamReader(new OpenApiReaderSettings { RuleSet = ValidationRuleSet.GetDefaultRuleSet() } - ).Read(stream, out var context); + ).ReadAsync(stream); + document = result.OpenApiDocument; + var context = result.OpenApiDiagnostic; if (context.Errors.Count != 0) { foreach (var error in context.Errors) @@ -355,7 +358,7 @@ internal static async Task ValidateOpenApiDocument(string openapi, LogLevel logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); logger.LogInformation(statsVisitor.GetStatisticsReport()); - + return 0; } catch(Exception ex) @@ -385,7 +388,9 @@ private static ILogger ConfigureLoggerInstance(LogLevel loglevel) var logger = LoggerFactory.Create((builder) => { builder - .AddConsole() + .AddConsole(c => { + c.LogToStandardErrorThreshold = LogLevel.Error; + }) #if DEBUG .AddDebug() #endif diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index 24abb4a98..4fcf3f16b 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.CommandLine; using System.IO; using System.Threading; @@ -11,7 +12,7 @@ namespace Microsoft.OpenApi.Hidi { static class Program { - static async Task Main(string[] args) + static async Task Main(string[] args) { var rootCommand = new RootCommand() { }; @@ -32,7 +33,7 @@ static async Task Main(string[] args) var formatOption = new Option("--format", "File format"); formatOption.AddAlias("-f"); - var logLevelOption = new Option("--loglevel", () => LogLevel.Warning, "The log level to use when logging messages to the main output."); + var logLevelOption = new Option("--loglevel", () => LogLevel.Information, "The log level to use when logging messages to the main output."); logLevelOption.AddAlias("-ll"); var filterByOperationIdsOption = new Option("--filter-by-operationids", "Filters OpenApiDocument by OperationId(s) provided"); @@ -80,7 +81,10 @@ static async Task Main(string[] args) rootCommand.Add(validateCommand); // Parse the incoming args and invoke the handler - return await rootCommand.InvokeAsync(args); + await rootCommand.InvokeAsync(args); + + //// Wait for logger to write messages to the console before exiting + await Task.Delay(10); } } } diff --git a/src/Microsoft.OpenApi.Hidi/appsettings.json b/src/Microsoft.OpenApi.Hidi/appsettings.json deleted file mode 100644 index 882248cf8..000000000 --- a/src/Microsoft.OpenApi.Hidi/appsettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug" - } - } -} \ No newline at end of file From f1b3f3b8d40d8d697611ab6033a1c433d0c411db Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 13 Mar 2022 18:05:58 -0400 Subject: [PATCH 31/49] Added scope to tracing --- .../Microsoft.OpenApi.Hidi.csproj | 2 +- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 417 ++++++++++-------- src/Microsoft.OpenApi.Hidi/Program.cs | 2 +- 3 files changed, 234 insertions(+), 187 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index e33f4777e..b501e2cd2 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -36,7 +36,7 @@ - + diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index 3d38ea678..e4da1c900 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -29,7 +29,10 @@ namespace Microsoft.OpenApi.Hidi { public class OpenApiService { - public static async Task ProcessOpenApiDocument( + /// + /// Implementation of the transform command + /// + public static async Task TransformOpenApiDocument( string openapi, string csdl, FileInfo output, @@ -69,127 +72,210 @@ CancellationToken cancellationToken if (!string.IsNullOrEmpty(csdl)) { - // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion - openApiFormat = format ?? GetOpenApiFormat(csdl, logger); - openApiVersion = version != null ? TryParseOpenApiSpecVersion(version) : OpenApiSpecVersion.OpenApi3_0; - - stream = await GetStream(csdl, logger, cancellationToken); - document = await ConvertCsdlToOpenApi(stream); + using (logger.BeginScope($"Convert CSDL: {csdl}", csdl)) + { + stopwatch.Start(); + // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion + openApiFormat = format ?? GetOpenApiFormat(csdl, logger); + openApiVersion = version != null ? TryParseOpenApiSpecVersion(version) : OpenApiSpecVersion.OpenApi3_0; + + stream = await GetStream(csdl, logger, cancellationToken); + document = await ConvertCsdlToOpenApi(stream); + stopwatch.Stop(); + logger.LogTrace("{timestamp}ms: Generated OpenAPI with {paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count); + } } else { stream = await GetStream(openapi, logger, cancellationToken); - // Parsing OpenAPI file - stopwatch.Start(); - logger.LogTrace("Parsing OpenApi file"); - var result = await new OpenApiStreamReader(new OpenApiReaderSettings + using (logger.BeginScope($"Parse OpenAPI: {openapi}",openapi)) { - ReferenceResolution = resolveexternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, - RuleSet = ValidationRuleSet.GetDefaultRuleSet() - } - ).ReadAsync(stream); + stopwatch.Restart(); + var result = await new OpenApiStreamReader(new OpenApiReaderSettings + { + ReferenceResolution = resolveexternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences, + RuleSet = ValidationRuleSet.GetDefaultRuleSet() + } + ).ReadAsync(stream); - document = result.OpenApiDocument; - stopwatch.Stop(); + document = result.OpenApiDocument; - var context = result.OpenApiDiagnostic; - if (context.Errors.Count > 0) - { - logger.LogTrace("{timestamp}ms: Parsed OpenAPI with errors. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count); + var context = result.OpenApiDiagnostic; + if (context.Errors.Count > 0) + { + logger.LogTrace("{timestamp}ms: Parsed OpenAPI with errors. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count); - var errorReport = new StringBuilder(); + var errorReport = new StringBuilder(); - foreach (var error in context.Errors) + foreach (var error in context.Errors) + { + logger.LogError("OpenApi Parsing error: {message}", error.ToString()); + errorReport.AppendLine(error.ToString()); + } + logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}"); + } + else { - logger.LogError("OpenApi Parsing error: {message}", error.ToString()); - errorReport.AppendLine(error.ToString()); + logger.LogTrace("{timestamp}ms: Parsed OpenApi successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count); } - logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}"); + + openApiFormat = format ?? GetOpenApiFormat(openapi, logger); + openApiVersion = version != null ? TryParseOpenApiSpecVersion(version) : result.OpenApiDiagnostic.SpecificationVersion; + stopwatch.Stop(); } - else + } + + using (logger.BeginScope("Filter")) + { + Func predicate = null; + + // Check if filter options are provided, then slice the OpenAPI document + if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags)) { - logger.LogTrace("{timestamp}ms: Parsed OpenApi successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count); + throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time."); } + if (!string.IsNullOrEmpty(filterbyoperationids)) + { + logger.LogTrace("Creating predicate based on the operationIds supplied."); + predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids); - openApiFormat = format ?? GetOpenApiFormat(openapi, logger); - openApiVersion = version != null ? TryParseOpenApiSpecVersion(version) : result.OpenApiDiagnostic.SpecificationVersion; - } + } + if (!string.IsNullOrEmpty(filterbytags)) + { + logger.LogTrace("Creating predicate based on the tags supplied."); + predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags); - Func predicate; + } + if (!string.IsNullOrEmpty(filterbycollection)) + { + var fileStream = await GetStream(filterbycollection, logger, cancellationToken); + var requestUrls = ParseJsonCollectionFile(fileStream, logger); - // Check if filter options are provided, then slice the OpenAPI document - if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags)) - { - throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time."); + logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection."); + predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source: document); + } + if (predicate != null) + { + stopwatch.Restart(); + document = OpenApiFilterService.CreateFilteredDocument(document, predicate); + stopwatch.Stop(); + logger.LogTrace("{timestamp}ms: Creating filtered OpenApi document with {paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count); + } } - if (!string.IsNullOrEmpty(filterbyoperationids)) - { - logger.LogTrace("Creating predicate based on the operationIds supplied."); - predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids); - logger.LogTrace("Creating subset OpenApi document."); - document = OpenApiFilterService.CreateFilteredDocument(document, predicate); - } - if (!string.IsNullOrEmpty(filterbytags)) + using (logger.BeginScope("Output")) { - logger.LogTrace("Creating predicate based on the tags supplied."); - predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags); + ; + using var outputStream = output?.Create(); + var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out; - logger.LogTrace("Creating subset OpenApi document."); - document = OpenApiFilterService.CreateFilteredDocument(document, predicate); - } - if (!string.IsNullOrEmpty(filterbycollection)) - { - var fileStream = await GetStream(filterbycollection, logger, cancellationToken); - var requestUrls = ParseJsonCollectionFile(fileStream, logger); + var settings = new OpenApiWriterSettings() + { + ReferenceInline = inline ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences + }; + + IOpenApiWriter writer = openApiFormat switch + { + OpenApiFormat.Json => new OpenApiJsonWriter(textWriter, settings), + OpenApiFormat.Yaml => new OpenApiYamlWriter(textWriter, settings), + _ => throw new ArgumentException("Unknown format"), + }; + + logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer"); - logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection."); - predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source: document); + stopwatch.Start(); + document.Serialize(writer, openApiVersion); + stopwatch.Stop(); - logger.LogTrace("Creating subset OpenApi document."); - document = OpenApiFilterService.CreateFilteredDocument(document, predicate); + logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms"); + textWriter.Flush(); } + return 0; + } + catch (Exception ex) + { +#if DEBUG + logger.LogCritical(ex, ex.Message); +#else + logger.LogCritical(ex.Message); + +#endif + return 1; + } + } - logger.LogTrace("Creating a new file"); - using var outputStream = output?.Create(); - var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out; + /// + /// Implementation of the validate command + /// + public static async Task ValidateOpenApiDocument( + string openapi, + LogLevel loglevel, + CancellationToken cancellationToken) + { + var logger = ConfigureLoggerInstance(loglevel); - var settings = new OpenApiWriterSettings() + try + { + if (string.IsNullOrEmpty(openapi)) { - ReferenceInline = inline ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences - }; + throw new ArgumentNullException(nameof(openapi)); + } + var stream = await GetStream(openapi, logger, cancellationToken); - IOpenApiWriter writer = openApiFormat switch + OpenApiDocument document; + Stopwatch stopwatch = Stopwatch.StartNew(); + using (logger.BeginScope($"Parsing OpenAPI: {openapi}", openapi)) { - OpenApiFormat.Json => new OpenApiJsonWriter(textWriter, settings), - OpenApiFormat.Yaml => new OpenApiYamlWriter(textWriter, settings), - _ => throw new ArgumentException("Unknown format"), - }; + stopwatch.Start(); + + var result = await new OpenApiStreamReader(new OpenApiReaderSettings + { + RuleSet = ValidationRuleSet.GetDefaultRuleSet() + } + ).ReadAsync(stream); - logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer"); + logger.LogTrace("{timestamp}ms: Completed parsing.", stopwatch.ElapsedMilliseconds); - stopwatch.Start(); - document.Serialize(writer, openApiVersion); - stopwatch.Stop(); + document = result.OpenApiDocument; + var context = result.OpenApiDiagnostic; + if (context.Errors.Count != 0) + { + using (logger.BeginScope("Detected errors")) + { + foreach (var error in context.Errors) + { + logger.LogError(error.ToString()); + } + } + } + stopwatch.Stop(); + } - logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms"); - textWriter.Flush(); + using (logger.BeginScope("Calculating statistics")) + { + var statsVisitor = new StatsVisitor(); + var walker = new OpenApiWalker(statsVisitor); + walker.Walk(document); + + logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); + logger.LogInformation(statsVisitor.GetStatisticsReport()); + } return 0; } catch (Exception ex) { -#if DEBUG - logger.LogCritical(ex, ex.Message); +#if DEBUG + logger.LogCritical(ex, ex.Message); #else logger.LogCritical(ex.Message); - #endif return 1; - } + } + } - + /// /// Converts CSDL to OpenAPI /// @@ -225,71 +311,6 @@ public static async Task ConvertCsdlToOpenApi(Stream csdl) return document; } - /// - /// Fixes the references in the resulting OpenApiDocument. - /// - /// The converted OpenApiDocument. - /// A valid OpenApiDocument instance. - public static OpenApiDocument FixReferences(OpenApiDocument document) - { - // This method is only needed because the output of ConvertToOpenApi isn't quite a valid OpenApiDocument instance. - // So we write it out, and read it back in again to fix it up. - - var sb = new StringBuilder(); - document.SerializeAsV3(new OpenApiYamlWriter(new StringWriter(sb))); - var doc = new OpenApiStringReader().Read(sb.ToString(), out _); - - return doc; - } - - private static async Task GetStream(string input, ILogger logger, CancellationToken cancellationToken) - { - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - Stream stream; - if (input.StartsWith("http")) - { - try - { - var httpClientHandler = new HttpClientHandler() - { - SslProtocols = System.Security.Authentication.SslProtocols.Tls12, - }; - using var httpClient = new HttpClient(httpClientHandler) - { - DefaultRequestVersion = HttpVersion.Version20 - }; - stream = await httpClient.GetStreamAsync(input, cancellationToken); - } - catch (HttpRequestException ex) - { - throw new InvalidOperationException($"Could not download the file at {input}", ex); - } - } - else - { - try - { - var fileInput = new FileInfo(input); - stream = fileInput.OpenRead(); - } - catch (Exception ex) when (ex is FileNotFoundException || - ex is PathTooLongException || - ex is DirectoryNotFoundException || - ex is IOException || - ex is UnauthorizedAccessException || - ex is SecurityException || - ex is NotSupportedException) - { - throw new InvalidOperationException($"Could not open the file at {input}", ex); - } - } - stopwatch.Stop(); - logger.LogTrace("{timestamp}ms: Read file {input}", stopwatch.ElapsedMilliseconds, input); - return stream; - } - /// /// Takes in a file stream, parses the stream into a JsonDocument and gets a list of paths and Http methods /// @@ -322,57 +343,83 @@ public static Dictionary> ParseJsonCollectionFile(Stream st return requestUrls; } - internal static async Task ValidateOpenApiDocument(string openapi, LogLevel loglevel, CancellationToken cancellationToken) + /// + /// Fixes the references in the resulting OpenApiDocument. + /// + /// The converted OpenApiDocument. + /// A valid OpenApiDocument instance. + private static OpenApiDocument FixReferences(OpenApiDocument document) { - var logger = ConfigureLoggerInstance(loglevel); + // This method is only needed because the output of ConvertToOpenApi isn't quite a valid OpenApiDocument instance. + // So we write it out, and read it back in again to fix it up. - try + var sb = new StringBuilder(); + document.SerializeAsV3(new OpenApiYamlWriter(new StringWriter(sb))); + var doc = new OpenApiStringReader().Read(sb.ToString(), out _); + + return doc; + } + + /// + /// Reads stream from file system or makes HTTP request depending on the input string + /// + private static async Task GetStream(string input, ILogger logger, CancellationToken cancellationToken) + { + Stream stream; + using (logger.BeginScope("Reading input stream")) { - if (string.IsNullOrEmpty(openapi)) - { - throw new ArgumentNullException(nameof(openapi)); - } - var stream = await GetStream(openapi, logger, cancellationToken); + var stopwatch = new Stopwatch(); + stopwatch.Start(); - OpenApiDocument document; - logger.LogTrace("Parsing the OpenApi file"); - var result = await new OpenApiStreamReader(new OpenApiReaderSettings + if (input.StartsWith("http")) { - RuleSet = ValidationRuleSet.GetDefaultRuleSet() + try + { + var httpClientHandler = new HttpClientHandler() + { + SslProtocols = System.Security.Authentication.SslProtocols.Tls12, + }; + using var httpClient = new HttpClient(httpClientHandler) + { + DefaultRequestVersion = HttpVersion.Version20 + }; + stream = await httpClient.GetStreamAsync(input, cancellationToken); + } + catch (HttpRequestException ex) + { + throw new InvalidOperationException($"Could not download the file at {input}", ex); + } } - ).ReadAsync(stream); - - document = result.OpenApiDocument; - var context = result.OpenApiDiagnostic; - if (context.Errors.Count != 0) + else { - foreach (var error in context.Errors) + try + { + var fileInput = new FileInfo(input); + stream = fileInput.OpenRead(); + } + catch (Exception ex) when (ex is FileNotFoundException || + ex is PathTooLongException || + ex is DirectoryNotFoundException || + ex is IOException || + ex is UnauthorizedAccessException || + ex is SecurityException || + ex is NotSupportedException) { - logger.LogError("OpenApi Parsing error: {message}", error.ToString()); + throw new InvalidOperationException($"Could not open the file at {input}", ex); } } - - var statsVisitor = new StatsVisitor(); - var walker = new OpenApiWalker(statsVisitor); - walker.Walk(document); - - logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); - logger.LogInformation(statsVisitor.GetStatisticsReport()); - - return 0; - } - catch(Exception ex) - { -#if DEBUG - logger.LogCritical(ex, ex.Message); -#else - logger.LogCritical(ex.Message); -#endif - return 1; + stopwatch.Stop(); + logger.LogTrace("{timestamp}ms: Read file {input}", stopwatch.ElapsedMilliseconds, input); } - + return stream; } + /// + /// Attempt to guess OpenAPI format based in input URL + /// + /// + /// + /// private static OpenApiFormat GetOpenApiFormat(string input, ILogger logger) { logger.LogTrace("Getting the OpenApi format"); @@ -388,10 +435,10 @@ private static ILogger ConfigureLoggerInstance(LogLevel loglevel) var logger = LoggerFactory.Create((builder) => { builder - .AddConsole(c => { - c.LogToStandardErrorThreshold = LogLevel.Error; + .AddSimpleConsole(c => { + c.IncludeScopes = true; }) -#if DEBUG +#if DEBUG .AddDebug() #endif .SetMinimumLevel(loglevel); diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index 4fcf3f16b..efbf7fea6 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -75,7 +75,7 @@ static async Task Main(string[] args) }; transformCommand.SetHandler ( - OpenApiService.ProcessOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); + OpenApiService.TransformOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); rootCommand.Add(transformCommand); rootCommand.Add(validateCommand); From df0fb111c82e8cd97f5f725888178ae5fbe33f37 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 13 Mar 2022 21:31:16 -0400 Subject: [PATCH 32/49] Fixed input parameters of transform --- src/Microsoft.OpenApi.Hidi/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index e9a1bc318..ac39aaad4 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -79,7 +79,7 @@ static async Task Main(string[] args) }; transformCommand.SetHandler ( - OpenApiService.TransformOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); + OpenApiService.TransformOpenApiDocument, descriptionOption, csdlOption, outputOption, cleanOutputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); rootCommand.Add(transformCommand); rootCommand.Add(validateCommand); From 3350d86bab72a22bd84372ea2ccfbe82a2575f7d Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Mon, 14 Mar 2022 09:37:54 +0300 Subject: [PATCH 33/49] Rename method --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index eebc5b5dd..d68daea80 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -302,13 +302,13 @@ public static Dictionary> ParseJsonCollectionFile(Stream st using var document = JsonDocument.Parse(stream); var root = document.RootElement; - requestUrls = Enumerate(root, requestUrls); - + requestUrls = EnumerateJsonDocument(root, requestUrls); logger.LogTrace("Finished fetching the list of paths and Http methods defined in the Postman collection."); + return requestUrls; } - private static Dictionary> Enumerate(JsonElement itemElement, Dictionary> paths) + private static Dictionary> EnumerateJsonDocument(JsonElement itemElement, Dictionary> paths) { var itemsArray = itemElement.GetProperty("item"); @@ -332,12 +332,12 @@ private static Dictionary> Enumerate(JsonElement itemElemen } else { - Enumerate(item, paths); + EnumerateJsonDocument(item, paths); } } else { - Enumerate(item, paths); + EnumerateJsonDocument(item, paths); } } From 3445c1d3199ebd8dc598d8d08a05f27904b4faca Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Tue, 15 Mar 2022 11:32:05 +0300 Subject: [PATCH 34/49] Log warning to console if url in collection isn't in the input OpenApi doc and continue processing the rest of the urls and add unit test --- .../Services/OpenApiFilterService.cs | 1 + .../Microsoft.OpenApi.Tests.csproj | 3 + .../Services/OpenApiFilterServiceTests.cs | 22 +++ .../UtilityFiles/postmanCollection_ver4.json | 145 ++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver4.json diff --git a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs index 7b9111e4d..57c5fef09 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs @@ -81,6 +81,7 @@ public static class OpenApiFilterService var openApiOperations = GetOpenApiOperations(rootNode, url, apiVersion); if (openApiOperations == null) { + Console.WriteLine($"The url {url} could not be found in the OpenApi description"); continue; } diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index eea157d2e..a187a2887 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -47,6 +47,9 @@ Always + + Always + Always diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs index 28c259fc6..29cb684d2 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs @@ -103,6 +103,28 @@ public void ThrowsExceptionWhenUrlsInCollectionAreMissingFromSourceDocument() Assert.Equal("The urls in the Postman collection supplied could not be found.", message); } + [Fact] + public void ContinueProcessingWhenUrlsInCollectionAreMissingFromSourceDocument() + { + // Arrange + var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles\\postmanCollection_ver4.json"); + var fileInput = new FileInfo(filePath); + var stream = fileInput.OpenRead(); + + // Act + var requestUrls = OpenApiService.ParseJsonCollectionFile(stream, _logger); + var pathCount = requestUrls.Count; + var predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source: _openApiDocumentMock); + var subsetOpenApiDocument = OpenApiFilterService.CreateFilteredDocument(_openApiDocumentMock, predicate); + var subsetPathCount = subsetOpenApiDocument.Paths.Count; + + // Assert + Assert.NotNull(subsetOpenApiDocument); + Assert.NotEmpty(subsetOpenApiDocument.Paths); + Assert.Equal(2, subsetPathCount); + Assert.NotEqual(pathCount, subsetPathCount); + } + [Fact] public void ThrowsInvalidOperationExceptionInCreatePredicateWhenInvalidArgumentsArePassed() { diff --git a/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver4.json b/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver4.json new file mode 100644 index 000000000..edafeb0bd --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver4.json @@ -0,0 +1,145 @@ +{ + "info": { + "_postman_id": "43402ca3-f018-7c9b-2315-f176d9b171a3", + "name": "Graph-Collection", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "users-GET", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/users", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "users" + ] + } + } + }, + { + "name": "users-POST", + "request": { + "method": "POST", + "url": { + "raw": "https://graph.microsoft.com/v1.0/users", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "users" + ] + } + } + }, + { + "name": "/appCatalogs", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/appCatalogs", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "appCatalogs" + ] + } + } + }, + { + "name": "/agreementAcceptances", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "agreementAcceptances" + ] + } + } + }, + { + "name": "{user-id}-GET", + "request": { + "method": "GET", + "url": { + "raw": "https://graph.microsoft.com/v1.0/users/{user-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "users", + "{user-id}" + ] + } + } + }, + { + "name": "{user-id}-PATCH", + "request": { + "method": "PATCH", + "url": { + "raw": "https://graph.microsoft.com/v1.0/users/{user-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "users", + "{user-id}" + ] + } + } + }, + { + "name": "{user-id}-DELETE", + "request": { + "method": "DELETE", + "url": { + "raw": "https://graph.microsoft.com/v1.0/users/{user-id}", + "protocol": "https", + "host": [ + "graph", + "microsoft", + "com" + ], + "path": [ + "v1.0", + "users", + "{user-id}" + ] + } + } + } + ] +} \ No newline at end of file From 19a9c73da2abc13d237d00fa09e7f82656336e84 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 16 Mar 2022 18:24:41 +0300 Subject: [PATCH 35/49] Update powershell script to fetch Hidi version number and use the output variables in the release notes --- .azure-pipelines/ci-build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index a4af01161..3d21b83ee 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -270,11 +270,11 @@ stages: inputs: source: current - pwsh: | - $artifactMainDirectory = Get-ChildItem -Filter Microsoft.OpenApi.Hidi-* -Directory -Recurse | select -First 1 - $artifactName = $artifactMainDirectory.Name -replace "Microsoft.OpenApi.Hidi-", "" - #Set Variable $artifactName - Write-Host "##vso[task.setvariable variable=artifactName; isSecret=false; isOutput=true;]$artifactName" - Write-Host "##vso[task.setvariable variable=artifactMainDirectory; isSecret=false; isOutput=true;]$artifactMainDirectory" + $artifactName = Get-ChildItem -Path $(Pipeline.Workspace) -Filter Microsoft.OpenApi.Hidi-* -recurse | select -First 1 + $artifactVersion= $artifactName -replace "Microsoft.OpenApi.Hidi-", "" + #Set Variable $artifactName and $artifactVersion + Write-Host "##vso[task.setvariable variable=artifactVersion; isSecret=false; isOutput=true]$artifactVersion" + Write-Host "##vso[task.setvariable variable=artifactName; isSecret=false; isOutput=true]$artifactName.FullName" displayName: 'Fetch Artifact Name' - task: NuGetCommand@2 @@ -289,10 +289,10 @@ stages: inputs: gitHubConnection: 'Github-MaggieKimani1' tagSource: userSpecifiedTag - tag: '$(artifactName)' + tag: '$(artifactVersion)' title: '$(artifactName)' releaseNotesSource: inline - assets: '$(artifactMainDirectory)\**\*.exe' + assets: '$(Pipeline.Workspace)\**\*.exe' changeLogType: issueBased - deployment: deploy_lib From 74b8b4beb31634e5752def9c9394faef2f2ba7b2 Mon Sep 17 00:00:00 2001 From: Maggie Kimani Date: Wed, 16 Mar 2022 18:35:41 +0300 Subject: [PATCH 36/49] Remove whitespace --- .azure-pipelines/ci-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index 3d21b83ee..31af5bd24 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -271,7 +271,7 @@ stages: source: current - pwsh: | $artifactName = Get-ChildItem -Path $(Pipeline.Workspace) -Filter Microsoft.OpenApi.Hidi-* -recurse | select -First 1 - $artifactVersion= $artifactName -replace "Microsoft.OpenApi.Hidi-", "" + $artifactVersion= $artifactName -replace "Microsoft.OpenApi.Hidi-", "" #Set Variable $artifactName and $artifactVersion Write-Host "##vso[task.setvariable variable=artifactVersion; isSecret=false; isOutput=true]$artifactVersion" Write-Host "##vso[task.setvariable variable=artifactName; isSecret=false; isOutput=true]$artifactName.FullName" From 13812b153a355faa230292f443a5eb432c6eecfd Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Wed, 23 Mar 2022 13:31:56 -0400 Subject: [PATCH 37/49] Added CSDL filter for entitysets and singletons --- src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt | 22 ++++++++++ .../Microsoft.OpenApi.Hidi.csproj | 8 ++++ src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 41 ++++++++++++++++++- src/Microsoft.OpenApi.Hidi/Program.cs | 8 +++- .../Services/OpenApiServiceTests.cs | 2 +- 5 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt diff --git a/src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt b/src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt new file mode 100644 index 000000000..ee3bf0d40 --- /dev/null +++ b/src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index d9a958db9..98def8818 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -31,6 +31,14 @@ true + + + + + + + + diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs index c15f77d6f..4b73c13d4 100644 --- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs @@ -24,6 +24,10 @@ using Microsoft.OpenApi.Writers; using static Microsoft.OpenApi.Hidi.OpenApiSpecVersionHelper; using System.Threading; +using System.Xml.Xsl; +using System.Xml; +using System.Runtime.CompilerServices; +using System.Reflection; namespace Microsoft.OpenApi.Hidi { @@ -35,6 +39,7 @@ public class OpenApiService public static async Task TransformOpenApiDocument( string openapi, string csdl, + string csdlFilter, FileInfo output, bool cleanoutput, string? version, @@ -85,6 +90,13 @@ CancellationToken cancellationToken openApiVersion = version != null ? TryParseOpenApiSpecVersion(version) : OpenApiSpecVersion.OpenApi3_0; stream = await GetStream(csdl, logger, cancellationToken); + + if (!string.IsNullOrEmpty(csdlFilter)) + { + XslCompiledTransform transform = GetFilterTransform(); + stream = ApplyFilter(csdl, csdlFilter, transform); + stream.Position = 0; + } document = await ConvertCsdlToOpenApi(stream); stopwatch.Stop(); logger.LogTrace("{timestamp}ms: Generated OpenAPI with {paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count); @@ -210,6 +222,31 @@ CancellationToken cancellationToken } } + 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))); + return transform; + } + + private static Stream ApplyFilter(string csdl, string entitySetOrSingleton, XslCompiledTransform transform) + { + Stream stream; + StreamReader inputReader = new(csdl); + XmlReader inputXmlReader = XmlReader.Create(inputReader); + MemoryStream filteredStream = new(); + StreamWriter writer = new(filteredStream); + XsltArgumentList args = new(); + args.AddParam("entitySetOrSingleton", "", entitySetOrSingleton); + transform.Transform(inputXmlReader, args, writer); + stream = filteredStream; + return stream; + } + + + /// /// Implementation of the validate command /// @@ -306,8 +343,8 @@ public static async Task ConvertCsdlToOpenApi(Stream csdl) EnableDiscriminatorValue = false, EnableDerivedTypesReferencesForRequestBody = false, EnableDerivedTypesReferencesForResponses = false, - ShowRootPath = true, - ShowLinks = true + ShowRootPath = false, + ShowLinks = false }; OpenApiDocument document = edmModel.ConvertToOpenApi(settings); diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index ac39aaad4..5a2a808dd 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -24,6 +24,9 @@ static async Task Main(string[] args) var csdlOption = new Option("--csdl", "Input CSDL file path or URL"); csdlOption.AddAlias("-cs"); + var csdlFilterOption = new Option("--csdlFilter", "Name of EntitySet or Singleton to filter CSDL on"); + csdlOption.AddAlias("-csf"); + var outputOption = new Option("--output", () => new FileInfo("./output"), "The output directory path for the generated file.") { Arity = ArgumentArity.ZeroOrOne }; outputOption.AddAlias("-o"); @@ -66,6 +69,7 @@ static async Task Main(string[] args) { descriptionOption, csdlOption, + csdlFilterOption, outputOption, cleanOutputOption, versionOption, @@ -78,8 +82,8 @@ static async Task Main(string[] args) resolveExternalOption, }; - transformCommand.SetHandler ( - OpenApiService.TransformOpenApiDocument, descriptionOption, csdlOption, outputOption, cleanOutputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); + transformCommand.SetHandler ( + OpenApiService.TransformOpenApiDocument, descriptionOption, csdlOption, csdlFilterOption, outputOption, cleanOutputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption); rootCommand.Add(transformCommand); rootCommand.Add(validateCommand); diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiServiceTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiServiceTests.cs index 1c9fd003b..af5437aa1 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiServiceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiServiceTests.cs @@ -22,7 +22,7 @@ public async Task ReturnConvertedCSDLFile() // Act var openApiDoc = await OpenApiService.ConvertCsdlToOpenApi(csdlStream); - var expectedPathCount = 6; + var expectedPathCount = 5; // Assert Assert.NotNull(openApiDoc); From 7aae53be9813a0b12478dbaf8f1e4c18a9c6a4dd Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 27 Mar 2022 09:07:50 -0400 Subject: [PATCH 38/49] Fixed issue with v2 external references --- .../Microsoft.OpenApi.Hidi.csproj | 4 +- .../Microsoft.OpenApi.Readers.csproj | 2 +- .../V2/OpenApiV2VersionService.cs | 48 ++++++++++++++++++- .../V3/OpenApiV3VersionService.cs | 2 +- .../Microsoft.OpenApi.Readers.Tests.csproj | 2 +- .../ConvertToOpenApiReferenceV2Tests.cs | 22 ++++++++- .../Microsoft.OpenApi.Tests.csproj | 8 ++-- 7 files changed, 76 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index cd8d14132..add38e832 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -33,10 +33,10 @@ - + - + diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index 1b4542073..4241daf6a 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -40,7 +40,7 @@ - + diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs index fbd4dbb85..c4d765734 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs @@ -130,6 +130,30 @@ private static string GetReferenceTypeV2Name(ReferenceType referenceType) } } + private static ReferenceType GetReferenceTypeV2FromName(string referenceType) + { + switch (referenceType) + { + case "definitions": + return ReferenceType.Schema; + + case "parameters": + return ReferenceType.Parameter; + + case "responses": + return ReferenceType.Response; + + case "tags": + return ReferenceType.Tag; + + case "securityDefinitions": + return ReferenceType.SecurityScheme; + + default: + throw new ArgumentException(); + } + } + /// /// Parse the string to a object. /// @@ -176,12 +200,34 @@ public OpenApiReference ConvertToOpenApiReference(string reference, ReferenceTyp } } + // Where fragments point into a non-OpenAPI document, the id will be the complete fragment identifier + string id = segments[1]; + // $ref: externalSource.yaml#/Pet + if (id.StartsWith("/definitions/")) + { + var localSegments = id.Split('/'); + var referencedType = GetReferenceTypeV2FromName(localSegments[1]); + if (type == null) + { + type = referencedType; + } + else + { + if (type != referencedType) + { + throw new OpenApiException("Referenced type mismatch"); + } + } + id = localSegments[2]; + } + + // $ref: externalSource.yaml#/Pet return new OpenApiReference { ExternalResource = segments[0], Type = type, - Id = segments[1].Substring(1) + Id = id }; } } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs index 3ee8b8d4e..65acbc4e0 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 086d80d75..7d346009e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -247,7 +247,7 @@ - + diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs index ff6641f88..bd9600e4f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs @@ -17,14 +17,32 @@ public ConvertToOpenApiReferenceV2Tests() Diagnostic = new OpenApiDiagnostic(); } + [Fact] + public void ParseExternalReferenceToV2OpenApi() + { + // Arrange + var versionService = new OpenApiV2VersionService(Diagnostic); + var externalResource = "externalSchema.json"; + var id = "mySchema"; + var input = $"{externalResource}#/definitions/{id}"; + + // Act + var reference = versionService.ConvertToOpenApiReference(input, null); + + // Assert + reference.ExternalResource.Should().Be(externalResource); + reference.Type.Should().NotBeNull(); + reference.Id.Should().Be(id); + } + [Fact] public void ParseExternalReference() { // Arrange var versionService = new OpenApiV2VersionService(Diagnostic); var externalResource = "externalSchema.json"; - var id = "externalPathSegment1/externalPathSegment2/externalPathSegment3"; - var input = $"{externalResource}#/{id}"; + var id = "/externalPathSegment1/externalPathSegment2/externalPathSegment3"; + var input = $"{externalResource}#{id}"; // Act var reference = versionService.ConvertToOpenApiReference(input, null); diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index de827c62f..df3c736e2 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -17,11 +17,11 @@ - + - - - + + + all From caa87da7a117071f0038d9b7bff780ee2e8d8ebd Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 27 Mar 2022 14:33:59 -0400 Subject: [PATCH 39/49] Fixed reporting collections with urls not in OpenAPI --- filteredGraph.yaml | 478 ++++++++++++++++++ .../Services/OpenApiFilterService.cs | 18 +- 2 files changed, 487 insertions(+), 9 deletions(-) create mode 100644 filteredGraph.yaml diff --git a/filteredGraph.yaml b/filteredGraph.yaml new file mode 100644 index 000000000..4dea96ea8 --- /dev/null +++ b/filteredGraph.yaml @@ -0,0 +1,478 @@ +openapi: 3.0.1 +info: + title: OData Service for namespace microsoft.graph - Subset + description: This OData service is located at https://graph.microsoft.com/v1.0 + version: 1.0.1 +servers: + - url: https://graph.microsoft.com/v1.0 +paths: + /admin/serviceAnnouncement: + get: + tags: + - admin.serviceAnnouncement + summary: Get serviceAnnouncement from admin + description: A container for service communications resources. Read-only. + operationId: admin.GetServiceAnnouncement + parameters: + - name: $select + in: query + description: Select properties to be returned + style: form + explode: false + schema: + uniqueItems: true + type: array + items: + enum: + - id + - healthOverviews + - issues + - messages + type: string + - name: $expand + in: query + description: Expand related entities + style: form + explode: false + schema: + uniqueItems: true + type: array + items: + enum: + - '*' + - healthOverviews + - issues + - messages + type: string + responses: + '200': + description: Retrieved navigation property + content: + application/json: + schema: + $ref: '#/components/schemas/microsoft.graph.serviceAnnouncement' + links: + healthOverviews: + operationId: admin.ServiceAnnouncement.ListHealthOverviews + issues: + operationId: admin.ServiceAnnouncement.ListIssues + messages: + operationId: admin.ServiceAnnouncement.ListMessages + 4XX: + $ref: '#/components/responses/error' + 5XX: + $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation + patch: + tags: + - admin.serviceAnnouncement + summary: Update the navigation property serviceAnnouncement in admin + operationId: admin.UpdateServiceAnnouncement + requestBody: + description: New navigation property values + content: + application/json: + schema: + $ref: '#/components/schemas/microsoft.graph.serviceAnnouncement' + required: true + responses: + '204': + description: Success + 4XX: + $ref: '#/components/responses/error' + 5XX: + $ref: '#/components/responses/error' + x-ms-docs-operation-type: operation +components: + schemas: + microsoft.graph.serviceAnnouncement: + allOf: + - $ref: '#/components/schemas/microsoft.graph.entity' + - title: serviceAnnouncement + type: object + properties: + healthOverviews: + type: array + items: + $ref: '#/components/schemas/microsoft.graph.serviceHealth' + description: 'A collection of service health information for tenant. This property is a contained navigation property, it is nullable and readonly.' + issues: + type: array + items: + $ref: '#/components/schemas/microsoft.graph.serviceHealthIssue' + description: 'A collection of service issues for tenant. This property is a contained navigation property, it is nullable and readonly.' + messages: + type: array + items: + $ref: '#/components/schemas/microsoft.graph.serviceUpdateMessage' + description: 'A collection of service messages for tenant. This property is a contained navigation property, it is nullable and readonly.' + microsoft.graph.entity: + title: entity + type: object + properties: + id: + type: string + description: Read-only. + microsoft.graph.serviceHealth: + allOf: + - $ref: '#/components/schemas/microsoft.graph.entity' + - title: serviceHealth + type: object + properties: + service: + type: string + description: The service name. Use the list healthOverviews operation to get exact string names for services subscribed by the tenant. + status: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.serviceHealthStatus' + description: 'Show the overral service health status. Possible values are: serviceOperational, investigating, restoringService, verifyingService, serviceRestored, postIncidentReviewPublished, serviceDegradation, serviceInterruption, extendedRecovery, falsePositive, investigationSuspended, resolved, mitigatedExternal, mitigated, resolvedExternal, confirmed, reported, unknownFutureValue. For more details, see serviceHealthStatus values.' + issues: + type: array + items: + $ref: '#/components/schemas/microsoft.graph.serviceHealthIssue' + description: 'A collection of issues that happened on the service, with detailed information for each issue.' + microsoft.graph.serviceHealthIssue: + allOf: + - $ref: '#/components/schemas/microsoft.graph.serviceAnnouncementBase' + - title: serviceHealthIssue + type: object + properties: + classification: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.serviceHealthClassificationType' + description: 'The type of service health issue. Possible values are: advisory, incident, unknownFutureValue.' + feature: + type: string + description: The feature name of the service issue. + nullable: true + featureGroup: + type: string + description: The feature group name of the service issue. + nullable: true + impactDescription: + type: string + description: The description of the service issue impact. + isResolved: + type: boolean + description: Indicates whether the issue is resolved. + origin: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.serviceHealthOrigin' + description: 'Indicates the origin of the service issue. Possible values are: microsoft, thirdParty, customer, unknownFutureValue.' + posts: + type: array + items: + $ref: '#/components/schemas/microsoft.graph.serviceHealthIssuePost' + description: Collection of historical posts for the service issue. + service: + type: string + description: Indicates the service affected by the issue. + status: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.serviceHealthStatus' + description: 'The status of the service issue. Possible values are: serviceOperational, investigating, restoringService, verifyingService, serviceRestored, postIncidentReviewPublished, serviceDegradation, serviceInterruption, extendedRecovery, falsePositive, investigationSuspended, resolved, mitigatedExternal, mitigated, resolvedExternal, confirmed, reported, unknownFutureValue. See more in the table below.' + microsoft.graph.serviceUpdateMessage: + allOf: + - $ref: '#/components/schemas/microsoft.graph.serviceAnnouncementBase' + - title: serviceUpdateMessage + type: object + properties: + actionRequiredByDateTime: + pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' + type: string + description: The expected deadline of the action for the message. + format: date-time + nullable: true + attachmentsArchive: + type: string + description: The zip file that contains all attachments for a message. + format: base64url + nullable: true + body: + $ref: '#/components/schemas/microsoft.graph.itemBody' + category: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.serviceUpdateCategory' + description: 'The service message category. Possible values are: preventOrFixIssue, planForChange, stayInformed, unknownFutureValue.' + hasAttachments: + type: boolean + description: Indicates whether the message has any attachment. + isMajorChange: + type: boolean + description: Indicates whether the message describes a major update for the service. + nullable: true + services: + type: array + items: + type: string + nullable: true + description: The affected services by the service message. + severity: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.serviceUpdateSeverity' + description: 'The severity of the service message. Possible values are: normal, high, critical, unknownFutureValue.' + tags: + type: array + items: + type: string + nullable: true + description: 'A collection of tags for the service message. Tags are provided by the service team/support team who post the message to tell whether this message contains privacy data, or whether this message is for a service new feature update, and so on.' + viewPoint: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.serviceUpdateMessageViewpoint' + description: 'Represents user viewpoints data of the service message. This data includes message status such as whether the user has archived, read, or marked the message as favorite. This property is null when accessed with application permissions.' + nullable: true + attachments: + type: array + items: + $ref: '#/components/schemas/microsoft.graph.serviceAnnouncementAttachment' + description: A collection of serviceAnnouncementAttachments. + microsoft.graph.ODataErrors.ODataError: + required: + - error + type: object + properties: + error: + $ref: '#/components/schemas/microsoft.graph.ODataErrors.MainError' + microsoft.graph.serviceHealthStatus: + title: serviceHealthStatus + enum: + - serviceOperational + - investigating + - restoringService + - verifyingService + - serviceRestored + - postIncidentReviewPublished + - serviceDegradation + - serviceInterruption + - extendedRecovery + - falsePositive + - investigationSuspended + - resolved + - mitigatedExternal + - mitigated + - resolvedExternal + - confirmed + - reported + - unknownFutureValue + type: string + microsoft.graph.serviceAnnouncementBase: + allOf: + - $ref: '#/components/schemas/microsoft.graph.entity' + - title: serviceAnnouncementBase + type: object + properties: + details: + type: array + items: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.keyValuePair' + nullable: true + description: Additional details about service event. This property doesn't support filters. + endDateTime: + pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' + type: string + description: The end time of the service event. + format: date-time + nullable: true + lastModifiedDateTime: + pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' + type: string + description: The last modified time of the service event. + format: date-time + startDateTime: + pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' + type: string + description: The start time of the service event. + format: date-time + title: + type: string + description: The title of the service event. + microsoft.graph.serviceHealthClassificationType: + title: serviceHealthClassificationType + enum: + - advisory + - incident + - unknownFutureValue + type: string + microsoft.graph.serviceHealthOrigin: + title: serviceHealthOrigin + enum: + - microsoft + - thirdParty + - customer + - unknownFutureValue + type: string + microsoft.graph.serviceHealthIssuePost: + title: serviceHealthIssuePost + type: object + properties: + createdDateTime: + pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' + type: string + description: The published time of the post. + format: date-time + description: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.itemBody' + description: The content of the service issue post. + nullable: true + postType: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.postType' + description: 'The post type of the service issue historical post. Possible values are: regular, quick, strategic, unknownFutureValue.' + nullable: true + microsoft.graph.itemBody: + title: itemBody + type: object + properties: + content: + type: string + description: The content of the item. + nullable: true + contentType: + anyOf: + - $ref: '#/components/schemas/microsoft.graph.bodyType' + description: The type of the content. Possible values are text and html. + nullable: true + microsoft.graph.serviceUpdateCategory: + title: serviceUpdateCategory + enum: + - preventOrFixIssue + - planForChange + - stayInformed + - unknownFutureValue + type: string + microsoft.graph.serviceUpdateSeverity: + title: serviceUpdateSeverity + enum: + - normal + - high + - critical + - unknownFutureValue + type: string + microsoft.graph.serviceUpdateMessageViewpoint: + title: serviceUpdateMessageViewpoint + type: object + properties: + isArchived: + type: boolean + description: Indicates whether the user archived the message. + nullable: true + isFavorited: + type: boolean + description: Indicates whether the user marked the message as favorite. + nullable: true + isRead: + type: boolean + description: Indicates whether the user read the message. + nullable: true + microsoft.graph.serviceAnnouncementAttachment: + allOf: + - $ref: '#/components/schemas/microsoft.graph.entity' + - title: serviceAnnouncementAttachment + type: object + properties: + content: + type: string + description: The attachment content. + format: base64url + nullable: true + contentType: + type: string + nullable: true + lastModifiedDateTime: + pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' + type: string + format: date-time + nullable: true + name: + type: string + nullable: true + size: + maximum: 2147483647 + minimum: -2147483648 + type: integer + format: int32 + microsoft.graph.ODataErrors.MainError: + required: + - code + - message + type: object + properties: + code: + type: string + message: + type: string + target: + type: string + nullable: true + details: + type: array + items: + $ref: '#/components/schemas/microsoft.graph.ODataErrors.ErrorDetails' + innererror: + $ref: '#/components/schemas/microsoft.graph.ODataErrors.InnerError' + microsoft.graph.keyValuePair: + title: keyValuePair + type: object + properties: + name: + type: string + description: Name for this key-value pair + value: + type: string + description: Value for this key-value pair + nullable: true + microsoft.graph.postType: + title: postType + enum: + - regular + - quick + - strategic + - unknownFutureValue + type: string + microsoft.graph.bodyType: + title: bodyType + enum: + - text + - html + type: string + microsoft.graph.ODataErrors.ErrorDetails: + required: + - code + - message + type: object + properties: + code: + type: string + message: + type: string + target: + type: string + nullable: true + microsoft.graph.ODataErrors.InnerError: + title: InnerError + type: object + properties: + request-id: + type: string + description: Request Id as tracked internally by the service + nullable: true + client-request-id: + type: string + description: Client request Id as sent by the client application. + nullable: true + Date: + pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' + type: string + description: Date when the error occured. + format: date-time + nullable: true + responses: + error: + description: error + content: + application/json: + schema: + $ref: '#/components/schemas/microsoft.graph.ODataErrors.ODataError' \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs index 57c5fef09..11dcaec14 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs @@ -73,24 +73,24 @@ public static class OpenApiFilterService var rootNode = CreateOpenApiUrlTreeNode(sources); // Iterate through urls dictionary and fetch operations for each url - foreach (var path in requestUrls) + foreach (var url in requestUrls) { var serverList = source.Servers; - var url = FormatUrlString(path.Key, serverList); + var path = ExtractPath(url.Key, serverList); - var openApiOperations = GetOpenApiOperations(rootNode, url, apiVersion); + var openApiOperations = GetOpenApiOperations(rootNode, path, apiVersion); if (openApiOperations == null) { - Console.WriteLine($"The url {url} could not be found in the OpenApi description"); + Console.WriteLine($"The url {url.Key} could not be found in the OpenApi description"); continue; } // Add the available ops if they are in the postman collection. See path.Value foreach (var ops in openApiOperations) { - if (path.Value.Contains(ops.Key.ToString().ToUpper())) + if (url.Value.Contains(ops.Key.ToString().ToUpper())) { - operationTypes.Add(ops.Key + url); + operationTypes.Add(ops.Key + path); } } } @@ -323,7 +323,7 @@ private static bool AddReferences(OpenApiComponents newComponents, OpenApiCompon return moreStuff; } - private static string FormatUrlString(string url, IList serverList) + private static string ExtractPath(string url, IList serverList) { var queryPath = string.Empty; foreach (var server in serverList) @@ -334,8 +334,8 @@ private static string FormatUrlString(string url, IList serverLis continue; } - var querySegments = url.Split(new[]{ serverUrl }, StringSplitOptions.None); - queryPath = querySegments[1]; + var urlComponents = url.Split(new[]{ serverUrl }, StringSplitOptions.None); + queryPath = urlComponents[1]; } return queryPath; From 53c56f7dc2cfa3824c738d9ca133a05c9ceb544e Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 27 Mar 2022 14:37:00 -0400 Subject: [PATCH 40/49] Removed openapi file that snook in. --- filteredGraph.yaml | 478 --------------------------------------------- 1 file changed, 478 deletions(-) delete mode 100644 filteredGraph.yaml diff --git a/filteredGraph.yaml b/filteredGraph.yaml deleted file mode 100644 index 4dea96ea8..000000000 --- a/filteredGraph.yaml +++ /dev/null @@ -1,478 +0,0 @@ -openapi: 3.0.1 -info: - title: OData Service for namespace microsoft.graph - Subset - description: This OData service is located at https://graph.microsoft.com/v1.0 - version: 1.0.1 -servers: - - url: https://graph.microsoft.com/v1.0 -paths: - /admin/serviceAnnouncement: - get: - tags: - - admin.serviceAnnouncement - summary: Get serviceAnnouncement from admin - description: A container for service communications resources. Read-only. - operationId: admin.GetServiceAnnouncement - parameters: - - name: $select - in: query - description: Select properties to be returned - style: form - explode: false - schema: - uniqueItems: true - type: array - items: - enum: - - id - - healthOverviews - - issues - - messages - type: string - - name: $expand - in: query - description: Expand related entities - style: form - explode: false - schema: - uniqueItems: true - type: array - items: - enum: - - '*' - - healthOverviews - - issues - - messages - type: string - responses: - '200': - description: Retrieved navigation property - content: - application/json: - schema: - $ref: '#/components/schemas/microsoft.graph.serviceAnnouncement' - links: - healthOverviews: - operationId: admin.ServiceAnnouncement.ListHealthOverviews - issues: - operationId: admin.ServiceAnnouncement.ListIssues - messages: - operationId: admin.ServiceAnnouncement.ListMessages - 4XX: - $ref: '#/components/responses/error' - 5XX: - $ref: '#/components/responses/error' - x-ms-docs-operation-type: operation - patch: - tags: - - admin.serviceAnnouncement - summary: Update the navigation property serviceAnnouncement in admin - operationId: admin.UpdateServiceAnnouncement - requestBody: - description: New navigation property values - content: - application/json: - schema: - $ref: '#/components/schemas/microsoft.graph.serviceAnnouncement' - required: true - responses: - '204': - description: Success - 4XX: - $ref: '#/components/responses/error' - 5XX: - $ref: '#/components/responses/error' - x-ms-docs-operation-type: operation -components: - schemas: - microsoft.graph.serviceAnnouncement: - allOf: - - $ref: '#/components/schemas/microsoft.graph.entity' - - title: serviceAnnouncement - type: object - properties: - healthOverviews: - type: array - items: - $ref: '#/components/schemas/microsoft.graph.serviceHealth' - description: 'A collection of service health information for tenant. This property is a contained navigation property, it is nullable and readonly.' - issues: - type: array - items: - $ref: '#/components/schemas/microsoft.graph.serviceHealthIssue' - description: 'A collection of service issues for tenant. This property is a contained navigation property, it is nullable and readonly.' - messages: - type: array - items: - $ref: '#/components/schemas/microsoft.graph.serviceUpdateMessage' - description: 'A collection of service messages for tenant. This property is a contained navigation property, it is nullable and readonly.' - microsoft.graph.entity: - title: entity - type: object - properties: - id: - type: string - description: Read-only. - microsoft.graph.serviceHealth: - allOf: - - $ref: '#/components/schemas/microsoft.graph.entity' - - title: serviceHealth - type: object - properties: - service: - type: string - description: The service name. Use the list healthOverviews operation to get exact string names for services subscribed by the tenant. - status: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.serviceHealthStatus' - description: 'Show the overral service health status. Possible values are: serviceOperational, investigating, restoringService, verifyingService, serviceRestored, postIncidentReviewPublished, serviceDegradation, serviceInterruption, extendedRecovery, falsePositive, investigationSuspended, resolved, mitigatedExternal, mitigated, resolvedExternal, confirmed, reported, unknownFutureValue. For more details, see serviceHealthStatus values.' - issues: - type: array - items: - $ref: '#/components/schemas/microsoft.graph.serviceHealthIssue' - description: 'A collection of issues that happened on the service, with detailed information for each issue.' - microsoft.graph.serviceHealthIssue: - allOf: - - $ref: '#/components/schemas/microsoft.graph.serviceAnnouncementBase' - - title: serviceHealthIssue - type: object - properties: - classification: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.serviceHealthClassificationType' - description: 'The type of service health issue. Possible values are: advisory, incident, unknownFutureValue.' - feature: - type: string - description: The feature name of the service issue. - nullable: true - featureGroup: - type: string - description: The feature group name of the service issue. - nullable: true - impactDescription: - type: string - description: The description of the service issue impact. - isResolved: - type: boolean - description: Indicates whether the issue is resolved. - origin: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.serviceHealthOrigin' - description: 'Indicates the origin of the service issue. Possible values are: microsoft, thirdParty, customer, unknownFutureValue.' - posts: - type: array - items: - $ref: '#/components/schemas/microsoft.graph.serviceHealthIssuePost' - description: Collection of historical posts for the service issue. - service: - type: string - description: Indicates the service affected by the issue. - status: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.serviceHealthStatus' - description: 'The status of the service issue. Possible values are: serviceOperational, investigating, restoringService, verifyingService, serviceRestored, postIncidentReviewPublished, serviceDegradation, serviceInterruption, extendedRecovery, falsePositive, investigationSuspended, resolved, mitigatedExternal, mitigated, resolvedExternal, confirmed, reported, unknownFutureValue. See more in the table below.' - microsoft.graph.serviceUpdateMessage: - allOf: - - $ref: '#/components/schemas/microsoft.graph.serviceAnnouncementBase' - - title: serviceUpdateMessage - type: object - properties: - actionRequiredByDateTime: - pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' - type: string - description: The expected deadline of the action for the message. - format: date-time - nullable: true - attachmentsArchive: - type: string - description: The zip file that contains all attachments for a message. - format: base64url - nullable: true - body: - $ref: '#/components/schemas/microsoft.graph.itemBody' - category: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.serviceUpdateCategory' - description: 'The service message category. Possible values are: preventOrFixIssue, planForChange, stayInformed, unknownFutureValue.' - hasAttachments: - type: boolean - description: Indicates whether the message has any attachment. - isMajorChange: - type: boolean - description: Indicates whether the message describes a major update for the service. - nullable: true - services: - type: array - items: - type: string - nullable: true - description: The affected services by the service message. - severity: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.serviceUpdateSeverity' - description: 'The severity of the service message. Possible values are: normal, high, critical, unknownFutureValue.' - tags: - type: array - items: - type: string - nullable: true - description: 'A collection of tags for the service message. Tags are provided by the service team/support team who post the message to tell whether this message contains privacy data, or whether this message is for a service new feature update, and so on.' - viewPoint: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.serviceUpdateMessageViewpoint' - description: 'Represents user viewpoints data of the service message. This data includes message status such as whether the user has archived, read, or marked the message as favorite. This property is null when accessed with application permissions.' - nullable: true - attachments: - type: array - items: - $ref: '#/components/schemas/microsoft.graph.serviceAnnouncementAttachment' - description: A collection of serviceAnnouncementAttachments. - microsoft.graph.ODataErrors.ODataError: - required: - - error - type: object - properties: - error: - $ref: '#/components/schemas/microsoft.graph.ODataErrors.MainError' - microsoft.graph.serviceHealthStatus: - title: serviceHealthStatus - enum: - - serviceOperational - - investigating - - restoringService - - verifyingService - - serviceRestored - - postIncidentReviewPublished - - serviceDegradation - - serviceInterruption - - extendedRecovery - - falsePositive - - investigationSuspended - - resolved - - mitigatedExternal - - mitigated - - resolvedExternal - - confirmed - - reported - - unknownFutureValue - type: string - microsoft.graph.serviceAnnouncementBase: - allOf: - - $ref: '#/components/schemas/microsoft.graph.entity' - - title: serviceAnnouncementBase - type: object - properties: - details: - type: array - items: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.keyValuePair' - nullable: true - description: Additional details about service event. This property doesn't support filters. - endDateTime: - pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' - type: string - description: The end time of the service event. - format: date-time - nullable: true - lastModifiedDateTime: - pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' - type: string - description: The last modified time of the service event. - format: date-time - startDateTime: - pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' - type: string - description: The start time of the service event. - format: date-time - title: - type: string - description: The title of the service event. - microsoft.graph.serviceHealthClassificationType: - title: serviceHealthClassificationType - enum: - - advisory - - incident - - unknownFutureValue - type: string - microsoft.graph.serviceHealthOrigin: - title: serviceHealthOrigin - enum: - - microsoft - - thirdParty - - customer - - unknownFutureValue - type: string - microsoft.graph.serviceHealthIssuePost: - title: serviceHealthIssuePost - type: object - properties: - createdDateTime: - pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' - type: string - description: The published time of the post. - format: date-time - description: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.itemBody' - description: The content of the service issue post. - nullable: true - postType: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.postType' - description: 'The post type of the service issue historical post. Possible values are: regular, quick, strategic, unknownFutureValue.' - nullable: true - microsoft.graph.itemBody: - title: itemBody - type: object - properties: - content: - type: string - description: The content of the item. - nullable: true - contentType: - anyOf: - - $ref: '#/components/schemas/microsoft.graph.bodyType' - description: The type of the content. Possible values are text and html. - nullable: true - microsoft.graph.serviceUpdateCategory: - title: serviceUpdateCategory - enum: - - preventOrFixIssue - - planForChange - - stayInformed - - unknownFutureValue - type: string - microsoft.graph.serviceUpdateSeverity: - title: serviceUpdateSeverity - enum: - - normal - - high - - critical - - unknownFutureValue - type: string - microsoft.graph.serviceUpdateMessageViewpoint: - title: serviceUpdateMessageViewpoint - type: object - properties: - isArchived: - type: boolean - description: Indicates whether the user archived the message. - nullable: true - isFavorited: - type: boolean - description: Indicates whether the user marked the message as favorite. - nullable: true - isRead: - type: boolean - description: Indicates whether the user read the message. - nullable: true - microsoft.graph.serviceAnnouncementAttachment: - allOf: - - $ref: '#/components/schemas/microsoft.graph.entity' - - title: serviceAnnouncementAttachment - type: object - properties: - content: - type: string - description: The attachment content. - format: base64url - nullable: true - contentType: - type: string - nullable: true - lastModifiedDateTime: - pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' - type: string - format: date-time - nullable: true - name: - type: string - nullable: true - size: - maximum: 2147483647 - minimum: -2147483648 - type: integer - format: int32 - microsoft.graph.ODataErrors.MainError: - required: - - code - - message - type: object - properties: - code: - type: string - message: - type: string - target: - type: string - nullable: true - details: - type: array - items: - $ref: '#/components/schemas/microsoft.graph.ODataErrors.ErrorDetails' - innererror: - $ref: '#/components/schemas/microsoft.graph.ODataErrors.InnerError' - microsoft.graph.keyValuePair: - title: keyValuePair - type: object - properties: - name: - type: string - description: Name for this key-value pair - value: - type: string - description: Value for this key-value pair - nullable: true - microsoft.graph.postType: - title: postType - enum: - - regular - - quick - - strategic - - unknownFutureValue - type: string - microsoft.graph.bodyType: - title: bodyType - enum: - - text - - html - type: string - microsoft.graph.ODataErrors.ErrorDetails: - required: - - code - - message - type: object - properties: - code: - type: string - message: - type: string - target: - type: string - nullable: true - microsoft.graph.ODataErrors.InnerError: - title: InnerError - type: object - properties: - request-id: - type: string - description: Request Id as tracked internally by the service - nullable: true - client-request-id: - type: string - description: Client request Id as sent by the client application. - nullable: true - Date: - pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$' - type: string - description: Date when the error occured. - format: date-time - nullable: true - responses: - error: - description: error - content: - application/json: - schema: - $ref: '#/components/schemas/microsoft.graph.ODataErrors.ODataError' \ No newline at end of file From 988496a0ea0266cd400f985ef9aa161168e2e47e Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 27 Mar 2022 16:49:34 -0400 Subject: [PATCH 41/49] Fixed command alias and some descriptions --- src/Microsoft.OpenApi.Hidi/Program.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs index 0eab5a693..8b466913c 100644 --- a/src/Microsoft.OpenApi.Hidi/Program.cs +++ b/src/Microsoft.OpenApi.Hidi/Program.cs @@ -24,8 +24,8 @@ static async Task Main(string[] args) var csdlOption = new Option("--csdl", "Input CSDL file path or URL"); csdlOption.AddAlias("-cs"); - var csdlFilterOption = new Option("--csdlFilter", "Name of EntitySet or Singleton to filter CSDL on"); - csdlOption.AddAlias("-csf"); + var csdlFilterOption = new Option("--csdl-filter", "Comma delimited list of EntitySets or Singletons to filter CSDL on. e.g. tasks,accounts"); + csdlFilterOption.AddAlias("-csf"); var outputOption = new Option("--output", () => new FileInfo("./output"), "The output directory path for the generated file.") { Arity = ArgumentArity.ZeroOrOne }; outputOption.AddAlias("-o"); @@ -42,13 +42,13 @@ static async Task Main(string[] args) var logLevelOption = new Option("--loglevel", () => LogLevel.Information, "The log level to use when logging messages to the main output."); logLevelOption.AddAlias("-ll"); - var filterByOperationIdsOption = new Option("--filter-by-operationids", "Filters OpenApiDocument by OperationId(s) provided"); + var filterByOperationIdsOption = new Option("--filter-by-operationids", "Filters OpenApiDocument by comma delimited list of OperationId(s) provided"); filterByOperationIdsOption.AddAlias("-op"); - var filterByTagsOption = new Option("--filter-by-tags", "Filters OpenApiDocument by Tag(s) provided"); + var filterByTagsOption = new Option("--filter-by-tags", "Filters OpenApiDocument by comma delimited list of Tag(s) provided. Also accepts a single regex."); filterByTagsOption.AddAlias("-t"); - var filterByCollectionOption = new Option("--filter-by-collection", "Filters OpenApiDocument by Postman collection provided"); + var filterByCollectionOption = new Option("--filter-by-collection", "Filters OpenApiDocument by Postman collection provided. Provide path to collection file."); filterByCollectionOption.AddAlias("-c"); var inlineLocalOption = new Option("--inlineLocal", "Inline local $ref instances"); From 17e738691844fee2f35c5475b64899452a84d566 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 27 Mar 2022 17:09:34 -0400 Subject: [PATCH 42/49] Updated Verify nupkg --- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 066d20658..5ae43a371 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -20,8 +20,8 @@ - - + + all From 271e1aa66af24548f93b999a91b609d80bc2c90a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Mar 2022 21:13:35 +0000 Subject: [PATCH 43/49] Bump Microsoft.OpenApi.OData from 1.0.10-preview2 to 1.0.10-preview3 Bumps [Microsoft.OpenApi.OData](https://github.com/Microsoft/OpenAPI.NET.OData) from 1.0.10-preview2 to 1.0.10-preview3. - [Release notes](https://github.com/Microsoft/OpenAPI.NET.OData/releases) - [Commits](https://github.com/Microsoft/OpenAPI.NET.OData/commits) --- updated-dependencies: - dependency-name: Microsoft.OpenApi.OData dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 98def8818..128521424 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -46,7 +46,7 @@ - + From 0ecaed80638ebf6de212e0324895873191b6ee3e Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 3 Apr 2022 20:40:05 -0400 Subject: [PATCH 44/49] Convert anyOf/oneOf to allOf with first schema when writing v2 --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 74aed7da1..036222261 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System.Collections.Generic; +using System.Linq; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -260,7 +261,7 @@ public void SerializeAsV3(IOpenApiWriter writer) { Reference.SerializeAsV3(writer); return; - } + } else { if (Reference.IsExternal) // Temporary until v2 @@ -644,6 +645,20 @@ internal void WriteAsSchemaProperties( // allOf writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, (w, s) => s.SerializeAsV2(w)); + // If there isn't already an AllOf, and the schema contains a oneOf or anyOf write an allOf with the first + // schema in the list as an attempt to guess at a graceful downgrade situation. + if (AllOf == null || AllOf.Count == 0) + { + // anyOf (Not Supported in V2) - Write the first schema only as an allOf. + writer.WriteOptionalCollection(OpenApiConstants.AllOf, AnyOf.Take(1), (w, s) => s.SerializeAsV2(w)); + + if (AnyOf == null || AnyOf.Count == 0) + { + // oneOf (Not Supported in V2) - Write the first schema only as an allOf. + writer.WriteOptionalCollection(OpenApiConstants.AllOf, OneOf.Take(1), (w, s) => s.SerializeAsV2(w)); + } + } + // properties writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, (w, key, s) => s.SerializeAsV2(w, Required, key)); From 172c4538e2a8e35192de2a95f005a443c678448f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 21:10:39 +0000 Subject: [PATCH 45/49] Bump Verify from 16.4.4 to 16.5.4 Bumps [Verify](https://github.com/VerifyTests/Verify) from 16.4.4 to 16.5.4. - [Release notes](https://github.com/VerifyTests/Verify/releases) - [Commits](https://github.com/VerifyTests/Verify/commits) --- updated-dependencies: - dependency-name: Verify 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 5ae43a371..d67a655cc 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -20,7 +20,7 @@ - + From c3e898038367b6f1662d7f94249a0faa37ebc322 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 21:10:44 +0000 Subject: [PATCH 46/49] Bump FluentAssertions from 6.5.1 to 6.6.0 Bumps [FluentAssertions](https://github.com/fluentassertions/fluentassertions) from 6.5.1 to 6.6.0. - [Release notes](https://github.com/fluentassertions/fluentassertions/releases) - [Changelog](https://github.com/fluentassertions/fluentassertions/blob/develop/AcceptApiChanges.ps1) - [Commits](https://github.com/fluentassertions/fluentassertions/compare/6.5.1...6.6.0) --- updated-dependencies: - dependency-name: FluentAssertions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../Microsoft.OpenApi.Readers.Tests.csproj | 2 +- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 7d346009e..bfcba0163 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -243,7 +243,7 @@ - + diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 5ae43a371..9ba4c4501 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -15,7 +15,7 @@ - + From d0f41afa89031a0093b8ea1ab4c40e330574eb8c Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Fri, 8 Apr 2022 20:00:43 +0200 Subject: [PATCH 47/49] resolve local file reference with Schema type properly --- .../V3/OpenApiV3VersionService.cs | 20 ++++++++----------- .../ConvertToOpenApiReferenceV3Tests.cs | 16 +++++++++++++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs index 65acbc4e0..c967cde55 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs @@ -75,18 +75,6 @@ public OpenApiReference ConvertToOpenApiReference( var segments = reference.Split('#'); if (segments.Length == 1) { - // Either this is an external reference as an entire file - // or a simple string-style reference for tag and security scheme. - if (type == null) - { - // "$ref": "Pet.json" - return new OpenApiReference - { - Type = type, - ExternalResource = segments[0] - }; - } - if (type == ReferenceType.Tag || type == ReferenceType.SecurityScheme) { return new OpenApiReference @@ -95,6 +83,14 @@ public OpenApiReference ConvertToOpenApiReference( Id = reference }; } + + // Either this is an external reference as an entire file + // or a simple string-style reference for tag and security scheme. + return new OpenApiReference + { + Type = type, + ExternalResource = segments[0] + }; } else if (segments.Length == 2) { diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs index c4e88998e..f7368b09b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs @@ -108,5 +108,21 @@ public void ParseSecuritySchemeReference() reference.ExternalResource.Should().BeNull(); reference.Id.Should().Be(id); } + + [Fact] + public void ParseLocalFileReference() + { + // Arrange + var versionService = new OpenApiV3VersionService(Diagnostic); + var referenceType = ReferenceType.Schema; + var input = $"../schemas/collection.json"; + + // Act + var reference = versionService.ConvertToOpenApiReference(input, referenceType); + + // Assert + reference.Type.Should().Be(referenceType); + reference.ExternalResource.Should().Be(input); + } } } From f1b1be1e739091bee88b9619df3c84b621adc155 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Apr 2022 19:19:16 +0000 Subject: [PATCH 48/49] Bump Verify.Xunit from 16.4.4 to 16.5.4 Bumps [Verify.Xunit](https://github.com/VerifyTests/Verify) from 16.4.4 to 16.5.4. - [Release notes](https://github.com/VerifyTests/Verify/releases) - [Commits](https://github.com/VerifyTests/Verify/commits) --- 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 2766de246..39026a9f7 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -21,7 +21,7 @@ - + all From c9835c243a07703e2e4b3e47332da0781975cab4 Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sat, 9 Apr 2022 16:57:03 -0400 Subject: [PATCH 49/49] Updated package version to preview6 --- src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj | 2 +- src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj | 2 +- src/Microsoft.OpenApi/Microsoft.OpenApi.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 128521424..72ec16c0b 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -15,7 +15,7 @@ Microsoft.OpenApi.Hidi hidi ./../../artifacts - 0.5.0-preview5 + 0.5.0-preview6 OpenAPI.NET CLI tool for slicing OpenAPI documents © Microsoft Corporation. All rights reserved. OpenAPI .NET diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index 77b9cad26..62da19f40 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -10,7 +10,7 @@ Microsoft Microsoft.OpenApi.Readers Microsoft.OpenApi.Readers - 1.3.1-preview5 + 1.3.1-preview6 OpenAPI.NET Readers for JSON and YAML documents © Microsoft Corporation. All rights reserved. OpenAPI .NET diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index 7f3671942..b6f960daa 100644 --- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj +++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj @@ -11,7 +11,7 @@ Microsoft Microsoft.OpenApi Microsoft.OpenApi - 1.3.1-preview5 + 1.3.1-preview6 .NET models with JSON and YAML writers for OpenAPI specification © Microsoft Corporation. All rights reserved. OpenAPI .NET