Skip to content

Commit

Permalink
Adding POCO converter for Timer extension (#2504)
Browse files Browse the repository at this point in the history
* Adding POCO converter for Timer extension

* Patch version bump and release notes.

* Clean up solution file

* Moved JsonSerializerOptions from Shared project to Timer project.

* Defining converter on `TimerTriggerAttribute` & removefd TimerExtensionStartup.

* Renamed test class name
  • Loading branch information
kshyju committed Jun 5, 2024
1 parent be0b8b3 commit 1197d60
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 3 deletions.
7 changes: 7 additions & 0 deletions DotNetWorker.sln
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.OpenTelemetry"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker.OpenTelemetry.Tests", "test\DotNetWorker.OpenTelemetry.Tests\DotNetWorker.OpenTelemetry.Tests.csproj", "{9AE6E00C-5E6F-4615-9C69-464E9B208E8C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Timer.Tests", "test\extensions\Worker.Extensions.Timer.Tests\Worker.Extensions.Timer.Tests.csproj", "{6947034E-C97F-4F78-940F-B6A398E23C9C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -374,6 +376,10 @@ Global
{9AE6E00C-5E6F-4615-9C69-464E9B208E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AE6E00C-5E6F-4615-9C69-464E9B208E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AE6E00C-5E6F-4615-9C69-464E9B208E8C}.Release|Any CPU.Build.0 = Release|Any CPU
{6947034E-C97F-4F78-940F-B6A398E23C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6947034E-C97F-4F78-940F-B6A398E23C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6947034E-C97F-4F78-940F-B6A398E23C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6947034E-C97F-4F78-940F-B6A398E23C9C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -439,6 +445,7 @@ Global
{198DA072-F94F-4585-A744-97B3DAC21686} = {B5821230-6E0A-4535-88A9-ED31B6F07596}
{82157559-DF60-496D-817F-84B34CFF76FD} = {083592CA-7DAB-44CE-8979-44FAFA46AEC3}
{9AE6E00C-5E6F-4615-9C69-464E9B208E8C} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378}
{6947034E-C97F-4F78-940F-B6A398E23C9C} = {AA4D318D-101B-49E7-A4EC-B34165AEDB33}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {497D2ED4-A13E-4BCA-8D29-F30CA7D0EA4A}
Expand Down
4 changes: 2 additions & 2 deletions extensions/Worker.Extensions.Timer/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
- My change description (#PR/#issue)
-->

### Microsoft.Azure.Functions.Worker.Extensions.Timer 4.3.0
### Microsoft.Azure.Functions.Worker.Extensions.Timer 4.3.1

- Add `BindingCapabilities` attribute to TimerTrigger to express function-level retry capabilities. (#1457)
- Adding a new converter for POCO serialization. (#2411)
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker.Converters;

namespace Microsoft.Azure.Functions.Worker.Extensions.Timer.Converters
{
internal sealed class TimerInfoConverter : IInputConverter
{
private static readonly JsonSerializerOptions _serializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};

public async ValueTask<ConversionResult> ConvertAsync(ConverterContext context)
{
if (context.TargetType != typeof(TimerInfo))
{
return ConversionResult.Unhandled();
}

byte[]? bytes = null;

if (context.Source is string sourceString)
{
bytes = Encoding.UTF8.GetBytes(sourceString);
}
else if (context.Source is ReadOnlyMemory<byte> sourceMemory)
{
bytes = sourceMemory.ToArray();
}

if (bytes == null)
{
return ConversionResult.Unhandled();
}

return await GetConversionResultFromDeserialization(bytes);
}

private async Task<ConversionResult> GetConversionResultFromDeserialization(byte[] bytes)
{
try
{
using var stream = new MemoryStream(bytes);
var deserializedObject = await JsonSerializer.DeserializeAsync<TimerInfo>(stream, _serializerOptions);
return ConversionResult.Success(deserializedObject);
}
catch (Exception ex)
{
return ConversionResult.Failed(ex);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.Worker.Extensions.Timer.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")]
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Microsoft.Azure.Functions.Worker.Converters;
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;
using Microsoft.Azure.Functions.Worker.Extensions.Timer.Converters;

namespace Microsoft.Azure.Functions.Worker
{
Expand All @@ -11,6 +13,8 @@ namespace Microsoft.Azure.Functions.Worker
/// a timer schedule.
/// </summary>
[BindingCapabilities(KnownBindingCapabilities.FunctionLevelRetry)]
[InputConverter(typeof(TimerInfoConverter))]
[ConverterFallbackBehavior(ConverterFallbackBehavior.Default)]
public sealed class TimerTriggerAttribute : TriggerBindingAttribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
<Description>Timer extensions for .NET isolated functions</Description>

<!--Version information-->
<VersionPrefix>4.3.0</VersionPrefix>
<VersionPrefix>4.3.1</VersionPrefix>
</PropertyGroup>

<Import Project="..\..\..\build\Extensions.props" />

<ItemGroup>
<ProjectReference Include="..\..\..\src\DotNetWorker.Core\DotNetWorker.Core.csproj" />
<ProjectReference Include="..\..\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Converters;
using Microsoft.Azure.Functions.Worker.Extensions.Timer.Converters;
using Microsoft.Azure.Functions.Worker.Http;
using Moq;

namespace Worker.Extensions.Timer.Tests
{
public sealed class TimerInfoConverterTests
{
[Fact]
public async Task ConvertAsync_ShouldConvertJsonStringToTimerInfo()
{
// The Source JSON contains properties which does not exist in the "TimerInfo" type (ex; Foo).
const string timerTriggerSourceJson = @"
{
""Foo"": ""Bar"",
""Schedule"": {
""adjustForDST"": true
},
""scheduleStatus"": {
""next"": ""2024-12-31T09:51:00-07:00""
},
""IsPastDue"": true
}";
var converter = new TimerInfoConverter();
var contextMock = new Mock<ConverterContext>();
contextMock.SetupGet(c => c.TargetType).Returns(typeof(TimerInfo));
contextMock.SetupGet(c => c.Source).Returns(timerTriggerSourceJson);

var result = await converter.ConvertAsync(contextMock.Object);

Assert.Equal(ConversionStatus.Succeeded, result.Status);
var timerInfo = Assert.IsType<TimerInfo>(result.Value);
Assert.True(timerInfo.IsPastDue);
Assert.Equal(DateTime.Parse("2024-12-31T09:51:00-07:00"), timerInfo?.ScheduleStatus?.Next);
}

[Fact]
public async Task ConvertAsync_ShouldReturnUnhandledWhenTargetTypeIsNotTimerInfo()
{
var converter = new TimerInfoConverter();
var contextMock = new Mock<ConverterContext>();
contextMock.SetupGet(c => c.TargetType).Returns(typeof(HttpRequestData));
contextMock.SetupGet(c => c.Source).Returns("{\"ScheduleStatus\": {\"Last\": \"2022-01-01T00:00:00Z\"}}");

var result = await converter.ConvertAsync(contextMock.Object);

Assert.Equal(ConversionStatus.Unhandled, result.Status);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Microsoft.Azure.Functions.Worker.Extensions.Timer.Tests</AssemblyName>
<RootNamespace>Microsoft.Azure.Functions.Worker.Extensions.Timer.Tests</RootNamespace>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\..\key.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageReference Include="Moq" Version="4.20.70" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\extensions\Worker.Extensions.Http\src\Worker.Extensions.Http.csproj" />
<ProjectReference Include="..\..\..\extensions\Worker.Extensions.Timer\src\Worker.Extensions.Timer.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>

0 comments on commit 1197d60

Please sign in to comment.