diff --git a/CHANGELOG.md b/CHANGELOG.md index cc64bd1ed9..70c7ae0a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for vendor specific content types generation/serialization. [#1197](https://github.com/microsoft/kiota/issues/1197) - Added support for 204 no content in generation and CSharp/Java/Go/TypeScript request adapters. #1410 - Added a draft swift generation implementation. #1444 +- Added support for yaml response type generation. [#302](https://github.com/microsoft/kiota/issues/302) +- Added support for xml response type generation. [#302](https://github.com/microsoft/kiota/issues/302) +- Added support for unstructured response generation (stream). [#546](https://github.com/microsoft/kiota/issues/546) ### Changed - Fixed a bug where the base url of the request adapter would be reset by the client. [#1443](https://github.com/microsoft/kiota/issues/1443) - Fixed a bug where request builder classes for collections endpoints would have a wrong name. #1052 -- Fix issue with ambiguous type names causing build errors and stack overflows. (Shell) #1052 +- Fixed issue with ambiguous type names causing build errors and stack overflows. (Shell) #1052 - Fixed a bug where symbols (properties, methods, classes) could contain invalid characters #1436 - Renamed parameters for requests: o => options, q => queryParameters, h => headers. [#1380](https://github.com/microsoft/kiota/issues/1380) diff --git a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs index 09310e7d5f..9b070b07c4 100644 --- a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs @@ -7,9 +7,12 @@ namespace Kiota.Builder.Extensions { public static class OpenApiOperationExtensions { private static readonly HashSet successCodes = new(StringComparer.OrdinalIgnoreCase) {"200", "201", "202"}; //204 excluded as it won't have a schema - private static readonly HashSet validMimeTypes = new (StringComparer.OrdinalIgnoreCase) { + private static readonly HashSet structuredMimeTypes = new (StringComparer.OrdinalIgnoreCase) { "application/json", - "text/plain" + "application/xml", + "text/plain", + "text/xml", + "text/yaml", }; /// /// cleans application/vnd.github.mercy-preview+json to application/json @@ -19,8 +22,7 @@ public static OpenApiSchema GetResponseSchema(this OpenApiOperation operation) { // Return Schema that represents all the possible success responses! var schemas = operation.Responses.Where(r => successCodes.Contains(r.Key)) - .SelectMany(re => re.Value.GetResponseSchemas()) - .Where(s => s is not null); + .SelectMany(re => re.Value.GetResponseSchemas()); return schemas.FirstOrDefault(); } @@ -29,7 +31,7 @@ public static IEnumerable GetResponseSchemas(this OpenApiResponse var schemas = response.Content .Where(c => !string.IsNullOrEmpty(c.Key)) .Select(c => (Key: c.Key.Split(';', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(), c.Value)) - .Where(c => validMimeTypes.Contains(c.Key) || validMimeTypes.Contains(vendorSpecificCleanup.Replace(c.Key, string.Empty))) + .Where(c => structuredMimeTypes.Contains(c.Key) || structuredMimeTypes.Contains(vendorSpecificCleanup.Replace(c.Key, string.Empty))) .Select(co => co.Value.Schema) .Where(s => s is not null); diff --git a/src/Kiota.Builder/GenerationConfiguration.cs b/src/Kiota.Builder/GenerationConfiguration.cs index ea243a2846..0fd03a6b77 100644 --- a/src/Kiota.Builder/GenerationConfiguration.cs +++ b/src/Kiota.Builder/GenerationConfiguration.cs @@ -12,7 +12,6 @@ public class GenerationConfiguration { public GenerationLanguage Language { get; set; } = GenerationLanguage.CSharp; public string ApiRootUrl { get; set; } public string[] PropertiesPrefixToStrip { get; set; } = new string[] { "@odata."}; - public HashSet IgnoredRequestContentTypes { get; set; } = new(); public bool UsesBackingStore { get; set; } public List Serializers { get; set; } = new(); public List Deserializers { get; set; } = new(); diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 831034382f..874a41c96d 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -303,8 +303,7 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr { foreach(var operation in currentNode .PathItems[Constants.DefaultOpenApiLabel] - .Operations - .Where(x => x.Value.RequestBody?.Content?.Any(y => !config.IgnoredRequestContentTypes.Contains(y.Key)) ?? true)) + .Operations) CreateOperationMethods(currentNode, operation.Key, operation.Value, codeClass); } CreateUrlManagement(codeClass, currentNode, isApiClientClass); @@ -622,13 +621,13 @@ private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationTyp var returnType = CreateModelDeclarations(currentNode, schema, operation, executorMethod, "Response"); executorMethod.ReturnType = returnType ?? throw new InvalidOperationException("Could not resolve return type for operation"); } else { - var returnType = voidType; - if(operation.Responses.Any(x => x.Value.Content.ContainsKey(RequestBodyBinaryContentType))) - returnType = "binary"; + string returnType; + if(operation.Responses.Any(x => noContentStatusCodes.Contains(x.Key))) + returnType = voidType; else if (operation.Responses.Any(x => x.Value.Content.ContainsKey(RequestBodyPlainTextContentType))) returnType = "string"; - else if(!operation.Responses.Any(x => noContentStatusCodes.Contains(x.Key))) - logger.LogWarning("could not find operation return type {operationType} {currentNodePath}", operationType, currentNode.Path); + else + returnType = "binary"; executorMethod.ReturnType = new CodeType { Name = returnType, IsExternal = true, }; } diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index 62cff01257..9e1ca7a2cf 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -1768,8 +1768,40 @@ public void ModelsUseDescriptionWhenAvailable(){ Assert.NotNull(responseClass); Assert.Equal("some description", responseClass.Description); } - [Fact] - public void GeneratesAVoidExecutorForSingle204() { + [InlineData("application/json", "204", true, "void")] + [InlineData("application/json", "204", false, "void")] + [InlineData("application/json", "200", true, "Myobject")] + [InlineData("application/json", "200", false, "binary")] + [InlineData("application/xml", "204", true, "void")] + [InlineData("application/xml", "204", false, "void")] + [InlineData("application/xml", "200", true, "Myobject")] + [InlineData("application/xml", "200", false, "binary")] + [InlineData("text/xml", "204", true, "void")] + [InlineData("text/xml", "204", false, "void")] + [InlineData("text/xml", "200", true, "Myobject")] + [InlineData("text/xml", "200", false, "binary")] + [InlineData("text/yaml", "204", true, "void")] + [InlineData("text/yaml", "204", false, "void")] + [InlineData("text/yaml", "200", true, "Myobject")] + [InlineData("text/yaml", "200", false, "binary")] + [InlineData("application/octet-stream", "204", true, "void")] + [InlineData("application/octet-stream", "204", false, "void")] + [InlineData("application/octet-stream", "200", true, "binary")] + [InlineData("application/octet-stream", "200", false, "binary")] + [InlineData("text/html", "204", true, "void")] + [InlineData("text/html", "204", false, "void")] + [InlineData("text/html", "200", true, "binary")] + [InlineData("text/html", "200", false, "binary")] + [InlineData("*/*", "204", true, "void")] + [InlineData("*/*", "204", false, "void")] + [InlineData("*/*", "200", true, "binary")] + [InlineData("*/*", "200", false, "binary")] + [InlineData("text/plain", "204", true, "void")] + [InlineData("text/plain", "204", false, "void")] + [InlineData("text/plain", "200", true, "Myobject")] + [InlineData("text/plain", "200", false, "string")] + [Theory] + public void GeneratesTheRightReturnTypeBasedOnContentAndStatus(string contentType, string statusCode, bool addModel, string returnType) { var myObjectSchema = new OpenApiSchema { Type = "object", Properties = new Dictionary { @@ -1792,10 +1824,10 @@ public void GeneratesAVoidExecutorForSingle204() { [OperationType.Get] = new OpenApiOperation() { Responses = new OpenApiResponses { - ["204"] = new OpenApiResponse { + [statusCode] = new OpenApiResponse { Content = { - ["application/json"] = new OpenApiMediaType { - Schema = myObjectSchema + [contentType] = new OpenApiMediaType { + Schema = addModel ? myObjectSchema : null } } }, @@ -1822,7 +1854,7 @@ public void GeneratesAVoidExecutorForSingle204() { Assert.NotNull(rbClass); var executor = rbClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); Assert.NotNull(executor); - Assert.Equal("void", executor.ReturnType.Name); + Assert.Equal(returnType, executor.ReturnType.Name); } [Fact] public void DoesntGenerateVoidExecutorOnMixed204(){