From 45968f3263f39206bc31dbf9f8488e1991a380cf Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 15 Aug 2021 13:34:03 -0400 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 7aae53be9813a0b12478dbaf8f1e4c18a9c6a4dd Mon Sep 17 00:00:00 2001 From: Darrel Miller Date: Sun, 27 Mar 2022 09:07:50 -0400 Subject: [PATCH 9/9] 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