Skip to content

Commit

Permalink
fix(kernel): cannot pass decorated structs to kernel as "any" (#997)
Browse files Browse the repository at this point in the history
When a struct is serialized with a $jsii.struct decoration from a jsii host language and passed into a value typed any, the kernel ignored the decoration. This resulted in a malformed object passed to the javascript code.

This fix undecorates the object and continues to deserialize it as a struct.

Also, increase kernel unit test coverage for serialization and deserialization of "any".

Fixes aws/aws-cdk#5066
  • Loading branch information
RomainMuller authored and Elad Ben-Israel committed Nov 18, 2019
1 parent 08c4294 commit 2bd3183
Show file tree
Hide file tree
Showing 16 changed files with 1,245 additions and 10 deletions.
65 changes: 65 additions & 0 deletions packages/jsii-calc/lib/compliance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2331,3 +2331,68 @@ export class ObjectWithPropertyProvider {

private constructor() { }
}

/**
* Make sure structs are un-decorated on the way in.
*
* @see https://github.com/aws/aws-cdk/issues/5066
*/
export class JsonFormatter {
public static stringify(value?: any): string | undefined {
return JSON.stringify(value, null, 2);
}

public static anyNull(): any {
return null;
}

public static anyUndefined(): any {
return undefined;
}

public static anyFunction(): any {
return () => 'boom';
}

public static anyDate(): any {
return new Date('2019-11-18T13:01:20.515Z');
}

public static anyNumber(): any {
return 123;
}

public static anyZero(): any {
return 0;
}

public static anyString(): any {
return 'foo';
}

public static anyEmptyString(): any {
return '';
}

public static anyBooleanTrue(): any {
return true;
}

public static anyBooleanFalse(): any {
return false;
}

public static anyArray(): any {
return [ 1, 2, 3, new Number(123), { foo: 'bar' } ];
}

public static anyHash(): any {
return { hello: 1234, world: new Number(122) };
}

public static anyRef(): any {
return new Number(444);
}

private constructor() { }
}
253 changes: 252 additions & 1 deletion packages/jsii-calc/test/assembly.jsii
Original file line number Diff line number Diff line change
Expand Up @@ -6826,6 +6826,257 @@
}
]
},
"jsii-calc.JsonFormatter": {
"assembly": "jsii-calc",
"docs": {
"see": "https://github.com/aws/aws-cdk/issues/5066",
"stability": "experimental",
"summary": "Make sure structs are un-decorated on the way in."
},
"fqn": "jsii-calc.JsonFormatter",
"kind": "class",
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2340
},
"methods": [
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2385
},
"name": "anyArray",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2381
},
"name": "anyBooleanFalse",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2377
},
"name": "anyBooleanTrue",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2357
},
"name": "anyDate",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2373
},
"name": "anyEmptyString",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2353
},
"name": "anyFunction",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2389
},
"name": "anyHash",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2345
},
"name": "anyNull",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2361
},
"name": "anyNumber",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2393
},
"name": "anyRef",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2369
},
"name": "anyString",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2349
},
"name": "anyUndefined",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2365
},
"name": "anyZero",
"returns": {
"type": {
"primitive": "any"
}
},
"static": true
},
{
"docs": {
"stability": "experimental"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2341
},
"name": "stringify",
"parameters": [
{
"name": "value",
"optional": true,
"type": {
"primitive": "any"
}
}
],
"returns": {
"optional": true,
"type": {
"primitive": "string"
}
},
"static": true
}
],
"name": "JsonFormatter"
},
"jsii-calc.LoadBalancedFargateServiceProps": {
"assembly": "jsii-calc",
"datatype": true,
Expand Down Expand Up @@ -11413,5 +11664,5 @@
}
},
"version": "0.20.6",
"fingerprint": "veHd1Q/376CoMR3O/DjjAiH9aVD/jcwnPEg88barg9I="
"fingerprint": "xRnF3HrD87xlfIYG262JFu5WEdrC0eQelRBEzyQyARo="
}
Original file line number Diff line number Diff line change
Expand Up @@ -1297,5 +1297,18 @@ public void CanUseInterfaceSetters()
obj.Property = "New Value";
Assert.True(obj.WasSet());
}

[Fact(DisplayName = Prefix + nameof(StructsAreUndecoratedOntheWayToKernel))]
public void StructsAreUndecoratedOntheWayToKernel()
{
var json = JsonFormatter.Stringify(new StructB {RequiredString = "Bazinga!", OptionalBoolean = false});
var actual = JObject.Parse(json);

var expected = new JObject();
expected.Add("RequiredString", "Bazinga!");
expected.Add("OptionalBoolean", false);

Assert.Equal(expected, actual);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ public sealed class JsiiByValueAttribute : Attribute
{
public JsiiByValueAttribute(string fqn)
{
Fqn = fqn ?? throw new ArgumentNullException(nameof(fqn));
FullyQualifiedName = fqn ?? throw new ArgumentNullException(nameof(fqn));
}

public string Fqn { get; }
public string FullyQualifiedName { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected override bool TryConvertClass(Type type, IReferenceMap referenceMap, o
}

var structInfo = new JObject();
structInfo.Add(new JProperty("fqn", byValueAttribute.Fqn));
structInfo.Add(new JProperty("fqn", byValueAttribute.FullyQualifiedName));
structInfo.Add(new JProperty("data", data));

var resultObject = new JObject();
Expand Down Expand Up @@ -353,6 +353,12 @@ TypeReference InferType(IReferenceMap referenceMap, Type type)
return new TypeReference(enumAttribute.FullyQualifiedName);
}

JsiiByValueAttribute structAttribute = type.GetCustomAttribute<JsiiByValueAttribute>();
if (structAttribute != null)
{
return new TypeReference(structAttribute.FullyQualifiedName);
}

if (typeof(string).IsAssignableFrom(type))
{
return new TypeReference(primitive: PrimitiveType.String);
Expand Down
Loading

0 comments on commit 2bd3183

Please sign in to comment.