Skip to content

Commit

Permalink
feat(.net): dynmic type checking for union-typed parameters (#3668)
Browse files Browse the repository at this point in the history
Weaving runtime type checks around union-typed parameters to help
developers receive actionable error messages in case mistakes are made.
The checks are only performed if the
`Amazon.JSII.Runtime.Configuration.RuntimeTypeChecking` configuration
property is `true` (which it is by default).

A pre-processor macro such as `DEBUG` could not be used as the packages
published to NuGet are already built and this does not afford any control
to the end-user.

Fixes #3640



---

By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license].

[Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
  • Loading branch information
RomainMuller committed Aug 9, 2022
1 parent 611323b commit f5e0603
Show file tree
Hide file tree
Showing 15 changed files with 2,328 additions and 180 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=d47431ca_002Dca0b_002D43c9_002D8da0_002Ddd234b4d01fd/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &amp;lt;Amazon.JSII.Runtime.IntegrationTests&amp;gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Project Location="/Users/rmuller/Development/aws/jsii/packages/@jsii/dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests" Presentation="&amp;lt;Amazon.JSII.Runtime.IntegrationTests&amp;gt;" /&gt;
&lt;Solution /&gt;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

using System;
using System.Collections.Generic;
using Amazon.JSII.Tests.CalculatorNamespace;
using Xunit;

#pragma warning disable CS0612

namespace Amazon.JSII.Runtime.IntegrationTests
{
public sealed class TypeCheckingTests : IClassFixture<ServiceContainerFixture>, IDisposable
{
const string Prefix = nameof(TypeCheckingTests) + ".";

private readonly IDisposable _serviceContainerFixture;

public TypeCheckingTests(ServiceContainerFixture serviceContainerFixture)
{
_serviceContainerFixture = serviceContainerFixture;
}

void IDisposable.Dispose()
{
_serviceContainerFixture.Dispose();
}

[Fact(DisplayName = Prefix + nameof(Constructor))]
public void Constructor()
{
var exception = Assert.Throws<System.ArgumentException>(() =>
new ClassWithCollectionOfUnions(new IDictionary<string, object>[]
{
new Dictionary<string, object>
{
{ "good", new StructA { RequiredString = "present"} },
{ "bad", $"Not a {nameof(StructA)} or {nameof(StructB)}" },
}
})
);
Assert.Equal("Expected argument unionProperty[0][\"bad\"] to be one of: Amazon.JSII.Tests.CalculatorNamespace.IStructA, Amazon.JSII.Tests.CalculatorNamespace.IStructB; received System.String (Parameter 'unionProperty')", exception.Message);
}

[Fact(DisplayName = Prefix + nameof(Setter))]
public void Setter()
{
var subject = new ClassWithCollectionOfUnions(Array.Empty<IDictionary<string, object>>());
var exception = Assert.Throws<System.ArgumentException>(() =>
subject.UnionProperty = new IDictionary<string, object>[]
{
new Dictionary<string, object>
{
{ "good", new StructA { RequiredString = "present" } },
{ "bad", $"Not a {nameof(StructA)} or {nameof(StructB)}" },
}
});
Assert.Equal("Expected value[0][\"bad\"] to be one of: Amazon.JSII.Tests.CalculatorNamespace.IStructA, Amazon.JSII.Tests.CalculatorNamespace.IStructB; received System.String (Parameter 'value')", exception.Message);
}

[Fact(DisplayName = Prefix + nameof(StaticMethod))]
public void StaticMethod()
{
var exception = Assert.Throws<System.ArgumentException>(() =>
StructUnionConsumer.IsStructA("Not a StructA"));
Assert.Equal("Expected argument struct to be one of: Amazon.JSII.Tests.CalculatorNamespace.IStructA, Amazon.JSII.Tests.CalculatorNamespace.IStructB; received System.String (Parameter 'struct')", exception.Message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace Amazon.JSII.Runtime
{
public static class Configuration
{
/// <summary>
/// Enables or disables runtime type checking of parameters when the original model expects a type union, which
/// is represented as <c>object</c> in .NET code.
/// </summary>
/// <remarks>
/// <para>This feature is enabled by default.</para>
/// <para>
/// This feature may be disabled as a work-around if a bug prevents your application from working correctly, or
/// in order to stop paying the performance cost of the runtime type checking.
/// </para>
/// </remarks>
public static bool RuntimeTypeChecking { get; set; } = true;
}
}
10 changes: 10 additions & 0 deletions packages/jsii-calc/lib/compliance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3050,3 +3050,13 @@ export class TwoMethodsWithSimilarCapitalization {
*/
public readonly fooBAR = 111;
}

export class ClassWithCollectionOfUnions {
public constructor(
public unionProperty: Array<Record<string, StructA | StructB>>,
) {}
}

export interface StructWithCollectionOfUnionts {
readonly unionProperty: Array<Record<string, StructA | StructB>>;
}
137 changes: 136 additions & 1 deletion packages/jsii-calc/test/assembly.jsii
Original file line number Diff line number Diff line change
Expand Up @@ -2447,6 +2447,90 @@
],
"symbolId": "lib/compliance:ClassThatImplementsThePrivateInterface"
},
"jsii-calc.ClassWithCollectionOfUnions": {
"assembly": "jsii-calc",
"docs": {
"stability": "stable"
},
"fqn": "jsii-calc.ClassWithCollectionOfUnions",
"initializer": {
"docs": {
"stability": "stable"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 3055
},
"parameters": [
{
"name": "unionProperty",
"type": {
"collection": {
"elementtype": {
"collection": {
"elementtype": {
"union": {
"types": [
{
"fqn": "jsii-calc.StructA"
},
{
"fqn": "jsii-calc.StructB"
}
]
}
},
"kind": "map"
}
},
"kind": "array"
}
}
}
]
},
"kind": "class",
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 3054
},
"name": "ClassWithCollectionOfUnions",
"properties": [
{
"docs": {
"stability": "stable"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 3056
},
"name": "unionProperty",
"type": {
"collection": {
"elementtype": {
"collection": {
"elementtype": {
"union": {
"types": [
{
"fqn": "jsii-calc.StructA"
},
{
"fqn": "jsii-calc.StructB"
}
]
}
},
"kind": "map"
}
},
"kind": "array"
}
}
}
],
"symbolId": "lib/compliance:ClassWithCollectionOfUnions"
},
"jsii-calc.ClassWithCollections": {
"assembly": "jsii-calc",
"docs": {
Expand Down Expand Up @@ -13549,6 +13633,57 @@
"name": "StructUnionConsumer",
"symbolId": "lib/compliance:StructUnionConsumer"
},
"jsii-calc.StructWithCollectionOfUnionts": {
"assembly": "jsii-calc",
"datatype": true,
"docs": {
"stability": "stable"
},
"fqn": "jsii-calc.StructWithCollectionOfUnionts",
"kind": "interface",
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 3060
},
"name": "StructWithCollectionOfUnionts",
"properties": [
{
"abstract": true,
"docs": {
"stability": "stable"
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 3061
},
"name": "unionProperty",
"type": {
"collection": {
"elementtype": {
"collection": {
"elementtype": {
"union": {
"types": [
{
"fqn": "jsii-calc.StructA"
},
{
"fqn": "jsii-calc.StructB"
}
]
}
},
"kind": "map"
}
},
"kind": "array"
}
}
}
],
"symbolId": "lib/compliance:StructWithCollectionOfUnionts"
},
"jsii-calc.StructWithEnum": {
"assembly": "jsii-calc",
"datatype": true,
Expand Down Expand Up @@ -17653,5 +17788,5 @@
}
},
"version": "3.20.120",
"fingerprint": "P+E1ta4n5zaxREzT7CqW574NyC10CNmKv5NAwZ5pRt8="
"fingerprint": "4J4zpGOrJJsMNdeBDVxr5OLa4LaClMA61HDRI4Wnajg="
}
Loading

0 comments on commit f5e0603

Please sign in to comment.