Skip to content

Commit

Permalink
Merge pull request #43 from microsoft/bugfix/array-content
Browse files Browse the repository at this point in the history
- fixes a bug where request bodies that are collections of single items would not serialize properly
  • Loading branch information
baywet authored Oct 27, 2022
2 parents d84df6a + 70b6611 commit 64b2241
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 68 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

## [1.0.0-preview.16] - 2022-10-28

### Changed

- Fixed a bug where request bodies that are collections of single items would not serialize properly

## [1.0.0-preview.15] - 2022-10-18

### Added
Expand Down
78 changes: 78 additions & 0 deletions Microsoft.Kiota.Abstractions.Tests/RequestInformationTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions.Tests.Mocks;
using Moq;
using Xunit;

Expand Down Expand Up @@ -194,6 +196,82 @@ public void GetsAndSetsResponseHandlerByType()
// Assert we now have an option
Assert.NotNull(requestInfo.GetRequestOption<ResponseHandlerOption>());
}
[Fact]
public void SetsObjectContent() {
var requestAdapterMock = new Mock<IRequestAdapter>();
var serializationWriterMock = new Mock<ISerializationWriter>();
var serializationWriterFactoryMock = new Mock<ISerializationWriterFactory>();
serializationWriterFactoryMock.Setup(x => x.GetSerializationWriter(It.IsAny<string>())).Returns(serializationWriterMock.Object);
requestAdapterMock.SetupGet(x => x.SerializationWriterFactory).Returns(serializationWriterFactoryMock.Object);
var requestInfo = new RequestInformation
{
HttpMethod = Method.POST,
UrlTemplate = "{+baseurl}/users{?%24count}"
};

requestInfo.SetContentFromParsable(requestAdapterMock.Object, "application/json", new TestEntity());

// Assert we now have an option
serializationWriterMock.Verify(x => x.WriteObjectValue(It.IsAny<string>(), It.IsAny<IParsable>()), Times.Once);
serializationWriterMock.Verify(x => x.WriteCollectionOfObjectValues(It.IsAny<string>(), It.IsAny<IEnumerable<IParsable>>()), Times.Never);
}
[Fact]
public void SetsObjectCollectionContentSingleElement() {
var requestAdapterMock = new Mock<IRequestAdapter>();
var serializationWriterMock = new Mock<ISerializationWriter>();
var serializationWriterFactoryMock = new Mock<ISerializationWriterFactory>();
serializationWriterFactoryMock.Setup(x => x.GetSerializationWriter(It.IsAny<string>())).Returns(serializationWriterMock.Object);
requestAdapterMock.SetupGet(x => x.SerializationWriterFactory).Returns(serializationWriterFactoryMock.Object);
var requestInfo = new RequestInformation
{
HttpMethod = Method.POST,
UrlTemplate = "{+baseurl}/users{?%24count}"
};

requestInfo.SetContentFromParsable(requestAdapterMock.Object, "application/json", new [] {new TestEntity()});

// Assert we now have an option
serializationWriterMock.Verify(x => x.WriteObjectValue(It.IsAny<string>(), It.IsAny<IParsable>()), Times.Never);
serializationWriterMock.Verify(x => x.WriteCollectionOfObjectValues(It.IsAny<string>(), It.IsAny<IEnumerable<IParsable>>()), Times.Once);
}
[Fact]
public void SetsScalarContent() {
var requestAdapterMock = new Mock<IRequestAdapter>();
var serializationWriterMock = new Mock<ISerializationWriter>();
var serializationWriterFactoryMock = new Mock<ISerializationWriterFactory>();
serializationWriterFactoryMock.Setup(x => x.GetSerializationWriter(It.IsAny<string>())).Returns(serializationWriterMock.Object);
requestAdapterMock.SetupGet(x => x.SerializationWriterFactory).Returns(serializationWriterFactoryMock.Object);
var requestInfo = new RequestInformation
{
HttpMethod = Method.POST,
UrlTemplate = "{+baseurl}/users{?%24count}"
};

requestInfo.SetContentFromScalar(requestAdapterMock.Object, "application/json", "foo");

// Assert we now have an option
serializationWriterMock.Verify(x => x.WriteStringValue(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
serializationWriterMock.Verify(x => x.WriteCollectionOfPrimitiveValues(It.IsAny<string>(), It.IsAny<IEnumerable<string>>()), Times.Never);
}
[Fact]
public void SetsScalarCollectionContent() {
var requestAdapterMock = new Mock<IRequestAdapter>();
var serializationWriterMock = new Mock<ISerializationWriter>();
var serializationWriterFactoryMock = new Mock<ISerializationWriterFactory>();
serializationWriterFactoryMock.Setup(x => x.GetSerializationWriter(It.IsAny<string>())).Returns(serializationWriterMock.Object);
requestAdapterMock.SetupGet(x => x.SerializationWriterFactory).Returns(serializationWriterFactoryMock.Object);
var requestInfo = new RequestInformation
{
HttpMethod = Method.POST,
UrlTemplate = "{+baseurl}/users{?%24count}"
};

requestInfo.SetContentFromScalarCollection(requestAdapterMock.Object, "application/json", new[] {"foo"});

// Assert we now have an option
serializationWriterMock.Verify(x => x.WriteStringValue(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
serializationWriterMock.Verify(x => x.WriteCollectionOfPrimitiveValues(It.IsAny<string>(), It.IsAny<IEnumerable<string>>()), Times.Once);
}
}

/// <summary>The messages in a mailbox or folder. Read-only. Nullable.</summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.Kiota.Abstractions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<Deterministic>true</Deterministic>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>preview.15</VersionSuffix>
<VersionSuffix>preview.16</VersionSuffix>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
Expand All @@ -23,7 +23,7 @@
<!-- Enable this line once we go live to prevent breaking changes -->
<!-- <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion> -->
<PackageReleaseNotes>
- Adds an API key authentication provider
- Fixed a bug where request bodies that are collections of single items would not serialize properly
</PackageReleaseNotes>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
Expand Down
151 changes: 85 additions & 66 deletions src/RequestInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,18 +195,28 @@ public void SetStreamContent(Stream content)
/// <param name="items">The models to serialize.</param>
/// <param name="contentType">The content type to set.</param>
/// <typeparam name="T">The model type to serialize.</typeparam>
public void SetContentFromParsable<T>(IRequestAdapter requestAdapter, string contentType, params T[] items) where T : IParsable
public void SetContentFromParsable<T>(IRequestAdapter requestAdapter, string contentType, IEnumerable<T> items) where T : IParsable
{
using var activity = _activitySource?.StartActivity(nameof(SetContentFromParsable));
using var writer = getSerializationWriter(requestAdapter, contentType, items);
if(items.Count() == 1) {
setRequestType(items[0], activity);
writer.WriteObjectValue(null, items[0]);
} else {
if (items.Count() > 0)
setRequestType(items[0], activity);
writer.WriteCollectionOfObjectValues(null, items);
}
setRequestType(items.FirstOrDefault(static x => x != null), activity);
writer.WriteCollectionOfObjectValues(null, items);
Headers.Add(ContentTypeHeader, contentType);
Content = writer.GetSerializedContent();
}
/// <summary>
/// Sets the request body from a model with the specified content type.
/// </summary>
/// <param name="requestAdapter">The core service to get the serialization writer from.</param>
/// <param name="item">The model to serialize.</param>
/// <param name="contentType">The content type to set.</param>
/// <typeparam name="T">The model type to serialize.</typeparam>
public void SetContentFromParsable<T>(IRequestAdapter requestAdapter, string contentType, T item) where T : IParsable
{
using var activity = _activitySource?.StartActivity(nameof(SetContentFromParsable));
using var writer = getSerializationWriter(requestAdapter, contentType, item);
setRequestType(item, activity);
writer.WriteObjectValue(null, item);
Headers.Add(ContentTypeHeader, contentType);
Content = writer.GetSerializedContent();
}
Expand All @@ -215,11 +225,11 @@ private void setRequestType(object result, Activity activity) {
if (result == null) return;
activity.SetTag("com.microsoft.kiota.request.type", result.GetType().FullName);
}
private ISerializationWriter getSerializationWriter<T>(IRequestAdapter requestAdapter, string contentType, params T[] items)
private ISerializationWriter getSerializationWriter<T>(IRequestAdapter requestAdapter, string contentType, T item)
{
if(string.IsNullOrEmpty(contentType)) throw new ArgumentNullException(nameof(contentType));
if(requestAdapter == null) throw new ArgumentNullException(nameof(requestAdapter));
if(items == null || !items.Any()) throw new InvalidOperationException($"{nameof(items)} cannot be null or empty");
if(item == null) throw new InvalidOperationException($"{nameof(item)} cannot be null");
return requestAdapter.SerializationWriterFactory.GetSerializationWriter(contentType);
}
/// <summary>
Expand All @@ -229,64 +239,73 @@ private ISerializationWriter getSerializationWriter<T>(IRequestAdapter requestAd
/// <param name="items">The scalar values to serialize.</param>
/// <param name="contentType">The content type to set.</param>
/// <typeparam name="T">The model type to serialize.</typeparam>
public void SetContentFromScalar<T>(IRequestAdapter requestAdapter, string contentType, params T[] items)
public void SetContentFromScalarCollection<T>(IRequestAdapter requestAdapter, string contentType, IEnumerable<T> items)
{
using var activity = _activitySource?.StartActivity(nameof(SetContentFromScalar));
using var activity = _activitySource?.StartActivity(nameof(SetContentFromScalarCollection));
using var writer = getSerializationWriter(requestAdapter, contentType, items);
if(items.Count() == 1)
setRequestType(items.FirstOrDefault(static x => x != null), activity);
writer.WriteCollectionOfPrimitiveValues(null, items);
Headers.Add(ContentTypeHeader, contentType);
Content = writer.GetSerializedContent();
}
/// <summary>
/// Sets the request body from a scalar value with the specified content type.
/// </summary>
/// <param name="requestAdapter">The core service to get the serialization writer from.</param>
/// <param name="item">The scalar value to serialize.</param>
/// <param name="contentType">The content type to set.</param>
/// <typeparam name="T">The model type to serialize.</typeparam>
public void SetContentFromScalar<T>(IRequestAdapter requestAdapter, string contentType, T item)
{
using var activity = _activitySource?.StartActivity(nameof(SetContentFromScalar));
using var writer = getSerializationWriter(requestAdapter, contentType, item);
setRequestType(item, activity);
switch(item)
{
setRequestType(items[0], activity);
switch(items[0])
{
case string s:
writer.WriteStringValue(null, s);
break;
case bool b:
writer.WriteBoolValue(null, b);
break;
case byte b:
writer.WriteByteValue(null, b);
break;
case sbyte b:
writer.WriteSbyteValue(null, b);
break;
case int i:
writer.WriteIntValue(null, i);
break;
case float f:
writer.WriteFloatValue(null, f);
break;
case long l:
writer.WriteLongValue(null, l);
break;
case double d:
writer.WriteDoubleValue(null, d);
break;
case Guid g:
writer.WriteGuidValue(null, g);
break;
case DateTimeOffset dto:
writer.WriteDateTimeOffsetValue(null, dto);
break;
case TimeSpan timeSpan:
writer.WriteTimeSpanValue(null, timeSpan);
break;
case Date date:
writer.WriteDateValue(null, date);
break;
case Time time:
writer.WriteTimeValue(null, time);
break;
case null:
writer.WriteNullValue(null);
break;
default:
throw new InvalidOperationException($"error serialization data value with unknown type {items[0]?.GetType()}");
}
} else {
if (items.Count() > 0)
setRequestType(items[0], activity);
writer.WriteCollectionOfPrimitiveValues(null, items);
case string s:
writer.WriteStringValue(null, s);
break;
case bool b:
writer.WriteBoolValue(null, b);
break;
case byte b:
writer.WriteByteValue(null, b);
break;
case sbyte b:
writer.WriteSbyteValue(null, b);
break;
case int i:
writer.WriteIntValue(null, i);
break;
case float f:
writer.WriteFloatValue(null, f);
break;
case long l:
writer.WriteLongValue(null, l);
break;
case double d:
writer.WriteDoubleValue(null, d);
break;
case Guid g:
writer.WriteGuidValue(null, g);
break;
case DateTimeOffset dto:
writer.WriteDateTimeOffsetValue(null, dto);
break;
case TimeSpan timeSpan:
writer.WriteTimeSpanValue(null, timeSpan);
break;
case Date date:
writer.WriteDateValue(null, date);
break;
case Time time:
writer.WriteTimeValue(null, time);
break;
case null:
writer.WriteNullValue(null);
break;
default:
throw new InvalidOperationException($"error serialization data value with unknown type {item?.GetType()}");
}
Headers.Add(ContentTypeHeader, contentType);
Content = writer.GetSerializedContent();
Expand Down

0 comments on commit 64b2241

Please sign in to comment.