Skip to content

Commit ec9ca62

Browse files
committed
Prohibit unmarshalling for not Interface type parameters
Signed-off-by: moznion <moznion@mail.moznion.net>
1 parent f780f71 commit ec9ca62

11 files changed

+116
-10
lines changed

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ module.exports = {
1010
},
1111
],
1212
},
13+
testTimeout: 10000,
1314
};

lib/dynamodb_record_transformer.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,27 @@ export class DynamodbRecordTransformer {
1212
);
1313
}
1414

15-
const typeName = node.typeArguments[0].getText();
15+
const typeArg = node.typeArguments[0];
16+
const typeName = typeArg.getText();
1617
if (node.arguments.length !== 1 || !node.arguments[0]) {
1718
throw new Error(
1819
`No argument on ${DynamodbRecordTransformer.funcName}(). Please put an argument that has ${typeName} type on the function`,
1920
);
2021
}
2122

23+
const type = typeChecker.getTypeFromTypeNode(typeArg);
24+
if (!type.isClassOrInterface()) {
25+
throw new Error(
26+
`A type parameter of ${DynamodbRecordTransformer.funcName}() must be class or interface, but ${typeName} is not`,
27+
);
28+
}
29+
2230
const argVarNameIdent = ts.factory.createIdentifier('arg');
2331
const objectProps = new FieldsCollector(
2432
DynamodbRecordTransformer.funcName,
2533
DynamodbRecordTransformer.shouldLenientTypeCheck,
2634
)
27-
.collectFields(node, typeChecker)
35+
.collectFields(node, typeChecker, typeName, type)
2836
.map(field => {
2937
return field?.generateCode(argVarNameIdent.text);
3038
})

lib/fields_collector.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,22 @@ import { warn } from './logger';
1717
export class FieldsCollector {
1818
constructor(private readonly funcName: string, private readonly shouldLenientTypeCheck: boolean) {}
1919

20-
public collectFields(node: ts.CallExpression, typeChecker: ts.TypeChecker): (DynamodbItemField | undefined)[] {
20+
public collectFields(
21+
node: ts.CallExpression,
22+
typeChecker: ts.TypeChecker,
23+
typeName: string,
24+
type: ts.Type,
25+
): (DynamodbItemField | undefined)[] {
2126
if (node.typeArguments === undefined || node.typeArguments.length !== 1 || !node.typeArguments[0]) {
2227
throw new Error(`No type argument on ${this.funcName}(). Please put a type argument on the function`);
2328
}
2429

25-
const typ = node.typeArguments[0];
26-
const typeName = typ.getText();
27-
2830
if (node.arguments.length !== 1 || !node.arguments[0]) {
2931
throw new Error(
3032
`No argument on ${this.funcName}(). Please put an argument that has ${typeName} type on the function`,
3133
);
3234
}
3335

34-
const type = typeChecker.getTypeFromTypeNode(typ);
3536
const properties = typeChecker.getPropertiesOfType(type);
3637
return properties.map((prop): DynamodbItemField | undefined => {
3738
if (

lib/from_dynamodb_record_transformer.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,32 @@ export class FromDynamodbRecordTransformer {
1212
);
1313
}
1414

15-
const typeName = node.typeArguments[0].getText();
15+
const typeArg = node.typeArguments[0];
16+
const typeName = typeArg.getText();
1617
if (node.arguments.length !== 1 || !node.arguments[0]) {
1718
throw new Error(
1819
`No argument on ${FromDynamodbRecordTransformer.funcName}(). Please put an argument that has ${typeName} type on the function`,
1920
);
2021
}
2122

23+
const type = typeChecker.getTypeFromTypeNode(typeArg);
24+
if (!type.isClassOrInterface()) {
25+
throw new Error(
26+
`A type parameter of ${FromDynamodbRecordTransformer.funcName}() must be interface, but ${typeName} is not`,
27+
);
28+
}
29+
if (type.isClass()) {
30+
throw new Error(
31+
`A type parameter of ${FromDynamodbRecordTransformer.funcName}() must be interface, but ${typeName} is a class`,
32+
);
33+
}
34+
2235
const argVarNameIdent = ts.factory.createIdentifier('arg');
2336
const objectProps = new FieldsCollector(
2437
FromDynamodbRecordTransformer.funcName,
2538
FromDynamodbRecordTransformer.shouldLenientTypeCheck,
2639
)
27-
.collectFields(node, typeChecker)
40+
.collectFields(node, typeChecker, typeName, type)
2841
.map(field => {
2942
return field?.generateCodeForUnmarshal(argVarNameIdent.text);
3043
})

tests/from_dynamodb_record_transformer.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, test } from '@jest/globals';
22
import { fromDynamodbRecord } from '../index';
33

44
describe('from dynamodb record transform', () => {
5-
test('TODO', () => {
5+
test('should unmarshal the attributes into arbitrary interface instance', () => {
66
interface Interface {
77
publicNum: number;
88
readonly readonlyNum: number;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { describe, expect, test } from '@jest/globals';
2+
import * as child_process from 'child_process';
3+
import { ExecException } from 'child_process';
4+
5+
describe('from dynamodb record transformation errors', () => {
6+
test('should raise error when the type parameter is missing', async () => {
7+
const err = await new Promise<ExecException | null>(resolve => {
8+
child_process.exec(
9+
'NODE_NO_WARNINGS=true TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK= npx ts-node -C ttypescript ./tests/from_dynamodb_record_transformer_error_src/no_type_parameter.ts',
10+
err => {
11+
resolve(err);
12+
},
13+
);
14+
});
15+
16+
expect(err).not.toBeNull();
17+
expect(err?.message).toMatch(
18+
/Error: No type argument on fromDynamodbRecord\(\)\. Please put a type argument on the function/,
19+
);
20+
});
21+
22+
test('should raise error when the type parameter is class', async () => {
23+
const err = await new Promise<ExecException | null>(resolve => {
24+
child_process.exec(
25+
'NODE_NO_WARNINGS=true TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK= npx ts-node -C ttypescript ./tests/from_dynamodb_record_transformer_error_src/class_type_parameter.ts',
26+
err => {
27+
resolve(err);
28+
},
29+
);
30+
});
31+
32+
expect(err).not.toBeNull();
33+
expect(err?.message).toMatch(
34+
/Error: A type parameter of fromDynamodbRecord\(\) must be interface, but Clazz is a class/,
35+
);
36+
});
37+
38+
test('should raise error when the type parameter is not class and interface', async () => {
39+
const err = await new Promise<ExecException | null>(resolve => {
40+
child_process.exec(
41+
'NODE_NO_WARNINGS=true TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK= npx ts-node -C ttypescript ./tests/from_dynamodb_record_transformer_error_src/object_literal_type_parameter.ts',
42+
err => {
43+
resolve(err);
44+
},
45+
);
46+
});
47+
48+
expect(err).not.toBeNull();
49+
expect(err?.message).toMatch(
50+
/Error: A type parameter of fromDynamodbRecord\(\) must be interface, but \{\} is not/,
51+
);
52+
});
53+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { fromDynamodbRecord } from '../../index';
2+
3+
class Clazz {}
4+
5+
fromDynamodbRecord<Clazz>({});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { fromDynamodbRecord } from '../../index';
2+
3+
fromDynamodbRecord({});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { fromDynamodbRecord } from '../../index';
2+
3+
fromDynamodbRecord<{}>({});

tests/transformation_error.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ describe('dynamodb record transformation errors', () => {
1919
);
2020
});
2121

22+
test('should raise error when the type parameter is not class and interface', async () => {
23+
const err = await new Promise<ExecException | null>(resolve => {
24+
child_process.exec(
25+
'NODE_NO_WARNINGS=true TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK= npx ts-node -C ttypescript ./tests/transformation_error_src/object_literal_type_parameter.ts',
26+
err => {
27+
resolve(err);
28+
},
29+
);
30+
});
31+
32+
expect(err).not.toBeNull();
33+
expect(err?.message).toMatch(
34+
/Error: A type parameter of dynamodbRecord\(\) must be class or interface, but \{\} is not/,
35+
);
36+
});
37+
2238
test('should raise error when the argument is missing', async () => {
2339
const err = await new Promise<ExecException | null>(resolve => {
2440
child_process.exec(

0 commit comments

Comments
 (0)