diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml
index a4af01161..31af5bd24 100644
--- a/.azure-pipelines/ci-build.yml
+++ b/.azure-pipelines/ci-build.yml
@@ -270,11 +270,11 @@ stages:
inputs:
source: current
- pwsh: |
- $artifactMainDirectory = Get-ChildItem -Filter Microsoft.OpenApi.Hidi-* -Directory -Recurse | select -First 1
- $artifactName = $artifactMainDirectory.Name -replace "Microsoft.OpenApi.Hidi-", ""
- #Set Variable $artifactName
- Write-Host "##vso[task.setvariable variable=artifactName; isSecret=false; isOutput=true;]$artifactName"
- Write-Host "##vso[task.setvariable variable=artifactMainDirectory; isSecret=false; isOutput=true;]$artifactMainDirectory"
+ $artifactName = Get-ChildItem -Path $(Pipeline.Workspace) -Filter Microsoft.OpenApi.Hidi-* -recurse | select -First 1
+ $artifactVersion= $artifactName -replace "Microsoft.OpenApi.Hidi-", ""
+ #Set Variable $artifactName and $artifactVersion
+ Write-Host "##vso[task.setvariable variable=artifactVersion; isSecret=false; isOutput=true]$artifactVersion"
+ Write-Host "##vso[task.setvariable variable=artifactName; isSecret=false; isOutput=true]$artifactName.FullName"
displayName: 'Fetch Artifact Name'
- task: NuGetCommand@2
@@ -289,10 +289,10 @@ stages:
inputs:
gitHubConnection: 'Github-MaggieKimani1'
tagSource: userSpecifiedTag
- tag: '$(artifactName)'
+ tag: '$(artifactVersion)'
title: '$(artifactName)'
releaseNotesSource: inline
- assets: '$(artifactMainDirectory)\**\*.exe'
+ assets: '$(Pipeline.Workspace)\**\*.exe'
changeLogType: issueBased
- deployment: deploy_lib
diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index 6f619ca85..8e5cb1f51 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -41,7 +41,7 @@ jobs:
- name: Checkout repository
id: checkout_repo
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 1d2d4106d..0adca3d2d 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout repository
id: checkout_repo
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v2
diff --git a/src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt b/src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt
new file mode 100644
index 000000000..ee3bf0d40
--- /dev/null
+++ b/src/Microsoft.OpenApi.Hidi/CsdlFilter.xslt
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
index e33f4777e..72ec16c0b 100644
--- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
+++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
@@ -15,7 +15,7 @@
Microsoft.OpenApi.Hidi
hidi
./../../artifacts
- 0.5.0-preview5
+ 0.5.0-preview6
OpenAPI.NET CLI tool for slicing OpenAPI documents
© Microsoft Corporation. All rights reserved.
OpenAPI .NET
@@ -31,14 +31,22 @@
true
+
+
+
+
+
+
+
+
-
+
-
+
-
+
diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
index 1f86e3c06..feb62042b 100644
--- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
+++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
@@ -10,8 +10,8 @@
using System.Net.Http;
using System.Security;
using System.Text;
-using System.Text.Json;
using System.Threading.Tasks;
+using System.Text.Json;
using Microsoft.Extensions.Logging;
using System.Xml.Linq;
using Microsoft.OData.Edm.Csdl;
@@ -22,23 +22,35 @@
using Microsoft.OpenApi.Services;
using Microsoft.OpenApi.Validations;
using Microsoft.OpenApi.Writers;
+using static Microsoft.OpenApi.Hidi.OpenApiSpecVersionHelper;
+using System.Threading;
+using System.Xml.Xsl;
+using System.Xml;
+using System.Runtime.CompilerServices;
+using System.Reflection;
namespace Microsoft.OpenApi.Hidi
{
public class OpenApiService
{
- public static async Task ProcessOpenApiDocument(
+ ///
+ /// Implementation of the transform command
+ ///
+ public static async Task TransformOpenApiDocument(
string openapi,
string csdl,
+ string csdlFilter,
FileInfo output,
- OpenApiSpecVersion? version,
+ bool cleanoutput,
+ string? version,
OpenApiFormat? format,
LogLevel loglevel,
- bool inline,
- bool resolveexternal,
+ bool inlineLocal,
+ bool inlineExternal,
string filterbyoperationids,
string filterbytags,
- string filterbycollection
+ string filterbycollection,
+ CancellationToken cancellationToken
)
{
var logger = ConfigureLoggerInstance(loglevel);
@@ -47,150 +59,265 @@ string filterbycollection
{
if (string.IsNullOrEmpty(openapi) && string.IsNullOrEmpty(csdl))
{
- throw new ArgumentNullException("Please input a file path");
+ throw new ArgumentException("Please input a file path");
}
- }
- catch (ArgumentNullException ex)
- {
- logger.LogError(ex.Message);
- return;
- }
- try
- {
if(output == null)
{
- throw new ArgumentException(nameof(output));
+ throw new ArgumentNullException(nameof(output));
+ }
+ if (cleanoutput && output.Exists)
+ {
+ output.Delete();
}
- }
- catch (ArgumentException ex)
- {
- logger.LogError(ex.Message);
- return;
- }
- try
- {
if (output.Exists)
{
- throw new IOException("The file you're writing to already exists. Please input a new file path.");
+ throw new IOException($"The file {output} already exists. Please input a new file path.");
}
- }
- catch (IOException ex)
- {
- logger.LogError(ex.Message);
- return;
- }
- Stream stream;
- OpenApiDocument document;
- OpenApiFormat openApiFormat;
- var stopwatch = new Stopwatch();
-
- if (!string.IsNullOrEmpty(csdl))
- {
- // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion
- openApiFormat = format ?? GetOpenApiFormat(csdl, logger);
- version ??= OpenApiSpecVersion.OpenApi3_0;
+ Stream stream;
+ OpenApiDocument document;
+ OpenApiFormat openApiFormat;
+ OpenApiSpecVersion openApiVersion;
+ var stopwatch = new Stopwatch();
- stream = await GetStream(csdl, logger);
- document = await ConvertCsdlToOpenApi(stream);
- }
- else
- {
- stream = await GetStream(openapi, logger);
-
- // Parsing OpenAPI file
- stopwatch.Start();
- logger.LogTrace("Parsing OpenApi file");
- var result = new OpenApiStreamReader(new OpenApiReaderSettings
+ if (!string.IsNullOrEmpty(csdl))
{
- ReferenceResolution = resolveexternal ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
- RuleSet = ValidationRuleSet.GetDefaultRuleSet()
+ using (logger.BeginScope($"Convert CSDL: {csdl}", csdl))
+ {
+ stopwatch.Start();
+ // Default to yaml and OpenApiVersion 3 during csdl to OpenApi conversion
+ openApiFormat = format ?? GetOpenApiFormat(csdl, logger);
+ openApiVersion = version != null ? TryParseOpenApiSpecVersion(version) : OpenApiSpecVersion.OpenApi3_0;
+
+ stream = await GetStream(csdl, logger, cancellationToken);
+
+ if (!string.IsNullOrEmpty(csdlFilter))
+ {
+ XslCompiledTransform transform = GetFilterTransform();
+ stream = ApplyFilter(csdl, csdlFilter, transform);
+ stream.Position = 0;
+ }
+ document = await ConvertCsdlToOpenApi(stream);
+ stopwatch.Stop();
+ logger.LogTrace("{timestamp}ms: Generated OpenAPI with {paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
+ }
}
- ).ReadAsync(stream).GetAwaiter().GetResult();
+ else
+ {
+ stream = await GetStream(openapi, logger, cancellationToken);
- document = result.OpenApiDocument;
- stopwatch.Stop();
+ using (logger.BeginScope($"Parse OpenAPI: {openapi}",openapi))
+ {
+ stopwatch.Restart();
+ var result = await new OpenApiStreamReader(new OpenApiReaderSettings
+ {
+ RuleSet = ValidationRuleSet.GetDefaultRuleSet(),
+ LoadExternalRefs = inlineExternal,
+ BaseUrl = openapi.StartsWith("http") ? new Uri(openapi) : new Uri("file:" + new FileInfo(openapi).DirectoryName + "\\")
+ }
+ ).ReadAsync(stream);
+
+ document = result.OpenApiDocument;
+
+ var context = result.OpenApiDiagnostic;
+ if (context.Errors.Count > 0)
+ {
+ logger.LogTrace("{timestamp}ms: Parsed OpenAPI with errors. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
+
+ var errorReport = new StringBuilder();
+
+ foreach (var error in context.Errors)
+ {
+ logger.LogError("OpenApi Parsing error: {message}", error.ToString());
+ errorReport.AppendLine(error.ToString());
+ }
+ logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}");
+ }
+ else
+ {
+ logger.LogTrace("{timestamp}ms: Parsed OpenApi successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
+ }
+
+ openApiFormat = format ?? GetOpenApiFormat(openapi, logger);
+ openApiVersion = version != null ? TryParseOpenApiSpecVersion(version) : result.OpenApiDiagnostic.SpecificationVersion;
+ stopwatch.Stop();
+ }
+ }
- var context = result.OpenApiDiagnostic;
- if (context.Errors.Count > 0)
+ using (logger.BeginScope("Filter"))
{
- var errorReport = new StringBuilder();
+ Func predicate = null;
- foreach (var error in context.Errors)
+ // Check if filter options are provided, then slice the OpenAPI document
+ if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags))
{
- errorReport.AppendLine(error.ToString());
+ throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time.");
+ }
+ if (!string.IsNullOrEmpty(filterbyoperationids))
+ {
+ logger.LogTrace("Creating predicate based on the operationIds supplied.");
+ predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids);
+
+ }
+ if (!string.IsNullOrEmpty(filterbytags))
+ {
+ logger.LogTrace("Creating predicate based on the tags supplied.");
+ predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags);
+
+ }
+ if (!string.IsNullOrEmpty(filterbycollection))
+ {
+ var fileStream = await GetStream(filterbycollection, logger, cancellationToken);
+ var requestUrls = ParseJsonCollectionFile(fileStream, logger);
+
+ logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection.");
+ predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source: document);
+ }
+ if (predicate != null)
+ {
+ stopwatch.Restart();
+ document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
+ stopwatch.Stop();
+ logger.LogTrace("{timestamp}ms: Creating filtered OpenApi document with {paths} paths.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
}
- logger.LogError($"{stopwatch.ElapsedMilliseconds}ms: OpenApi Parsing errors {string.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())}");
}
- else
+
+ using (logger.BeginScope("Output"))
{
- logger.LogTrace("{timestamp}ms: Parsed OpenApi successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, document.Paths.Count);
- }
+ ;
+ using var outputStream = output?.Create();
+ var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out;
- openApiFormat = format ?? GetOpenApiFormat(openapi, logger);
- version ??= result.OpenApiDiagnostic.SpecificationVersion;
- }
+ var settings = new OpenApiWriterSettings()
+ {
+ InlineLocalReferences = inlineLocal,
+ InlineExternalReferences = inlineExternal
+ };
- Func predicate;
+ IOpenApiWriter writer = openApiFormat switch
+ {
+ OpenApiFormat.Json => new OpenApiJsonWriter(textWriter, settings),
+ OpenApiFormat.Yaml => new OpenApiYamlWriter(textWriter, settings),
+ _ => throw new ArgumentException("Unknown format"),
+ };
- // Check if filter options are provided, then slice the OpenAPI document
- if (!string.IsNullOrEmpty(filterbyoperationids) && !string.IsNullOrEmpty(filterbytags))
- {
- throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time.");
- }
- if (!string.IsNullOrEmpty(filterbyoperationids))
- {
- logger.LogTrace("Creating predicate based on the operationIds supplied.");
- predicate = OpenApiFilterService.CreatePredicate(operationIds: filterbyoperationids);
+ logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer");
- logger.LogTrace("Creating subset OpenApi document.");
- document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
- }
- if (!string.IsNullOrEmpty(filterbytags))
- {
- logger.LogTrace("Creating predicate based on the tags supplied.");
- predicate = OpenApiFilterService.CreatePredicate(tags: filterbytags);
+ stopwatch.Start();
+ document.Serialize(writer, openApiVersion);
+ stopwatch.Stop();
- logger.LogTrace("Creating subset OpenApi document.");
- document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
+ logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms");
+ textWriter.Flush();
+ }
+ return 0;
}
- if (!string.IsNullOrEmpty(filterbycollection))
+ catch (Exception ex)
{
- var fileStream = await GetStream(filterbycollection, logger);
- var requestUrls = ParseJsonCollectionFile(fileStream, logger);
+#if DEBUG
+ logger.LogCritical(ex, ex.Message);
+#else
+ logger.LogCritical(ex.Message);
+
+#endif
+ return 1;
+ }
+ }
- logger.LogTrace("Creating predicate based on the paths and Http methods defined in the Postman collection.");
- predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source:document);
+ private static XslCompiledTransform GetFilterTransform()
+ {
+ XslCompiledTransform transform = new();
+ Assembly assembly = typeof(OpenApiService).GetTypeInfo().Assembly;
+ Stream xslt = assembly.GetManifestResourceStream("Microsoft.OpenApi.Hidi.CsdlFilter.xslt");
+ transform.Load(new XmlTextReader(new StreamReader(xslt)));
+ return transform;
+ }
- logger.LogTrace("Creating subset OpenApi document.");
- document = OpenApiFilterService.CreateFilteredDocument(document, predicate);
- }
-
- logger.LogTrace("Creating a new file");
- using var outputStream = output?.Create();
- var textWriter = outputStream != null ? new StreamWriter(outputStream) : Console.Out;
+ private static Stream ApplyFilter(string csdl, string entitySetOrSingleton, XslCompiledTransform transform)
+ {
+ Stream stream;
+ StreamReader inputReader = new(csdl);
+ XmlReader inputXmlReader = XmlReader.Create(inputReader);
+ MemoryStream filteredStream = new();
+ StreamWriter writer = new(filteredStream);
+ XsltArgumentList args = new();
+ args.AddParam("entitySetOrSingleton", "", entitySetOrSingleton);
+ transform.Transform(inputXmlReader, args, writer);
+ stream = filteredStream;
+ return stream;
+ }
- var settings = new OpenApiWriterSettings()
- {
- ReferenceInline = inline ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences
- };
- IOpenApiWriter writer = openApiFormat switch
+
+ ///
+ /// Implementation of the validate command
+ ///
+ public static async Task ValidateOpenApiDocument(
+ string openapi,
+ LogLevel loglevel,
+ CancellationToken cancellationToken)
+ {
+ var logger = ConfigureLoggerInstance(loglevel);
+
+ try
{
- OpenApiFormat.Json => new OpenApiJsonWriter(textWriter, settings),
- OpenApiFormat.Yaml => new OpenApiYamlWriter(textWriter, settings),
- _ => throw new ArgumentException("Unknown format"),
- };
+ if (string.IsNullOrEmpty(openapi))
+ {
+ throw new ArgumentNullException(nameof(openapi));
+ }
+ var stream = await GetStream(openapi, logger, cancellationToken);
- logger.LogTrace("Serializing to OpenApi document using the provided spec version and writer");
-
- stopwatch.Start();
- document.Serialize(writer, (OpenApiSpecVersion)version);
- stopwatch.Stop();
+ OpenApiDocument document;
+ Stopwatch stopwatch = Stopwatch.StartNew();
+ using (logger.BeginScope($"Parsing OpenAPI: {openapi}", openapi))
+ {
+ stopwatch.Start();
- logger.LogTrace($"Finished serializing in {stopwatch.ElapsedMilliseconds}ms");
+ var result = await new OpenApiStreamReader(new OpenApiReaderSettings
+ {
+ RuleSet = ValidationRuleSet.GetDefaultRuleSet()
+ }
+ ).ReadAsync(stream);
+
+ logger.LogTrace("{timestamp}ms: Completed parsing.", stopwatch.ElapsedMilliseconds);
+
+ document = result.OpenApiDocument;
+ var context = result.OpenApiDiagnostic;
+ if (context.Errors.Count != 0)
+ {
+ using (logger.BeginScope("Detected errors"))
+ {
+ foreach (var error in context.Errors)
+ {
+ logger.LogError(error.ToString());
+ }
+ }
+ }
+ stopwatch.Stop();
+ }
+
+ using (logger.BeginScope("Calculating statistics"))
+ {
+ var statsVisitor = new StatsVisitor();
+ var walker = new OpenApiWalker(statsVisitor);
+ walker.Walk(document);
+
+ logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report..");
+ logger.LogInformation(statsVisitor.GetStatisticsReport());
+ }
+
+ return 0;
+ }
+ catch (Exception ex)
+ {
+#if DEBUG
+ logger.LogCritical(ex, ex.Message);
+#else
+ logger.LogCritical(ex.Message);
+#endif
+ return 1;
+ }
- textWriter.Flush();
}
///
@@ -218,8 +345,8 @@ public static async Task ConvertCsdlToOpenApi(Stream csdl)
EnableDiscriminatorValue = false,
EnableDerivedTypesReferencesForRequestBody = false,
EnableDerivedTypesReferencesForResponses = false,
- ShowRootPath = true,
- ShowLinks = true
+ ShowRootPath = false,
+ ShowLinks = false
};
OpenApiDocument document = edmModel.ConvertToOpenApi(settings);
@@ -271,7 +398,7 @@ private static async Task GetStream(string input, ILogger logger)
return null;
}
}
- else
+ else
{
try
{
@@ -307,59 +434,137 @@ public static Dictionary> ParseJsonCollectionFile(Stream st
logger.LogTrace("Parsing the json collection file into a JsonDocument");
using var document = JsonDocument.Parse(stream);
var root = document.RootElement;
- var itemElement = root.GetProperty("item");
- foreach (var requestObject in itemElement.EnumerateArray().Select(item => item.GetProperty("request")))
- {
- // Fetch list of methods and urls from collection, store them in a dictionary
- var path = requestObject.GetProperty("url").GetProperty("raw").ToString();
- var method = requestObject.GetProperty("method").ToString();
- if (!requestUrls.ContainsKey(path))
+ requestUrls = EnumerateJsonDocument(root, requestUrls);
+ logger.LogTrace("Finished fetching the list of paths and Http methods defined in the Postman collection.");
+
+ return requestUrls;
+ }
+
+ private static Dictionary> EnumerateJsonDocument(JsonElement itemElement, Dictionary> paths)
+ {
+ var itemsArray = itemElement.GetProperty("item");
+
+ foreach (var item in itemsArray.EnumerateArray())
+ {
+ if(item.ValueKind == JsonValueKind.Object)
{
- requestUrls.Add(path, new List { method });
+ if(item.TryGetProperty("request", out var request))
+ {
+ // Fetch list of methods and urls from collection, store them in a dictionary
+ var path = request.GetProperty("url").GetProperty("raw").ToString();
+ var method = request.GetProperty("method").ToString();
+ if (!paths.ContainsKey(path))
+ {
+ paths.Add(path, new List { method });
+ }
+ else
+ {
+ paths[path].Add(method);
+ }
+ }
+ else
+ {
+ EnumerateJsonDocument(item, paths);
+ }
}
else
{
- requestUrls[path].Add(method);
+ EnumerateJsonDocument(item, paths);
}
}
- logger.LogTrace("Finished fetching the list of paths and Http methods defined in the Postman collection.");
- return requestUrls;
+
+ return paths;
}
- internal static async Task ValidateOpenApiDocument(string openapi, LogLevel loglevel)
- {
- if (string.IsNullOrEmpty(openapi))
- {
- throw new ArgumentNullException(nameof(openapi));
- }
- var logger = ConfigureLoggerInstance(loglevel);
- var stream = await GetStream(openapi, logger);
+ ///
+ /// Fixes the references in the resulting OpenApiDocument.
+ ///
+ /// The converted OpenApiDocument.
+ /// A valid OpenApiDocument instance.
+ // private static OpenApiDocument FixReferences2(OpenApiDocument document)
+ // {
+ // // This method is only needed because the output of ConvertToOpenApi isn't quite a valid OpenApiDocument instance.
+ // // So we write it out, and read it back in again to fix it up.
+
+ // OpenApiDocument document;
+ // logger.LogTrace("Parsing the OpenApi file");
+ // var result = await new OpenApiStreamReader(new OpenApiReaderSettings
+ // {
+ // RuleSet = ValidationRuleSet.GetDefaultRuleSet(),
+ // BaseUrl = new Uri(openapi)
+ // }
+ // ).ReadAsync(stream);
+
+ // document = result.OpenApiDocument;
+ // var context = result.OpenApiDiagnostic;
+ // var sb = new StringBuilder();
+ // document.SerializeAsV3(new OpenApiYamlWriter(new StringWriter(sb)));
+ // var doc = new OpenApiStringReader().Read(sb.ToString(), out _);
+
+ // return doc;
+ // }
- OpenApiDocument document;
- logger.LogTrace("Parsing the OpenApi file");
- document = new OpenApiStreamReader(new OpenApiReaderSettings
+ ///
+ /// Reads stream from file system or makes HTTP request depending on the input string
+ ///
+ private static async Task GetStream(string input, ILogger logger, CancellationToken cancellationToken)
+ {
+ Stream stream;
+ using (logger.BeginScope("Reading input stream"))
{
- RuleSet = ValidationRuleSet.GetDefaultRuleSet()
- }
- ).Read(stream, out var context);
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
- if (context.Errors.Count != 0)
- {
- foreach (var error in context.Errors)
+ if (input.StartsWith("http"))
{
- Console.WriteLine(error.ToString());
+ try
+ {
+ var httpClientHandler = new HttpClientHandler()
+ {
+ SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
+ };
+ using var httpClient = new HttpClient(httpClientHandler)
+ {
+ DefaultRequestVersion = HttpVersion.Version20
+ };
+ stream = await httpClient.GetStreamAsync(input, cancellationToken);
+ }
+ catch (HttpRequestException ex)
+ {
+ throw new InvalidOperationException($"Could not download the file at {input}", ex);
+ }
}
+ else
+ {
+ try
+ {
+ var fileInput = new FileInfo(input);
+ stream = fileInput.OpenRead();
+ }
+ catch (Exception ex) when (ex is FileNotFoundException ||
+ ex is PathTooLongException ||
+ ex is DirectoryNotFoundException ||
+ ex is IOException ||
+ ex is UnauthorizedAccessException ||
+ ex is SecurityException ||
+ ex is NotSupportedException)
+ {
+ throw new InvalidOperationException($"Could not open the file at {input}", ex);
+ }
+ }
+ stopwatch.Stop();
+ logger.LogTrace("{timestamp}ms: Read file {input}", stopwatch.ElapsedMilliseconds, input);
}
-
- var statsVisitor = new StatsVisitor();
- var walker = new OpenApiWalker(statsVisitor);
- walker.Walk(document);
-
- logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report..");
- Console.WriteLine(statsVisitor.GetStatisticsReport());
+ return stream;
}
+ ///
+ /// Attempt to guess OpenAPI format based in input URL
+ ///
+ ///
+ ///
+ ///
private static OpenApiFormat GetOpenApiFormat(string input, ILogger logger)
{
logger.LogTrace("Getting the OpenApi format");
@@ -369,16 +574,18 @@ private static OpenApiFormat GetOpenApiFormat(string input, ILogger logger)
private static ILogger ConfigureLoggerInstance(LogLevel loglevel)
{
// Configure logger options
- #if DEBUG
+#if DEBUG
loglevel = loglevel > LogLevel.Debug ? LogLevel.Debug : loglevel;
- #endif
+#endif
var logger = LoggerFactory.Create((builder) => {
builder
- .AddConsole()
- #if DEBUG
+ .AddSimpleConsole(c => {
+ c.IncludeScopes = true;
+ })
+#if DEBUG
.AddDebug()
- #endif
+#endif
.SetMinimumLevel(loglevel);
}).CreateLogger();
diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs
new file mode 100644
index 000000000..a78255be2
--- /dev/null
+++ b/src/Microsoft.OpenApi.Hidi/OpenApiSpecVersionHelper.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Linq;
+
+namespace Microsoft.OpenApi.Hidi
+{
+ public static class OpenApiSpecVersionHelper
+ {
+ public static OpenApiSpecVersion TryParseOpenApiSpecVersion(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new InvalidOperationException("Please provide a version");
+ }
+ var res = value.Split('.', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
+
+ if (int.TryParse(res, out int result))
+ {
+
+ if (result >= 2 && result < 3)
+ {
+ return OpenApiSpecVersion.OpenApi2_0;
+ }
+ }
+
+ return OpenApiSpecVersion.OpenApi3_0; // default
+ }
+ }
+}
diff --git a/src/Microsoft.OpenApi.Hidi/Program.cs b/src/Microsoft.OpenApi.Hidi/Program.cs
index 95e6f63f2..8b466913c 100644
--- a/src/Microsoft.OpenApi.Hidi/Program.cs
+++ b/src/Microsoft.OpenApi.Hidi/Program.cs
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
+using System;
using System.CommandLine;
using System.IO;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -10,7 +12,7 @@ namespace Microsoft.OpenApi.Hidi
{
static class Program
{
- static async Task Main(string[] args)
+ static async Task Main(string[] args)
{
var rootCommand = new RootCommand() {
};
@@ -22,32 +24,38 @@ static async Task Main(string[] args)
var csdlOption = new Option("--csdl", "Input CSDL file path or URL");
csdlOption.AddAlias("-cs");
+ var csdlFilterOption = new Option("--csdl-filter", "Comma delimited list of EntitySets or Singletons to filter CSDL on. e.g. tasks,accounts");
+ csdlFilterOption.AddAlias("-csf");
+
var outputOption = new Option("--output", () => new FileInfo("./output"), "The output directory path for the generated file.") { Arity = ArgumentArity.ZeroOrOne };
outputOption.AddAlias("-o");
- var versionOption = new Option("--version", "OpenAPI specification version");
+ var cleanOutputOption = new Option("--clean-output", "Overwrite an existing file");
+ cleanOutputOption.AddAlias("-co");
+
+ var versionOption = new Option("--version", "OpenAPI specification version");
versionOption.AddAlias("-v");
var formatOption = new Option("--format", "File format");
formatOption.AddAlias("-f");
- var logLevelOption = new Option("--loglevel", () => LogLevel.Warning, "The log level to use when logging messages to the main output.");
+ var logLevelOption = new Option("--loglevel", () => LogLevel.Information, "The log level to use when logging messages to the main output.");
logLevelOption.AddAlias("-ll");
- var filterByOperationIdsOption = new Option("--filter-by-operationids", "Filters OpenApiDocument by OperationId(s) provided");
+ var filterByOperationIdsOption = new Option("--filter-by-operationids", "Filters OpenApiDocument by comma delimited list of OperationId(s) provided");
filterByOperationIdsOption.AddAlias("-op");
- var filterByTagsOption = new Option("--filter-by-tags", "Filters OpenApiDocument by Tag(s) provided");
+ var filterByTagsOption = new Option("--filter-by-tags", "Filters OpenApiDocument by comma delimited list of Tag(s) provided. Also accepts a single regex.");
filterByTagsOption.AddAlias("-t");
- var filterByCollectionOption = new Option("--filter-by-collection", "Filters OpenApiDocument by Postman collection provided");
+ var filterByCollectionOption = new Option("--filter-by-collection", "Filters OpenApiDocument by Postman collection provided. Provide path to collection file.");
filterByCollectionOption.AddAlias("-c");
- var 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")
{
@@ -55,31 +63,36 @@ static async Task Main(string[] args)
logLevelOption
};
- validateCommand.SetHandler(OpenApiService.ValidateOpenApiDocument, descriptionOption, logLevelOption);
+ validateCommand.SetHandler(OpenApiService.ValidateOpenApiDocument, descriptionOption, logLevelOption);
var transformCommand = new Command("transform")
{
descriptionOption,
csdlOption,
+ csdlFilterOption,
outputOption,
+ cleanOutputOption,
versionOption,
formatOption,
logLevelOption,
filterByOperationIdsOption,
filterByTagsOption,
filterByCollectionOption,
- inlineOption,
- resolveExternalOption,
+ inlineLocalOption,
+ inlineExternalOption
};
- transformCommand.SetHandler (
- OpenApiService.ProcessOpenApiDocument, descriptionOption, csdlOption, outputOption, versionOption, formatOption, logLevelOption, inlineOption, resolveExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption);
+ transformCommand.SetHandler (
+ OpenApiService.TransformOpenApiDocument, descriptionOption, csdlOption, csdlFilterOption, outputOption, cleanOutputOption, versionOption, formatOption, logLevelOption, inlineLocalOption, inlineExternalOption, filterByOperationIdsOption, filterByTagsOption, filterByCollectionOption);
rootCommand.Add(transformCommand);
rootCommand.Add(validateCommand);
// Parse the incoming args and invoke the handler
- return await rootCommand.InvokeAsync(args);
+ await rootCommand.InvokeAsync(args);
+
+ //// Wait for logger to write messages to the console before exiting
+ await Task.Delay(10);
}
}
}
diff --git a/src/Microsoft.OpenApi.Hidi/appsettings.json b/src/Microsoft.OpenApi.Hidi/appsettings.json
deleted file mode 100644
index 882248cf8..000000000
--- a/src/Microsoft.OpenApi.Hidi/appsettings.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Debug"
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj
index b4c41e6aa..62da19f40 100644
--- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj
+++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj
@@ -10,7 +10,7 @@
Microsoft
Microsoft.OpenApi.Readers
Microsoft.OpenApi.Readers
- 1.3.1-preview5
+ 1.3.1-preview6
OpenAPI.NET Readers for JSON and YAML documents
© Microsoft Corporation. All rights reserved.
OpenAPI .NET
@@ -40,7 +40,7 @@
-
+
@@ -66,4 +66,4 @@
SRResource.Designer.cs
-
+
\ No newline at end of file
diff --git a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs
index 732708459..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,
///
- /// 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 cccf06a68..13bdbdef8 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.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 bb00fb370..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);
+ 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)
+ 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();
- 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.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/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 bdaedf560..c967cde55 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;
@@ -64,6 +64,8 @@ public OpenApiV3VersionService(OpenApiDiagnostic diagnostic)
///
/// 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)
@@ -73,18 +75,6 @@ public OpenApiReference ConvertToOpenApiReference(
var segments = reference.Split('#');
if (segments.Length == 1)
{
- // Either this is an external reference as an entire file
- // or a simple string-style reference for tag and security scheme.
- if (type == null)
- {
- // "$ref": "Pet.json"
- return new OpenApiReference
- {
- Type = type,
- ExternalResource = segments[0]
- };
- }
-
if (type == ReferenceType.Tag || type == ReferenceType.SecurityScheme)
{
return new OpenApiReference
@@ -93,6 +83,14 @@ public OpenApiReference ConvertToOpenApiReference(
Id = reference
};
}
+
+ // Either this is an external reference as an entire file
+ // or a simple string-style reference for tag and security scheme.
+ return new OpenApiReference
+ {
+ Type = type,
+ ExternalResource = segments[0]
+ };
}
else if (segments.Length == 2)
{
@@ -114,8 +112,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.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/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/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj
index 7f3671942..b6f960daa 100644
--- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj
+++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj
@@ -11,7 +11,7 @@
Microsoft
Microsoft.OpenApi
Microsoft.OpenApi
- 1.3.1-preview5
+ 1.3.1-preview6
.NET models with JSON and YAML writers for OpenAPI specification
© Microsoft Corporation. All rights reserved.
OpenAPI .NET
diff --git a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs
index 4f685d2de..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.
@@ -70,15 +70,41 @@ public void SerializeAsV3(IOpenApiWriter writer)
throw Error.ArgumentNull(nameof(writer));
}
- if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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/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..6ffc260d1 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;
@@ -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/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs
index d9bc08e30..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.
@@ -64,13 +64,38 @@ public void SerializeAsV3(IOpenApiWriter writer)
throw Error.ArgumentNull(nameof(writer));
}
- if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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 eb6736183..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
@@ -96,15 +96,42 @@ public void SerializeAsV3(IOpenApiWriter writer)
throw Error.ArgumentNull(nameof(writer));
}
- if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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 fb7396db2..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.
@@ -71,15 +71,42 @@ public void SerializeAsV3(IOpenApiWriter writer)
throw Error.ArgumentNull(nameof(writer));
}
- if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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 03b465109..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;
@@ -12,7 +13,7 @@ namespace Microsoft.OpenApi.Models
///
/// Parameter Object.
///
- public class OpenApiParameter : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible
+ public class OpenApiParameter : IOpenApiSerializable, IOpenApiReferenceable, IEffective, IOpenApiExtensible
{
private bool? _explode;
@@ -145,13 +146,39 @@ public void SerializeAsV3(IOpenApiWriter writer)
throw Error.ArgumentNull(nameof(writer));
}
- if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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);
}
///
@@ -332,6 +367,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
writer.WriteEndObject();
}
+
}
///
diff --git a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs
index b222ba34f..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.
@@ -74,15 +74,38 @@ public void SerializeAsV3(IOpenApiWriter writer)
{
throw Error.ArgumentNull(nameof(writer));
}
+ var target = this;
- if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineAllReferences)
+ 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().ReferenceInline != ReferenceInlineSetting.InlineAllReferences)
+ 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/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs
index 2c8f738ca..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.
///
@@ -54,7 +59,7 @@ public string ReferenceV3
{
if (IsExternal)
{
- return GetExternalReference();
+ return GetExternalReferenceV3();
}
if (!Type.HasValue)
@@ -85,7 +90,7 @@ public string ReferenceV2
{
if (IsExternal)
{
- return GetExternalReference();
+ return GetExternalReferenceV2();
}
if (!Type.HasValue)
@@ -171,11 +176,21 @@ public void SerializeAsV2(IOpenApiWriter writer)
writer.WriteEndObject();
}
- private string GetExternalReference()
+ private string GetExternalReferenceV3()
+ {
+ if (Id != null)
+ {
+ return ExternalResource + "#/components/" + Type.GetDisplayName() + "/"+ Id;
+ }
+
+ return ExternalResource;
+ }
+
+ private string GetExternalReferenceV2()
{
if (Id != null)
{
- return ExternalResource + "#/" + Id;
+ return ExternalResource + "#/" + GetReferenceTypeNameAsV2((ReferenceType)Type) + "/" + Id;
}
return ExternalResource;
diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs
index d6308efcf..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
@@ -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 bc2e4e525..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.
@@ -61,13 +61,38 @@ public void SerializeAsV3(IOpenApiWriter writer)
throw Error.ArgumentNull(nameof(writer));
}
- if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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 60e314fcf..036222261 100644
--- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs
+++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using System.Collections.Generic;
+using System.Linq;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Writers;
@@ -11,7 +12,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,14 +253,22 @@ public void SerializeAsV3(IOpenApiWriter writer)
}
var settings = writer.GetSettings();
+ var target = this;
if (Reference != null)
{
- if (settings.ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ if (!settings.ShouldInlineReference(Reference))
{
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.
if (!settings.LoopDetector.PushLoop(this))
@@ -270,7 +279,7 @@ public void SerializeAsV3(IOpenApiWriter writer)
}
}
- SerializeAsV3WithoutReference(writer);
+ target.SerializeAsV3WithoutReference(writer);
if (Reference != null)
{
@@ -283,6 +292,7 @@ public void SerializeAsV3(IOpenApiWriter writer)
///
public void SerializeAsV3WithoutReference(IOpenApiWriter writer)
{
+
writer.WriteStartObject();
// title
@@ -442,14 +452,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.ReferenceInline != ReferenceInlineSetting.InlineLocalReferences)
+ 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))
@@ -466,7 +485,7 @@ internal void SerializeAsV2(
parentRequiredProperties = new HashSet();
}
- SerializeAsV2WithoutReference(writer, parentRequiredProperties, propertyName);
+ target.SerializeAsV2WithoutReference(writer, parentRequiredProperties, propertyName);
}
///
@@ -626,6 +645,20 @@ internal void WriteAsSchemaProperties(
// allOf
writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, (w, s) => s.SerializeAsV2(w));
+ // If there isn't already an AllOf, and the schema contains a oneOf or anyOf write an allOf with the first
+ // schema in the list as an attempt to guess at a graceful downgrade situation.
+ if (AllOf == null || AllOf.Count == 0)
+ {
+ // anyOf (Not Supported in V2) - Write the first schema only as an allOf.
+ writer.WriteOptionalCollection(OpenApiConstants.AllOf, AnyOf.Take(1), (w, s) => s.SerializeAsV2(w));
+
+ if (AnyOf == null || AnyOf.Count == 0)
+ {
+ // oneOf (Not Supported in V2) - Write the first schema only as an allOf.
+ writer.WriteOptionalCollection(OpenApiConstants.AllOf, OneOf.Take(1), (w, s) => s.SerializeAsV2(w));
+ }
+ }
+
// properties
writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, (w, key, s) =>
s.SerializeAsV2(w, Required, key));
@@ -666,5 +699,21 @@ internal void WriteAsSchemaProperties(
// extensions
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)
+ {
+ 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/OpenApiFilterService.cs b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs
index 7b9111e4d..11dcaec14 100644
--- a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs
+++ b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs
@@ -73,23 +73,24 @@ public static class OpenApiFilterService
var rootNode = CreateOpenApiUrlTreeNode(sources);
// Iterate through urls dictionary and fetch operations for each url
- foreach (var path in requestUrls)
+ foreach (var url in requestUrls)
{
var serverList = source.Servers;
- var url = FormatUrlString(path.Key, serverList);
+ var path = ExtractPath(url.Key, serverList);
- var openApiOperations = GetOpenApiOperations(rootNode, url, apiVersion);
+ var openApiOperations = GetOpenApiOperations(rootNode, path, apiVersion);
if (openApiOperations == null)
{
+ Console.WriteLine($"The url {url.Key} could not be found in the OpenApi description");
continue;
}
// Add the available ops if they are in the postman collection. See path.Value
foreach (var ops in openApiOperations)
{
- if (path.Value.Contains(ops.Key.ToString().ToUpper()))
+ if (url.Value.Contains(ops.Key.ToString().ToUpper()))
{
- operationTypes.Add(ops.Key + url);
+ operationTypes.Add(ops.Key + path);
}
}
}
@@ -322,7 +323,7 @@ private static bool AddReferences(OpenApiComponents newComponents, OpenApiCompon
return moreStuff;
}
- private static string FormatUrlString(string url, IList serverList)
+ private static string ExtractPath(string url, IList serverList)
{
var queryPath = string.Empty;
foreach (var server in serverList)
@@ -333,8 +334,8 @@ private static string FormatUrlString(string url, IList serverLis
continue;
}
- var querySegments = url.Split(new[]{ serverUrl }, StringSplitOptions.None);
- queryPath = querySegments[1];
+ var urlComponents = url.Split(new[]{ serverUrl }, StringSplitOptions.None);
+ queryPath = urlComponents[1];
}
return queryPath;
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/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/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
index 086d80d75..bfcba0163 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
+++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
@@ -243,11 +243,11 @@
-
+
-
+
diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs
index d684144cb..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
@@ -19,13 +15,14 @@ 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()
{
- ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences,
- CustomExternalLoader = new MockLoader()
+ LoadExternalRefs = true,
+ CustomExternalLoader = new MockLoader(),
+ BaseUrl = new Uri("file://c:\\")
});
// Todo: this should be ReadAsync
@@ -48,13 +45,14 @@ 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()
{
- ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences,
- CustomExternalLoader = new ResourceLoader()
+ LoadExternalRefs = true,
+ CustomExternalLoader = new ResourceLoader(),
+ BaseUrl = new Uri("fie://c:\\")
});
ReadResult result;
@@ -65,12 +63,13 @@ 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]
.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);
@@ -78,8 +77,9 @@ public async Task LoadTodoDocumentIntoWorkspace()
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/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.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs
index c4e88998e..f7368b09b 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs
@@ -108,5 +108,21 @@ public void ParseSecuritySchemeReference()
reference.ExternalResource.Should().BeNull();
reference.Id.Should().Be(id);
}
+
+ [Fact]
+ public void ParseLocalFileReference()
+ {
+ // Arrange
+ var versionService = new OpenApiV3VersionService(Diagnostic);
+ var referenceType = ReferenceType.Schema;
+ var input = $"../schemas/collection.json";
+
+ // Act
+ var reference = versionService.ConvertToOpenApiReference(input, referenceType);
+
+ // Assert
+ reference.Type.Should().Be(referenceType);
+ reference.ExternalResource.Should().Be(input);
+ }
}
}
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/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
index 360eeea92..39026a9f7 100644
--- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
+++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
@@ -15,13 +15,13 @@
-
+
-
+
-
-
-
+
+
+
all
@@ -30,7 +30,7 @@
-
+
@@ -44,6 +44,12 @@
Always
+
+ Always
+
+
+ Always
+
Always
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);
diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
index 263e5dd12..0b1dc1a11 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) { }
@@ -502,9 +508,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) { }
}
@@ -528,7 +532,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; }
@@ -538,6 +542,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) { }
@@ -560,7 +565,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; }
@@ -577,6 +582,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) { }
@@ -604,7 +610,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; }
@@ -616,6 +622,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) { }
@@ -674,7 +681,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; }
@@ -693,12 +700,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; }
@@ -710,6 +718,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) { }
@@ -723,6 +732,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; }
@@ -732,7 +742,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; }
@@ -741,12 +751,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; }
@@ -756,6 +767,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) { }
@@ -765,7 +777,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; }
@@ -807,6 +819,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) { }
@@ -1351,6 +1364,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
@@ -1369,6 +1385,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/Services/OpenApiFilterServiceTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs
index 78f8ec048..29cb684d2 100644
--- a/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs
@@ -69,6 +69,23 @@ public void ReturnFilteredOpenApiDocumentBasedOnPostmanCollection()
Assert.Equal(3, subsetOpenApiDocument.Paths.Count);
}
+ [Fact]
+ public void ShouldParseNestedPostmanCollection()
+ {
+ // Arrange
+ var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles\\postmanCollection_ver3.json");
+ var fileInput = new FileInfo(filePath);
+ var stream = fileInput.OpenRead();
+
+ // Act
+ var requestUrls = OpenApiService.ParseJsonCollectionFile(stream, _logger);
+ var pathCount = requestUrls.Count;
+
+ // Assert
+ Assert.NotNull(requestUrls);
+ Assert.Equal(30, pathCount);
+ }
+
[Fact]
public void ThrowsExceptionWhenUrlsInCollectionAreMissingFromSourceDocument()
{
@@ -86,6 +103,28 @@ public void ThrowsExceptionWhenUrlsInCollectionAreMissingFromSourceDocument()
Assert.Equal("The urls in the Postman collection supplied could not be found.", message);
}
+ [Fact]
+ public void ContinueProcessingWhenUrlsInCollectionAreMissingFromSourceDocument()
+ {
+ // Arrange
+ var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles\\postmanCollection_ver4.json");
+ var fileInput = new FileInfo(filePath);
+ var stream = fileInput.OpenRead();
+
+ // Act
+ var requestUrls = OpenApiService.ParseJsonCollectionFile(stream, _logger);
+ var pathCount = requestUrls.Count;
+ var predicate = OpenApiFilterService.CreatePredicate(requestUrls: requestUrls, source: _openApiDocumentMock);
+ var subsetOpenApiDocument = OpenApiFilterService.CreateFilteredDocument(_openApiDocumentMock, predicate);
+ var subsetPathCount = subsetOpenApiDocument.Paths.Count;
+
+ // Assert
+ Assert.NotNull(subsetOpenApiDocument);
+ Assert.NotEmpty(subsetOpenApiDocument.Paths);
+ Assert.Equal(2, subsetPathCount);
+ Assert.NotEqual(pathCount, subsetPathCount);
+ }
+
[Fact]
public void ThrowsInvalidOperationExceptionInCreatePredicateWhenInvalidArgumentsArePassed()
{
diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiServiceTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiServiceTests.cs
index 1c9fd003b..af5437aa1 100644
--- a/test/Microsoft.OpenApi.Tests/Services/OpenApiServiceTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiServiceTests.cs
@@ -22,7 +22,7 @@ public async Task ReturnConvertedCSDLFile()
// Act
var openApiDoc = await OpenApiService.ConvertCsdlToOpenApi(csdlStream);
- var expectedPathCount = 6;
+ var expectedPathCount = 5;
// Assert
Assert.NotNull(openApiDoc);
diff --git a/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver3.json b/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver3.json
new file mode 100644
index 000000000..2c7637ed7
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver3.json
@@ -0,0 +1,1382 @@
+{
+ "info": {
+ "_postman_id": "6281bdba-62b8-2276-a5d6-268e87f48c89",
+ "name": "Graph-Collection",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "admin",
+ "item": [
+ {
+ "name": "/admin",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews",
+ "{serviceHealth-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews",
+ "{serviceHealth-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews",
+ "{serviceHealth-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews",
+ "{serviceHealth-id}",
+ "issues"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews",
+ "{serviceHealth-id}",
+ "issues"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews",
+ "{serviceHealth-id}",
+ "issues",
+ "{serviceHealthIssue-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews",
+ "{serviceHealth-id}",
+ "issues",
+ "{serviceHealthIssue-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews",
+ "{serviceHealth-id}",
+ "issues",
+ "{serviceHealthIssue-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}/microsoft.graph.incidentReport()",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews/{serviceHealth-id}/issues/{serviceHealthIssue-id}/microsoft.graph.incidentReport()",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "healthOverviews",
+ "{serviceHealth-id}",
+ "issues",
+ "{serviceHealthIssue-id}",
+ "microsoft.graph.incidentReport()"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/issues",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "issues"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/issues",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "issues"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "issues",
+ "{serviceHealthIssue-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "issues",
+ "{serviceHealthIssue-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "issues",
+ "{serviceHealthIssue-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}/microsoft.graph.incidentReport()",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues/{serviceHealthIssue-id}/microsoft.graph.incidentReport()",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "issues",
+ "{serviceHealthIssue-id}",
+ "microsoft.graph.incidentReport()"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/microsoft.graph.archive",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.archive",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "microsoft.graph.archive"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/microsoft.graph.favorite",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.favorite",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "microsoft.graph.favorite"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/microsoft.graph.markRead",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.markRead",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "microsoft.graph.markRead"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/microsoft.graph.markUnread",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.markUnread",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "microsoft.graph.markUnread"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/microsoft.graph.unarchive",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.unarchive",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "microsoft.graph.unarchive"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/microsoft.graph.unfavorite",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/microsoft.graph.unfavorite",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "microsoft.graph.unfavorite"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}",
+ "attachments"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}",
+ "attachments"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}",
+ "attachments",
+ "{serviceAnnouncementAttachment-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}",
+ "attachments",
+ "{serviceAnnouncementAttachment-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}",
+ "attachments",
+ "{serviceAnnouncementAttachment-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}/content",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}/content",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}",
+ "attachments",
+ "{serviceAnnouncementAttachment-id}",
+ "content"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}/content",
+ "request": {
+ "method": "PUT",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachments/{serviceAnnouncementAttachment-id}/content",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}",
+ "attachments",
+ "{serviceAnnouncementAttachment-id}",
+ "content"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachmentsArchive",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachmentsArchive",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}",
+ "attachmentsArchive"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachmentsArchive",
+ "request": {
+ "method": "PUT",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages/{serviceUpdateMessage-id}/attachmentsArchive",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "admin",
+ "serviceAnnouncement",
+ "messages",
+ "{serviceUpdateMessage-id}",
+ "attachmentsArchive"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "agreementAcceptances",
+ "item": [
+ {
+ "name": "/agreementAcceptances",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "agreementAcceptances"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/agreementAcceptances",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "agreementAcceptances"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/agreementAcceptances/{agreementAcceptance-id}",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances/{agreementAcceptance-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "agreementAcceptances",
+ "{agreementAcceptance-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/agreementAcceptances/{agreementAcceptance-id}",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances/{agreementAcceptance-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "agreementAcceptances",
+ "{agreementAcceptance-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/agreementAcceptances/{agreementAcceptance-id}",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances/{agreementAcceptance-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "agreementAcceptances",
+ "{agreementAcceptance-id}"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "appCatalogs",
+ "item": [
+ {
+ "name": "/appCatalogs",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}",
+ "appDefinitions"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}",
+ "appDefinitions"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}",
+ "appDefinitions",
+ "{teamsAppDefinition-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}",
+ "appDefinitions",
+ "{teamsAppDefinition-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}",
+ "appDefinitions",
+ "{teamsAppDefinition-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}",
+ "appDefinitions",
+ "{teamsAppDefinition-id}",
+ "bot"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}",
+ "appDefinitions",
+ "{teamsAppDefinition-id}",
+ "bot"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{teamsApp-id}/appDefinitions/{teamsAppDefinition-id}/bot",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs",
+ "teamsApps",
+ "{teamsApp-id}",
+ "appDefinitions",
+ "{teamsAppDefinition-id}",
+ "bot"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver4.json b/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver4.json
new file mode 100644
index 000000000..edafeb0bd
--- /dev/null
+++ b/test/Microsoft.OpenApi.Tests/UtilityFiles/postmanCollection_ver4.json
@@ -0,0 +1,145 @@
+{
+ "info": {
+ "_postman_id": "43402ca3-f018-7c9b-2315-f176d9b171a3",
+ "name": "Graph-Collection",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "users-GET",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/users",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "users"
+ ]
+ }
+ }
+ },
+ {
+ "name": "users-POST",
+ "request": {
+ "method": "POST",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/users",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "users"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/appCatalogs",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/appCatalogs",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "appCatalogs"
+ ]
+ }
+ }
+ },
+ {
+ "name": "/agreementAcceptances",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/agreementAcceptances",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "agreementAcceptances"
+ ]
+ }
+ }
+ },
+ {
+ "name": "{user-id}-GET",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/users/{user-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "users",
+ "{user-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "{user-id}-PATCH",
+ "request": {
+ "method": "PATCH",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/users/{user-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "users",
+ "{user-id}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "{user-id}-DELETE",
+ "request": {
+ "method": "DELETE",
+ "url": {
+ "raw": "https://graph.microsoft.com/v1.0/users/{user-id}",
+ "protocol": "https",
+ "host": [
+ "graph",
+ "microsoft",
+ "com"
+ ],
+ "path": [
+ "v1.0",
+ "users",
+ "{user-id}"
+ ]
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
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 a73fdbb9b..bfaa3da51 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);
@@ -468,6 +468,8 @@ private static OpenApiDocument CreateDocWithSimpleSchemaToInline()
["thing"] = thingSchema}
}
};
+ thingSchema.Reference.HostDocument = doc;
+
return doc;
}
@@ -515,7 +517,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);
@@ -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,13 +620,11 @@ 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);
- var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences });
+ var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { InlineLocalReferences = true });
// Act
doc.SerializeAsV2(writer);