diff --git a/CHANGELOG.md b/CHANGELOG.md index 3728f90de8..95917684bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for enum query parameter types. [#2490](https://github.com/microsoft/kiota/issues/2490) - Added settings in the vscode extension for: backingStore, additionalData, excludeBackwardCompatible, cleanOutput, clearCache, serializers, deserializers, disabledValidationRules, structuredMimeTypes. [#3355](https://github.com/microsoft/kiota/issues/3355) - Support for primary error message in PHP [#3276](https://github.com/microsoft/kiota/issues/3276) +- Added support for multiple content type request bodies. [#3377](https://github.com/microsoft/kiota/issues/3377) +- Added support for multiple content type responses. [#3377](https://github.com/microsoft/kiota/issues/3377) - Support for primary error message in Python [#3277](https://github.com/microsoft/kiota/issues/3277) ### Changed @@ -23,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug where a "models" API path segment in the description would derail generation. [#3400](https://github.com/microsoft/kiota/issues/3400) - Changes to the configuration of RequestInformation are preserved instead of being overwritten. [#3401](https://github.com/microsoft/kiota/pull/3401). - Fix bug where import statements in typescript wasn't using import type notation for types that are erased at runtime. [#3190](https://github.com/microsoft/kiota/issues/3190) +- The structured content type generation parameter now supports prioritization with `q=value` syntax. [#3377](https://github.com/microsoft/kiota/issues/3377) ## [1.7.0] - 2023-10-05 diff --git a/it/csharp/basic/basic.csproj b/it/csharp/basic/basic.csproj index 9559fd1297..7c4ccede47 100644 --- a/it/csharp/basic/basic.csproj +++ b/it/csharp/basic/basic.csproj @@ -10,15 +10,15 @@ - - + + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/it/go/basic/go.mod b/it/go/basic/go.mod index 0d5d557d62..fa28d22b72 100644 --- a/it/go/basic/go.mod +++ b/it/go/basic/go.mod @@ -3,12 +3,12 @@ module integrationtest go 1.20 require ( - github.com/microsoft/kiota-abstractions-go v1.2.0 - github.com/microsoft/kiota-http-go v0.16.1 - github.com/microsoft/kiota-serialization-form-go v0.9.0 + github.com/microsoft/kiota-abstractions-go v1.3.0 + github.com/microsoft/kiota-http-go v1.1.0 + github.com/microsoft/kiota-serialization-form-go v1.0.0 github.com/microsoft/kiota-serialization-json-go v1.0.4 github.com/microsoft/kiota-serialization-multipart-go v1.0.0 - github.com/microsoft/kiota-serialization-text-go v0.7.0 + github.com/microsoft/kiota-serialization-text-go v1.0.0 ) require ( @@ -16,12 +16,13 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/std-uritemplate/std-uritemplate/go v0.0.42 // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect - go.opentelemetry.io/otel v1.16.0 // indirect - go.opentelemetry.io/otel/metric v1.16.0 // indirect - go.opentelemetry.io/otel/trace v1.16.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/it/go/basic/go.sum b/it/go/basic/go.sum index 358aa45b1c..351348a3fb 100644 --- a/it/go/basic/go.sum +++ b/it/go/basic/go.sum @@ -10,32 +10,50 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/microsoft/kiota-abstractions-go v1.2.0 h1:lUriJgqdCY/QajwWQOgTCQE9Atywfe2NHhgoTCSXTRE= github.com/microsoft/kiota-abstractions-go v1.2.0/go.mod h1:RkxyZ5x87Njik7iVeQY9M2wtrrL1MJZcXiI/BxD/82g= +github.com/microsoft/kiota-abstractions-go v1.3.0 h1:mZTAg+Lf43+hoqTYWT53F/Dg+f0bqtHULnTI/GyiXn8= +github.com/microsoft/kiota-abstractions-go v1.3.0/go.mod h1:yPSuzNSOIVQSFFe1iT+3Lu5zmis22E8Wg+bkyjhd+pY= github.com/microsoft/kiota-http-go v0.16.1 h1:5SZbSwHs14Xve5VMQHHz00lwL/kEg3H9rgESAUrXnvw= github.com/microsoft/kiota-http-go v0.16.1/go.mod h1:pKSaeSaBwh3Zadbnzw3kALEZbCZA1gq7A5PuxwVd/aU= +github.com/microsoft/kiota-http-go v1.1.0 h1:L5I93EiNtlP/X6YzeTlhjWt7Q1DxzC9CmWSVtX3b0tE= +github.com/microsoft/kiota-http-go v1.1.0/go.mod h1:zESUM6ovki9LEupqziCbxJ+FAYoF0dFDYZVpOkAfSLc= github.com/microsoft/kiota-serialization-form-go v0.9.0 h1:ZMyvuxg7z1LmRWJOXr5QuJlwnD/tuNatb+k1KPURBFQ= github.com/microsoft/kiota-serialization-form-go v0.9.0/go.mod h1:FQqYzIrGX6KUoDOlg+DhDWoGaZoB8AicBYGOsBq0Dw4= +github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= +github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= github.com/microsoft/kiota-serialization-json-go v1.0.4 h1:5TaISWwd2Me8clrK7SqNATo0tv9seOq59y4I5953egQ= github.com/microsoft/kiota-serialization-json-go v1.0.4/go.mod h1:rM4+FsAY+9AEpBsBzkFFis+b/LZLlNKKewuLwK9Q6Mg= github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJympbXNaeV07K0d46IfuEd5v9+pBs= github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v0.7.0 h1:uayeq8fpDcZgL0zDyLkYZsH6zNnEXKgp+bRWfR5LcxA= github.com/microsoft/kiota-serialization-text-go v0.7.0/go.mod h1:2su1PTllHCMNkHugmvpYad+AKBXUUGoiNP3xOAJUL7w= +github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= +github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/std-uritemplate/std-uritemplate/go v0.0.42 h1:rG+XlE4drkVWs2NLfGS15N+vg+CUcjXElQKvJ0fctlI= +github.com/std-uritemplate/std-uritemplate/go v0.0.42/go.mod h1:Qov4Ay4U83j37XjgxMYevGJFLbnZ2o9cEOhGufBKgKY= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/it/go/go.mod b/it/go/go.mod index 2b64da67d0..18a22f9f94 100644 --- a/it/go/go.mod +++ b/it/go/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 github.com/microsoft/kiota-abstractions-go v1.3.0 - github.com/microsoft/kiota-authentication-azure-go v1.0.0 + github.com/microsoft/kiota-authentication-azure-go v1.0.1 github.com/microsoft/kiota-http-go v1.1.0 github.com/microsoft/kiota-serialization-form-go v1.0.0 github.com/microsoft/kiota-serialization-json-go v1.0.4 diff --git a/it/go/go.sum b/it/go/go.sum index 72d599f858..71d8e13d2a 100644 --- a/it/go/go.sum +++ b/it/go/go.sum @@ -29,6 +29,8 @@ github.com/microsoft/kiota-abstractions-go v1.3.0 h1:mZTAg+Lf43+hoqTYWT53F/Dg+f0 github.com/microsoft/kiota-abstractions-go v1.3.0/go.mod h1:yPSuzNSOIVQSFFe1iT+3Lu5zmis22E8Wg+bkyjhd+pY= github.com/microsoft/kiota-authentication-azure-go v1.0.0 h1:29FNZZ/4nnCOwFcGWlB/sxPvWz487HA2bXH8jR5k2Rk= github.com/microsoft/kiota-authentication-azure-go v1.0.0/go.mod h1:rnx3PRlkGdXDcA/0lZQTbBwyYGmc+3POt7HpE/e4jGw= +github.com/microsoft/kiota-authentication-azure-go v1.0.1 h1:F4HH+2QQHSecQg50gVEZaUcxA8/XxCaC2oOMYv2gTIM= +github.com/microsoft/kiota-authentication-azure-go v1.0.1/go.mod h1:IbifJeoi+sULI0vjnsWYSmDu5atFo/4FZ6WCoAkPjsc= github.com/microsoft/kiota-http-go v1.1.0 h1:L5I93EiNtlP/X6YzeTlhjWt7Q1DxzC9CmWSVtX3b0tE= github.com/microsoft/kiota-http-go v1.1.0/go.mod h1:zESUM6ovki9LEupqziCbxJ+FAYoF0dFDYZVpOkAfSLc= github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= diff --git a/src/Kiota.Builder/CodeDOM/CodeMethod.cs b/src/Kiota.Builder/CodeDOM/CodeMethod.cs index 1d2cc952db..d41be2b74d 100644 --- a/src/Kiota.Builder/CodeDOM/CodeMethod.cs +++ b/src/Kiota.Builder/CodeDOM/CodeMethod.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Kiota.Builder.Extensions; +using Kiota.Builder.OrderComparers; namespace Kiota.Builder.CodeDOM; @@ -124,9 +125,15 @@ public HttpMethod? HttpMethod get; set; } public string RequestBodyContentType { get; set; } = string.Empty; -#pragma warning disable CA2227 - public HashSet AcceptedResponseTypes { get; set; } = new(StringComparer.OrdinalIgnoreCase); -#pragma warning restore CA2227 + public IList AcceptedResponseTypes { get; private set; } = new List(); + public void AddAcceptedResponsesTypes(IEnumerable types) + { + if (types == null) return; + if (AcceptedResponseTypes is List list) + list.AddRange(types); + } + public bool ShouldAddAcceptHeader => AcceptedResponseTypes.Any(); + public string AcceptHeaderValue => string.Join(", ", AcceptedResponseTypes); public AccessModifier Access { get; set; } = AccessModifier.Public; #nullable disable // exposing property is required private CodeTypeBase returnType; @@ -302,7 +309,7 @@ public object Clone() Parent = Parent, OriginalIndexer = OriginalIndexer, errorMappings = new(errorMappings), - AcceptedResponseTypes = new(AcceptedResponseTypes, StringComparer.OrdinalIgnoreCase), + AcceptedResponseTypes = new List(AcceptedResponseTypes), PagingInformation = PagingInformation?.Clone() as PagingInformation, Documentation = (CodeDocumentation)Documentation.Clone(), Deprecation = Deprecation, diff --git a/src/Kiota.Builder/CodeDOM/CodeParameter.cs b/src/Kiota.Builder/CodeDOM/CodeParameter.cs index 5c5826337b..6f855f8539 100644 --- a/src/Kiota.Builder/CodeDOM/CodeParameter.cs +++ b/src/Kiota.Builder/CodeDOM/CodeParameter.cs @@ -46,6 +46,10 @@ public enum CodeParameterKind /// Configuration for the request to be sent with the headers, query parameters, and middleware options /// RequestConfiguration, + /// + /// The content type of the request body when it couldn't be inferred from the description. + /// + RequestBodyContentType, } public class CodeParameter : CodeTerminalWithKind, ICloneable, IDocumentedElement, IDeprecableElement diff --git a/src/Kiota.Builder/CodeRenderers/CodeRenderer.cs b/src/Kiota.Builder/CodeRenderers/CodeRenderer.cs index 29e5f826c9..c8a3bc3272 100644 --- a/src/Kiota.Builder/CodeRenderers/CodeRenderer.cs +++ b/src/Kiota.Builder/CodeRenderers/CodeRenderer.cs @@ -3,9 +3,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; - using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; +using Kiota.Builder.OrderComparers; using Kiota.Builder.Writers; namespace Kiota.Builder.CodeRenderers; diff --git a/src/Kiota.Builder/CodeRenderers/PythonCodeRenderer.cs b/src/Kiota.Builder/CodeRenderers/PythonCodeRenderer.cs index 4898655ebc..3322532b61 100644 --- a/src/Kiota.Builder/CodeRenderers/PythonCodeRenderer.cs +++ b/src/Kiota.Builder/CodeRenderers/PythonCodeRenderer.cs @@ -1,4 +1,5 @@ using Kiota.Builder.Configuration; +using Kiota.Builder.OrderComparers; namespace Kiota.Builder.CodeRenderers; public class PythonCodeRenderer : CodeRenderer diff --git a/src/Kiota.Builder/Configuration/GenerationConfiguration.cs b/src/Kiota.Builder/Configuration/GenerationConfiguration.cs index c8149068f1..1254f4d2c7 100644 --- a/src/Kiota.Builder/Configuration/GenerationConfiguration.cs +++ b/src/Kiota.Builder/Configuration/GenerationConfiguration.cs @@ -105,14 +105,14 @@ public bool CleanOutput { get; set; } - public HashSet StructuredMimeTypes + public StructuredMimeTypesCollection StructuredMimeTypes { get; set; - } = new(4, StringComparer.OrdinalIgnoreCase) { - "application/json", - "text/plain", - "application/x-www-form-urlencoded", - "multipart/form-data", + } = new StructuredMimeTypesCollection { + "application/json;q=1", + "text/plain;q=0.9", + "application/x-www-form-urlencoded;q=0.2", + "multipart/form-data;q=0.1", }; public HashSet IncludePatterns { get; set; } = new(0, StringComparer.OrdinalIgnoreCase); public HashSet ExcludePatterns { get; set; } = new(0, StringComparer.OrdinalIgnoreCase); @@ -139,7 +139,7 @@ public object Clone() Serializers = new(Serializers ?? Enumerable.Empty(), StringComparer.OrdinalIgnoreCase), Deserializers = new(Deserializers ?? Enumerable.Empty(), StringComparer.OrdinalIgnoreCase), CleanOutput = CleanOutput, - StructuredMimeTypes = new(StructuredMimeTypes ?? Enumerable.Empty(), StringComparer.OrdinalIgnoreCase), + StructuredMimeTypes = new(StructuredMimeTypes ?? Enumerable.Empty()), IncludePatterns = new(IncludePatterns ?? Enumerable.Empty(), StringComparer.OrdinalIgnoreCase), ExcludePatterns = new(ExcludePatterns ?? Enumerable.Empty(), StringComparer.OrdinalIgnoreCase), ClearCache = ClearCache, @@ -164,7 +164,7 @@ internal void UpdateConfigurationFromLanguagesInformation(LanguagesInformation l if (languageInfo.StructuredMimeTypes.Any() && comparer.Equals(StructuredMimeTypes, defaultConfiguration.StructuredMimeTypes) && !comparer.Equals(languageInfo.StructuredMimeTypes, StructuredMimeTypes)) - StructuredMimeTypes = new(languageInfo.StructuredMimeTypes, StringComparer.OrdinalIgnoreCase); + StructuredMimeTypes = new(languageInfo.StructuredMimeTypes); } } #pragma warning restore CA1056 diff --git a/src/Kiota.Builder/Configuration/StructuredMimeTypesCollection.cs b/src/Kiota.Builder/Configuration/StructuredMimeTypesCollection.cs new file mode 100644 index 0000000000..5b43c39a11 --- /dev/null +++ b/src/Kiota.Builder/Configuration/StructuredMimeTypesCollection.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Kiota.Builder.Configuration; + +public partial class StructuredMimeTypesCollection : ICollection +{ + [GeneratedRegex(@"(?[^;]+);?q?=?(?[\d.]+)?", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline, 2000)] + private static partial Regex mimeTypesRegex(); + private readonly static Regex mimeTypesRegexInstance = mimeTypesRegex(); + private readonly Dictionary _mimeTypes; + + public int Count => _mimeTypes.Count; + + public bool IsReadOnly => false; + public StructuredMimeTypesCollection() : this(Array.Empty()) { } + public StructuredMimeTypesCollection(IEnumerable mimeTypes) + { + ArgumentNullException.ThrowIfNull(mimeTypes); + _mimeTypes = mimeTypes.Select(GetKeyAndPriority) + .OfType>() + .ToDictionary(static x => x.Key, static x => x.Value, StringComparer.OrdinalIgnoreCase); + } + private static KeyValuePair? GetKeyAndPriority(string rawFormat) + { + if (string.IsNullOrEmpty(rawFormat)) + return null; + var match = mimeTypesRegexInstance.Match(rawFormat); + if (match.Success) + { + var priority = match.Groups["priority"].Success && float.TryParse(match.Groups["priority"].Value, CultureInfo.InvariantCulture, out var resultPriority) ? resultPriority : 1; + return new KeyValuePair(match.Groups["mime"].Value, priority); + } + else + { + return null; + } + } + public IEnumerator GetEnumerator() + { + return _mimeTypes.OrderByDescending(static x => x.Value).Select(NormalizeMimeType).GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + public bool Contains(string item) + { + if (string.IsNullOrEmpty(item)) + return false; + return _mimeTypes.ContainsKey(item); + } + public float? GetPriority(string mimeType) + { + if (string.IsNullOrEmpty(mimeType)) + return null; + return _mimeTypes.TryGetValue(mimeType, out var priority) ? priority : null; + } + + public void Add(string item) + { + if (GetKeyAndPriority(item) is { } result) + _mimeTypes.TryAdd(result.Key, result.Value); + } + + public void Clear() + { + _mimeTypes.Clear(); + } + + /// + public void CopyTo(string[] array, int arrayIndex) + { + _mimeTypes.OrderByDescending(static x => x.Value).Select(NormalizeMimeType).ToArray().CopyTo(array, arrayIndex); + } + private static string NormalizeMimeType(KeyValuePair mimeType) + { + return NormalizeMimeType(mimeType.Key, mimeType.Value); + } + private static string NormalizeMimeType(string key, float value) + { + return $"{key};q={value}"; + } + /// + public bool Remove(string item) + { + if (GetKeyAndPriority(item) is { } result && _mimeTypes.ContainsKey(result.Key)) + { + _mimeTypes.Remove(result.Key); + return true; + } + else + return false; + } + public IEnumerable GetAcceptedTypes(IEnumerable searchTypes) + { + ArgumentNullException.ThrowIfNull(searchTypes); + return searchTypes.Select(GetKeyAndPriority) + .OfType>() + .Select(static x => x.Key) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(x => _mimeTypes.TryGetValue(x, out var result) ? NormalizeMimeType(x, result) : null) + .OfType() + .Order(StringComparer.OrdinalIgnoreCase); + } + public IEnumerable GetContentTypes(IEnumerable searchTypes) + { + ArgumentNullException.ThrowIfNull(searchTypes); + return searchTypes.Select(GetKeyAndPriority) + .OfType>() + .Select(static x => x.Key) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(x => _mimeTypes.TryGetValue(x, out var result) ? new KeyValuePair?(new(x, result)) : null) + .OfType>() + .OrderByDescending(static x => x.Value) + .ThenByDescending(static x => x.Key, StringComparer.OrdinalIgnoreCase) + .Select(static x => x.Key); + } +} diff --git a/src/Kiota.Builder/GlobComparer.cs b/src/Kiota.Builder/EqualityComparers/GlobComparer.cs similarity index 91% rename from src/Kiota.Builder/GlobComparer.cs rename to src/Kiota.Builder/EqualityComparers/GlobComparer.cs index d0c1d11bb5..61a9350137 100644 --- a/src/Kiota.Builder/GlobComparer.cs +++ b/src/Kiota.Builder/EqualityComparers/GlobComparer.cs @@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis; using DotNet.Globbing; -namespace Kiota.Builder; +namespace Kiota.Builder.EqualityComparers; internal class GlobComparer : IEqualityComparer { diff --git a/src/Kiota.Builder/EqualityComparers/OpenApiSchemaReferenceComparer.cs b/src/Kiota.Builder/EqualityComparers/OpenApiSchemaReferenceComparer.cs new file mode 100644 index 0000000000..a0d504b7a0 --- /dev/null +++ b/src/Kiota.Builder/EqualityComparers/OpenApiSchemaReferenceComparer.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.OpenApi.Models; + +namespace Kiota.Builder.EqualityComparers; + +internal class OpenApiSchemaReferenceComparer : IEqualityComparer +{ + public bool Equals(OpenApiSchema? x, OpenApiSchema? y) + { + return (x, y) switch + { + (null, null) => true, + (null, _) => false, + (_, null) => false, + _ when x.Reference is not null && !string.IsNullOrEmpty(x.Reference.Id) && y.Reference is not null && !string.IsNullOrEmpty(y.Reference.Id) => + x.Reference.Id.Equals(y.Reference.Id, StringComparison.OrdinalIgnoreCase), + _ => x == y, + }; + } + public int GetHashCode([DisallowNull] OpenApiSchema obj) + { + return obj.Reference is not null && !string.IsNullOrEmpty(obj.Reference.Id) ? obj.Reference.Id.GetHashCode(StringComparison.OrdinalIgnoreCase) : obj.GetHashCode(); + } +} diff --git a/src/Kiota.Builder/OpenApiServerComparer.cs b/src/Kiota.Builder/EqualityComparers/OpenApiServerComparer.cs similarity index 95% rename from src/Kiota.Builder/OpenApiServerComparer.cs rename to src/Kiota.Builder/EqualityComparers/OpenApiServerComparer.cs index 3bd5cdfe48..da206ac7c5 100644 --- a/src/Kiota.Builder/OpenApiServerComparer.cs +++ b/src/Kiota.Builder/EqualityComparers/OpenApiServerComparer.cs @@ -4,7 +4,7 @@ using System.Text.RegularExpressions; using Microsoft.OpenApi.Models; -namespace Kiota.Builder; +namespace Kiota.Builder.EqualityComparers; internal sealed partial class OpenApiServerComparer : IEqualityComparer { diff --git a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs index 20c5106b98..1a67303f37 100644 --- a/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; - +using Kiota.Builder.Configuration; using Microsoft.OpenApi.Models; namespace Kiota.Builder.Extensions; @@ -13,33 +13,33 @@ public static class OpenApiOperationExtensions /// cleans application/vnd.github.mercy-preview+json to application/json /// private static readonly Regex vendorSpecificCleanup = new(@"[^/]+\+", RegexOptions.Compiled, Constants.DefaultRegexTimeout); - public static OpenApiSchema? GetResponseSchema(this OpenApiOperation operation, HashSet structuredMimeTypes) + internal static OpenApiSchema? GetResponseSchema(this OpenApiOperation operation, StructuredMimeTypesCollection structuredMimeTypes) { ArgumentNullException.ThrowIfNull(operation); // Return Schema that represents all the possible success responses! return operation.GetResponseSchemas(SuccessCodes, structuredMimeTypes) .FirstOrDefault(); } - internal static IEnumerable GetResponseSchemas(this OpenApiOperation operation, HashSet successCodesToUse, HashSet structuredMimeTypes) + internal static IEnumerable GetResponseSchemas(this OpenApiOperation operation, HashSet successCodesToUse, StructuredMimeTypesCollection structuredMimeTypes) { // Return Schema that represents all the possible success responses! return operation.Responses.Where(r => successCodesToUse.Contains(r.Key)) .OrderBy(static x => x.Key, StringComparer.OrdinalIgnoreCase) .SelectMany(re => re.Value.Content.GetValidSchemas(structuredMimeTypes)); } - public static OpenApiSchema? GetRequestSchema(this OpenApiOperation operation, HashSet structuredMimeTypes) + internal static OpenApiSchema? GetRequestSchema(this OpenApiOperation operation, StructuredMimeTypesCollection structuredMimeTypes) { ArgumentNullException.ThrowIfNull(operation); return operation.RequestBody?.Content .GetValidSchemas(structuredMimeTypes).FirstOrDefault(); } - private static readonly HashSet multipartMimeTypes = new(1, StringComparer.OrdinalIgnoreCase) { "multipart/form-data" }; - public static bool IsMultipartFormDataSchema(this IDictionary source, HashSet structuredMimeTypes) + private static readonly StructuredMimeTypesCollection multipartMimeTypes = new(new string[] { "multipart/form-data" }); + internal static bool IsMultipartFormDataSchema(this IDictionary source, StructuredMimeTypesCollection structuredMimeTypes) { return source.GetValidSchemas(structuredMimeTypes).FirstOrDefault() is OpenApiSchema schema && source.GetValidSchemas(multipartMimeTypes).FirstOrDefault() == schema; } - public static IEnumerable GetValidSchemas(this IDictionary source, HashSet structuredMimeTypes) + internal static IEnumerable GetValidSchemas(this IDictionary source, StructuredMimeTypesCollection structuredMimeTypes) { if (!(structuredMimeTypes?.Any() ?? false)) throw new ArgumentNullException(nameof(structuredMimeTypes)); @@ -51,7 +51,7 @@ public static IEnumerable GetValidSchemas(this IDictionary s is not null) ?? Enumerable.Empty(); } - public static OpenApiSchema? GetResponseSchema(this OpenApiResponse response, HashSet structuredMimeTypes) + internal static OpenApiSchema? GetResponseSchema(this OpenApiResponse response, StructuredMimeTypesCollection structuredMimeTypes) { ArgumentNullException.ThrowIfNull(response); return response.Content.GetValidSchemas(structuredMimeTypes).FirstOrDefault(); diff --git a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs index b641b044d2..c3913ef313 100644 --- a/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs +++ b/src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs @@ -83,12 +83,12 @@ public static IEnumerable GetPathParametersForCurrentSegment(t /// /// Returns the class name for the node with more or less precision depending on the provided arguments /// - public static string GetClassName(this OpenApiUrlTreeNode currentNode, HashSet structuredMimeTypes, string? suffix = default, string? prefix = default, OpenApiOperation? operation = default, OpenApiResponse? response = default, OpenApiSchema? schema = default, bool requestBody = false) + internal static string GetClassName(this OpenApiUrlTreeNode currentNode, StructuredMimeTypesCollection structuredMimeTypes, string? suffix = default, string? prefix = default, OpenApiOperation? operation = default, OpenApiResponse? response = default, OpenApiSchema? schema = default, bool requestBody = false) { ArgumentNullException.ThrowIfNull(currentNode); return currentNode.GetSegmentName(structuredMimeTypes, suffix, prefix, operation, response, schema, requestBody, static x => x.LastOrDefault() ?? string.Empty); } - public static string GetNavigationPropertyName(this OpenApiUrlTreeNode currentNode, HashSet structuredMimeTypes, string? suffix = default, string? prefix = default, OpenApiOperation? operation = default, OpenApiResponse? response = default, OpenApiSchema? schema = default, bool requestBody = false) + internal static string GetNavigationPropertyName(this OpenApiUrlTreeNode currentNode, StructuredMimeTypesCollection structuredMimeTypes, string? suffix = default, string? prefix = default, OpenApiOperation? operation = default, OpenApiResponse? response = default, OpenApiSchema? schema = default, bool requestBody = false) { ArgumentNullException.ThrowIfNull(currentNode); var result = currentNode.GetSegmentName(structuredMimeTypes, suffix, prefix, operation, response, schema, requestBody, static x => string.Join(string.Empty, x.Select(static (y, idx) => idx == 0 ? y : y.ToFirstCharacterUpperCase())), false); @@ -97,7 +97,7 @@ public static string GetNavigationPropertyName(this OpenApiUrlTreeNode currentNo return result; } private static readonly HashSet httpVerbs = new(StringComparer.OrdinalIgnoreCase) { "get", "post", "put", "patch", "delete", "head", "options", "trace" }; - private static string GetSegmentName(this OpenApiUrlTreeNode currentNode, HashSet structuredMimeTypes, string? suffix, string? prefix, OpenApiOperation? operation, OpenApiResponse? response, OpenApiSchema? schema, bool requestBody, Func, string> segmentsReducer, bool skipExtension = true) + private static string GetSegmentName(this OpenApiUrlTreeNode currentNode, StructuredMimeTypesCollection structuredMimeTypes, string? suffix, string? prefix, OpenApiOperation? operation, OpenApiResponse? response, OpenApiSchema? schema, bool requestBody, Func, string> segmentsReducer, bool skipExtension = true) { var referenceName = schema?.Reference?.GetClassName(); var rawClassName = referenceName is not null && !string.IsNullOrEmpty(referenceName) ? diff --git a/src/Kiota.Builder/Kiota.Builder.csproj b/src/Kiota.Builder/Kiota.Builder.csproj index 57ba2c0c1d..6efd91d4e2 100644 --- a/src/Kiota.Builder/Kiota.Builder.csproj +++ b/src/Kiota.Builder/Kiota.Builder.csproj @@ -53,9 +53,13 @@ - + + + + - + \ No newline at end of file diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 0d3b8cb576..07edb94940 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -16,6 +16,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.CodeRenderers; using Kiota.Builder.Configuration; +using Kiota.Builder.EqualityComparers; using Kiota.Builder.Exceptions; using Kiota.Builder.Extensions; using Kiota.Builder.Lock; @@ -1419,11 +1420,12 @@ private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationTyp Parent = parentClass, Deprecation = deprecationInformation, }; - if (schema != null) + var mediaTypes = schema switch { - var mediaType = operation.Responses.Values.SelectMany(static x => x.Content).First(x => x.Value.Schema == schema).Key; - generatorMethod.AcceptedResponseTypes.Add(mediaType); - } + null => operation.Responses.Values.SelectMany(static x => x.Content).Select(static x => x.Key), + _ => config.StructuredMimeTypes.GetAcceptedTypes(operation.Responses.Values.SelectMany(static x => x.Content).Where(x => schemaReferenceComparer.Equals(schema, x.Value.Schema)).Select(static x => x.Key)), + }; + generatorMethod.AddAcceptedResponsesTypes(mediaTypes); if (config.Language == GenerationLanguage.CLI) SetPathAndQueryParameters(generatorMethod, currentNode, operation); AddRequestBuilderMethodParameters(currentNode, operationType, operation, requestConfigClass, generatorMethod); @@ -1435,6 +1437,7 @@ private void CreateOperationMethods(OpenApiUrlTreeNode currentNode, OperationTyp logger.LogWarning(ex, "Could not create method for {Operation} in {Path} because the schema was invalid", operation.OperationId, currentNode.Path); } } + private static readonly OpenApiSchemaReferenceComparer schemaReferenceComparer = new(); private static readonly Func GetCodeParameterFromApiParameter = x => { var codeName = x.Name.SanitizeParameterNameForCodeSymbols(); @@ -1526,7 +1529,7 @@ private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, O var mediaType = operation.RequestBody.Content.First(x => x.Value.Schema == requestBodySchema).Value; foreach (var encodingEntry in mediaType.Encoding .Where(x => !string.IsNullOrEmpty(x.Value.ContentType) && - config.StructuredMimeTypes.Contains(x.Value.ContentType.Split(';', StringSplitOptions.RemoveEmptyEntries)[0]))) + config.StructuredMimeTypes.Contains(x.Value.ContentType))) { if (CreateModelDeclarations(currentNode, requestBodySchema.Properties[encodingEntry.Key], operation, method, $"{operationType}RequestBody", isRequestBody: true) is CodeType propertyType && propertyType.TypeDefinition is not null) @@ -1550,7 +1553,7 @@ private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, O }, Deprecation = requestBodySchema.GetDeprecationInformation(), }); - method.RequestBodyContentType = operation.RequestBody.Content.First(x => x.Value.Schema == requestBodySchema).Key; + method.RequestBodyContentType = config.StructuredMimeTypes.GetContentTypes(operation.RequestBody.Content.Where(x => schemaReferenceComparer.Equals(x.Value.Schema, requestBodySchema)).Select(static x => x.Key)).First(); } else if (operation.RequestBody?.Content?.Any() ?? false) { @@ -1571,6 +1574,22 @@ private void AddRequestBuilderMethodParameters(OpenApiUrlTreeNode currentNode, O }, }; method.AddParameter(nParam); + var contentTypes = operation.RequestBody.Content.Select(static x => x.Key).ToArray(); + if (contentTypes.Length == 1 && !"*/*".Equals(contentTypes[0], StringComparison.OrdinalIgnoreCase)) + method.RequestBodyContentType = contentTypes[0]; + else + method.AddParameter(new CodeParameter + { + Kind = CodeParameterKind.RequestBodyContentType, + Name = "contentType", + Optional = false, + Type = new CodeType + { + Name = "string", + IsExternal = true, + IsNullable = false, + }, + }); } method.AddParameter(new CodeParameter { diff --git a/src/Kiota.Builder/Lock/KiotaLock.cs b/src/Kiota.Builder/Lock/KiotaLock.cs index 099d781477..a299234a5d 100644 --- a/src/Kiota.Builder/Lock/KiotaLock.cs +++ b/src/Kiota.Builder/Lock/KiotaLock.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Kiota.Builder.Configuration; namespace Kiota.Builder.Lock; @@ -70,7 +71,9 @@ public bool IncludeAdditionalData /// /// The structured mime types used for this client. /// - public HashSet StructuredMimeTypes { get; set; } = new(); +#pragma warning disable CA1002 + public List StructuredMimeTypes { get; set; } = new(); +#pragma warning restore CA1002 /// /// The path patterns for API endpoints to include for this client. /// @@ -100,7 +103,7 @@ public void UpdateGenerationConfigurationFromLock(GenerationConfiguration config config.IncludeAdditionalData = IncludeAdditionalData; config.Serializers = Serializers; config.Deserializers = Deserializers; - config.StructuredMimeTypes = StructuredMimeTypes; + config.StructuredMimeTypes = new(StructuredMimeTypes); config.IncludePatterns = IncludePatterns; config.ExcludePatterns = ExcludePatterns; config.OpenAPIFilePath = DescriptionLocation; @@ -127,7 +130,7 @@ public KiotaLock(GenerationConfiguration config) IncludeAdditionalData = config.IncludeAdditionalData; Serializers = config.Serializers; Deserializers = config.Deserializers; - StructuredMimeTypes = config.StructuredMimeTypes; + StructuredMimeTypes = config.StructuredMimeTypes.ToList(); IncludePatterns = config.IncludePatterns; ExcludePatterns = config.ExcludePatterns; DescriptionLocation = config.OpenAPIFilePath; diff --git a/src/Kiota.Builder/BaseCodeParameterOrderComparer.cs b/src/Kiota.Builder/OrderComparers/BaseCodeParameterOrderComparer.cs similarity index 77% rename from src/Kiota.Builder/BaseCodeParameterOrderComparer.cs rename to src/Kiota.Builder/OrderComparers/BaseCodeParameterOrderComparer.cs index eab584983c..c350366388 100644 --- a/src/Kiota.Builder/BaseCodeParameterOrderComparer.cs +++ b/src/Kiota.Builder/OrderComparers/BaseCodeParameterOrderComparer.cs @@ -1,7 +1,7 @@ using System; using Kiota.Builder.CodeDOM; -namespace Kiota.Builder; +namespace Kiota.Builder.OrderComparers; public class BaseCodeParameterOrderComparer : BaseStringComparisonComparer { public override int Compare(CodeParameter? x, CodeParameter? y) @@ -28,12 +28,13 @@ protected virtual int GetKindOrderHint(CodeParameterKind kind) CodeParameterKind.Path => 4, CodeParameterKind.RequestConfiguration => 5, CodeParameterKind.RequestBody => 6, - CodeParameterKind.Serializer => 7, - CodeParameterKind.BackingStore => 8, - CodeParameterKind.SetterValue => 9, - CodeParameterKind.ParseNode => 10, - CodeParameterKind.Custom => 11, - _ => 12, + CodeParameterKind.RequestBodyContentType => 7, + CodeParameterKind.Serializer => 8, + CodeParameterKind.BackingStore => 9, + CodeParameterKind.SetterValue => 10, + CodeParameterKind.ParseNode => 11, + CodeParameterKind.Custom => 12, + _ => 13, }; } private const int OptionalWeight = 10000; diff --git a/src/Kiota.Builder/CodeElementOrderComparer.cs b/src/Kiota.Builder/OrderComparers/CodeElementOrderComparer.cs similarity index 97% rename from src/Kiota.Builder/CodeElementOrderComparer.cs rename to src/Kiota.Builder/OrderComparers/CodeElementOrderComparer.cs index c4fe158f41..e209f7e182 100644 --- a/src/Kiota.Builder/CodeElementOrderComparer.cs +++ b/src/Kiota.Builder/OrderComparers/CodeElementOrderComparer.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; using System.Linq; using Kiota.Builder.CodeDOM; -namespace Kiota.Builder; +namespace Kiota.Builder.OrderComparers; public class CodeElementOrderComparer : BaseStringComparisonComparer { public override int Compare(CodeElement? x, CodeElement? y) diff --git a/src/Kiota.Builder/CodeElementOrderComparerPython.cs b/src/Kiota.Builder/OrderComparers/CodeElementOrderComparerPython.cs similarity index 95% rename from src/Kiota.Builder/CodeElementOrderComparerPython.cs rename to src/Kiota.Builder/OrderComparers/CodeElementOrderComparerPython.cs index a15461e36e..5e39989ce9 100644 --- a/src/Kiota.Builder/CodeElementOrderComparerPython.cs +++ b/src/Kiota.Builder/OrderComparers/CodeElementOrderComparerPython.cs @@ -1,6 +1,6 @@ using Kiota.Builder.CodeDOM; -namespace Kiota.Builder; +namespace Kiota.Builder.OrderComparers; public class CodeElementOrderComparerPython : CodeElementOrderComparer { protected override int GetTypeFactor(CodeElement element) diff --git a/src/Kiota.Builder/CodeElementOrderComparerWithExternalMethods.cs b/src/Kiota.Builder/OrderComparers/CodeElementOrderComparerWithExternalMethods.cs similarity index 94% rename from src/Kiota.Builder/CodeElementOrderComparerWithExternalMethods.cs rename to src/Kiota.Builder/OrderComparers/CodeElementOrderComparerWithExternalMethods.cs index 6f94eafc3b..950c317bba 100644 --- a/src/Kiota.Builder/CodeElementOrderComparerWithExternalMethods.cs +++ b/src/Kiota.Builder/OrderComparers/CodeElementOrderComparerWithExternalMethods.cs @@ -1,6 +1,6 @@ using Kiota.Builder.CodeDOM; -namespace Kiota.Builder; +namespace Kiota.Builder.OrderComparers; public class CodeElementOrderComparerWithExternalMethods : CodeElementOrderComparer { protected override int GetTypeFactor(CodeElement element) diff --git a/src/Kiota.Builder/Validation/UrlFormEncodedComplex.cs b/src/Kiota.Builder/Validation/UrlFormEncodedComplex.cs index fdeb7577dd..254525fa4f 100644 --- a/src/Kiota.Builder/Validation/UrlFormEncodedComplex.cs +++ b/src/Kiota.Builder/Validation/UrlFormEncodedComplex.cs @@ -1,7 +1,6 @@  -using System; -using System.Collections.Generic; using System.Linq; +using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Validations; @@ -9,7 +8,7 @@ namespace Kiota.Builder.Validation; public class UrlFormEncodedComplex : ValidationRule { - private static readonly HashSet validContentTypes = new(StringComparer.OrdinalIgnoreCase) { + private static readonly StructuredMimeTypesCollection validContentTypes = new() { "application/x-www-form-urlencoded", }; public UrlFormEncodedComplex() : base(static (context, operation) => diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index 8bbf7c0845..39724792ba 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; - using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using Kiota.Builder.OrderComparers; namespace Kiota.Builder.Writers.CSharp; public class CodeMethodWriter : BaseElementWriter @@ -50,7 +50,8 @@ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter w var returnTypeWithoutCollectionInformation = conventions.GetTypeString(codeElement.ReturnType, codeElement, false); var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var requestConfig = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration); - var requestParams = new RequestParams(requestBodyParam, requestConfig); + var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType); + var requestParams = new RequestParams(requestBodyParam, requestConfig, requestContentType); switch (codeElement.Kind) { case CodeMethodKind.Serializer: @@ -89,7 +90,7 @@ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter w case CodeMethodKind.CommandBuilder: var origParams = codeElement.OriginalMethod?.Parameters ?? codeElement.Parameters; requestBodyParam = origParams.OfKind(CodeParameterKind.RequestBody); - requestParams = new RequestParams(requestBodyParam, null); + requestParams = new RequestParams(requestBodyParam, null, null); WriteCommandBuilderBody(codeElement, parentClass, requestParams, isVoid, returnType, writer); break; case CodeMethodKind.Factory: @@ -375,7 +376,7 @@ protected void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams re .Methods .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod) ?.Name; - var parametersList = new CodeParameter?[] { requestParams.requestBody, requestParams.requestConfiguration } + var parametersList = new CodeParameter?[] { requestParams.requestBody, requestParams.requestContentType, requestParams.requestConfiguration } .Select(static x => x?.Name).Where(static x => x != null).Aggregate(static (x, y) => $"{x}, {y}"); writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});"); var errorMappingVarName = "default"; @@ -433,13 +434,18 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req writer.CloseBlock(); } - if (codeElement.AcceptedResponseTypes.Any()) - writer.WriteLine($"{RequestInfoVarName}.Headers.TryAdd(\"Accept\", \"{string.Join(", ", codeElement.AcceptedResponseTypes)}\");"); + if (codeElement.ShouldAddAcceptHeader) + writer.WriteLine($"{RequestInfoVarName}.Headers.TryAdd(\"Accept\", \"{codeElement.AcceptHeaderValue}\");"); if (requestParams.requestBody != null) { var suffix = requestParams.requestBody.Type.IsCollection ? "Collection" : string.Empty; if (requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"{RequestInfoVarName}.SetStreamContent({requestParams.requestBody.Name});"); + { + if (requestParams.requestContentType is not null) + writer.WriteLine($"{RequestInfoVarName}.SetStreamContent({requestParams.requestBody.Name}, {requestParams.requestContentType.Name});"); + else if (!string.IsNullOrEmpty(codeElement.RequestBodyContentType)) + writer.WriteLine($"{RequestInfoVarName}.SetStreamContent({requestParams.requestBody.Name}, \"{codeElement.RequestBodyContentType}\");"); + } else if (currentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProperty) if (requestParams.requestBody.Type is CodeType bodyType && (bodyType.TypeDefinition is CodeClass || bodyType.Name.Equals("MultipartBody", StringComparison.OrdinalIgnoreCase))) writer.WriteLine($"{RequestInfoVarName}.SetContentFromParsable({requestAdapterProperty.Name.ToFirstCharacterUpperCase()}, \"{codeElement.RequestBodyContentType}\", {requestParams.requestBody.Name});"); diff --git a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs index 7055214838..58a0062344 100644 --- a/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Go/CodeMethodWriter.cs @@ -24,7 +24,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri writer.IncreaseIndent(); var requestOptionsParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration); var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); - var requestParams = new RequestParams(requestBodyParam, requestOptionsParam); + var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType); + var requestParams = new RequestParams(requestBodyParam, requestOptionsParam, requestContentType); switch (codeElement.Kind) { case CodeMethodKind.Serializer: @@ -819,7 +820,6 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req var requestAdapterPropertyName = BaseRequestBuilderVarName + "." + parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter)?.Name.ToFirstCharacterUpperCase(); var contextParameterName = codeElement.Parameters.OfKind(CodeParameterKind.Cancellation)?.Name.ToFirstCharacterLowerCase(); writer.WriteLine($"{RequestInfoVarName} := {conventions.AbstractionsHash}.NewRequestInformation()"); - if (requestParams.requestConfiguration != null) { var headers = requestParams.Headers; @@ -854,14 +854,19 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req writer.WriteLines($"{RequestInfoVarName}.UrlTemplate = {GetPropertyCall(urlTemplateProperty, "\"\"")}", $"{RequestInfoVarName}.PathParameters = {GetPropertyCall(urlTemplateParamsProperty, "\"\"")}"); writer.WriteLine($"{RequestInfoVarName}.Method = {conventions.AbstractionsHash}.{codeElement.HttpMethod.Value.ToString().ToUpperInvariant()}"); - if (codeElement.AcceptedResponseTypes.Any()) - writer.WriteLine($"{RequestInfoVarName}.Headers.TryAdd(\"Accept\", \"{string.Join(", ", codeElement.AcceptedResponseTypes)}\")"); + if (codeElement.ShouldAddAcceptHeader) + writer.WriteLine($"{RequestInfoVarName}.Headers.TryAdd(\"Accept\", \"{codeElement.AcceptHeaderValue}\")"); if (requestParams.requestBody != null) { var bodyParamReference = $"{requestParams.requestBody.Name.ToFirstCharacterLowerCase()}"; var collectionSuffix = requestParams.requestBody.Type.IsCollection ? "Collection" : string.Empty; - if (requestParams.requestBody.Type.Name.Equals("binary", StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"{RequestInfoVarName}.SetStreamContent({bodyParamReference})"); + if (requestParams.requestBody.Type.Name.Equals("binary", StringComparison.OrdinalIgnoreCase) || requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) + { + if (requestParams.requestContentType is not null) + writer.WriteLine($"{RequestInfoVarName}.SetStreamContentAndContentType({bodyParamReference}, {requestParams.requestContentType.Name.ToFirstCharacterLowerCase()})"); + else if (!string.IsNullOrEmpty(codeElement.RequestBodyContentType)) + writer.WriteLine($"{RequestInfoVarName}.SetStreamContentAndContentType({bodyParamReference}, \"{codeElement.RequestBodyContentType}\")"); + } else if (requestParams.requestBody.Type is CodeType bodyType && (bodyType.TypeDefinition is CodeClass || bodyType.TypeDefinition is CodeInterface || bodyType.Name.Equals("MultipartBody", StringComparison.OrdinalIgnoreCase))) { if (bodyType.IsCollection) diff --git a/src/Kiota.Builder/Writers/Go/GoCodeParameterOrderComparer.cs b/src/Kiota.Builder/Writers/Go/GoCodeParameterOrderComparer.cs index 1e5ed5a0bf..934d2c2fe3 100644 --- a/src/Kiota.Builder/Writers/Go/GoCodeParameterOrderComparer.cs +++ b/src/Kiota.Builder/Writers/Go/GoCodeParameterOrderComparer.cs @@ -1,4 +1,5 @@ using Kiota.Builder.CodeDOM; +using Kiota.Builder.OrderComparers; namespace Kiota.Builder.Writers.Go; @@ -16,11 +17,12 @@ protected override int GetKindOrderHint(CodeParameterKind kind) CodeParameterKind.Path => 4, CodeParameterKind.RequestConfiguration => 5, CodeParameterKind.RequestBody => 6, - CodeParameterKind.Serializer => 7, - CodeParameterKind.BackingStore => 8, - CodeParameterKind.SetterValue => 9, - CodeParameterKind.ParseNode => 10, - CodeParameterKind.Custom => 11, + CodeParameterKind.RequestBodyContentType => 7, + CodeParameterKind.Serializer => 8, + CodeParameterKind.BackingStore => 9, + CodeParameterKind.SetterValue => 10, + CodeParameterKind.ParseNode => 11, + CodeParameterKind.Custom => 12, _ => 13, }; } diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index 70f05ece23..32c2834154 100644 --- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using Kiota.Builder.OrderComparers; namespace Kiota.Builder.Writers.Java; public class CodeMethodWriter : BaseElementWriter @@ -24,7 +25,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var inherits = parentClass.StartBlock.Inherits != null && !parentClass.IsErrorDefinition; var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var configParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration); - var requestParams = new RequestParams(requestBodyParam, configParam); + var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType); + var requestParams = new RequestParams(requestBodyParam, configParam, requestContentType); AddNullChecks(codeElement, writer); switch (codeElement.Kind) { @@ -566,8 +568,8 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is CodeProperty urlTemplateProperty) writer.WriteLines($"{RequestInfoVarName}.urlTemplate = {GetPropertyCall(urlTemplateProperty, "\"\"")};", $"{RequestInfoVarName}.pathParameters = {GetPropertyCall(urlTemplateParamsProperty, "null")};"); - if (codeElement.AcceptedResponseTypes.Any()) - writer.WriteLine($"{RequestInfoVarName}.headers.tryAdd(\"Accept\", \"{string.Join(", ", codeElement.AcceptedResponseTypes)}\");"); + if (codeElement.ShouldAddAcceptHeader) + writer.WriteLine($"{RequestInfoVarName}.headers.tryAdd(\"Accept\", \"{codeElement.AcceptHeaderValue}\");"); if (requestParams.requestBody != null && currentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProperty) @@ -575,7 +577,12 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req var toArrayPostfix = requestParams.requestBody.Type.IsCollection ? $".toArray(new {requestParams.requestBody.Type.Name}[0])" : string.Empty; var collectionPostfix = requestParams.requestBody.Type.IsCollection ? "Collection" : string.Empty; if (requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name});"); + { + if (requestParams.requestContentType is not null) + writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name}, {requestParams.requestContentType.Name});"); + else if (!string.IsNullOrEmpty(codeElement.RequestBodyContentType)) + writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name}, \"{codeElement.RequestBodyContentType}\");"); + } else if (requestParams.requestBody.Type is CodeType bodyType && (bodyType.TypeDefinition is CodeClass || bodyType.Name.Equals("MultipartBody", StringComparison.OrdinalIgnoreCase))) writer.WriteLine($"{RequestInfoVarName}.setContentFromParsable({requestAdapterProperty.Name}, \"{codeElement.RequestBodyContentType}\", {requestParams.requestBody.Name}{toArrayPostfix});"); else diff --git a/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs index 01fa5de23b..b439ea8e0a 100644 --- a/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Php/CodeMethodWriter.cs @@ -3,6 +3,7 @@ using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using Kiota.Builder.OrderComparers; namespace Kiota.Builder.Writers.Php; public class CodeMethodWriter : BaseElementWriter { @@ -29,7 +30,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri codeClass.IsOfKind(CodeClassKind.Model); var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var config = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration); - var requestParams = new RequestParams(requestBodyParam, config); + var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType); + var requestParams = new RequestParams(requestBodyParam, config, requestContentType); WriteMethodPhpDocs(codeElement, writer); WriteMethodsAndParameters(codeElement, writer, codeElement.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor)); @@ -561,7 +563,12 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req { var suffix = requestParams.requestBody.Type.IsCollection ? "Collection" : string.Empty; if (requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"{RequestInfoVarName}->setStreamContent({conventions.GetParameterName(requestParams.requestBody)});"); + { + if (requestParams.requestContentType is not null) + writer.WriteLine($"{RequestInfoVarName}->setStreamContent({conventions.GetParameterName(requestParams.requestBody)}, {conventions.GetParameterName(requestParams.requestContentType)});"); + else if (!string.IsNullOrEmpty(codeElement.RequestBodyContentType)) + writer.WriteLine($"{RequestInfoVarName}->setStreamContent({conventions.GetParameterName(requestParams.requestBody)}, \"{codeElement.RequestBodyContentType}\");"); + } else if (currentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProperty) if (requestParams.requestBody.Type is CodeType bodyType && bodyType.TypeDefinition is CodeClass) writer.WriteLine($"{RequestInfoVarName}->setContentFromParsable{suffix}($this->{requestAdapterProperty.Name.ToFirstCharacterLowerCase()}, \"{codeElement.RequestBodyContentType}\", {conventions.GetParameterName(requestParams.requestBody)});"); @@ -598,8 +605,8 @@ private void WriteRequestConfiguration(RequestParams requestParams, LanguageWrit private void WriteAcceptHeaderDef(CodeMethod codeMethod, LanguageWriter writer) { - if (codeMethod.AcceptedResponseTypes.Any()) - writer.WriteLine($"{RequestInfoVarName}->tryAddHeader('Accept', \"{string.Join(", ", codeMethod.AcceptedResponseTypes)}\");"); + if (codeMethod.ShouldAddAcceptHeader) + writer.WriteLine($"{RequestInfoVarName}->tryAddHeader('Accept', \"{codeMethod.AcceptHeaderValue}\");"); } private void WriteDeserializerBody(CodeClass parentClass, LanguageWriter writer, CodeMethod method, bool extendsModelClass = false) { diff --git a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs index 9a400cd7df..48dd0bfb27 100644 --- a/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Python/CodeMethodWriter.cs @@ -36,7 +36,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var inherits = parentClass.StartBlock.Inherits != null && !parentClass.IsErrorDefinition; var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var requestConfigParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration); - var requestParams = new RequestParams(requestBodyParam, requestConfigParam); + var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType); + var requestParams = new RequestParams(requestBodyParam, requestConfigParam, requestContentType); if (!codeElement.IsOfKind(CodeMethodKind.Setter) && !(codeElement.IsOfKind(CodeMethodKind.Constructor) && parentClass.IsOfKind(CodeClassKind.RequestBuilder))) foreach (var parameter in codeElement.Parameters.Where(static x => !x.Optional).OrderBy(static x => x.Name)) @@ -616,8 +617,8 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req writer.WriteLines($"{RequestInfoVarName}.url_template = {GetPropertyCall(urlTemplateProperty, "''")}", $"{RequestInfoVarName}.path_parameters = {GetPropertyCall(urlTemplateParamsProperty, "''")}"); writer.WriteLine($"{RequestInfoVarName}.http_method = Method.{codeElement.HttpMethod.Value.ToString().ToUpperInvariant()}"); - if (codeElement.AcceptedResponseTypes.Any()) - writer.WriteLine($"{RequestInfoVarName}.try_add_request_header(\"Accept\", \"{string.Join(", ", codeElement.AcceptedResponseTypes)}\")"); + if (codeElement.ShouldAddAcceptHeader) + writer.WriteLine($"{RequestInfoVarName}.try_add_request_header(\"Accept\", \"{codeElement.AcceptHeaderValue}\")"); if (currentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProperty) UpdateRequestInformationFromRequestBody(codeElement, requestParams, requestAdapterProperty, writer); writer.WriteLine($"return {RequestInfoVarName}"); @@ -838,7 +839,12 @@ private void UpdateRequestInformationFromRequestBody(CodeMethod codeElement, Req if (requestParams.requestBody != null) { if (requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"{RequestInfoVarName}.set_stream_content({requestParams.requestBody.Name})"); + { + if (requestParams.requestContentType is not null) + writer.WriteLine($"{RequestInfoVarName}.set_stream_content({requestParams.requestBody.Name}, {requestParams.requestContentType.Name})"); + else if (!string.IsNullOrEmpty(codeElement.RequestBodyContentType)) + writer.WriteLine($"{RequestInfoVarName}.set_stream_content({requestParams.requestBody.Name}, \"{codeElement.RequestBodyContentType}\")"); + } else { var setMethodName = requestParams.requestBody.Type is CodeType bodyType && bodyType.TypeDefinition is CodeClass ? "set_content_from_parsable" : "set_content_from_scalar"; diff --git a/src/Kiota.Builder/Writers/Python/PythonCodeParameterOrderComparer.cs b/src/Kiota.Builder/Writers/Python/PythonCodeParameterOrderComparer.cs index c6ff579191..1cd8dc6795 100644 --- a/src/Kiota.Builder/Writers/Python/PythonCodeParameterOrderComparer.cs +++ b/src/Kiota.Builder/Writers/Python/PythonCodeParameterOrderComparer.cs @@ -1,4 +1,5 @@ using Kiota.Builder.CodeDOM; +using Kiota.Builder.OrderComparers; namespace Kiota.Builder.Writers.Python; public class PythonCodeParameterOrderComparer : BaseCodeParameterOrderComparer @@ -14,11 +15,12 @@ protected override int GetKindOrderHint(CodeParameterKind kind) CodeParameterKind.Path => 4, CodeParameterKind.RequestConfiguration => 5, CodeParameterKind.RequestBody => 6, - CodeParameterKind.Serializer => 7, - CodeParameterKind.BackingStore => 8, - CodeParameterKind.SetterValue => 9, - CodeParameterKind.ParseNode => 10, - CodeParameterKind.Custom => 11, + CodeParameterKind.RequestBodyContentType => 7, + CodeParameterKind.Serializer => 8, + CodeParameterKind.BackingStore => 9, + CodeParameterKind.SetterValue => 10, + CodeParameterKind.ParseNode => 11, + CodeParameterKind.Custom => 12, _ => 13, }; } diff --git a/src/Kiota.Builder/Writers/RequestParams.cs b/src/Kiota.Builder/Writers/RequestParams.cs index db8b1cc3f3..a62fbe627d 100644 --- a/src/Kiota.Builder/Writers/RequestParams.cs +++ b/src/Kiota.Builder/Writers/RequestParams.cs @@ -1,7 +1,7 @@ using Kiota.Builder.CodeDOM; namespace Kiota.Builder.Writers; -public record RequestParams(CodeParameter? requestBody, CodeParameter? requestConfiguration) +public record RequestParams(CodeParameter? requestBody, CodeParameter? requestConfiguration, CodeParameter? requestContentType) { public CodeProperty? Headers => requestConfiguration?.GetHeadersProperty(); public CodeProperty? QueryParameters => requestConfiguration?.GetQueryProperty(); diff --git a/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs index cbccdf170a..950301d162 100644 --- a/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Ruby/CodeMethodWriter.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; - using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using Kiota.Builder.OrderComparers; namespace Kiota.Builder.Writers.Ruby; public class CodeMethodWriter : BaseElementWriter @@ -21,7 +21,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var inherits = parentClass.StartBlock.Inherits != null; var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var config = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration); - var requestParams = new RequestParams(requestBodyParam, config); + var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType); + var requestParams = new RequestParams(requestBodyParam, config, requestContentType); WriteMethodPrototype(codeElement, writer); AddNullChecks(codeElement, writer); switch (codeElement.Kind) @@ -301,7 +302,12 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req if (requestParams.requestBody != null) { if (requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"request_info.set_stream_content({requestParams.requestBody.Name})"); + { + if (requestParams.requestContentType is not null) + writer.WriteLine($"request_info.set_stream_content({requestParams.requestBody.Name}, {requestParams.requestContentType.Name})"); + else if (!string.IsNullOrEmpty(codeElement.RequestBodyContentType)) + writer.WriteLine($"request_info.set_stream_content({requestParams.requestBody.Name}, \"{codeElement.RequestBodyContentType}\")"); + } else if (parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProperty) writer.WriteLine($"request_info.set_content_from_parsable(@{requestAdapterProperty.Name.ToSnakeCase()}, \"{codeElement.RequestBodyContentType}\", {requestParams.requestBody.Name})"); } @@ -311,8 +317,8 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req writer.WriteLines($"request_info.url_template = {GetPropertyCall(urlTemplateProperty, "''")}", $"request_info.path_parameters = {GetPropertyCall(urlTemplateParamsProperty, "''")}"); writer.WriteLine($"request_info.http_method = :{codeElement.HttpMethod.Value.ToString().ToUpperInvariant()}"); - if (codeElement.AcceptedResponseTypes.Any()) - writer.WriteLine($"request_info.headers.try_add('Accept', '{string.Join(", ", codeElement.AcceptedResponseTypes)}')"); + if (codeElement.ShouldAddAcceptHeader) + writer.WriteLine($"request_info.headers.try_add('Accept', '{codeElement.AcceptHeaderValue}')"); writer.WriteLine("return request_info"); } private static string GetPropertyCall(CodeProperty property, string defaultValue) => property == null ? defaultValue : $"@{property.NamePrefix}{property.Name.ToSnakeCase()}"; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 42f5354569..5885e248e1 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; - using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using Kiota.Builder.OrderComparers; namespace Kiota.Builder.Writers.TypeScript; public class CodeMethodWriter : BaseElementWriter @@ -30,7 +30,8 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri var inherits = parentClass.StartBlock.Inherits != null && !parentClass.IsErrorDefinition; var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody); var requestConfigParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration); - var requestParams = new RequestParams(requestBodyParam, requestConfigParam); + var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType); + var requestParams = new RequestParams(requestBodyParam, requestConfigParam, requestContentType); WriteDefensiveStatements(codeElement, writer); switch (codeElement.Kind) { @@ -365,12 +366,17 @@ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams req writer.WriteLines($"{RequestInfoVarName}.urlTemplate = {GetPropertyCall(urlTemplateProperty)};", $"{RequestInfoVarName}.pathParameters = {GetPropertyCall(urlTemplateParamsProperty)};"); writer.WriteLine($"{RequestInfoVarName}.httpMethod = HttpMethod.{codeElement.HttpMethod.Value.ToString().ToUpperInvariant()};"); - if (codeElement.AcceptedResponseTypes.Any()) - writer.WriteLine($"{RequestInfoVarName}.tryAddRequestHeaders(\"Accept\", \"{string.Join(", ", codeElement.AcceptedResponseTypes)}\");"); + if (codeElement.ShouldAddAcceptHeader) + writer.WriteLine($"{RequestInfoVarName}.tryAddRequestHeaders(\"Accept\", \"{codeElement.AcceptHeaderValue}\");"); if (requestParams.requestBody != null) { if (requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase)) - writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name});"); + { + if (requestParams.requestContentType is not null) + writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name}, {requestParams.requestContentType.Name.ToFirstCharacterLowerCase()});"); + else if (!string.IsNullOrEmpty(codeElement.RequestBodyContentType)) + writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name}, \"{codeElement.RequestBodyContentType}\");"); + } else if (currentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProperty) { ComposeContentInRequestGeneratorBody(requestParams.requestBody, requestAdapterProperty, codeElement.RequestBodyContentType, writer); diff --git a/src/kiota/Handlers/KiotaGenerationCommandHandler.cs b/src/kiota/Handlers/KiotaGenerationCommandHandler.cs index c94e794751..bb95aec6d4 100644 --- a/src/kiota/Handlers/KiotaGenerationCommandHandler.cs +++ b/src/kiota/Handlers/KiotaGenerationCommandHandler.cs @@ -107,9 +107,8 @@ public override async Task InvokeAsync(InvocationContext context) .SelectMany(static x => x.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) .ToHashSet(StringComparer.OrdinalIgnoreCase); if (structuredMimeTypes.Any()) - Configuration.Generation.StructuredMimeTypes = structuredMimeTypes.SelectMany(static x => x.Split(new[] { ' ' })) - .Select(static x => x.TrimQuotes()) - .ToHashSet(StringComparer.OrdinalIgnoreCase); + Configuration.Generation.StructuredMimeTypes = new(structuredMimeTypes.SelectMany(static x => x.Split(new[] { ' ' })) + .Select(static x => x.TrimQuotes())); Configuration.Generation.OpenAPIFilePath = GetAbsolutePath(Configuration.Generation.OpenAPIFilePath); Configuration.Generation.OutputPath = NormalizeSlashesInPath(GetAbsolutePath(Configuration.Generation.OutputPath)); diff --git a/src/kiota/KiotaConfigurationExtensions.cs b/src/kiota/KiotaConfigurationExtensions.cs index 09fccae1a6..a44dce586b 100644 --- a/src/kiota/KiotaConfigurationExtensions.cs +++ b/src/kiota/KiotaConfigurationExtensions.cs @@ -60,14 +60,27 @@ public static void BindConfiguration(this KiotaConfiguration configObject, IConf configObject.Generation.IncludeAdditionalData = bool.TryParse(configuration[$"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.IncludeAdditionalData)}"], out var includeAdditionalData) && includeAdditionalData; configObject.Generation.CleanOutput = bool.TryParse(configuration[$"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.CleanOutput)}"], out var cleanOutput) && cleanOutput; configObject.Generation.ClearCache = bool.TryParse(configuration[$"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.ClearCache)}"], out var clearCache) && clearCache; + configObject.Generation.ExcludeBackwardCompatible = bool.TryParse(configuration[$"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.ExcludeBackwardCompatible)}"], out var excludeBackwardCompatible) && excludeBackwardCompatible; configObject.Generation.MaxDegreeOfParallelism = int.TryParse(configuration[$"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.MaxDegreeOfParallelism)}"], out var maxDegreeOfParallelism) ? maxDegreeOfParallelism : configObject.Generation.MaxDegreeOfParallelism; - configuration.GetSection($"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.StructuredMimeTypes)}").LoadHashSet(configObject.Generation.StructuredMimeTypes); + configuration.GetSection($"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.StructuredMimeTypes)}").LoadCollection(configObject.Generation.StructuredMimeTypes); configuration.GetSection($"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.Serializers)}").LoadHashSet(configObject.Generation.Serializers); configuration.GetSection($"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.Deserializers)}").LoadHashSet(configObject.Generation.Deserializers); configuration.GetSection($"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.IncludePatterns)}").LoadHashSet(configObject.Generation.IncludePatterns); configuration.GetSection($"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.ExcludePatterns)}").LoadHashSet(configObject.Generation.ExcludePatterns); configuration.GetSection($"{nameof(configObject.Generation)}:{nameof(GenerationConfiguration.DisabledValidationRules)}").LoadHashSet(configObject.Generation.DisabledValidationRules); } + private static void LoadCollection(this IConfigurationSection section, ICollection collection) + { + ArgumentNullException.ThrowIfNull(collection); + if (section is null) return; + var children = section.GetChildren(); + if (children.Any() && collection.Any()) collection.Clear(); + foreach (var item in children) + { + if (section[item.Key] is string value && !string.IsNullOrEmpty(value)) + collection.Add(value); + } + } private static void LoadHashSet(this IConfigurationSection section, HashSet hashSet) { ArgumentNullException.ThrowIfNull(hashSet); diff --git a/src/kiota/KiotaHost.cs b/src/kiota/KiotaHost.cs index 66e2e4f37d..79d23c8d0a 100644 --- a/src/kiota/KiotaHost.cs +++ b/src/kiota/KiotaHost.cs @@ -367,7 +367,7 @@ private static Command GetGenerateCommand() var structuredMimeTypesOption = new Option>( "--structured-mime-types", () => defaultConfiguration.StructuredMimeTypes.ToList(), - "The MIME types to use for structured data model generation. Accepts multiple values."); + "The MIME types with optional priorities as defined in RFC9110 Accept header to use for structured data model generation. Accepts multiple values."); structuredMimeTypesOption.AddAlias("-m"); var (includePatterns, excludePatterns) = GetIncludeAndExcludeOptions(defaultConfiguration.IncludePatterns, defaultConfiguration.ExcludePatterns); diff --git a/tests/Kiota.Builder.Tests/Configuration/StructuredMimeTypesCollectionTests.cs b/tests/Kiota.Builder.Tests/Configuration/StructuredMimeTypesCollectionTests.cs new file mode 100644 index 0000000000..bd4b21e7d3 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Configuration/StructuredMimeTypesCollectionTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using Kiota.Builder.Configuration; +using Xunit; + +namespace Kiota.Builder.Tests.Configuration; + +public sealed class StructuredMimeTypesCollectionTests +{ + [Fact] + public void Defensive() + { + Assert.Throws(() => new StructuredMimeTypesCollection(null!)); + } + [Fact] + public void ParsesWithOrWithoutPriorities() + { + var mimeTypes = new StructuredMimeTypesCollection(new[] { "application/json", "application/xml;q=0.8" }); + Assert.Equal("application/json;q=1", mimeTypes.First(), StringComparer.OrdinalIgnoreCase); + Assert.Equal("application/xml;q=0.8", mimeTypes.Last(), StringComparer.OrdinalIgnoreCase); + Assert.DoesNotContain("application/atom+xml", mimeTypes); + Assert.Null(mimeTypes.GetPriority("application/atom+xml")); + Assert.Equal(1, mimeTypes.GetPriority("application/json")); + Assert.Equal(0.8f, mimeTypes.GetPriority("application/xml")); + } + [Fact] + public void DoesNotAddDuplicates() + { + Assert.Throws(() => new StructuredMimeTypesCollection(new[] { "application/json", "application/json;q=0.8" })); + } + [Fact] + public void ClearsEntries() + { + var mimeTypes = new StructuredMimeTypesCollection(new[] { "application/json", "application/xml;q=0.8" }); + Assert.Equal(2, mimeTypes.Count); + mimeTypes.Clear(); + Assert.Empty(mimeTypes); + } + [Theory] + [InlineData("application/json, application/xml, application/yaml", "application/json", "application/json;q=1")] + [InlineData("application/json, application/xml, application/yaml", "application/json,text/plain", "application/json;q=1")] + [InlineData("application/json, application/xml, application/yaml;q=0.8", "application/json,text/plain,application/yaml", "application/json;q=1,application/yaml;q=0.8")] + public void MatchesAccept(string configuredTypes, string declaredTypes, string expectedTypes) + { + var mimeTypes = new StructuredMimeTypesCollection(configuredTypes.Split(',').Select(static x => x.Trim())); + var result = mimeTypes.GetAcceptedTypes(declaredTypes.Split(',').Select(static x => x.Trim())); + var deserializedExpectedTypes = expectedTypes.Split(',').Select(static x => x.Trim()); + foreach (var expectedType in deserializedExpectedTypes) + Assert.Contains(expectedType, result); + } + [Theory] + [InlineData("application/json, application/xml;q=0.9, application/yaml;q=0.8", "application/json", "application/json")] + [InlineData("application/json, application/xml;q=0.9, application/yaml;q=0.8", "application/json,text/plain", "application/json")] + [InlineData("application/json, application/xml;q=0.9, application/yaml;q=0.8", "application/json,text/plain,application/yaml", "application/json")] + public void MatchesContentType(string configuredTypes, string declaredTypes, string expectedTypes) + { + var mimeTypes = new StructuredMimeTypesCollection(configuredTypes.Split(',').Select(static x => x.Trim())); + var result = mimeTypes.GetContentTypes(declaredTypes.Split(',').Select(static x => x.Trim())); + var deserializedExpectedTypes = expectedTypes.Split(',').Select(static x => x.Trim()); + foreach (var expectedType in deserializedExpectedTypes) + Assert.Contains(expectedType, result); + } +} diff --git a/tests/Kiota.Builder.Tests/ContentTypeMappingTests.cs b/tests/Kiota.Builder.Tests/ContentTypeMappingTests.cs new file mode 100644 index 0000000000..f77456ac18 --- /dev/null +++ b/tests/Kiota.Builder.Tests/ContentTypeMappingTests.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Configuration; +using Kiota.Builder.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using Moq; +using Xunit; + +namespace Kiota.Builder.Tests; + +public sealed class ContentTypeMappingTests : IDisposable +{ + private readonly List _tempFiles = new(); + public void Dispose() + { + foreach (var file in _tempFiles) + File.Delete(file); + _httpClient.Dispose(); + GC.SuppressFinalize(this); + } + private readonly HttpClient _httpClient = new(); + + [InlineData("application/json", "206", true, "default", "myobject")] + [InlineData("application/json", "206", false, "default", "binary")] + [InlineData("application/json", "205", true, "default", "void")] + [InlineData("application/json", "205", false, "default", "void")] + [InlineData("application/json", "204", true, "default", "void")] + [InlineData("application/json", "204", false, "default", "void")] + [InlineData("application/json", "203", true, "default", "myobject")] + [InlineData("application/json", "203", false, "default", "binary")] + [InlineData("application/json", "202", true, "default", "myobject")] + [InlineData("application/json", "202", false, "default", "void")] + [InlineData("application/json", "201", true, "default", "myobject")] + [InlineData("application/json", "201", false, "default", "void")] + [InlineData("application/json", "200", true, "default", "myobject")] + [InlineData("application/json", "200", false, "default", "binary")] + [InlineData("application/json", "2XX", true, "default", "myobject")] + [InlineData("application/json", "2XX", false, "default", "binary")] + [InlineData("application/xml", "204", true, "default", "void")] + [InlineData("application/xml", "204", false, "default", "void")] + [InlineData("application/xml", "200", true, "default", "binary")] // MyObject when we support xml deserialization + [InlineData("application/xml", "200", false, "default", "binary")] + [InlineData("text/xml", "204", true, "default", "void")] + [InlineData("text/xml", "204", false, "default", "void")] + [InlineData("text/xml", "200", true, "default", "binary")] // MyObject when we support xml deserialization + [InlineData("text/xml", "200", false, "default", "binary")] + [InlineData("text/yaml", "204", true, "default", "void")] + [InlineData("text/yaml", "204", false, "default", "void")] + [InlineData("text/yaml", "200", true, "default", "binary")] // MyObject when we support xml deserialization + [InlineData("text/yaml", "200", false, "default", "binary")] + [InlineData("application/octet-stream", "204", true, "default", "void")] + [InlineData("application/octet-stream", "204", false, "default", "void")] + [InlineData("application/octet-stream", "200", true, "default", "binary")] + [InlineData("application/octet-stream", "200", false, "default", "binary")] + [InlineData("text/html", "204", true, "default", "void")] + [InlineData("text/html", "204", false, "default", "void")] + [InlineData("text/html", "200", true, "default", "binary")] + [InlineData("text/html", "200", false, "default", "binary")] + [InlineData("*/*", "204", true, "default", "void")] + [InlineData("*/*", "204", false, "default", "void")] + [InlineData("*/*", "200", true, "default", "binary")] + [InlineData("*/*", "200", false, "default", "binary")] + [InlineData("text/plain", "204", true, "default", "void")] + [InlineData("text/plain", "204", false, "default", "void")] + [InlineData("text/plain", "200", true, "default", "myobject")] + [InlineData("text/plain", "200", false, "default", "string")] + [InlineData("text/plain", "204", true, "application/json", "void")] + [InlineData("text/plain", "204", false, "application/json", "void")] + [InlineData("text/plain", "200", true, "application/json", "string")] + [InlineData("text/plain", "200", false, "application/json", "string")] + [InlineData("text/yaml", "204", true, "application/json", "void")] + [InlineData("text/yaml", "204", false, "application/json", "void")] + [InlineData("text/yaml", "200", true, "application/json", "binary")] + [InlineData("text/yaml", "200", false, "application/json", "binary")] + [Theory] + public void GeneratesTheRightReturnTypeBasedOnContentAndStatus(string contentType, string statusCode, bool addModel, string acceptedContentType, string returnType) + { + var myObjectSchema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string", + } + } + }, + Reference = new OpenApiReference + { + Id = "myobject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + }; + var document = new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["answer"] = new OpenApiPathItem + { + Operations = { + [OperationType.Get] = new OpenApiOperation + { + Responses = new OpenApiResponses + { + [statusCode] = new OpenApiResponse { + Content = { + [contentType] = new OpenApiMediaType { + Schema = addModel ? myObjectSchema : null + } + } + }, + } + } + } + } + }, + Components = new() + { + Schemas = new Dictionary { + { + "myobject", myObjectSchema + } + } + } + }; + var mockLogger = new Mock>(); + var builder = new KiotaBuilder( + mockLogger.Object, + new GenerationConfiguration + { + ClientClassName = "TestClient", + ClientNamespaceName = "TestSdk", + ApiRootUrl = "https://localhost", + StructuredMimeTypes = acceptedContentType.Equals("default", StringComparison.OrdinalIgnoreCase) ? + new GenerationConfiguration().StructuredMimeTypes : + new() { acceptedContentType } + }, _httpClient); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + var rbNS = codeModel.FindNamespaceByName("TestSdk.Answer"); + Assert.NotNull(rbNS); + var rbClass = rbNS.Classes.FirstOrDefault(x => x.IsOfKind(CodeClassKind.RequestBuilder)); + Assert.NotNull(rbClass); + var executor = rbClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); + Assert.NotNull(executor); + Assert.Equal(returnType, executor.ReturnType.Name); + } + [InlineData("application/json", true, "default", "myobject")] + [InlineData("application/json", false, "default", "binary")] + [InlineData("application/xml", false, "default", "binary")] + [InlineData("application/xml", true, "default", "binary")] //MyObject when we support it + [InlineData("text/xml", false, "default", "binary")] + [InlineData("text/xml", true, "default", "binary")] //MyObject when we support it + [InlineData("text/yaml", false, "default", "binary")] + [InlineData("text/yaml", true, "default", "binary")] //MyObject when we support it + [InlineData("application/octet-stream", true, "default", "binary")] + [InlineData("application/octet-stream", false, "default", "binary")] + [InlineData("text/html", true, "default", "binary")] + [InlineData("text/html", false, "default", "binary")] + [InlineData("*/*", true, "default", "binary")] + [InlineData("*/*", false, "default", "binary")] + [InlineData("text/plain", false, "default", "binary")] + [InlineData("text/plain", true, "default", "myobject")] + [InlineData("text/plain", true, "application/json", "binary")] + [InlineData("text/plain", false, "application/json", "binary")] + [InlineData("text/yaml", true, "application/json", "binary")] + [InlineData("text/yaml", false, "application/json", "binary")] + [Theory] + public void GeneratesTheRightParameterTypeBasedOnContentAndStatus(string contentType, bool addModel, string acceptedContentType, string parameterType) + { + var myObjectSchema = new OpenApiSchema + { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string", + } + } + }, + Reference = new OpenApiReference + { + Id = "myobject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + }; + var document = new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["answer"] = new OpenApiPathItem + { + Operations = { + [OperationType.Post] = new OpenApiOperation + { + RequestBody = new OpenApiRequestBody { + Content = { + [contentType] = new OpenApiMediaType { + Schema = addModel ? myObjectSchema : null + } + } + }, + Responses = new OpenApiResponses + { + ["204"] = new OpenApiResponse(), + } + } + } + } + }, + Components = new() + { + Schemas = new Dictionary { + { + "myobject", myObjectSchema + } + } + } + }; + var mockLogger = new Mock>(); + var builder = new KiotaBuilder( + mockLogger.Object, + new GenerationConfiguration + { + ClientClassName = "TestClient", + ClientNamespaceName = "TestSdk", + ApiRootUrl = "https://localhost", + StructuredMimeTypes = acceptedContentType.Equals("default", StringComparison.OrdinalIgnoreCase) ? + new GenerationConfiguration().StructuredMimeTypes : + new() { acceptedContentType } + }, _httpClient); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + var rbNS = codeModel.FindNamespaceByName("TestSdk.Answer"); + Assert.NotNull(rbNS); + var rbClass = rbNS.Classes.FirstOrDefault(x => x.IsOfKind(CodeClassKind.RequestBuilder)); + Assert.NotNull(rbClass); + var executor = rbClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); + Assert.NotNull(executor); + Assert.Equal(parameterType, executor.Parameters.OfKind(CodeParameterKind.RequestBody).Type.Name); + } + [Theory] + [InlineData("application/json, text/plain", "application/json", "application/json;q=1", "text/plain;q=1")] + [InlineData("application/json, text/plain, application/yaml", "application/json;q=0.8,application/yaml;q=1", "application/yaml;q=1,application/json;q=0.8", "text/plain;q=1")] + [InlineData("*/*", "application/json;q=0.8", "*/*", "application/json;q=0.8")] + [InlineData("application/json, */*", "application/json;q=0.8", "application/json;q=0.8", "*/*")] + [InlineData("application/png, application/jpg", "application/json;q=0.8", "application/png, application/jpg", "application/json;q=0.8")] + public void GeneratesTheRightAcceptHeaderBasedOnContentAndStatus(string contentMediaTypes, string structuredMimeTypes, string expectedAcceptHeader, string unexpectedMimeTypes) + { + var document = new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["answer"] = new OpenApiPathItem + { + Operations = { + [OperationType.Get] = new OpenApiOperation + { + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse { + Content = contentMediaTypes.Split(',').Select(x => new {Key = x.Trim(), value = new OpenApiMediaType { + Schema = new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string", + } + } + }, + Reference = new OpenApiReference + { + Id = "myobject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + } + } + }).ToDictionary(x => x.Key, x => x.value) + }, + } + } + } + } + }, + Components = new() + { + Schemas = new Dictionary { + { + "myobject", new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string", + } + } + }, + Reference = new OpenApiReference + { + Id = "myobject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + } + } + } + } + }; + var mockLogger = new Mock>(); + var builder = new KiotaBuilder( + mockLogger.Object, + new GenerationConfiguration + { + ClientClassName = "TestClient", + ClientNamespaceName = "TestSdk", + ApiRootUrl = "https://localhost", + StructuredMimeTypes = new(structuredMimeTypes.Split(',').Select(x => x.Trim())) + }, _httpClient); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + var rbNS = codeModel.FindNamespaceByName("TestSdk.Answer"); + Assert.NotNull(rbNS); + var rbClass = rbNS.Classes.FirstOrDefault(static x => x.IsOfKind(CodeClassKind.RequestBuilder)); + Assert.NotNull(rbClass); + var generator = rbClass.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.RequestGenerator)); + Assert.NotNull(generator); + foreach (var header in expectedAcceptHeader.Split(',')) + Assert.Contains(header.Trim(), generator.AcceptedResponseTypes); + foreach (var header in unexpectedMimeTypes.Split(',')) + Assert.DoesNotContain(header.Trim(), generator.AcceptedResponseTypes); + } + [Theory] + [InlineData("application/json, text/plain", "application/json", "application/json", "text/plain")] + [InlineData("application/json, text/plain, application/yaml", "application/json;q=0.8,application/yaml;q=1", "application/yaml", "text/plain")] + [InlineData("*/*", "application/json;q=0.8", "", "application/json")] + [InlineData("application/json, */*", "application/json;q=0.8", "application/json", "*/*")] + [InlineData("application/png, application/jpg", "application/json;q=0.8", "", "application/json")] + public void GeneratesTheRightContentTypeHeaderBasedOnContentAndStatus(string contentMediaTypes, string structuredMimeTypes, string expectedContentTypeHeader, string unexpectedMimeTypes) + { + var document = new OpenApiDocument + { + Paths = new OpenApiPaths + { + ["answer"] = new OpenApiPathItem + { + Operations = { + [OperationType.Post] = new OpenApiOperation + { + RequestBody = new OpenApiRequestBody + { + Content = contentMediaTypes.Split(',').Select(x => new {Key = x.Trim(), value = new OpenApiMediaType { + Schema = new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string", + } + } + }, + Reference = new OpenApiReference + { + Id = "myobject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + } + } + }).ToDictionary(x => x.Key, x => x.value) + }, + } + } + } + }, + Components = new() + { + Schemas = new Dictionary { + { + "myobject", new OpenApiSchema { + Type = "object", + Properties = new Dictionary { + { + "id", new OpenApiSchema { + Type = "string", + } + } + }, + Reference = new OpenApiReference + { + Id = "myobject", + Type = ReferenceType.Schema + }, + UnresolvedReference = false + } + } + } + } + }; + var mockLogger = new Mock>(); + var builder = new KiotaBuilder( + mockLogger.Object, + new GenerationConfiguration + { + ClientClassName = "TestClient", + ClientNamespaceName = "TestSdk", + ApiRootUrl = "https://localhost", + StructuredMimeTypes = new(structuredMimeTypes.Split(',').Select(x => x.Trim())) + }, _httpClient); + var node = builder.CreateUriSpace(document); + var codeModel = builder.CreateSourceModel(node); + var rbNS = codeModel.FindNamespaceByName("TestSdk.Answer"); + Assert.NotNull(rbNS); + var rbClass = rbNS.Classes.FirstOrDefault(static x => x.IsOfKind(CodeClassKind.RequestBuilder)); + Assert.NotNull(rbClass); + var generator = rbClass.Methods.FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.RequestGenerator)); + Assert.NotNull(generator); + if (string.IsNullOrEmpty(expectedContentTypeHeader)) + { + Assert.Empty(generator.RequestBodyContentType); + Assert.NotNull(generator.Parameters.OfKind(CodeParameterKind.RequestBodyContentType)); + } + else + foreach (var header in expectedContentTypeHeader.Split(',')) + Assert.Contains(header.Trim(), generator.RequestBodyContentType); + foreach (var header in unexpectedMimeTypes.Split(',')) + Assert.DoesNotContain(header.Trim(), generator.RequestBodyContentType); + } +} diff --git a/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs index 892f710189..79114497b6 100644 --- a/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Extensions/OpenApiOperationExtensionsTests.cs @@ -58,8 +58,7 @@ public void GetsResponseSchema() public void Defensive() { var source = new Dictionary(); - Assert.Empty(source.GetValidSchemas(new HashSet { "application/json" })); - Assert.Throws(() => source.GetValidSchemas(new HashSet())); + Assert.Empty(source.GetValidSchemas(new() { "application/json" })); Assert.Throws(() => source.GetValidSchemas(null)); } } diff --git a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs index d69ee88d16..4775493de7 100644 --- a/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs +++ b/tests/Kiota.Builder.Tests/KiotaBuilderTests.cs @@ -817,8 +817,8 @@ await File.WriteAllTextAsync(tempFilePath, @"openapi: 3.0.1 Assert.NotNull(treeNode); Assert.Equal("GraphClient", configuration.ClientClassName); Assert.Equal("Microsoft.Graph", configuration.ClientNamespaceName); - Assert.Contains("application/json", configuration.StructuredMimeTypes); - Assert.Contains("application/xml", configuration.StructuredMimeTypes); + Assert.Contains("application/json;q=1", configuration.StructuredMimeTypes); + Assert.Contains("application/xml;q=1", configuration.StructuredMimeTypes); _tempFiles.Add(tempFilePath); } [Fact] @@ -4898,132 +4898,6 @@ public void ModelsUseDescriptionWhenAvailable(bool excludeBackwardCompatible) Assert.Equal("some path item description", responseProperty.Documentation.Description); } - [InlineData("application/json", "206", true, "default", "myobject")] - [InlineData("application/json", "206", false, "default", "binary")] - [InlineData("application/json", "205", true, "default", "void")] - [InlineData("application/json", "205", false, "default", "void")] - [InlineData("application/json", "204", true, "default", "void")] - [InlineData("application/json", "204", false, "default", "void")] - [InlineData("application/json", "203", true, "default", "myobject")] - [InlineData("application/json", "203", false, "default", "binary")] - [InlineData("application/json", "202", true, "default", "myobject")] - [InlineData("application/json", "202", false, "default", "void")] - [InlineData("application/json", "201", true, "default", "myobject")] - [InlineData("application/json", "201", false, "default", "void")] - [InlineData("application/json", "200", true, "default", "myobject")] - [InlineData("application/json", "200", false, "default", "binary")] - [InlineData("application/json", "2XX", true, "default", "myobject")] - [InlineData("application/json", "2XX", false, "default", "binary")] - [InlineData("application/xml", "204", true, "default", "void")] - [InlineData("application/xml", "204", false, "default", "void")] - [InlineData("application/xml", "200", true, "default", "binary")] // MyObject when we support xml deserialization - [InlineData("application/xml", "200", false, "default", "binary")] - [InlineData("text/xml", "204", true, "default", "void")] - [InlineData("text/xml", "204", false, "default", "void")] - [InlineData("text/xml", "200", true, "default", "binary")] // MyObject when we support xml deserialization - [InlineData("text/xml", "200", false, "default", "binary")] - [InlineData("text/yaml", "204", true, "default", "void")] - [InlineData("text/yaml", "204", false, "default", "void")] - [InlineData("text/yaml", "200", true, "default", "binary")] // MyObject when we support xml deserialization - [InlineData("text/yaml", "200", false, "default", "binary")] - [InlineData("application/octet-stream", "204", true, "default", "void")] - [InlineData("application/octet-stream", "204", false, "default", "void")] - [InlineData("application/octet-stream", "200", true, "default", "binary")] - [InlineData("application/octet-stream", "200", false, "default", "binary")] - [InlineData("text/html", "204", true, "default", "void")] - [InlineData("text/html", "204", false, "default", "void")] - [InlineData("text/html", "200", true, "default", "binary")] - [InlineData("text/html", "200", false, "default", "binary")] - [InlineData("*/*", "204", true, "default", "void")] - [InlineData("*/*", "204", false, "default", "void")] - [InlineData("*/*", "200", true, "default", "binary")] - [InlineData("*/*", "200", false, "default", "binary")] - [InlineData("text/plain", "204", true, "default", "void")] - [InlineData("text/plain", "204", false, "default", "void")] - [InlineData("text/plain", "200", true, "default", "myobject")] - [InlineData("text/plain", "200", false, "default", "string")] - [InlineData("text/plain", "204", true, "application/json", "void")] - [InlineData("text/plain", "204", false, "application/json", "void")] - [InlineData("text/plain", "200", true, "application/json", "string")] - [InlineData("text/plain", "200", false, "application/json", "string")] - [InlineData("text/yaml", "204", true, "application/json", "void")] - [InlineData("text/yaml", "204", false, "application/json", "void")] - [InlineData("text/yaml", "200", true, "application/json", "binary")] - [InlineData("text/yaml", "200", false, "application/json", "binary")] - [Theory] - public void GeneratesTheRightReturnTypeBasedOnContentAndStatus(string contentType, string statusCode, bool addModel, string acceptedContentType, string returnType) - { - var myObjectSchema = new OpenApiSchema - { - Type = "object", - Properties = new Dictionary { - { - "id", new OpenApiSchema { - Type = "string", - } - } - }, - Reference = new OpenApiReference - { - Id = "myobject", - Type = ReferenceType.Schema - }, - UnresolvedReference = false - }; - var document = new OpenApiDocument - { - Paths = new OpenApiPaths - { - ["answer"] = new OpenApiPathItem - { - Operations = { - [OperationType.Get] = new OpenApiOperation - { - Responses = new OpenApiResponses - { - [statusCode] = new OpenApiResponse { - Content = { - [contentType] = new OpenApiMediaType { - Schema = addModel ? myObjectSchema : null - } - } - }, - } - } - } - } - }, - Components = new() - { - Schemas = new Dictionary { - { - "myobject", myObjectSchema - } - } - } - }; - var mockLogger = new Mock>(); - var builder = new KiotaBuilder( - mockLogger.Object, - new GenerationConfiguration - { - ClientClassName = "TestClient", - ClientNamespaceName = "TestSdk", - ApiRootUrl = "https://localhost", - StructuredMimeTypes = acceptedContentType.Equals("default", StringComparison.OrdinalIgnoreCase) ? - new GenerationConfiguration().StructuredMimeTypes : - new(StringComparer.OrdinalIgnoreCase) { acceptedContentType } - }, _httpClient); - var node = builder.CreateUriSpace(document); - var codeModel = builder.CreateSourceModel(node); - var rbNS = codeModel.FindNamespaceByName("TestSdk.Answer"); - Assert.NotNull(rbNS); - var rbClass = rbNS.Classes.FirstOrDefault(x => x.IsOfKind(CodeClassKind.RequestBuilder)); - Assert.NotNull(rbClass); - var executor = rbClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); - Assert.NotNull(executor); - Assert.Equal(returnType, executor.ReturnType.Name); - } [Fact] public void Considers200WithSchemaOver2XXWithSchema() { @@ -5259,101 +5133,6 @@ public void Considers204WithNoSchemaOver206WithNoSchema() Assert.NotNull(executor); Assert.Equal("void", executor.ReturnType.Name); } - [InlineData("application/json", true, "default", "myobject")] - [InlineData("application/json", false, "default", "binary")] - [InlineData("application/xml", false, "default", "binary")] - [InlineData("application/xml", true, "default", "binary")] //MyObject when we support it - [InlineData("text/xml", false, "default", "binary")] - [InlineData("text/xml", true, "default", "binary")] //MyObject when we support it - [InlineData("text/yaml", false, "default", "binary")] - [InlineData("text/yaml", true, "default", "binary")] //MyObject when we support it - [InlineData("application/octet-stream", true, "default", "binary")] - [InlineData("application/octet-stream", false, "default", "binary")] - [InlineData("text/html", true, "default", "binary")] - [InlineData("text/html", false, "default", "binary")] - [InlineData("*/*", true, "default", "binary")] - [InlineData("*/*", false, "default", "binary")] - [InlineData("text/plain", false, "default", "binary")] - [InlineData("text/plain", true, "default", "myobject")] - [InlineData("text/plain", true, "application/json", "binary")] - [InlineData("text/plain", false, "application/json", "binary")] - [InlineData("text/yaml", true, "application/json", "binary")] - [InlineData("text/yaml", false, "application/json", "binary")] - [Theory] - public void GeneratesTheRightParameterTypeBasedOnContentAndStatus(string contentType, bool addModel, string acceptedContentType, string parameterType) - { - var myObjectSchema = new OpenApiSchema - { - Type = "object", - Properties = new Dictionary { - { - "id", new OpenApiSchema { - Type = "string", - } - } - }, - Reference = new OpenApiReference - { - Id = "myobject", - Type = ReferenceType.Schema - }, - UnresolvedReference = false - }; - var document = new OpenApiDocument - { - Paths = new OpenApiPaths - { - ["answer"] = new OpenApiPathItem - { - Operations = { - [OperationType.Post] = new OpenApiOperation - { - RequestBody = new OpenApiRequestBody { - Content = { - [contentType] = new OpenApiMediaType { - Schema = addModel ? myObjectSchema : null - } - } - }, - Responses = new OpenApiResponses - { - ["204"] = new OpenApiResponse(), - } - } - } - } - }, - Components = new() - { - Schemas = new Dictionary { - { - "myobject", myObjectSchema - } - } - } - }; - var mockLogger = new Mock>(); - var builder = new KiotaBuilder( - mockLogger.Object, - new GenerationConfiguration - { - ClientClassName = "TestClient", - ClientNamespaceName = "TestSdk", - ApiRootUrl = "https://localhost", - StructuredMimeTypes = acceptedContentType.Equals("default", StringComparison.OrdinalIgnoreCase) ? - new GenerationConfiguration().StructuredMimeTypes : - new(StringComparer.OrdinalIgnoreCase) { acceptedContentType } - }, _httpClient); - var node = builder.CreateUriSpace(document); - var codeModel = builder.CreateSourceModel(node); - var rbNS = codeModel.FindNamespaceByName("TestSdk.Answer"); - Assert.NotNull(rbNS); - var rbClass = rbNS.Classes.FirstOrDefault(x => x.IsOfKind(CodeClassKind.RequestBuilder)); - Assert.NotNull(rbClass); - var executor = rbClass.Methods.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestExecutor)); - Assert.NotNull(executor); - Assert.Equal(parameterType, executor.Parameters.OfKind(CodeParameterKind.RequestBody).Type.Name); - } [Fact] public void DoesntGenerateVoidExecutorOnMixed204() { @@ -7046,7 +6825,7 @@ public async Task SupportsMultiPartFormAsRequestBody() Assert.NotNull(rbClass); var postMethod = rbClass.FindChildByName("Post", false); Assert.NotNull(postMethod); - var bodyParameter = postMethod.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.RequestBody)); + var bodyParameter = postMethod.Parameters.FirstOrDefault(static x => x.IsOfKind(CodeParameterKind.RequestBody)); Assert.NotNull(bodyParameter); Assert.Equal("MultipartBody", bodyParameter.Type.Name, StringComparer.OrdinalIgnoreCase); var addressClass = codeModel.FindChildByName("Address"); diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeElementComparerPythonTests.cs b/tests/Kiota.Builder.Tests/OrderComparers/CodeElementComparerPythonTests.cs similarity index 96% rename from tests/Kiota.Builder.Tests/CodeDOM/CodeElementComparerPythonTests.cs rename to tests/Kiota.Builder.Tests/OrderComparers/CodeElementComparerPythonTests.cs index 717306bb37..4c457e7448 100644 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeElementComparerPythonTests.cs +++ b/tests/Kiota.Builder.Tests/OrderComparers/CodeElementComparerPythonTests.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using Kiota.Builder.CodeDOM; +using Kiota.Builder.OrderComparers; using Xunit; -namespace Kiota.Builder.Tests.CodeDOM; +namespace Kiota.Builder.Tests.OrderComparers; public class CodeElementComparerPythonTests { [Fact] diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeElementComparerTests.cs b/tests/Kiota.Builder.Tests/OrderComparers/CodeElementComparerTests.cs similarity index 98% rename from tests/Kiota.Builder.Tests/CodeDOM/CodeElementComparerTests.cs rename to tests/Kiota.Builder.Tests/OrderComparers/CodeElementComparerTests.cs index efac713cdf..8f1e2a0570 100644 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeElementComparerTests.cs +++ b/tests/Kiota.Builder.Tests/OrderComparers/CodeElementComparerTests.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using Kiota.Builder.CodeDOM; +using Kiota.Builder.OrderComparers; using Xunit; -namespace Kiota.Builder.Tests.CodeDOM; +namespace Kiota.Builder.Tests.OrderComparers; public class CodeElementComparerTests { [Fact] diff --git a/tests/Kiota.Builder.Tests/CodeDOM/CodeParameterOrderComparerTests.cs b/tests/Kiota.Builder.Tests/OrderComparers/CodeParameterOrderComparerTests.cs similarity index 98% rename from tests/Kiota.Builder.Tests/CodeDOM/CodeParameterOrderComparerTests.cs rename to tests/Kiota.Builder.Tests/OrderComparers/CodeParameterOrderComparerTests.cs index fd1f3142a5..5329f1c003 100644 --- a/tests/Kiota.Builder.Tests/CodeDOM/CodeParameterOrderComparerTests.cs +++ b/tests/Kiota.Builder.Tests/OrderComparers/CodeParameterOrderComparerTests.cs @@ -2,6 +2,7 @@ using System.Linq; using Kiota.Builder.CodeDOM; +using Kiota.Builder.OrderComparers; using Kiota.Builder.Writers.Go; using Kiota.Builder.Writers.Python; @@ -9,7 +10,7 @@ using Xunit; -namespace Kiota.Builder.Tests.CodeDOM; +namespace Kiota.Builder.Tests.OrderComparers; public class CodeParameterOrderComparerTests { [Fact] diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs index d55e5dcfe3..16945ceff1 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs @@ -1021,6 +1021,56 @@ public void WritesRequestGeneratorBodyForScalarCollection() AssertExtensions.CurlyBracesAreClosed(result, 1); } [Fact] + public void WritesRequestGeneratorBodyKnownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new CSharpConventionService().StreamTypeName, + IsExternal = true, + }; + method.RequestBodyContentType = "application/json"; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("SetStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + [Fact] + public void WritesRequestGeneratorBodyUnknownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new CSharpConventionService().StreamTypeName, + IsExternal = true, + }; + method.AddParameter(new CodeParameter + { + Name = "requestContentType", + Type = new CodeType() + { + Name = "string", + IsExternal = true, + }, + Kind = CodeParameterKind.RequestBodyContentType, + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("SetStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains(", requestContentType", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result, 1); + } + [Fact] public void WritesInheritedDeSerializerBody() { setup(true); diff --git a/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs index 117d50adf9..769f9555d0 100644 --- a/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Go/CodeMethodWriterTests.cs @@ -1293,6 +1293,56 @@ public async Task WritesRequestGeneratorBodyForParsableCollection() AssertExtensions.CurlyBracesAreClosed(result); } [Fact] + public void WritesRequestGeneratorBodyKnownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(useComplexTypeForBody: false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new GoConventionService().StreamTypeName, + IsExternal = true, + }; + method.RequestBodyContentType = "application/json"; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("SetStreamContentAndContentType", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyUnknownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(useComplexTypeForBody: false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new GoConventionService().StreamTypeName, + IsExternal = true, + }; + method.AddParameter(new CodeParameter + { + Name = "requestContentType", + Type = new CodeType() + { + Name = "string", + IsExternal = true, + }, + Kind = CodeParameterKind.RequestBodyContentType, + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("SetStreamContentAndContentType", result, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains(", requestContentType", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesInheritedDeSerializerBody() { setup(true); diff --git a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs index b50fdc0d87..7e4fcc9a16 100644 --- a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs @@ -1328,6 +1328,56 @@ public void WritesIntersectionDeSerializerBody() AssertExtensions.CurlyBracesAreClosed(result); } [Fact] + public void WritesRequestGeneratorBodyKnownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new JavaConventionService().StreamTypeName, + IsExternal = true, + }; + method.RequestBodyContentType = "application/json"; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyUnknownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new JavaConventionService().StreamTypeName, + IsExternal = true, + }; + method.AddParameter(new CodeParameter + { + Name = "requestContentType", + Type = new CodeType() + { + Name = "string", + IsExternal = true, + }, + Kind = CodeParameterKind.RequestBodyContentType, + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains(", requestContentType", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesInheritedDeSerializerBody() { setup(true); diff --git a/tests/Kiota.Builder.Tests/Writers/Php/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Php/CodeMethodWriterTests.cs index 6650e1a924..c0f8cd3783 100644 --- a/tests/Kiota.Builder.Tests/Writers/Php/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Php/CodeMethodWriterTests.cs @@ -941,6 +941,56 @@ public void WritesUnionDeSerializerBody() AssertExtensions.CurlyBracesAreClosed(result); } [Fact] + public void WritesRequestGeneratorBodyKnownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new PhpConventionService().StreamTypeName, + IsExternal = true, + }; + method.RequestBodyContentType = "application/json"; + languageWriter.Write(method); + var result = stringWriter.ToString(); + Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyUnknownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new PhpConventionService().StreamTypeName, + IsExternal = true, + }; + method.AddParameter(new CodeParameter + { + Name = "requestContentType", + Type = new CodeType() + { + Name = "string", + IsExternal = true, + }, + Kind = CodeParameterKind.RequestBodyContentType, + }); + languageWriter.Write(method); + var result = stringWriter.ToString(); + Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains(", $requestContentType", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesIntersectionDeSerializerBody() { setup(); diff --git a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs index a17efa9aab..82ca6a77cc 100644 --- a/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Python/CodeMethodWriterTests.cs @@ -707,6 +707,56 @@ public void WritesRequestGeneratorBodyForParsable() Assert.Contains("return request_info", result); } [Fact] + public void WritesRequestGeneratorBodyKnownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new PythonConventionService().StreamTypeName, + IsExternal = true, + }; + method.RequestBodyContentType = "application/json"; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("set_stream_content", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyUnknownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new PythonConventionService().StreamTypeName, + IsExternal = true, + }; + method.AddParameter(new CodeParameter + { + Name = "request_content_type", + Type = new CodeType() + { + Name = "string", + IsExternal = true, + }, + Kind = CodeParameterKind.RequestBodyContentType, + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("set_stream_content", result, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains(", request_content_type", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesInheritedDeSerializerBody() { setup(true); diff --git a/tests/Kiota.Builder.Tests/Writers/Ruby/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Ruby/CodeMethodWriterTests.cs index f08ac02348..0867495221 100644 --- a/tests/Kiota.Builder.Tests/Writers/Ruby/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Ruby/CodeMethodWriterTests.cs @@ -508,7 +508,7 @@ public void WritesRequestGeneratorBody() setup(); method.Kind = CodeMethodKind.RequestGenerator; method.HttpMethod = HttpMethod.Get; - method.AcceptedResponseTypes = new() { "application/json" }; + method.AcceptedResponseTypes.Add("application/json"); AddRequestProperties(); AddRequestBodyParameters(); writer.Write(method); @@ -525,6 +525,56 @@ public void WritesRequestGeneratorBody() Assert.Contains("return request_info", result); } [Fact] + public void WritesRequestGeneratorBodyKnownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new RubyConventionService().StreamTypeName, + IsExternal = true, + }; + method.RequestBodyContentType = "application/json"; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("set_stream_content", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyUnknownRequestBodyType() + { + setup(); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new RubyConventionService().StreamTypeName, + IsExternal = true, + }; + method.AddParameter(new CodeParameter + { + Name = "request_content_type", + Type = new CodeType() + { + Name = "string", + IsExternal = true, + }, + Kind = CodeParameterKind.RequestBodyContentType, + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("set_stream_content", result, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains(", request_content_type", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] public void WritesInheritedDeSerializerBody() { setup(true); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs index b166f292b0..d6efa5487d 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeFileWriterTests.cs @@ -5,6 +5,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; +using Kiota.Builder.OrderComparers; using Kiota.Builder.Refiners; using Kiota.Builder.Writers; using Xunit; diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index 47babf4fca..2d1dcf134d 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -308,6 +308,62 @@ public async Task WritesRequestGeneratorBodyForParsable() Assert.Contains("return requestInfo;", result); AssertExtensions.CurlyBracesAreClosed(result); } + [Fact] + public void WritesRequestGeneratorBodyKnownRequestBodyType() + { + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + AddRequestProperties(); + AddRequestBodyParameters(true); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new TypeScriptConventionService().StreamTypeName, + IsExternal = true, + }; + method.RequestBodyContentType = "application/json"; + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains("application/json", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } + [Fact] + public void WritesRequestGeneratorBodyUnknownRequestBodyType() + { + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Get; + AddRequestProperties(); + AddRequestBodyParameters(true); + method.Kind = CodeMethodKind.RequestGenerator; + method.HttpMethod = HttpMethod.Post; + AddRequestProperties(); + AddRequestBodyParameters(false); + method.Parameters.OfKind(CodeParameterKind.RequestBody).Type = new CodeType + { + Name = new TypeScriptConventionService().StreamTypeName, + IsExternal = true, + }; + method.AddParameter(new CodeParameter + { + Name = "requestContentType", + Type = new CodeType() + { + Name = "string", + IsExternal = true, + }, + Kind = CodeParameterKind.RequestBodyContentType, + }); + writer.Write(method); + var result = tw.ToString(); + Assert.Contains("setStreamContent", result, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("application/json", result, StringComparison.OrdinalIgnoreCase); + Assert.Contains(", requestContentType", result, StringComparison.OrdinalIgnoreCase); + AssertExtensions.CurlyBracesAreClosed(result); + } [Fact] public void WritesMethodAsyncDescription()