Skip to content

Commit

Permalink
Merge pull request #141 from microsoft/feature/serialization-helpers
Browse files Browse the repository at this point in the history
feature/serialization helpers
  • Loading branch information
baywet authored Nov 1, 2023
2 parents 06f7a4f + 7a0c444 commit 9b60161
Show file tree
Hide file tree
Showing 26 changed files with 689 additions and 83 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.6.0] - 2023-10-31

### Added

- Added helper methods to facilitate serialization and deserialization of models. [microsoft/kiota#3406](https://github.com/microsoft/kiota/issues/3406)

## [1.5.0] - 2023-10-19

### Added
Expand Down
14 changes: 7 additions & 7 deletions Microsoft.Kiota.Abstractions.Tests/ApiClientBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ namespace Microsoft.Kiota.Abstractions.Tests
public class ApiClientBuilderTests
{
private const string StreamContentType = "application/octet-stream";

[Fact]
public void EnableBackingStoreForSerializationWriterFactory()
{
// Arrange
var serializationFactoryRegistry = new SerializationWriterFactoryRegistry();
var mockSerializationWriterFactory = new Mock<ISerializationWriterFactory>();
serializationFactoryRegistry.ContentTypeAssociatedFactories.TryAdd(StreamContentType, mockSerializationWriterFactory.Object);

Assert.IsNotType<BackingStoreSerializationWriterProxyFactory>(serializationFactoryRegistry.ContentTypeAssociatedFactories[StreamContentType]);

// Act
ApiClientBuilder.EnableBackingStoreForSerializationWriterFactory(serializationFactoryRegistry);

// Assert the type has changed due to backing store enabling
Assert.IsType<BackingStoreSerializationWriterProxyFactory>(serializationFactoryRegistry.ContentTypeAssociatedFactories[StreamContentType]);
}
Expand Down Expand Up @@ -52,12 +52,12 @@ public void EnableBackingStoreForParseNodeFactory()
var parseNodeRegistry = new ParseNodeFactoryRegistry();
var mockParseNodeFactory = new Mock<IParseNodeFactory>();
parseNodeRegistry.ContentTypeAssociatedFactories.TryAdd(StreamContentType, mockParseNodeFactory.Object);

Assert.IsNotType<BackingStoreParseNodeFactory>(parseNodeRegistry.ContentTypeAssociatedFactories[StreamContentType]);

// Act
ApiClientBuilder.EnableBackingStoreForParseNodeFactory(parseNodeRegistry);

// Assert the type has changed due to backing store enabling
Assert.IsType<BackingStoreParseNodeFactory>(parseNodeRegistry.ContentTypeAssociatedFactories[StreamContentType]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,47 @@

namespace Microsoft.Kiota.Abstractions.Tests;

public class ApiKeyAuthenticationProviderTests {
public class ApiKeyAuthenticationProviderTests
{
[Fact]
public void DefensiveProgramming() {
public void DefensiveProgramming()
{
Assert.Throws<ArgumentNullException>(() => new ApiKeyAuthenticationProvider(null, "param", ApiKeyAuthenticationProvider.KeyLocation.Header));
Assert.Throws<ArgumentNullException>(() => new ApiKeyAuthenticationProvider("key", null, ApiKeyAuthenticationProvider.KeyLocation.Header));

var value = new ApiKeyAuthenticationProvider("key", "param", ApiKeyAuthenticationProvider.KeyLocation.Header);
Assert.ThrowsAsync<ArgumentNullException>(() => value.AuthenticateRequestAsync(null));
}
[Fact]
public async Task AddsInHeader() {
public async Task AddsInHeader()
{
var value = new ApiKeyAuthenticationProvider("key", "param", ApiKeyAuthenticationProvider.KeyLocation.Header);
var request = new RequestInformation {
var request = new RequestInformation
{
UrlTemplate = "https://localhost{?param1}",
};
await value.AuthenticateRequestAsync(request);
Assert.False(request.URI.ToString().EndsWith("param=key", StringComparison.OrdinalIgnoreCase));
Assert.Contains("param", request.Headers.Keys);
}
[Fact]
public async Task AddsInQueryParameters() {
public async Task AddsInQueryParameters()
{
var value = new ApiKeyAuthenticationProvider("key", "param", ApiKeyAuthenticationProvider.KeyLocation.QueryParameter);
var request = new RequestInformation {
var request = new RequestInformation
{
UrlTemplate = "https://localhost{?param1}",
};
await value.AuthenticateRequestAsync(request);
Assert.EndsWith("?param=key", request.URI.ToString());
Assert.DoesNotContain("param", request.Headers.Keys);
}
[Fact]
public async Task AddsInQueryParametersWithOtherParameters() {
public async Task AddsInQueryParametersWithOtherParameters()
{
var value = new ApiKeyAuthenticationProvider("key", "param", ApiKeyAuthenticationProvider.KeyLocation.QueryParameter);
var request = new RequestInformation {
var request = new RequestInformation
{
UrlTemplate = "https://localhost{?param1}",
};
request.QueryParameters.Add("param1", "value1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ public async Task BaseBearerTokenAuthenticationProviderSetsBearerHeader()
}

[Theory]
[InlineData("https://graph.microsoft.com",true)]// PASS
[InlineData("https://graph.microsoft.us/v1.0/me",true)]// PASS as we don't look at the path segment
[InlineData("https://test.microsoft.com",false)]// Fail
[InlineData("https://grAph.MicrosofT.com",true)] // PASS since we don't care about case
[InlineData("https://developer.microsoft.com",false)] // Failed
[InlineData("https://graph.microsoft.com", true)]// PASS
[InlineData("https://graph.microsoft.us/v1.0/me", true)]// PASS as we don't look at the path segment
[InlineData("https://test.microsoft.com", false)]// Fail
[InlineData("https://grAph.MicrosofT.com", true)] // PASS since we don't care about case
[InlineData("https://developer.microsoft.com", false)] // Failed
public void AllowedHostValidatorValidatesUrls(string urlToTest, bool expectedResult)
{
// Test through the constructor
// Arrange
var allowList = new[] { "graph.microsoft.com", "graph.microsoft.us"};
var allowList = new[] { "graph.microsoft.com", "graph.microsoft.us" };
var validator = new AllowedHostsValidator(allowList);

// Act
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions.Tests.Mocks;
using Moq;
using Xunit;

namespace Microsoft.Kiota.Abstractions.Tests.Serialization;

public class DeserializationHelpersTests
{
private const string _jsonContentType = "application/json";
[Fact]
public void DefensiveObject()
{
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.Deserialize<TestEntity>(null, (Stream)null, null));
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.Deserialize<TestEntity>(_jsonContentType, (Stream)null, null));
using var stream = new MemoryStream();
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.Deserialize<TestEntity>(_jsonContentType, stream, null));
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.Deserialize<TestEntity>(_jsonContentType, "", null));
}
[Fact]
public void DefensiveObjectCollection()
{
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.DeserializeCollection<TestEntity>(null, (Stream)null, null));
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.DeserializeCollection<TestEntity>(_jsonContentType, (Stream)null, null));
using var stream = new MemoryStream();
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.DeserializeCollection<TestEntity>(_jsonContentType, stream, null));
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.DeserializeCollection<TestEntity>(_jsonContentType, "", null));
}
[Fact]
public void DeserializesObjectWithoutReflection()
{
var strValue = "{'id':'123'}";
var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetObjectValue(It.IsAny<ParsableFactory<TestEntity>>())).Returns(new TestEntity()
{
Id = "123"
});
var mockJsonParseNodeFactory = new Mock<IParseNodeFactory>();
mockJsonParseNodeFactory.Setup(x => x.GetRootParseNode(It.IsAny<string>(), It.IsAny<Stream>())).Returns(mockParseNode.Object);
mockJsonParseNodeFactory.Setup(x => x.ValidContentType).Returns(_jsonContentType);
ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[_jsonContentType] = mockJsonParseNodeFactory.Object;

var result = KiotaSerializer.Deserialize(_jsonContentType, strValue, TestEntity.CreateFromDiscriminatorValue);

Assert.NotNull(result);
}
[Fact]
public void DeserializesObjectWithReflection()
{
var strValue = "{'id':'123'}";
var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetObjectValue(It.IsAny<ParsableFactory<TestEntity>>())).Returns(new TestEntity()
{
Id = "123"
});
var mockJsonParseNodeFactory = new Mock<IParseNodeFactory>();
mockJsonParseNodeFactory.Setup(x => x.GetRootParseNode(It.IsAny<string>(), It.IsAny<Stream>())).Returns(mockParseNode.Object);
mockJsonParseNodeFactory.Setup(x => x.ValidContentType).Returns(_jsonContentType);
ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[_jsonContentType] = mockJsonParseNodeFactory.Object;

var result = KiotaSerializer.Deserialize<TestEntity>(_jsonContentType, strValue);

Assert.NotNull(result);
}
[Fact]
public void DeserializesCollectionOfObject()
{
var strValue = "{'id':'123'}";
var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetCollectionOfObjectValues(It.IsAny<ParsableFactory<TestEntity>>())).Returns(new List<TestEntity> {
new TestEntity()
{
Id = "123"
}
});
var mockJsonParseNodeFactory = new Mock<IParseNodeFactory>();
mockJsonParseNodeFactory.Setup(x => x.GetRootParseNode(It.IsAny<string>(), It.IsAny<Stream>())).Returns(mockParseNode.Object);
mockJsonParseNodeFactory.Setup(x => x.ValidContentType).Returns(_jsonContentType);
ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[_jsonContentType] = mockJsonParseNodeFactory.Object;

var result = KiotaSerializer.DeserializeCollection(_jsonContentType, strValue, TestEntity.CreateFromDiscriminatorValue);

Assert.NotNull(result);
Assert.Single(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using Microsoft.Kiota.Abstractions.Serialization;

namespace Microsoft.Kiota.Abstractions.Tests.Serialization.Mocks
{
public class TestEntity : IParsable, IAdditionalDataHolder
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>Read-only.</summary>
public string Id { get; set; }
/// <summary>Read-only.</summary>
public TimeSpan? WorkDuration { get; set; }
/// <summary>Read-only.</summary>
public Date? BirthDay { get; set; }
/// <summary>Read-only.</summary>
public Time? StartWorkTime { get; set; }
/// <summary>Read-only.</summary>
public Time? EndWorkTime { get; set; }
/// <summary>Read-only.</summary>
public DateTimeOffset? CreatedDateTime { get; set; }
/// <summary>Read-only.</summary>
public string OfficeLocation { get; set; }
/// <summary>
/// Instantiates a new entity and sets the default values.
/// </summary>
public TestEntity()
{
AdditionalData = new Dictionary<string, object>();
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>> {
{"id", n => { Id = n.GetStringValue(); } },
{"createdDateTime", n => { CreatedDateTime = n.GetDateTimeOffsetValue(); } },
{"officeLocation", n => { OfficeLocation = n.GetStringValue(); } },
{"workDuration", n => { WorkDuration = n.GetTimeSpanValue(); } },
{"birthDay", n => { BirthDay = n.GetDateValue(); } },
{"startWorkTime", n => { StartWorkTime = n.GetTimeValue(); } },
{"endWorkTime", n => { EndWorkTime = n.GetTimeValue(); } },
};
}
/// <summary>
/// Serializes information the current object
/// <param name="writer">Serialization writer to use to serialize this model</param>
/// </summary>
public virtual void Serialize(ISerializationWriter writer)
{
_ = writer ?? throw new ArgumentNullException(nameof(writer));
writer.WriteStringValue("id", Id);
writer.WriteDateTimeOffsetValue("createdDateTime", CreatedDateTime);
writer.WriteStringValue("officeLocation", OfficeLocation);
writer.WriteTimeSpanValue("workDuration", WorkDuration);
writer.WriteDateValue("birthDay", BirthDay);
writer.WriteTimeValue("startWorkTime", StartWorkTime);
writer.WriteTimeValue("endWorkTime", EndWorkTime);
writer.WriteAdditionalData(AdditionalData);
}
public static TestEntity CreateFromDiscriminator(IParseNode parseNode)
{
var discriminatorValue = parseNode.GetChildNode("@odata.type")?.GetStringValue();
return discriminatorValue switch
{
"microsoft.graph.user" => new TestEntity(),
"microsoft.graph.group" => new TestEntity(),
_ => new TestEntity(),
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions.Tests.Mocks;
using Moq;
using Xunit;

namespace Microsoft.Kiota.Abstractions.Tests.Serialization;

public class SerializationHelpersTests
{
private const string _jsonContentType = "application/json";
[Fact]
public void DefensiveObject()
{
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.SerializeAsStream(null, (TestEntity)null));
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.SerializeAsStream(_jsonContentType, (TestEntity)null));
}
[Fact]
public void DefensiveObjectCollection()
{
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.SerializeAsStream(null, (IEnumerable<TestEntity>)null));
Assert.Throws<ArgumentNullException>(() => KiotaSerializer.SerializeAsStream(_jsonContentType, (IEnumerable<TestEntity>)null));
}
[Fact]
public void SerializesObject()
{
var mockSerializationWriter = new Mock<ISerializationWriter>();
mockSerializationWriter.Setup(x => x.GetSerializedContent()).Returns(new MemoryStream(UTF8Encoding.UTF8.GetBytes("{'id':'123'}")));
var mockSerializationWriterFactory = new Mock<ISerializationWriterFactory>();
mockSerializationWriterFactory.Setup(x => x.GetSerializationWriter(It.IsAny<string>())).Returns(mockSerializationWriter.Object);
SerializationWriterFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[_jsonContentType] = mockSerializationWriterFactory.Object;

var result = KiotaSerializer.SerializeAsString(_jsonContentType, new TestEntity()
{
Id = "123"
});

Assert.Equal("{'id':'123'}", result);

mockSerializationWriterFactory.Verify(x => x.GetSerializationWriter(It.IsAny<string>()), Times.Once);
mockSerializationWriter.Verify(x => x.WriteObjectValue(It.IsAny<string>(), It.IsAny<TestEntity>()), Times.Once);
mockSerializationWriter.Verify(x => x.GetSerializedContent(), Times.Once);
}
[Fact]
public void SerializesObjectCollection()
{
var mockSerializationWriter = new Mock<ISerializationWriter>();
mockSerializationWriter.Setup(x => x.GetSerializedContent()).Returns(new MemoryStream(UTF8Encoding.UTF8.GetBytes("[{'id':'123'}]")));
var mockSerializationWriterFactory = new Mock<ISerializationWriterFactory>();
mockSerializationWriterFactory.Setup(x => x.GetSerializationWriter(It.IsAny<string>())).Returns(mockSerializationWriter.Object);
SerializationWriterFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[_jsonContentType] = mockSerializationWriterFactory.Object;

var result = KiotaSerializer.SerializeAsString(_jsonContentType, new List<TestEntity> {
new()
{
Id = "123"
}
});

Assert.Equal("[{'id':'123'}]", result);

mockSerializationWriterFactory.Verify(x => x.GetSerializationWriter(It.IsAny<string>()), Times.Once);
mockSerializationWriter.Verify(x => x.WriteCollectionOfObjectValues(It.IsAny<string>(), It.IsAny<IEnumerable<TestEntity>>()), Times.Once);
mockSerializationWriter.Verify(x => x.GetSerializedContent(), Times.Once);
}
}
Loading

0 comments on commit 9b60161

Please sign in to comment.