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);