Skip to content

Commit 4e70209

Browse files
author
Elad Ben-Israel
authored
fix: "Malformed enum value" when using @Scoped packages (#139)
Fixes #138 The code that validated the format of jsii enum values was too rigid and didn't allow "/" in type names. When using a package from an npm scope, the enum value will look like this `@scope/foo.EnumType/MemberName`. Also, add a verification that the enum member exists in the enum and fail otherwise. Required fix in kernel, java and dotnet runtimes.
1 parent 8416b6a commit 4e70209

File tree

20 files changed

+348
-18
lines changed

20 files changed

+348
-18
lines changed

packages/jsii-calc-lib/lib/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,12 @@ export interface StructWithOnlyOptionals {
8282
optional2?: number
8383
optional3?: boolean
8484
}
85+
86+
/**
87+
* Check that enums from @scoped packages can be references.
88+
* See awslabs/jsii#138
89+
*/
90+
export enum EnumFromScopedModule {
91+
Value1,
92+
Value2
93+
}

packages/jsii-calc-lib/test/assembly.jsii

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"fingerprint": "GbVnxjsdaq598NCRdOJ8entjQRwThRmFgdeLNaQbIZY=",
2+
"fingerprint": "yzHGjyI4/53o87fPNjR/Nz/4G58kg5jVoKtgwdqaEU8=",
33
"author": {
44
"name": "Amazon Web Services",
55
"organization": true,
@@ -73,6 +73,24 @@
7373
}
7474
},
7575
"types": {
76+
"@scope/jsii-calc-lib.EnumFromScopedModule": {
77+
"assembly": "@scope/jsii-calc-lib",
78+
"docs": {
79+
"comment": "Check that enums from @scoped packages can be references.\nSee awslabs/jsii#138"
80+
},
81+
"fqn": "@scope/jsii-calc-lib.EnumFromScopedModule",
82+
"kind": "enum",
83+
"members": [
84+
{
85+
"name": "Value1"
86+
},
87+
{
88+
"name": "Value2"
89+
}
90+
],
91+
"name": "EnumFromScopedModule",
92+
"namespace": "@scope/jsii-calc-lib"
93+
},
7694
"@scope/jsii-calc-lib.IFriendly": {
7795
"assembly": "@scope/jsii-calc-lib",
7896
"docs": {

packages/jsii-calc/lib/index.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// tslint:disable
2-
import { Operation, Value, Number, IFriendly, MyFirstStruct, StructWithOnlyOptionals } from '@scope/jsii-calc-lib'
2+
import { Operation, Value, Number, IFriendly, MyFirstStruct, StructWithOnlyOptionals, EnumFromScopedModule } from '@scope/jsii-calc-lib'
33
import * as fs from 'fs';
44
import * as path from 'path';
55
import * as os from 'os';
@@ -1101,4 +1101,19 @@ export class UseCalcBase {
11011101

11021102
export interface ImplictBaseOfBase extends base.BaseProps {
11031103
goo: Date;
1104+
}
1105+
1106+
/**
1107+
* See awslabs/jsii#138
1108+
*/
1109+
export class ReferenceEnumFromScopedPackage {
1110+
public foo?: EnumFromScopedModule = EnumFromScopedModule.Value2;
1111+
1112+
public loadFoo(): EnumFromScopedModule | undefined {
1113+
return this.foo;
1114+
}
1115+
1116+
public saveFoo(value: EnumFromScopedModule) {
1117+
this.foo = value;
1118+
}
11041119
}

packages/jsii-calc/test/assembly.jsii

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"fingerprint": "ZR90w/OQ/vVxm0Cg1sicOKwbZxbGK43MXqj2ksUyCrk=",
2+
"fingerprint": "ENUxKESaYuaH8SRuMSsikJscFgsj+E8Ey1WvHIsNsZg=",
33
"author": {
44
"name": "Amazon Web Services",
55
"organization": true,
@@ -1919,6 +1919,48 @@
19191919
}
19201920
]
19211921
},
1922+
"jsii-calc.ReferenceEnumFromScopedPackage": {
1923+
"assembly": "jsii-calc",
1924+
"docs": {
1925+
"comment": "See awslabs/jsii#138"
1926+
},
1927+
"fqn": "jsii-calc.ReferenceEnumFromScopedPackage",
1928+
"initializer": {
1929+
"initializer": true
1930+
},
1931+
"kind": "class",
1932+
"methods": [
1933+
{
1934+
"name": "loadFoo",
1935+
"returns": {
1936+
"fqn": "@scope/jsii-calc-lib.EnumFromScopedModule",
1937+
"optional": true
1938+
}
1939+
},
1940+
{
1941+
"name": "saveFoo",
1942+
"parameters": [
1943+
{
1944+
"name": "value",
1945+
"type": {
1946+
"fqn": "@scope/jsii-calc-lib.EnumFromScopedModule"
1947+
}
1948+
}
1949+
]
1950+
}
1951+
],
1952+
"name": "ReferenceEnumFromScopedPackage",
1953+
"namespace": "jsii-calc",
1954+
"properties": [
1955+
{
1956+
"name": "foo",
1957+
"type": {
1958+
"fqn": "@scope/jsii-calc-lib.EnumFromScopedModule",
1959+
"optional": true
1960+
}
1961+
}
1962+
]
1963+
},
19221964
"jsii-calc.ReturnsNumber": {
19231965
"assembly": "jsii-calc",
19241966
"fqn": "jsii-calc.ReturnsNumber",

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/EnumValue.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ public EnumValue(string value)
1111
{
1212
Value = value ?? throw new ArgumentNullException(nameof(value));
1313

14-
string[] valueTokens = value.Split('/');
15-
if (valueTokens.Length != 2)
14+
int sep = value.LastIndexOf('/');
15+
if (sep == -1)
1616
{
1717
throw new ArgumentException($"Unexpected format for enum value: {value}", nameof(value));
1818
}
1919

20-
FullyQualifiedName = valueTokens[0];
21-
MemberName = valueTokens[1];
20+
FullyQualifiedName = value.Substring(0, sep);
21+
MemberName = value.Substring(sep + 1);
2222
}
2323

2424
public EnumValue(string fullyQualifiedName, string memberName) : this($"{fullyQualifiedName}/{memberName}")

packages/jsii-dotnet-runtime/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs

+11
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,17 @@ public void GetAndSetEnumValues()
266266
Assert.Equal("<<[[{{(((1 * (0 + 9)) * (0 + 9)) * (0 + 9))}}]]>>", calc.ToString());
267267
}
268268

269+
[Fact(DisplayName = Prefix + nameof(EnumFromScopedModule))]
270+
public void UseEnumFromScopedModule()
271+
{
272+
ReferenceEnumFromScopedPackage obj = new ReferenceEnumFromScopedPackage();
273+
Assert.Equal(EnumFromScopedModule.Value2, obj.Foo);
274+
obj.Foo = EnumFromScopedModule.Value1;
275+
Assert.Equal(EnumFromScopedModule.Value1, obj.LoadFoo());
276+
obj.SaveFoo(EnumFromScopedModule.Value2);
277+
Assert.Equal(EnumFromScopedModule.Value2, obj.Foo);
278+
}
279+
269280
[Fact(DisplayName = Prefix + nameof(UndefinedAndNull))]
270281
public void UndefinedAndNull()
271282
{

packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/ComplianceTest.java

+12
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
import software.amazon.jsii.tests.calculator.NumberGenerator;
2525
import software.amazon.jsii.tests.calculator.Polymorphism;
2626
import software.amazon.jsii.tests.calculator.Power;
27+
import software.amazon.jsii.tests.calculator.ReferenceEnumFromScopedPackage;
2728
import software.amazon.jsii.tests.calculator.Statics;
2829
import software.amazon.jsii.tests.calculator.Sum;
2930
import software.amazon.jsii.tests.calculator.SyncVirtualMethods;
3031
import software.amazon.jsii.tests.calculator.UnionProperties;
3132
import software.amazon.jsii.tests.calculator.UsesInterfaceWithProperties;
3233
import software.amazon.jsii.tests.calculator.composition.CompositionStringStyle;
34+
import software.amazon.jsii.tests.calculator.lib.EnumFromScopedModule;
3335
import software.amazon.jsii.tests.calculator.lib.IFriendly;
3436
import software.amazon.jsii.tests.calculator.lib.MyFirstStruct;
3537
import software.amazon.jsii.tests.calculator.lib.Number;
@@ -242,6 +244,16 @@ public void getAndSetEnumValues() {
242244
assertEquals("<<[[{{(((1 * (0 + 9)) * (0 + 9)) * (0 + 9))}}]]>>", calc.toString());
243245
}
244246

247+
@Test
248+
public void useEnumFromScopedModule() {
249+
ReferenceEnumFromScopedPackage obj = new ReferenceEnumFromScopedPackage();
250+
assertEquals(EnumFromScopedModule.Value2, obj.getFoo());
251+
obj.setFoo(EnumFromScopedModule.Value1);
252+
assertEquals(EnumFromScopedModule.Value1, obj.loadFoo());
253+
obj.saveFoo(EnumFromScopedModule.Value2);
254+
assertEquals(EnumFromScopedModule.Value2, obj.getFoo());
255+
}
256+
245257
@Test
246258
public void undefinedAndNull() {
247259
Calculator calculator = new Calculator();

packages/jsii-java-runtime/project/src/main/java/software/amazon/jsii/JsiiEngine.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -264,13 +264,13 @@ private JsiiObject createNative(final String fqn) {
264264
*/
265265
@SuppressWarnings({ "unchecked", "rawtypes" })
266266
public Enum<?> findEnumValue(final String enumRef) {
267-
String[] parts = enumRef.split("/");
268-
if (parts.length != 2) {
267+
int sep = enumRef.lastIndexOf('/');
268+
if (sep == -1) {
269269
throw new JsiiException("Malformed enum reference: " + enumRef);
270270
}
271271

272-
String typeName = parts[0];
273-
String valueName = parts[1];
272+
String typeName = enumRef.substring(0, sep);
273+
String valueName = enumRef.substring(sep + 1);
274274

275275
String enumClass = resolveJavaClassName(typeName);
276276
try {

packages/jsii-kernel/lib/kernel.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -855,14 +855,21 @@ export class Kernel {
855855
// enums
856856
if (typeof v === 'object' && TOKEN_ENUM in v) {
857857
this._debug('Enum:', v);
858-
const parts = v[TOKEN_ENUM].split('/');
859-
if (parts.length !== 2) {
858+
859+
const value = v[TOKEN_ENUM] as string;
860+
const sep = value.lastIndexOf('/');
861+
if (sep === -1) {
860862
throw new Error(`Malformed enum value: ${v[TOKEN_ENUM]}`);
861863
}
862-
const typeName = parts[0];
863-
const valueName = parts[1];
864+
865+
const typeName = value.substr(0, sep);
866+
const valueName = value.substr(sep + 1);
864867

865868
const enumValue = this._findSymbol(typeName)[valueName];
869+
if (enumValue === undefined) {
870+
throw new Error(`No enum member named ${valueName} in ${typeName}`);
871+
}
872+
866873
this._debug('resolved enum value:', enumValue);
867874
return enumValue;
868875
}

packages/jsii-kernel/test/test.kernel.ts

+23
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,29 @@ defineTest('in/out enum values', async (test, sandbox) => {
167167
test.deepEqual(sandbox.get({ objref: alltypes, property: 'enumProperty' }).value, { '$jsii.enum': 'jsii-calc.AllTypesEnum/ThisIsGreat' });
168168
});
169169

170+
defineTest('enum values from @scoped packages awslabs/jsii#138', async (test, sandbox) => {
171+
const objref = sandbox.create({ fqn: 'jsii-calc.ReferenceEnumFromScopedPackage' });
172+
173+
const value = sandbox.get({ objref, property: 'foo' });
174+
test.deepEqual(value, { value: { '$jsii.enum': '@scope/jsii-calc-lib.EnumFromScopedModule/Value2' } });
175+
176+
sandbox.set({ objref, property: 'foo', value: { '$jsii.enum': '@scope/jsii-calc-lib.EnumFromScopedModule/Value1' }});
177+
const ret = sandbox.invoke({ objref, method: 'loadFoo' });
178+
test.deepEqual(ret, { result: { '$jsii.enum': '@scope/jsii-calc-lib.EnumFromScopedModule/Value1' } });
179+
180+
sandbox.invoke({ objref, method: 'saveFoo', args: [ { '$jsii.enum': '@scope/jsii-calc-lib.EnumFromScopedModule/Value2' } ] });
181+
const value2 = sandbox.get({ objref, property: 'foo' });
182+
test.deepEqual(value2, { value: { '$jsii.enum': '@scope/jsii-calc-lib.EnumFromScopedModule/Value2' } });
183+
});
184+
185+
defineTest('fails for invalid enum member name', async (test, sandbox) => {
186+
const objref = sandbox.create({ fqn: 'jsii-calc.ReferenceEnumFromScopedPackage' });
187+
188+
test.throws(() => {
189+
sandbox.set({ objref, property: 'foo', value: { '$jsii.enum': '@scope/jsii-calc-lib.EnumFromScopedModule/ValueX' }});
190+
}, /No enum member named ValueX/);
191+
});
192+
170193
defineTest('set for a non existing property', async (test, sandbox) => {
171194
const obj = sandbox.create({ fqn: 'jsii-calc.SyncVirtualMethods' });
172195
test.throws(() => sandbox.set({ objref: obj, property: 'idontexist', value: 'Foo' }));

packages/jsii-pacmak/bin/jsii-pacmak.ts

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ import { SPEC_FILE_NAME } from '../node_modules/jsii-spec';
8787
return; // already built
8888
}
8989

90+
visited.add(packageDir);
91+
9092
// read package.json and extract the "jsii" configuration from it.
9193
const pkg = await fs.readJson(path.join(packageDir, 'package.json'));
9294
if (!pkg.jsii || !pkg.jsii.outdir || !pkg.jsii.targets) {

packages/jsii-pacmak/test/diff-test.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function assert-generator() {
4343
echo "The diff-test.sh harness will replace it with the real expected tarball" >> ${tarball_placeholder}
4444
done
4545

46-
if ! diff -arq ${outdir} ${expected}; then
46+
if ! diff --strip-trailing-cr -arq ${outdir} ${expected}; then
4747
echo
4848
echo "------------------------------------------------------------------------"
4949
echo " diff-test for pacmak generator ${module} failed"

packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.Calculator.Lib/.jsii

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"fingerprint": "GbVnxjsdaq598NCRdOJ8entjQRwThRmFgdeLNaQbIZY=",
2+
"fingerprint": "yzHGjyI4/53o87fPNjR/Nz/4G58kg5jVoKtgwdqaEU8=",
33
"author": {
44
"name": "Amazon Web Services",
55
"organization": true,
@@ -73,6 +73,24 @@
7373
}
7474
},
7575
"types": {
76+
"@scope/jsii-calc-lib.EnumFromScopedModule": {
77+
"assembly": "@scope/jsii-calc-lib",
78+
"docs": {
79+
"comment": "Check that enums from @scoped packages can be references.\nSee awslabs/jsii#138"
80+
},
81+
"fqn": "@scope/jsii-calc-lib.EnumFromScopedModule",
82+
"kind": "enum",
83+
"members": [
84+
{
85+
"name": "Value1"
86+
},
87+
{
88+
"name": "Value2"
89+
}
90+
],
91+
"name": "EnumFromScopedModule",
92+
"namespace": "@scope/jsii-calc-lib"
93+
},
7694
"@scope/jsii-calc-lib.IFriendly": {
7795
"assembly": "@scope/jsii-calc-lib",
7896
"docs": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Amazon.JSII.Runtime.Deputy;
2+
3+
namespace Amazon.JSII.Tests.Calculator.Lib
4+
{
5+
/// <summary>
6+
/// Check that enums from @scoped packages can be references.
7+
/// See awslabs/jsii#138
8+
/// </summary>
9+
[JsiiEnum(typeof(EnumFromScopedModule), "@scope/jsii-calc-lib.EnumFromScopedModule")]
10+
public enum EnumFromScopedModule
11+
{
12+
[JsiiEnumMember("Value1")]
13+
Value1,
14+
[JsiiEnumMember("Value2")]
15+
Value2
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package software.amazon.jsii.tests.calculator.lib;
2+
/**
3+
* Check that enums from @scoped packages can be references.
4+
* See awslabs/jsii#138
5+
*/
6+
@software.amazon.jsii.Jsii(module = software.amazon.jsii.tests.calculator.lib.$Module.class, fqn = "@scope/jsii-calc-lib.EnumFromScopedModule")
7+
public enum EnumFromScopedModule {
8+
Value1,
9+
Value2,
10+
}

packages/jsii-pacmak/test/expected.jsii-calc-lib/sphinx/_scope_jsii-calc-lib.rst

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ Reference
66

77
.. py:module:: @scope/jsii-calc-lib
88
9+
EnumFromScopedModule (enum)
10+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
11+
12+
.. py:class:: EnumFromScopedModule
13+
14+
.. py:data:: Value1
15+
16+
.. py:data:: Value2
17+
18+
919
IFriendly (interface)
1020
^^^^^^^^^^^^^^^^^^^^^
1121

0 commit comments

Comments
 (0)