From 15a00f19035aed198d0e989693566dcd5f8bcf33 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 26 May 2021 14:13:37 -0400 Subject: [PATCH 1/6] - upgrades open api lib to latest preview --- src/Kiota.Builder/Kiota.Builder.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Kiota.Builder.csproj b/src/Kiota.Builder/Kiota.Builder.csproj index 860e1cfb21..5b4466f235 100644 --- a/src/Kiota.Builder/Kiota.Builder.csproj +++ b/src/Kiota.Builder/Kiota.Builder.csproj @@ -6,8 +6,8 @@ - - + + From 8a6b74e2d3f555ad4e86a3ba757ab9aba6d07824 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 26 May 2021 14:15:47 -0400 Subject: [PATCH 2/6] - fixes #124 replaces custom url tree node by open api library version --- src/Kiota.Builder/Constants.cs | 5 + ...ons.cs => OpenApiUrlTreeNodeExtensions.cs} | 38 ++++--- src/Kiota.Builder/KiotaBuilder.cs | 51 +++++----- src/Kiota.Builder/OpenApiUriSpaceNode.cs | 92 ----------------- .../Kiota.Builder.Tests/KiotaBuilderTests.cs | 3 +- tests/Kiota.Builder.Tests/UriSpaceTests.cs | 98 ------------------- 6 files changed, 55 insertions(+), 232 deletions(-) create mode 100644 src/Kiota.Builder/Constants.cs rename src/Kiota.Builder/Extensions/{OpenApiUrlSpaceNodeExtensions.cs => OpenApiUrlTreeNodeExtensions.cs} (69%) delete mode 100644 src/Kiota.Builder/OpenApiUriSpaceNode.cs delete mode 100644 tests/Kiota.Builder.Tests/UriSpaceTests.cs diff --git a/src/Kiota.Builder/Constants.cs b/src/Kiota.Builder/Constants.cs new file mode 100644 index 0000000000..ef8fb0f0fc --- /dev/null +++ b/src/Kiota.Builder/Constants.cs @@ -0,0 +1,5 @@ +namespace Kiota.Builder { + public static class Constants { + public const string DefaultOpenApiLabel = "default"; + } +} diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlSpaceNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs similarity index 69% rename from src/Kiota.Builder/Extensions/OpenApiUrlSpaceNodeExtensions.cs rename to src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs index 1268e53995..afb2755e35 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlSpaceNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -3,22 +3,23 @@ using System.Linq; using System.Text.RegularExpressions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; namespace Kiota.Builder.Extensions { - public static class OpenApiUrlSpaceNodeExtensions { + public static class OpenApiUrlTreeNodeExtensions { // where component id and the value is the set of openapiurlNode referencing it - public static Dictionary> GetComponentsReferenceIndex(this OpenApiUrlSpaceNode rootNode) { - var result = new Dictionary>(StringComparer.OrdinalIgnoreCase); - AddAllPathsEntries(rootNode, result); + public static Dictionary> GetComponentsReferenceIndex(this OpenApiUrlTreeNode rootNode, string label) { + var result = new Dictionary>(StringComparer.OrdinalIgnoreCase); + AddAllPathsEntries(rootNode, result, label); return result; } - private static void AddAllPathsEntries(OpenApiUrlSpaceNode currentNode, Dictionary> index) { - if(currentNode == null) + private static void AddAllPathsEntries(OpenApiUrlTreeNode currentNode, Dictionary> index, string label) { + if(currentNode == null && string.IsNullOrEmpty(label)) return; - if(currentNode.PathItem != null && currentNode.HasOperations()) { - var nodeOperations = currentNode.PathItem.Operations.Values; + if(currentNode.PathItems.ContainsKey(label) && currentNode.HasOperations(label)) { + var nodeOperations = currentNode.PathItems[label].Operations.Values; var requestSchemasFirstLevel = nodeOperations.SelectMany(x => x.RequestBody?.Content?.Values?.Select(y => y.Schema) ?? Enumerable.Empty()); var responseSchemasFirstLevel = nodeOperations.SelectMany(x => x?.Responses?.Values?.SelectMany(y => @@ -35,9 +36,9 @@ private static void AddAllPathsEntries(OpenApiUrlSpaceNode currentNode, Dictiona if(currentNode.Children != null) foreach(var child in currentNode.Children.Values) - AddAllPathsEntries(child, index); + AddAllPathsEntries(child, index, label); } - internal static string GetNodeNamespaceFromPath(this OpenApiUrlSpaceNode currentNode, string prefix) => + internal static string GetNodeNamespaceFromPath(this OpenApiUrlTreeNode currentNode, string prefix) => prefix + ((currentNode?.Path?.Contains(pathNameSeparator) ?? false) ? "." + currentNode?.Path @@ -51,25 +52,30 @@ internal static string GetNodeNamespaceFromPath(this OpenApiUrlSpaceNode current /// /// Returns the class name for the node with more or less precision depending on the provided arguments /// - internal static string GetClassName(this OpenApiUrlSpaceNode currentNode, string suffix = default, string prefix = default, OpenApiOperation operation = default) { + internal static string GetClassName(this OpenApiUrlTreeNode currentNode, string suffix = default, string prefix = default, OpenApiOperation operation = default) { var rawClassName = operation?.GetResponseSchema()?.Reference?.GetClassName() ?? currentNode?.GetIdentifier()?.ReplaceValueIdentifier(); if((currentNode?.DoesNodeBelongToItemSubnamespace() ?? false) && idClassNameCleanup.IsMatch(rawClassName)) rawClassName = idClassNameCleanup.Replace(rawClassName, string.Empty); return prefix + rawClassName + suffix; } - internal static bool DoesNodeBelongToItemSubnamespace(this OpenApiUrlSpaceNode currentNode) => + internal static string GetPathItemDescription(this OpenApiUrlTreeNode currentNode, string label, string defaultValue = default) => + currentNode?.PathItems?.ContainsKey(label) ?? false ? + currentNode.PathItems[label].Description ?? + currentNode.PathItems[label].Summary ?? + defaultValue : + defaultValue; + internal static bool DoesNodeBelongToItemSubnamespace(this OpenApiUrlTreeNode currentNode) => (currentNode?.Segment?.StartsWith("{") ?? false) && (currentNode?.Segment?.EndsWith("}") ?? false); - internal static bool HasOperations(this OpenApiUrlSpaceNode currentNode) => currentNode?.PathItem?.Operations?.Any() ?? false; - internal static bool IsParameter(this OpenApiUrlSpaceNode currentNode) + internal static bool IsParameter(this OpenApiUrlTreeNode currentNode) { return currentNode?.Segment?.StartsWith("{") ?? false; } - internal static bool IsFunction(this OpenApiUrlSpaceNode currentNode) + internal static bool IsFunction(this OpenApiUrlTreeNode currentNode) { return currentNode?.Segment?.Contains("(") ?? false; } - internal static string GetIdentifier(this OpenApiUrlSpaceNode currentNode) + internal static string GetIdentifier(this OpenApiUrlTreeNode currentNode) { if(currentNode == null) return string.Empty; string identifier; diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 47c7be28ee..9aa348dd15 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -13,6 +13,7 @@ using Microsoft.OpenApi.Any; using Kiota.Builder.Refiners; using System.Security; +using Microsoft.OpenApi.Services; namespace Kiota.Builder { @@ -137,18 +138,18 @@ public OpenApiDocument CreateOpenApiDocument(Stream input) /// /// OpenAPI Document of the API to be processed /// Root node of the API URI space - public OpenApiUrlSpaceNode CreateUriSpace(OpenApiDocument doc) + public OpenApiUrlTreeNode CreateUriSpace(OpenApiDocument doc) { var stopwatch = new Stopwatch(); stopwatch.Start(); - var node = OpenApiUrlSpaceNode.Create(doc); - ComponentsReferencesIndex = node.GetComponentsReferenceIndex(); + var node = OpenApiUrlTreeNode.Create(doc, Constants.DefaultOpenApiLabel); + ComponentsReferencesIndex = node.GetComponentsReferenceIndex(Constants.DefaultOpenApiLabel); stopwatch.Stop(); logger.LogTrace("{timestamp}ms: Created UriSpace tree", stopwatch.ElapsedMilliseconds); return node; } - private Dictionary> ComponentsReferencesIndex; + private Dictionary> ComponentsReferencesIndex; private CodeNamespace rootNamespace; /// @@ -156,7 +157,7 @@ public OpenApiUrlSpaceNode CreateUriSpace(OpenApiDocument doc) /// /// Root OpenApiUriSpaceNode of API to be generated /// - public CodeNamespace CreateSourceModel(OpenApiUrlSpaceNode root) + public CodeNamespace CreateSourceModel(OpenApiUrlTreeNode root) { var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -208,13 +209,13 @@ public async Task CreateLanguageSourceFilesAsync(GenerationLanguage language, Co private static readonly string requestBuilderSuffix = "RequestBuilder"; /// - /// Create a CodeClass instance that is a request builder class for the OpenApiUrlSpaceNode + /// Create a CodeClass instance that is a request builder class for the OpenApiUrlTreeNode /// - private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUrlSpaceNode currentNode, OpenApiUrlSpaceNode rootNode) + private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, OpenApiUrlTreeNode rootNode) { // Determine Class Name CodeClass codeClass; - var isRootClientClass = String.IsNullOrEmpty(currentNode.GetIdentifier()); + var isRootClientClass = currentNode == rootNode; if (isRootClientClass) { codeClass = new CodeClass(currentNamespace) { @@ -229,7 +230,7 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr codeClass = new CodeClass((currentNode.DoesNodeBelongToItemSubnamespace() ? currentNamespace.EnsureItemNamespace() : currentNamespace)) { Name = className, ClassKind = CodeClassKind.RequestBuilder, - Description = currentNode.PathItem?.Description ?? currentNode.PathItem?.Summary ?? $"Builds and executes requests for operations under {currentNode.Path}", + Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel, $"Builds and executes requests for operations under {currentNode.Path}"), }; } var targetNS = currentNode.DoesNodeBelongToItemSubnamespace() ? currentNamespace.EnsureItemNamespace() : currentNamespace; @@ -259,9 +260,9 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr } // Add methods for Operations - if (currentNode.HasOperations()) + if (currentNode.HasOperations(Constants.DefaultOpenApiLabel)) { - foreach(var operation in currentNode.PathItem.Operations) + foreach(var operation in currentNode.PathItems[Constants.DefaultOpenApiLabel].Operations) CreateOperationMethods(rootNode, currentNode, operation.Key, operation.Value, codeClass); } CreatePathManagement(codeClass, currentNode, isRootClientClass); @@ -274,7 +275,7 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr }); } - private void CreatePathManagement(CodeClass currentClass, OpenApiUrlSpaceNode currentNode, bool isRootClientClass) { + private void CreatePathManagement(CodeClass currentClass, OpenApiUrlTreeNode currentNode, bool isRootClientClass) { var pathProperty = new CodeProperty(currentClass) { Access = AccessModifier.Private, Name = "pathSegment", @@ -385,7 +386,7 @@ private IEnumerable GetUnmappedTypeDefinitions(CodeElement codeElement return childElementsUnmappedTypes; } } - private CodeIndexer CreateIndexer(string childIdentifier, string childType, CodeClass codeClass, OpenApiUrlSpaceNode currentNode) + private CodeIndexer CreateIndexer(string childIdentifier, string childType, CodeClass codeClass, OpenApiUrlTreeNode currentNode) { var prop = new CodeIndexer(codeClass) { @@ -436,7 +437,7 @@ private CodeProperty CreateProperty(string childIdentifier, string childType, Co return prop; } private const string requestBodyBinaryContentType = "application/octet-stream"; - private void CreateOperationMethods(OpenApiUrlSpaceNode rootNode, OpenApiUrlSpaceNode currentNode, OperationType operationType, OpenApiOperation operation, CodeClass parentClass) + private void CreateOperationMethods(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OperationType operationType, OpenApiOperation operation, CodeClass parentClass) { var parameterClass = CreateOperationParameter(currentNode, operationType, operation, parentClass); @@ -487,7 +488,7 @@ private void CreateOperationMethods(OpenApiUrlSpaceNode rootNode, OpenApiUrlSpac AddRequestBuilderMethodParameters(rootNode, currentNode, operation, parameterClass, generatorMethod); logger.LogTrace("Creating method {name} of {type}", generatorMethod.Name, generatorMethod.ReturnType); } - private void AddRequestBuilderMethodParameters(OpenApiUrlSpaceNode rootNode, OpenApiUrlSpaceNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) { + private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) { var nonBinaryRequestBody = operation.RequestBody?.Content?.FirstOrDefault(x => !requestBodyBinaryContentType.Equals(x.Key, StringComparison.OrdinalIgnoreCase)); if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null) { @@ -559,7 +560,7 @@ private string GetShortestNamespaceNameForModelByReferenceId(string referenceId) } else throw new InvalidOperationException($"could not find a shortest namespace name for reference id {referenceId}"); } - private CodeType CreateModelClassAndType(OpenApiUrlSpaceNode rootNode, OpenApiUrlSpaceNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, CodeNamespace codeNamespace, string classNameSuffix = "") { + private CodeType CreateModelClassAndType(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, CodeNamespace codeNamespace, string classNameSuffix = "") { var className = currentNode.GetClassName(operation: operation, suffix: classNameSuffix); var codeDeclaration = AddModelDeclarationIfDoesntExit(rootNode, currentNode, schema, operation, className, codeNamespace, parentElement); return new CodeType(parentElement) { @@ -567,7 +568,7 @@ private CodeType CreateModelClassAndType(OpenApiUrlSpaceNode rootNode, OpenApiUr Name = className, }; } - private CodeTypeBase CreateModelClasses(OpenApiUrlSpaceNode rootNode, OpenApiUrlSpaceNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) + private CodeTypeBase CreateModelClasses(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) { var originalReference = schema?.Reference; var originalReferenceId = originalReference?.Id; @@ -619,16 +620,16 @@ private CodeTypeBase CreateModelClasses(OpenApiUrlSpaceNode rootNode, OpenApiUrl else throw new InvalidOperationException("un handled case, might be object type or array type"); // object type array of object are technically already handled in properties but if we have a root with those we might be missing some cases here } - private CodeElement GetExistingDeclaration(bool checkInAllNamespaces, CodeNamespace currentNamespace, OpenApiUrlSpaceNode currentNode, string declarationName) { + private CodeElement GetExistingDeclaration(bool checkInAllNamespaces, CodeNamespace currentNamespace, OpenApiUrlTreeNode currentNode, string declarationName) { var searchNameSpace = GetSearchNamespace(checkInAllNamespaces, currentNode, currentNamespace); return searchNameSpace.FindChildByName(declarationName, checkInAllNamespaces) as CodeElement; } - private CodeNamespace GetSearchNamespace(bool checkInAllNamespaces, OpenApiUrlSpaceNode currentNode, CodeNamespace currentNamespace) { + private CodeNamespace GetSearchNamespace(bool checkInAllNamespaces, OpenApiUrlTreeNode currentNode, CodeNamespace currentNamespace) { if(checkInAllNamespaces) return rootNamespace; else if (currentNode.DoesNodeBelongToItemSubnamespace()) return rootNamespace.EnsureItemNamespace(); else return currentNamespace; } - private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlSpaceNode rootNode, OpenApiUrlSpaceNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string declarationName, CodeNamespace currentNamespace, CodeElement parentElement, CodeClass inheritsFrom = null, bool checkInAllNamespaces = false) { + private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string declarationName, CodeNamespace currentNamespace, CodeElement parentElement, CodeClass inheritsFrom = null, bool checkInAllNamespaces = false) { var existingDeclaration = GetExistingDeclaration(checkInAllNamespaces, currentNamespace, currentNode, declarationName); if(existingDeclaration == null) // we can find it in the components { @@ -636,7 +637,7 @@ private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlSpaceNode rootNode var newEnum = new CodeEnum(currentNamespace) { Name = declarationName, Options = schema.Enum.OfType().Select(x => x.Value).Where(x => !"null".Equals(x)).ToList(),//TODO set the flag property - Description = currentNode.PathItem.Description ?? currentNode.PathItem.Summary, + Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel), }; return currentNamespace.AddEnum(newEnum).First(); } else { @@ -648,7 +649,7 @@ private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlSpaceNode rootNode var newClass = currentNamespace.AddClass(new CodeClass(currentNamespace) { Name = declarationName, ClassKind = CodeClassKind.Model, - Description = currentNode.PathItem.Description ?? currentNode.PathItem.Summary + Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel) }).First(); if(inheritsFrom != null) { var declaration = newClass.StartBlock as CodeClass.Declaration; @@ -661,7 +662,7 @@ private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlSpaceNode rootNode return existingDeclaration; } private const string OpenApiObjectType = "object"; - private void CreatePropertiesForModelClass(OpenApiUrlSpaceNode rootNode, OpenApiUrlSpaceNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeNamespace ns, CodeClass model, CodeElement parent) { + private void CreatePropertiesForModelClass(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeNamespace ns, CodeClass model, CodeElement parent) { AddSerializationMembers(model, schema?.AdditionalPropertiesAllowed ?? false); if(schema?.Properties?.Any() ?? false) { @@ -741,9 +742,9 @@ private static void AddSerializationMembers(CodeClass model, bool includeAdditio model.AddProperty(additionalDataProp); } } - private CodeClass CreateOperationParameter(OpenApiUrlSpaceNode node, OperationType operationType, OpenApiOperation operation, CodeClass parentClass) + private CodeClass CreateOperationParameter(OpenApiUrlTreeNode node, OperationType operationType, OpenApiOperation operation, CodeClass parentClass) { - var parameters = node.PathItem.Parameters.Union(operation.Parameters).Where(p => p.In == ParameterLocation.Query); + var parameters = node.PathItems[Constants.DefaultOpenApiLabel].Parameters.Union(operation.Parameters).Where(p => p.In == ParameterLocation.Query); if(parameters.Any()) { var parameterClass = new CodeClass(parentClass) { diff --git a/src/Kiota.Builder/OpenApiUriSpaceNode.cs b/src/Kiota.Builder/OpenApiUriSpaceNode.cs deleted file mode 100644 index ffdef6b643..0000000000 --- a/src/Kiota.Builder/OpenApiUriSpaceNode.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Microsoft.OpenApi.Models; -using Kiota.Builder.Extensions; - -namespace Kiota.Builder -{ - public class OpenApiUrlSpaceNode - { - public IDictionary Children { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); - public string Segment {get;set;} - public string Layer {get;set;} - - public OpenApiPathItem PathItem {get;set;} - public String Path {get;set;} = ""; - - public OpenApiUrlSpaceNode(string segment) - { - Segment = segment; - } - public static OpenApiUrlSpaceNode Create(OpenApiDocument doc, string layer = "") - { - OpenApiUrlSpaceNode root = null; - - var paths = doc?.Paths; - if (paths != null) - { - root = new OpenApiUrlSpaceNode(""); - - foreach (var path in paths) - { - root.Attach(path.Key, path.Value, layer); - } - } - return root; - } - - public void Attach(OpenApiDocument doc, string layer) - { - var paths = doc?.Paths; - if (paths != null) - { - foreach (var path in paths) - { - this.Attach(path.Key, path.Value, layer); - } - } - } - - public OpenApiUrlSpaceNode Attach(string path, OpenApiPathItem pathItem, string layer) - { - if (path.StartsWith("/")) // remove leading slash - { - path = path.Substring(1); - } - var segments = path.Split('/'); - return Attach(segments, pathItem, layer, ""); - } - - private OpenApiUrlSpaceNode Attach(IEnumerable segments, OpenApiPathItem pathItem, string layer, string currentPath) - { - - var segment = segments.FirstOrDefault(); - if (string.IsNullOrEmpty(segment)) - { - if (PathItem == null) - { - PathItem = pathItem; - Path = currentPath; - Layer = layer; - } - return this; - } - - // If the child segment has already been defined, then insert into it - if (Children.ContainsKey(segment)) - { - return Children[segment].Attach(segments.Skip(1), pathItem, layer, currentPath + pathNameSeparator + segment ); - } - else - { - var node = new OpenApiUrlSpaceNode(segment); - node.Path = currentPath + pathNameSeparator + segment; - Children[segment] = node; - return node.Attach(segments.Skip(1), pathItem, layer, currentPath + pathNameSeparator + segment); - } - } - private readonly char pathNameSeparator = '\\'; - } -} diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 4df5161c12..9576049574 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Services; using Moq; using Xunit; @@ -9,7 +10,7 @@ public class KiotaBuilderTests [Fact] public void Single_root_node_creates_single_request_builder_class() { - var node = new OpenApiUrlSpaceNode(""); + var node = OpenApiUrlTreeNode.Create(); var mockLogger = new Mock>(); var builder = new KiotaBuilder(mockLogger.Object, new GenerationConfiguration() { ClientClassName = "Graph" }); var codeModel = builder.CreateSourceModel(node); diff --git a/tests/Kiota.Builder.Tests/UriSpaceTests.cs b/tests/Kiota.Builder.Tests/UriSpaceTests.cs deleted file mode 100644 index e30db9d3d3..0000000000 --- a/tests/Kiota.Builder.Tests/UriSpaceTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Microsoft.OpenApi.Models; -using Xunit; - -namespace Kiota.Builder.Tests -{ - public class UriSpaceTests - { - [Fact] - public void CreateEmptyUrlSpace() - { - var doc = new OpenApiDocument() { }; - - var rootNode = OpenApiUrlSpaceNode.Create(doc); - - Assert.Null(rootNode); - } - - [Fact] - public void Create_Single_Root() - { - var doc = new OpenApiDocument() - { - Paths = new OpenApiPaths() - { - ["/"] = new OpenApiPathItem() - } - }; - - var rootNode = OpenApiUrlSpaceNode.Create(doc); - - Assert.NotNull(rootNode); - Assert.NotNull(rootNode.PathItem); - Assert.Equal(0, rootNode.Children.Count); - } - - [Fact] - public void Create_a_path_without_a_root() - { - var doc = new OpenApiDocument() - { - Paths = new OpenApiPaths() - { - ["/home"] = new OpenApiPathItem() - } - }; - - var rootNode = OpenApiUrlSpaceNode.Create(doc); - - Assert.NotNull(rootNode); - Assert.Null(rootNode.PathItem); - Assert.Equal(1, rootNode.Children.Count); - Assert.Equal("home", rootNode.Children["home"].Segment); - Assert.NotNull(rootNode.Children["home"].PathItem); - } - - [Fact] - public void Create_multiple_paths() - { - var doc = new OpenApiDocument() - { - Paths = new OpenApiPaths() - { - ["/"] = new OpenApiPathItem(), - ["/home"] = new OpenApiPathItem(), - ["/start"] = new OpenApiPathItem() - } - }; - - var rootNode = OpenApiUrlSpaceNode.Create(doc); - - Assert.NotNull(rootNode); - Assert.Equal(2, rootNode.Children.Count); - Assert.Equal("home", rootNode.Children["home"].Segment); - Assert.Equal("start", rootNode.Children["start"].Segment); - } - - [Fact] - public void Create_paths_with_many_levels() - { - var doc = new OpenApiDocument() - { - Paths = new OpenApiPaths() - { - ["/"] = new OpenApiPathItem(), - ["/home/sweet/home"] = new OpenApiPathItem(), - ["/start/end"] = new OpenApiPathItem() - } - }; - - var rootNode = OpenApiUrlSpaceNode.Create(doc); - - Assert.NotNull(rootNode); - Assert.Equal(2, rootNode.Children.Count); - Assert.NotNull(rootNode.Children["home"].Children["sweet"].Children["home"].PathItem); - Assert.Equal("end", rootNode.Children["start"].Children["end"].Segment); - } - } -} From 85e7c90db730a31d4951975978b5f9def6b6c60d Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 26 May 2021 14:17:26 -0400 Subject: [PATCH 3/6] - updates changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8193f23831..de14dee8b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Expands code coverage to 88% #147 - Removes json assumption for request body to support multiple formats #170 - Escapes language reserved keywords #184 +- Replaces custom URL tree node by class provided by OpenAPI.net #179 ## [0.0.4] - 2021-04-28 From 57b394c7c210b5ba67b3c5a5c55e822342143157 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 26 May 2021 14:37:57 -0400 Subject: [PATCH 4/6] - model creation methods refactoring to improve readbility --- src/Kiota.Builder/KiotaBuilder.cs | 126 ++++++++++++++++-------------- 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 9aa348dd15..25f54d3147 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -452,7 +452,7 @@ private void CreateOperationMethods(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeN parentClass.AddMethod(executorMethod); if (schema != null) { - var returnType = CreateModelClasses(rootNode, currentNode, schema, operation, executorMethod); + var returnType = CreateModelDeclarations(rootNode, currentNode, schema, operation, executorMethod); executorMethod.ReturnType = returnType ?? throw new InvalidOperationException("Could not resolve return type for operation"); } else { var returnType = "Entity";//TODO remove this temporary default when the method above handles all cases @@ -493,7 +493,7 @@ private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode rootNode, Open if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null) { var requestBodySchema = nonBinaryRequestBody.Value.Value.Schema; - var requestBodyType = CreateModelClasses(rootNode, currentNode, requestBodySchema, operation, method); + var requestBodyType = CreateModelDeclarations(rootNode, currentNode, requestBodySchema, operation, method); method.AddParameter(new CodeParameter(method) { Name = "body", Type = requestBodyType, @@ -560,7 +560,7 @@ private string GetShortestNamespaceNameForModelByReferenceId(string referenceId) } else throw new InvalidOperationException($"could not find a shortest namespace name for reference id {referenceId}"); } - private CodeType CreateModelClassAndType(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, CodeNamespace codeNamespace, string classNameSuffix = "") { + private CodeType CreateModelDeclarationAndType(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, CodeNamespace codeNamespace, string classNameSuffix = "") { var className = currentNode.GetClassName(operation: operation, suffix: classNameSuffix); var codeDeclaration = AddModelDeclarationIfDoesntExit(rootNode, currentNode, schema, operation, className, codeNamespace, parentElement); return new CodeType(parentElement) { @@ -568,54 +568,60 @@ private CodeType CreateModelClassAndType(OpenApiUrlTreeNode rootNode, OpenApiUrl Name = className, }; } - private CodeTypeBase CreateModelClasses(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) + private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) { + var allOfs = schema.AllOf.FlattenEmptyEntries(x => x.AllOf); + var lastSchema = allOfs.Last(); + CodeElement codeDeclaration = null; + string className = string.Empty; + foreach(var currentSchema in allOfs) { + var isLastSchema = currentSchema == lastSchema; + var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(this.config.ClientNamespaceName) : GetShortestNamespaceNameForModelByReferenceId(currentSchema.Reference.Id); + var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName); + if(shortestNamespace == null) + shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); + className = isLastSchema ? currentNode.GetClassName(operation: operation) : currentSchema.GetSchemaTitle(); + codeDeclaration = AddModelDeclarationIfDoesntExit(rootNode, currentNode, currentSchema, operation, className, shortestNamespace, parentElement, codeDeclaration as CodeClass, true); + } + + return new CodeType(parentElement) { + TypeDefinition = codeDeclaration, + Name = className, + }; + } + private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) { + var schemas = schema.AnyOf.Union(schema.OneOf); + var unionType = new CodeUnionType(parentElement) { + Name = currentNode.GetClassName(operation: operation, suffix: "Response"), + }; + foreach(var currentSchema in schemas) { + var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(this.config.ClientNamespaceName) : GetShortestNamespaceNameForModelByReferenceId(currentSchema.Reference.Id); + var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName); + if(shortestNamespace == null) + shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); + var className = currentSchema.GetSchemaTitle(); + var codeDeclaration = AddModelDeclarationIfDoesntExit(rootNode, currentNode, currentSchema, operation, className, shortestNamespace, parentElement); + unionType.AddType(new CodeType(unionType) { + TypeDefinition = codeDeclaration, + Name = className, + }); + } + return unionType; + } + private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) { var originalReference = schema?.Reference; var originalReferenceId = originalReference?.Id; var codeNamespace = parentElement.GetImmediateParentOfType(); if (originalReference == null) { // Inline schema, i.e. specific to the Operation - return CreateModelClassAndType(rootNode, currentNode, schema, operation, parentElement, codeNamespace, "Response"); + return CreateModelDeclarationAndType(rootNode, currentNode, schema, operation, parentElement, codeNamespace, "Response"); } else if(schema?.AllOf?.Any() ?? false) { - var allOfs = schema.AllOf.FlattenEmptyEntries(x => x.AllOf); - var lastSchema = allOfs.Last(); - CodeElement codeDeclaration = null; - string className = string.Empty; - foreach(var currentSchema in allOfs) { - var isLastSchema = currentSchema == lastSchema; - var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(this.config.ClientNamespaceName) : GetShortestNamespaceNameForModelByReferenceId(currentSchema.Reference.Id); - var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName); - if(shortestNamespace == null) - shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); - className = isLastSchema ? currentNode.GetClassName(operation: operation) : currentSchema.GetSchemaTitle(); - codeDeclaration = AddModelDeclarationIfDoesntExit(rootNode, currentNode, currentSchema, operation, className, shortestNamespace, parentElement, codeDeclaration as CodeClass, true); - } - - return new CodeType(parentElement) { - TypeDefinition = codeDeclaration, - Name = className, - }; + return CreateInheritedModelDeclaration(rootNode, currentNode, schema, operation, parentElement); } else if((schema?.AnyOf?.Any() ?? false) || (schema?.OneOf?.Any() ?? false)) { - var schemas = schema.AnyOf.Union(schema.OneOf); - var unionType = new CodeUnionType(parentElement) { - Name = currentNode.GetClassName(operation: operation, suffix: "Response"), - }; - foreach(var currentSchema in schemas) { - var shortestNamespaceName = currentSchema.Reference == null ? currentNode.GetNodeNamespaceFromPath(this.config.ClientNamespaceName) : GetShortestNamespaceNameForModelByReferenceId(currentSchema.Reference.Id); - var shortestNamespace = rootNamespace.FindNamespaceByName(shortestNamespaceName); - if(shortestNamespace == null) - shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); - var className = currentSchema.GetSchemaTitle(); - var codeDeclaration = AddModelDeclarationIfDoesntExit(rootNode, currentNode, currentSchema, operation, className, shortestNamespace, parentElement); - unionType.AddType(new CodeType(unionType) { - TypeDefinition = codeDeclaration, - Name = className, - }); - } - return unionType; + return CreateUnionModelDeclaration(rootNode, currentNode, schema, operation, parentElement); } else if(schema?.Type?.Equals("object") ?? false) { // referenced schema, no inheritance or union type - return CreateModelClassAndType(rootNode, currentNode, schema, operation, parentElement, codeNamespace); + return CreateModelDeclarationAndType(rootNode, currentNode, schema, operation, parentElement, codeNamespace); } else throw new InvalidOperationException("un handled case, might be object type or array type"); // object type array of object are technically already handled in properties but if we have a root with those we might be missing some cases here @@ -640,27 +646,29 @@ private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlTreeNode rootNode, Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel), }; return currentNamespace.AddEnum(newEnum).First(); - } else { - if(inheritsFrom == null && schema.AllOf.Count > 1) { //the last is always the current class, we want the one before the last as parent - var parentSchema = schema.AllOf.Except(new OpenApiSchema[] {schema.AllOf.Last()}).FirstOrDefault(); - if(parentSchema != null) - inheritsFrom = AddModelDeclarationIfDoesntExit(rootNode, currentNode, parentSchema, operation, parentSchema.GetSchemaTitle(), currentNamespace, parentElement, null, true) as CodeClass; - } - var newClass = currentNamespace.AddClass(new CodeClass(currentNamespace) { - Name = declarationName, - ClassKind = CodeClassKind.Model, - Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel) - }).First(); - if(inheritsFrom != null) { - var declaration = newClass.StartBlock as CodeClass.Declaration; - declaration.Inherits = new CodeType(declaration) { TypeDefinition = inheritsFrom, Name = inheritsFrom.Name }; - } - CreatePropertiesForModelClass(rootNode, currentNode, schema, operation, currentNamespace, newClass, parentElement); - return newClass; - } + } else + return AddModelClass(rootNode, currentNode, schema, operation, declarationName, currentNamespace, parentElement, inheritsFrom); } else return existingDeclaration; } + private CodeClass AddModelClass(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string declarationName, CodeNamespace currentNamespace, CodeElement parentElement, CodeClass inheritsFrom = null) { + if(inheritsFrom == null && schema.AllOf.Count > 1) { //the last is always the current class, we want the one before the last as parent + var parentSchema = schema.AllOf.Except(new OpenApiSchema[] {schema.AllOf.Last()}).FirstOrDefault(); + if(parentSchema != null) + inheritsFrom = AddModelDeclarationIfDoesntExit(rootNode, currentNode, parentSchema, operation, parentSchema.GetSchemaTitle(), currentNamespace, parentElement, null, true) as CodeClass; + } + var newClass = currentNamespace.AddClass(new CodeClass(currentNamespace) { + Name = declarationName, + ClassKind = CodeClassKind.Model, + Description = currentNode.GetPathItemDescription(Constants.DefaultOpenApiLabel) + }).First(); + if(inheritsFrom != null) { + var declaration = newClass.StartBlock as CodeClass.Declaration; + declaration.Inherits = new CodeType(declaration) { TypeDefinition = inheritsFrom, Name = inheritsFrom.Name }; + } + CreatePropertiesForModelClass(rootNode, currentNode, schema, operation, currentNamespace, newClass, parentElement); + return newClass; + } private const string OpenApiObjectType = "object"; private void CreatePropertiesForModelClass(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeNamespace ns, CodeClass model, CodeElement parent) { AddSerializationMembers(model, schema?.AdditionalPropertiesAllowed ?? false); From 08b05cebe07c326df8eed482933ec2640b0e82d9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 26 May 2021 15:08:59 -0400 Subject: [PATCH 5/6] - removes unused parameters --- src/Kiota.Builder/KiotaBuilder.cs | 52 +++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 25f54d3147..96bf75c0ec 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -263,7 +263,7 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr if (currentNode.HasOperations(Constants.DefaultOpenApiLabel)) { foreach(var operation in currentNode.PathItems[Constants.DefaultOpenApiLabel].Operations) - CreateOperationMethods(rootNode, currentNode, operation.Key, operation.Value, codeClass); + CreateOperationMethods(currentNode, operation.Key, operation.Value, codeClass); } CreatePathManagement(codeClass, currentNode, isRootClientClass); @@ -437,7 +437,7 @@ private CodeProperty CreateProperty(string childIdentifier, string childType, Co return prop; } private const string requestBodyBinaryContentType = "application/octet-stream"; - private void CreateOperationMethods(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OperationType operationType, OpenApiOperation operation, CodeClass parentClass) + private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationType operationType, OpenApiOperation operation, CodeClass parentClass) { var parameterClass = CreateOperationParameter(currentNode, operationType, operation, parentClass); @@ -452,7 +452,7 @@ private void CreateOperationMethods(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeN parentClass.AddMethod(executorMethod); if (schema != null) { - var returnType = CreateModelDeclarations(rootNode, currentNode, schema, operation, executorMethod); + var returnType = CreateModelDeclarations(currentNode, schema, operation, executorMethod); executorMethod.ReturnType = returnType ?? throw new InvalidOperationException("Could not resolve return type for operation"); } else { var returnType = "Entity";//TODO remove this temporary default when the method above handles all cases @@ -464,7 +464,7 @@ private void CreateOperationMethods(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeN } - AddRequestBuilderMethodParameters(rootNode, currentNode, operation, parameterClass, executorMethod); + AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, executorMethod); var handlerParam = new CodeParameter(executorMethod) { Name = "responseHandler", @@ -485,15 +485,15 @@ private void CreateOperationMethods(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeN }; generatorMethod.ReturnType = new CodeType(generatorMethod) { Name = "RequestInfo", IsNullable = false, IsExternal = true}; parentClass.AddMethod(generatorMethod); - AddRequestBuilderMethodParameters(rootNode, currentNode, operation, parameterClass, generatorMethod); + AddRequestBuilderMethodParameters(currentNode, operation, parameterClass, generatorMethod); logger.LogTrace("Creating method {name} of {type}", generatorMethod.Name, generatorMethod.ReturnType); } - private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) { + private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, OpenApiOperation operation, CodeClass parameterClass, CodeMethod method) { var nonBinaryRequestBody = operation.RequestBody?.Content?.FirstOrDefault(x => !requestBodyBinaryContentType.Equals(x.Key, StringComparison.OrdinalIgnoreCase)); if (nonBinaryRequestBody.HasValue && nonBinaryRequestBody.Value.Value != null) { var requestBodySchema = nonBinaryRequestBody.Value.Value.Schema; - var requestBodyType = CreateModelDeclarations(rootNode, currentNode, requestBodySchema, operation, method); + var requestBodyType = CreateModelDeclarations(currentNode, requestBodySchema, operation, method); method.AddParameter(new CodeParameter(method) { Name = "body", Type = requestBodyType, @@ -560,15 +560,15 @@ private string GetShortestNamespaceNameForModelByReferenceId(string referenceId) } else throw new InvalidOperationException($"could not find a shortest namespace name for reference id {referenceId}"); } - private CodeType CreateModelDeclarationAndType(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, CodeNamespace codeNamespace, string classNameSuffix = "") { + private CodeType CreateModelDeclarationAndType(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement, CodeNamespace codeNamespace, string classNameSuffix = "") { var className = currentNode.GetClassName(operation: operation, suffix: classNameSuffix); - var codeDeclaration = AddModelDeclarationIfDoesntExit(rootNode, currentNode, schema, operation, className, codeNamespace, parentElement); + var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, schema, className, codeNamespace, parentElement); return new CodeType(parentElement) { TypeDefinition = codeDeclaration, Name = className, }; } - private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) { + private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) { var allOfs = schema.AllOf.FlattenEmptyEntries(x => x.AllOf); var lastSchema = allOfs.Last(); CodeElement codeDeclaration = null; @@ -580,7 +580,7 @@ private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode rootNode if(shortestNamespace == null) shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); className = isLastSchema ? currentNode.GetClassName(operation: operation) : currentSchema.GetSchemaTitle(); - codeDeclaration = AddModelDeclarationIfDoesntExit(rootNode, currentNode, currentSchema, operation, className, shortestNamespace, parentElement, codeDeclaration as CodeClass, true); + codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace, parentElement, codeDeclaration as CodeClass, true); } return new CodeType(parentElement) { @@ -588,7 +588,7 @@ private CodeTypeBase CreateInheritedModelDeclaration(OpenApiUrlTreeNode rootNode Name = className, }; } - private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) { + private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) { var schemas = schema.AnyOf.Union(schema.OneOf); var unionType = new CodeUnionType(parentElement) { Name = currentNode.GetClassName(operation: operation, suffix: "Response"), @@ -599,7 +599,7 @@ private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode rootNode, Op if(shortestNamespace == null) shortestNamespace = rootNamespace.AddNamespace(shortestNamespaceName); var className = currentSchema.GetSchemaTitle(); - var codeDeclaration = AddModelDeclarationIfDoesntExit(rootNode, currentNode, currentSchema, operation, className, shortestNamespace, parentElement); + var codeDeclaration = AddModelDeclarationIfDoesntExit(currentNode, currentSchema, className, shortestNamespace, parentElement); unionType.AddType(new CodeType(unionType) { TypeDefinition = codeDeclaration, Name = className, @@ -607,21 +607,21 @@ private CodeTypeBase CreateUnionModelDeclaration(OpenApiUrlTreeNode rootNode, Op } return unionType; } - private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) + private CodeTypeBase CreateModelDeclarations(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeElement parentElement) { var originalReference = schema?.Reference; var originalReferenceId = originalReference?.Id; var codeNamespace = parentElement.GetImmediateParentOfType(); if (originalReference == null) { // Inline schema, i.e. specific to the Operation - return CreateModelDeclarationAndType(rootNode, currentNode, schema, operation, parentElement, codeNamespace, "Response"); + return CreateModelDeclarationAndType(currentNode, schema, operation, parentElement, codeNamespace, "Response"); } else if(schema?.AllOf?.Any() ?? false) { - return CreateInheritedModelDeclaration(rootNode, currentNode, schema, operation, parentElement); + return CreateInheritedModelDeclaration(currentNode, schema, operation, parentElement); } else if((schema?.AnyOf?.Any() ?? false) || (schema?.OneOf?.Any() ?? false)) { - return CreateUnionModelDeclaration(rootNode, currentNode, schema, operation, parentElement); + return CreateUnionModelDeclaration(currentNode, schema, operation, parentElement); } else if(schema?.Type?.Equals("object") ?? false) { // referenced schema, no inheritance or union type - return CreateModelDeclarationAndType(rootNode, currentNode, schema, operation, parentElement, codeNamespace); + return CreateModelDeclarationAndType(currentNode, schema, operation, parentElement, codeNamespace); } else throw new InvalidOperationException("un handled case, might be object type or array type"); // object type array of object are technically already handled in properties but if we have a root with those we might be missing some cases here @@ -635,7 +635,7 @@ private CodeNamespace GetSearchNamespace(bool checkInAllNamespaces, OpenApiUrlTr else if (currentNode.DoesNodeBelongToItemSubnamespace()) return rootNamespace.EnsureItemNamespace(); else return currentNamespace; } - private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string declarationName, CodeNamespace currentNamespace, CodeElement parentElement, CodeClass inheritsFrom = null, bool checkInAllNamespaces = false) { + private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeElement parentElement, CodeClass inheritsFrom = null, bool checkInAllNamespaces = false) { var existingDeclaration = GetExistingDeclaration(checkInAllNamespaces, currentNamespace, currentNode, declarationName); if(existingDeclaration == null) // we can find it in the components { @@ -647,15 +647,15 @@ private CodeElement AddModelDeclarationIfDoesntExit(OpenApiUrlTreeNode rootNode, }; return currentNamespace.AddEnum(newEnum).First(); } else - return AddModelClass(rootNode, currentNode, schema, operation, declarationName, currentNamespace, parentElement, inheritsFrom); + return AddModelClass(currentNode, schema, declarationName, currentNamespace, parentElement, inheritsFrom); } else return existingDeclaration; } - private CodeClass AddModelClass(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, string declarationName, CodeNamespace currentNamespace, CodeElement parentElement, CodeClass inheritsFrom = null) { + private CodeClass AddModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, string declarationName, CodeNamespace currentNamespace, CodeElement parentElement, CodeClass inheritsFrom = null) { if(inheritsFrom == null && schema.AllOf.Count > 1) { //the last is always the current class, we want the one before the last as parent var parentSchema = schema.AllOf.Except(new OpenApiSchema[] {schema.AllOf.Last()}).FirstOrDefault(); if(parentSchema != null) - inheritsFrom = AddModelDeclarationIfDoesntExit(rootNode, currentNode, parentSchema, operation, parentSchema.GetSchemaTitle(), currentNamespace, parentElement, null, true) as CodeClass; + inheritsFrom = AddModelDeclarationIfDoesntExit(currentNode, parentSchema, parentSchema.GetSchemaTitle(), currentNamespace, parentElement, null, true) as CodeClass; } var newClass = currentNamespace.AddClass(new CodeClass(currentNamespace) { Name = declarationName, @@ -666,11 +666,11 @@ private CodeClass AddModelClass(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode var declaration = newClass.StartBlock as CodeClass.Declaration; declaration.Inherits = new CodeType(declaration) { TypeDefinition = inheritsFrom, Name = inheritsFrom.Name }; } - CreatePropertiesForModelClass(rootNode, currentNode, schema, operation, currentNamespace, newClass, parentElement); + CreatePropertiesForModelClass(currentNode, schema, currentNamespace, newClass, parentElement); return newClass; } private const string OpenApiObjectType = "object"; - private void CreatePropertiesForModelClass(OpenApiUrlTreeNode rootNode, OpenApiUrlTreeNode currentNode, OpenApiSchema schema, OpenApiOperation operation, CodeNamespace ns, CodeClass model, CodeElement parent) { + private void CreatePropertiesForModelClass(OpenApiUrlTreeNode currentNode, OpenApiSchema schema, CodeNamespace ns, CodeClass model, CodeElement parent) { AddSerializationMembers(model, schema?.AdditionalPropertiesAllowed ?? false); if(schema?.Properties?.Any() ?? false) { @@ -684,14 +684,14 @@ private void CreatePropertiesForModelClass(OpenApiUrlTreeNode rootNode, OpenApiU var shortestNamespaceName = GetShortestNamespaceNameForModelByReferenceId(propertyDefinitionSchema.Reference.Id); var targetNamespace = string.IsNullOrEmpty(shortestNamespaceName) ? ns : (rootNamespace.FindNamespaceByName(shortestNamespaceName) ?? rootNamespace.AddNamespace(shortestNamespaceName)); - definition = AddModelDeclarationIfDoesntExit(rootNode, currentNode, propertyDefinitionSchema, operation, className, targetNamespace, parent, null, true); + definition = AddModelDeclarationIfDoesntExit(currentNode, propertyDefinitionSchema, className, targetNamespace, parent, null, true); } return CreateProperty(x.Key, className ?? x.Value.Type, model, typeSchema: x.Value, typeDefinition: definition); }) .ToArray()); } else if(schema?.AllOf?.Any(x => x?.Type?.Equals(OpenApiObjectType) ?? false) ?? false) - CreatePropertiesForModelClass(rootNode, currentNode, schema.AllOf.Last(x => x.Type.Equals(OpenApiObjectType)), operation, ns, model, parent); + CreatePropertiesForModelClass(currentNode, schema.AllOf.Last(x => x.Type.Equals(OpenApiObjectType)), ns, model, parent); } private const string deserializeFieldsPropName = "DeserializeFields"; private const string serializeMethodName = "Serialize"; From 8dd931653843f8a132c847c3347ac70d78fc5ad4 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 26 May 2021 16:22:37 -0400 Subject: [PATCH 6/6] - adds unit tests for tree node extensions methods --- .../OpenApiUrlTreeNodeExtensions.cs | 27 ++--- .../OpenApiUrlTreeNodeExtensionsTests.cs | 100 ++++++++++++++++++ 2 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs index afb2755e35..6c3f5df7dc 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -15,7 +15,7 @@ public static Dictionary> GetComponentsRefer return result; } private static void AddAllPathsEntries(OpenApiUrlTreeNode currentNode, Dictionary> index, string label) { - if(currentNode == null && string.IsNullOrEmpty(label)) + if(currentNode == null || string.IsNullOrEmpty(label)) return; if(currentNode.PathItems.ContainsKey(label) && currentNode.HasOperations(label)) { @@ -38,10 +38,11 @@ private static void AddAllPathsEntries(OpenApiUrlTreeNode currentNode, Dictionar foreach(var child in currentNode.Children.Values) AddAllPathsEntries(child, index, label); } - internal static string GetNodeNamespaceFromPath(this OpenApiUrlTreeNode currentNode, string prefix) => + public static string GetNodeNamespaceFromPath(this OpenApiUrlTreeNode currentNode, string prefix) => prefix + ((currentNode?.Path?.Contains(pathNameSeparator) ?? false) ? - "." + currentNode?.Path + (string.IsNullOrEmpty(prefix) ? string.Empty : ".") + + currentNode?.Path ?.Split(pathNameSeparator, StringSplitOptions.RemoveEmptyEntries) ?.Where(x => !x.StartsWith('{')) ?.Aggregate((x, y) => $"{x}.{y}") : @@ -52,30 +53,30 @@ internal static string GetNodeNamespaceFromPath(this OpenApiUrlTreeNode currentN /// /// Returns the class name for the node with more or less precision depending on the provided arguments /// - internal static string GetClassName(this OpenApiUrlTreeNode currentNode, string suffix = default, string prefix = default, OpenApiOperation operation = default) { + public static string GetClassName(this OpenApiUrlTreeNode currentNode, string suffix = default, string prefix = default, OpenApiOperation operation = default) { var rawClassName = operation?.GetResponseSchema()?.Reference?.GetClassName() ?? currentNode?.GetIdentifier()?.ReplaceValueIdentifier(); if((currentNode?.DoesNodeBelongToItemSubnamespace() ?? false) && idClassNameCleanup.IsMatch(rawClassName)) rawClassName = idClassNameCleanup.Replace(rawClassName, string.Empty); return prefix + rawClassName + suffix; } - internal static string GetPathItemDescription(this OpenApiUrlTreeNode currentNode, string label, string defaultValue = default) => - currentNode?.PathItems?.ContainsKey(label) ?? false ? + public static string GetPathItemDescription(this OpenApiUrlTreeNode currentNode, string label, string defaultValue = default) => + !string.IsNullOrEmpty(label) && (currentNode?.PathItems.ContainsKey(label) ?? false) ? currentNode.PathItems[label].Description ?? currentNode.PathItems[label].Summary ?? defaultValue : defaultValue; - internal static bool DoesNodeBelongToItemSubnamespace(this OpenApiUrlTreeNode currentNode) => - (currentNode?.Segment?.StartsWith("{") ?? false) && (currentNode?.Segment?.EndsWith("}") ?? false); - internal static bool IsParameter(this OpenApiUrlTreeNode currentNode) + public static bool DoesNodeBelongToItemSubnamespace(this OpenApiUrlTreeNode currentNode) => + (currentNode?.Segment.StartsWith("{") ?? false) && currentNode.Segment.EndsWith("}"); + public static bool IsParameter(this OpenApiUrlTreeNode currentNode) { - return currentNode?.Segment?.StartsWith("{") ?? false; + return currentNode?.Segment.StartsWith("{") ?? false; } - internal static bool IsFunction(this OpenApiUrlTreeNode currentNode) + public static bool IsFunction(this OpenApiUrlTreeNode currentNode) { - return currentNode?.Segment?.Contains("(") ?? false; + return currentNode?.Segment.Contains("(") ?? false; } - internal static string GetIdentifier(this OpenApiUrlTreeNode currentNode) + public static string GetIdentifier(this OpenApiUrlTreeNode currentNode) { if(currentNode == null) return string.Empty; string identifier; diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs new file mode 100644 index 0000000000..9355e20bb4 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiUrlTreeNodeExtensionsTests.cs @@ -0,0 +1,100 @@ +using System.Linq; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; + +namespace Kiota.Builder.Extensions.Tests { + public class OpenApiUrlTreeNodeExtensionsTests { + [Fact] + public void Defensive() { + Assert.Empty(OpenApiUrlTreeNodeExtensions.GetIdentifier(null)); + Assert.False(OpenApiUrlTreeNodeExtensions.IsFunction(null)); + Assert.False(OpenApiUrlTreeNodeExtensions.IsParameter(null)); + Assert.False(OpenApiUrlTreeNodeExtensions.DoesNodeBelongToItemSubnamespace(null)); + Assert.Empty(OpenApiUrlTreeNodeExtensions.GetComponentsReferenceIndex(null, null)); + Assert.Empty(OpenApiUrlTreeNodeExtensions.GetComponentsReferenceIndex(null, label)); + Assert.Empty(OpenApiUrlTreeNodeExtensions.GetComponentsReferenceIndex(OpenApiUrlTreeNode.Create(), null)); + Assert.Empty(OpenApiUrlTreeNodeExtensions.GetComponentsReferenceIndex(OpenApiUrlTreeNode.Create(), label)); + Assert.Null(OpenApiUrlTreeNodeExtensions.GetPathItemDescription(null, null)); + Assert.Null(OpenApiUrlTreeNodeExtensions.GetPathItemDescription(null, label)); + Assert.Null(OpenApiUrlTreeNodeExtensions.GetPathItemDescription(OpenApiUrlTreeNode.Create(), null)); + Assert.Null(OpenApiUrlTreeNodeExtensions.GetPathItemDescription(OpenApiUrlTreeNode.Create(), label)); + } + private const string label = "default"; + [Fact] + public void GetsDescription() { + var node = OpenApiUrlTreeNode.Create(); + node.PathItems.Add(label, new() { + Description = "description", + Summary = "summary" + }); + Assert.Equal(label, OpenApiUrlTreeNode.Create().GetPathItemDescription(label, label)); + Assert.Equal("description", node.GetPathItemDescription(label, label)); + node.PathItems[label].Description = null; + Assert.Equal("summary", node.GetPathItemDescription(label, label)); + } + [Fact] + public void IsFunction() { + var doc = new OpenApiDocument { + Paths = new(), + }; + doc.Paths.Add("function()", new() {}); + var node = OpenApiUrlTreeNode.Create(doc, label); + Assert.False(node.IsFunction()); + Assert.True(node.Children.First().Value.IsFunction()); + } + [Fact] + public void IsParameter() { + var doc = new OpenApiDocument { + Paths = new(), + }; + doc.Paths.Add("{param}", new() {}); + var node = OpenApiUrlTreeNode.Create(doc, label); + Assert.False(node.IsParameter()); + Assert.True(node.Children.First().Value.IsParameter()); + } + [Fact] + public void DoesNodeBelongToItemSubnamespace() { + var doc = new OpenApiDocument { + Paths = new(), + }; + doc.Paths.Add("{param}", new() {}); + var node = OpenApiUrlTreeNode.Create(doc, label); + Assert.False(node.DoesNodeBelongToItemSubnamespace()); + Assert.True(node.Children.First().Value.DoesNodeBelongToItemSubnamespace()); + + doc = new OpenApiDocument { + Paths = new(), + }; + doc.Paths.Add("param}", new() {}); + node = OpenApiUrlTreeNode.Create(doc, label); + Assert.False(node.Children.First().Value.DoesNodeBelongToItemSubnamespace()); + + doc = new OpenApiDocument { + Paths = new(), + }; + doc.Paths.Add("{param", new() {}); + node = OpenApiUrlTreeNode.Create(doc, label); + Assert.False(node.Children.First().Value.DoesNodeBelongToItemSubnamespace()); + } + [Fact] + public void GetIdentifier() { + var doc = new OpenApiDocument { + Paths = new(), + }; + doc.Paths.Add("function(parm1)", new() {}); + var node = OpenApiUrlTreeNode.Create(doc, label); + Assert.DoesNotContain("(", node.Children.First().Value.GetIdentifier()); + } + [Fact] + public void GetNodeNamespaceFromPath() { + var doc = new OpenApiDocument { + Paths = new(), + }; + doc.Paths.Add("\\users\\messages", new() {}); + var node = OpenApiUrlTreeNode.Create(doc, label); + Assert.Equal("graph.users.messages", node.Children.First().Value.GetNodeNamespaceFromPath("graph")); + Assert.Equal("users.messages", node.Children.First().Value.GetNodeNamespaceFromPath(null)); + } + } +}