From 478297933c9c60785f923c5745f2dfbe7d33bc36 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 9 Feb 2023 14:49:28 -0500 Subject: [PATCH 1/3] - adds external references resolution Signed-off-by: Vincent Biret --- src/Kiota.Builder/KiotaBuilder.cs | 37 +++++++++++++------ .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 6 +-- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index dc49d97eea..ed2ce58e58 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -88,7 +88,7 @@ private void CleanOutputDirectory() sw.Start(); if (originalDocument == null) { - openApiDocument = CreateOpenApiDocument(input, generating); + openApiDocument = await CreateOpenApiDocument(input, generating); if (openApiDocument != null) originalDocument = new OpenApiDocument(openApiDocument); } @@ -298,7 +298,7 @@ ex is SecurityException || return input; } - public OpenApiDocument? CreateOpenApiDocument(Stream input, bool generating = false) + public async Task CreateOpenApiDocument(Stream input, bool generating = false) { var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -308,7 +308,7 @@ ex is SecurityException || ValidationRuleSet.GetDefaultRuleSet(); //workaround since validation rule set doesn't support clearing rules if (generating) ruleSet.AddKiotaValidationRules(config); - var reader = new OpenApiStreamReader(new OpenApiReaderSettings + var settings = new OpenApiReaderSettings { ExtensionParsers = new() { @@ -326,26 +326,41 @@ ex is SecurityException || }, }, RuleSet = ruleSet, - }); - var doc = reader.Read(input, out var diag); + }; + try + { + var rawUri = config.OpenAPIFilePath.TrimEnd('/'); + var lastSlashIndex = rawUri.LastIndexOf('/'); + if (lastSlashIndex < 0) + lastSlashIndex = rawUri.Length - 1; + var documentUri = new Uri(rawUri[..lastSlashIndex]); + settings.BaseUrl = documentUri; + settings.LoadExternalRefs = true; + } + catch + { + // couldn't parse the URL, it's probably a local file + } + var reader = new OpenApiStreamReader(settings); + var readResult = await reader.ReadAsync(input); stopwatch.Stop(); if (generating) - foreach (var warning in diag.Warnings) + foreach (var warning in readResult.OpenApiDiagnostic.Warnings) logger.LogWarning("OpenAPI warning: {pointer} - {warning}", warning.Pointer, warning.Message); - if (diag.Errors.Any()) + if (readResult.OpenApiDiagnostic.Errors.Any()) { - logger.LogTrace("{timestamp}ms: Parsed OpenAPI with errors. {count} paths found.", stopwatch.ElapsedMilliseconds, doc?.Paths?.Count ?? 0); - foreach (var parsingError in diag.Errors) + logger.LogTrace("{timestamp}ms: Parsed OpenAPI with errors. {count} paths found.", stopwatch.ElapsedMilliseconds, readResult.OpenApiDocument?.Paths?.Count ?? 0); + foreach (var parsingError in readResult.OpenApiDiagnostic.Errors) { logger.LogError("OpenAPI error: {pointer} - {message}", parsingError.Pointer, parsingError.Message); } } else { - logger.LogTrace("{timestamp}ms: Parsed OpenAPI successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, doc?.Paths?.Count ?? 0); + logger.LogTrace("{timestamp}ms: Parsed OpenAPI successfully. {count} paths found.", stopwatch.ElapsedMilliseconds, readResult.OpenApiDocument?.Paths?.Count ?? 0); } - return doc; + return readResult.OpenApiDocument; } public static string GetDeeperMostCommonNamespaceNameForModels(OpenApiDocument document) { diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index c34ae8142f..6858601988 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -81,7 +81,7 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.1 var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = builder.CreateOpenApiDocument(fs); + var document = await builder.CreateOpenApiDocument(fs); var node = builder.CreateUriSpace(document); var codeModel = builder.CreateSourceModel(node); var modelsNS = codeModel.FindNamespaceByName("ApiSdk.models"); @@ -117,7 +117,7 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.1 var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = builder.CreateOpenApiDocument(fs); + var document = await builder.CreateOpenApiDocument(fs); var node = builder.CreateUriSpace(document); var extensionResult = await builder.GetLanguagesInformationAsync(new CancellationToken()); Assert.NotNull(extensionResult); @@ -179,7 +179,7 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.1 var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = builder.CreateOpenApiDocument(fs); + var document = await builder.CreateOpenApiDocument(fs); var node = builder.CreateUriSpace(document); var extensionResult = await builder.GetLanguagesInformationAsync(new CancellationToken()); Assert.Null(extensionResult); From 5e98fca1ee6d2a5233e0196580238699f102b76c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Feb 2023 15:57:34 -0500 Subject: [PATCH 2/3] - adds support for relative server URL Signed-off-by: Vincent Biret --- CHANGELOG.md | 1 + src/Kiota.Builder/KiotaBuilder.cs | 33 +++++++++--- .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 51 ++++++++++++++++--- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f73c3526..1c05bef7ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added constructors and query parameter factory methods to request configuration classes and constructors to query parameter classes in PHP. +- Added support for relative server URL. [#2278](https://github.com/microsoft/kiota/issues/2278) ### Changed diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index ed2ce58e58..72a402316e 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -88,7 +88,7 @@ private void CleanOutputDirectory() sw.Start(); if (originalDocument == null) { - openApiDocument = await CreateOpenApiDocument(input, generating); + openApiDocument = await CreateOpenApiDocumentAsync(input, generating, cancellationToken); if (openApiDocument != null) originalDocument = new OpenApiDocument(openApiDocument); } @@ -243,9 +243,27 @@ public static void FilterPathsByPatterns(OpenApiDocument doc, List include .ToList() .ForEach(x => doc.Paths.Remove(x)); } - private void SetApiRootUrl() + internal void SetApiRootUrl() { - config.ApiRootUrl = openApiDocument?.Servers.FirstOrDefault()?.Url.TrimEnd('/'); + var candidateUrl = openApiDocument?.Servers.FirstOrDefault()?.Url; + if (string.IsNullOrEmpty(candidateUrl)) + { + logger.LogWarning("No server url found in the OpenAPI document. The base url will need to be set when using the client."); + return; + } + else if (!candidateUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase) && config.OpenAPIFilePath.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + try + { + candidateUrl = new Uri(new Uri(config.OpenAPIFilePath), candidateUrl).ToString(); + } + catch + { + logger.LogWarning("Could not resolve the server url from the OpenAPI document. The base url will need to be set when using the client."); + return; + } + } + config.ApiRootUrl = candidateUrl.TrimEnd(ForwardSlash); } private void StopLogAndReset(Stopwatch sw, string prefix) { @@ -298,7 +316,8 @@ ex is SecurityException || return input; } - public async Task CreateOpenApiDocument(Stream input, bool generating = false) + private static readonly char ForwardSlash = '/'; + public async Task CreateOpenApiDocumentAsync(Stream input, bool generating = false, CancellationToken cancellationToken = default) { var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -329,8 +348,8 @@ ex is SecurityException || }; try { - var rawUri = config.OpenAPIFilePath.TrimEnd('/'); - var lastSlashIndex = rawUri.LastIndexOf('/'); + var rawUri = config.OpenAPIFilePath.TrimEnd(ForwardSlash); + var lastSlashIndex = rawUri.LastIndexOf(ForwardSlash); if (lastSlashIndex < 0) lastSlashIndex = rawUri.Length - 1; var documentUri = new Uri(rawUri[..lastSlashIndex]); @@ -342,7 +361,7 @@ ex is SecurityException || // couldn't parse the URL, it's probably a local file } var reader = new OpenApiStreamReader(settings); - var readResult = await reader.ReadAsync(input); + var readResult = await reader.ReadAsync(input); //TODO pass the cancellation token when the library patch is out stopwatch.Stop(); if (generating) foreach (var warning in readResult.OpenApiDiagnostic.Warnings) diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 6858601988..8e641cbe6f 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -29,6 +29,45 @@ public void Dispose() _httpClient.Dispose(); GC.SuppressFinalize(this); } + [InlineData("https://graph.microsoft.com/description.yaml", "/v1.0", "https://graph.microsoft.com/v1.0")] + [InlineData("/home/vsts/a/s/1", "/v1.0", "/v1.0")] + [InlineData("https://graph.microsoft.com/docs/description.yaml", "../v1.0", "https://graph.microsoft.com/v1.0")] + [InlineData("https://graph.microsoft.com/description.yaml", "https://graph.microsoft.com/v1.0", "https://graph.microsoft.com/v1.0")] + [Theory] + public async Task SupportsRelativeServerUrl(string descriptionUrl, string serverRelativeUrl, string expexted) + { + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + await File.WriteAllTextAsync(tempFilePath, @$"openapi: 3.0.1 +info: + title: OData Service for namespace microsoft.graph + description: This OData service is located at https://graph.microsoft.com/v1.0 + version: 1.0.1 +servers: + - url: {serverRelativeUrl} +paths: + /enumeration: + get: + responses: + '200': + content: + application/json: + schema: + type: string"); + var mockLogger = new Mock>(); + var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = descriptionUrl }, _httpClient); + await using var fs = new FileStream(tempFilePath, FileMode.Open); + var document = await builder.CreateOpenApiDocumentAsync(fs); + var node = builder.CreateUriSpace(document); + builder.SetApiRootUrl(); + var codeModel = builder.CreateSourceModel(node); + var rootNS = codeModel.FindNamespaceByName("ApiSdk"); + Assert.NotNull(rootNS); + var clientBuilder = rootNS.FindChildByName("Graph", false); + Assert.NotNull(clientBuilder); + var constructor = clientBuilder.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor)); + Assert.NotNull(constructor); + Assert.Equal(expexted, constructor.BaseUrl); + } private readonly HttpClient _httpClient = new(); [Fact] public async Task ParsesEnumDescriptions() @@ -81,7 +120,7 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.1 var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = await builder.CreateOpenApiDocument(fs); + var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); var codeModel = builder.CreateSourceModel(node); var modelsNS = codeModel.FindNamespaceByName("ApiSdk.models"); @@ -117,7 +156,7 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.1 var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = await builder.CreateOpenApiDocument(fs); + var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); var extensionResult = await builder.GetLanguagesInformationAsync(new CancellationToken()); Assert.NotNull(extensionResult); @@ -179,7 +218,7 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.1 var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = await builder.CreateOpenApiDocument(fs); + var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); var extensionResult = await builder.GetLanguagesInformationAsync(new CancellationToken()); Assert.Null(extensionResult); @@ -4961,7 +5000,7 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.0 var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = builder.CreateOpenApiDocument(fs); + var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); var codeModel = builder.CreateSourceModel(node); var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.me"); @@ -5014,7 +5053,7 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.0 var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = builder.CreateOpenApiDocument(fs); + var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document); var codeModel = builder.CreateSourceModel(node); var requestBuilderNS = codeModel.FindNamespaceByName("ApiSdk.me"); @@ -5058,7 +5097,7 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.0 var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration { ClientClassName = "Graph", OpenAPIFilePath = tempFilePath }, _httpClient); await using var fs = new FileStream(tempFilePath, FileMode.Open); - var document = builder.CreateOpenApiDocument(fs); + var document = await builder.CreateOpenApiDocumentAsync(fs); var node = builder.CreateUriSpace(document!); var codeModel = builder.CreateSourceModel(node); var collectionRequestBuilderNamespace = codeModel.FindNamespaceByName("ApiSdk.me.posts"); From f8a3117e6b11833effd5981d455453617fec0bb7 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 21 Feb 2023 08:09:42 -0500 Subject: [PATCH 3/3] - upgrades open.net Signed-off-by: Vincent Biret --- src/Kiota.Builder/Kiota.Builder.csproj | 4 ++-- src/Kiota.Builder/KiotaBuilder.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Kiota.Builder.csproj b/src/Kiota.Builder/Kiota.Builder.csproj index db37801367..13b00ab8fd 100644 --- a/src/Kiota.Builder/Kiota.Builder.csproj +++ b/src/Kiota.Builder/Kiota.Builder.csproj @@ -34,8 +34,8 @@ - - + + diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 72a402316e..92f8f7e477 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -361,7 +361,7 @@ ex is SecurityException || // couldn't parse the URL, it's probably a local file } var reader = new OpenApiStreamReader(settings); - var readResult = await reader.ReadAsync(input); //TODO pass the cancellation token when the library patch is out + var readResult = await reader.ReadAsync(input, cancellationToken); stopwatch.Stop(); if (generating) foreach (var warning in readResult.OpenApiDiagnostic.Warnings)