Skip to content

Commit 70f3f27

Browse files
authored
Merge pull request #23 from moznion/change_lenient_opt
Give "lenient type check" option through the function argument
2 parents b4ff54a + ce515f1 commit 70f3f27

10 files changed

+158
-143
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,11 @@ NOTE: if the TypeScript property has `unknown` type and the value is `null` then
187187

188188
## Options
189189

190-
### `TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK` env var (default: `<empty>`)
190+
### Lenient type checking (default: `false`)
191191

192192
By default, if this plugin encounters unsupported types, it raises the error and halts the transformation.
193193

194-
But if `TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK` environment variable is not empty, it proceeds the transformation with ignoring the unsupported typed property even if it gets the unsupported types.
194+
But if `true` value is given through the second argument of the function, it proceeds the transformation with ignoring the unsupported typed property even if it gets the unsupported types.
195195

196196
## Note
197197

index.d.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import { AttributeValue } from '@aws-sdk/client-dynamodb';
22

3-
export function dynamodbRecord<T extends object>(item: T): Record<keyof T, AttributeValue>;
4-
export function fromDynamodbRecord<T extends object>(attrs: Record<string, AttributeValue>): T;
3+
export function dynamodbRecord<T extends object>(
4+
item: T,
5+
shouldLenientTypeCheck?: boolean,
6+
): Record<keyof T, AttributeValue>;
7+
export function fromDynamodbRecord<T extends object>(
8+
attrs: Record<string, AttributeValue>,
9+
shouldLenientTypeCheck?: boolean,
10+
): T;

lib/dynamodb_record_transformer.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { FieldsCollector } from './fields_collector';
33

44
export class DynamodbRecordTransformer {
55
public static readonly funcName = 'dynamodbRecord';
6-
private static readonly shouldLenientTypeCheck = !!process.env['TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK']; // TODO
76

87
public static visitNode(node: ts.CallExpression, typeChecker: ts.TypeChecker): ts.Node | undefined {
98
if (node.typeArguments === undefined || node.typeArguments.length !== 1 || !node.typeArguments[0]) {
@@ -14,12 +13,14 @@ export class DynamodbRecordTransformer {
1413

1514
const typeArg = node.typeArguments[0];
1615
const typeName = typeArg.getText();
17-
if (node.arguments.length !== 1 || !node.arguments[0]) {
16+
if (node.arguments.length < 1 || !node.arguments[0]) {
1817
throw new Error(
1918
`No argument on ${DynamodbRecordTransformer.funcName}(). Please put an argument that has ${typeName} type on the function`,
2019
);
2120
}
2221

22+
const shouldLenientTypeCheck = node.arguments[1]?.getText() === 'true';
23+
2324
const type = typeChecker.getTypeFromTypeNode(typeArg);
2425
if (!type.isClassOrInterface()) {
2526
throw new Error(
@@ -28,10 +29,7 @@ export class DynamodbRecordTransformer {
2829
}
2930

3031
const argVarNameIdent = ts.factory.createIdentifier('arg');
31-
const objectProps = new FieldsCollector(
32-
DynamodbRecordTransformer.funcName,
33-
DynamodbRecordTransformer.shouldLenientTypeCheck,
34-
)
32+
const objectProps = new FieldsCollector(DynamodbRecordTransformer.funcName, shouldLenientTypeCheck)
3533
.collectFields(node, typeChecker, typeName, type)
3634
.map(field => {
3735
return field?.generateCode(argVarNameIdent.text);

lib/fields_collector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class FieldsCollector {
2929
throw new Error(`No type argument on ${this.funcName}(). Please put a type argument on the function`);
3030
}
3131

32-
if (node.arguments.length !== 1 || !node.arguments[0]) {
32+
if (node.arguments.length < 1 || !node.arguments[0]) {
3333
throw new Error(
3434
`No argument on ${this.funcName}(). Please put an argument that has ${typeName} type on the function`,
3535
);

lib/from_dynamodb_record_transformer.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { FieldsCollector } from './fields_collector';
33

44
export class FromDynamodbRecordTransformer {
55
public static readonly funcName = 'fromDynamodbRecord';
6-
private static readonly shouldLenientTypeCheck = !!process.env['TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK']; // TODO
76

87
public static visitNode(node: ts.CallExpression, typeChecker: ts.TypeChecker): ts.Node | undefined {
98
if (node.typeArguments === undefined || node.typeArguments.length !== 1 || !node.typeArguments[0]) {
@@ -14,12 +13,14 @@ export class FromDynamodbRecordTransformer {
1413

1514
const typeArg = node.typeArguments[0];
1615
const typeName = typeArg.getText();
17-
if (node.arguments.length !== 1 || !node.arguments[0]) {
16+
if (node.arguments.length < 1 || !node.arguments[0]) {
1817
throw new Error(
1918
`No argument on ${FromDynamodbRecordTransformer.funcName}(). Please put an argument that has ${typeName} type on the function`,
2019
);
2120
}
2221

22+
const shouldLenientTypeCheck = node.arguments[1]?.getText() === 'true';
23+
2324
const type = typeChecker.getTypeFromTypeNode(typeArg);
2425
if (!type.isClassOrInterface()) {
2526
throw new Error(
@@ -33,10 +34,7 @@ export class FromDynamodbRecordTransformer {
3334
}
3435

3536
const argVarNameIdent = ts.factory.createIdentifier('arg');
36-
const objectProps = new FieldsCollector(
37-
FromDynamodbRecordTransformer.funcName,
38-
FromDynamodbRecordTransformer.shouldLenientTypeCheck,
39-
)
37+
const objectProps = new FieldsCollector(FromDynamodbRecordTransformer.funcName, shouldLenientTypeCheck)
4038
.collectFields(node, typeChecker, typeName, type)
4139
.map(field => {
4240
return field?.generateCodeForUnmarshal(argVarNameIdent.text);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"types": "index.d.ts",
77
"scripts": {
88
"build": "tsc",
9-
"test": "TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK=true jest --no-cache",
9+
"test": "jest --no-cache",
1010
"fmt": "find -name '*.ts' -not -path './node_modules/*' | xargs prettier --write",
1111
"lint": "eslint .",
1212
"release": "npm run build && npm publish"

tests/from_dynamodb_record_transformer.test.ts

Lines changed: 106 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -29,119 +29,122 @@ describe('from dynamodb record transform', () => {
2929
readonly kvMapToNull: { [key: string]: unknown };
3030
}
3131

32-
const got = fromDynamodbRecord<Interface>({
33-
publicNum: { N: '123' },
34-
readonlyNum: { N: '234' },
35-
string: { S: 'str' },
36-
bytes: { B: new TextEncoder().encode('bytes') },
37-
bool: { BOOL: true },
38-
nil: { NULL: true },
39-
stringSlice: { L: [{ S: 'slice' }, { S: 'str' }] },
40-
stringArray: { L: [{ S: 'array' }, { S: 'str' }] },
41-
numSlice: { L: [{ N: '123' }, { N: '234' }] },
42-
numArray: { L: [{ N: '345' }, { N: '456' }] },
43-
numSet: { NS: ['123', '234'] },
44-
stringSet: { SS: ['set', 'str'] },
45-
bytesSet: { BS: [new TextEncoder().encode('set'), new TextEncoder().encode('bytes')] },
46-
mapToNum: {
47-
M: {
48-
mapTo: { N: '123' },
49-
num: {
50-
N: '234',
32+
const got = fromDynamodbRecord<Interface>(
33+
{
34+
publicNum: { N: '123' },
35+
readonlyNum: { N: '234' },
36+
string: { S: 'str' },
37+
bytes: { B: new TextEncoder().encode('bytes') },
38+
bool: { BOOL: true },
39+
nil: { NULL: true },
40+
stringSlice: { L: [{ S: 'slice' }, { S: 'str' }] },
41+
stringArray: { L: [{ S: 'array' }, { S: 'str' }] },
42+
numSlice: { L: [{ N: '123' }, { N: '234' }] },
43+
numArray: { L: [{ N: '345' }, { N: '456' }] },
44+
numSet: { NS: ['123', '234'] },
45+
stringSet: { SS: ['set', 'str'] },
46+
bytesSet: { BS: [new TextEncoder().encode('set'), new TextEncoder().encode('bytes')] },
47+
mapToNum: {
48+
M: {
49+
mapTo: { N: '123' },
50+
num: {
51+
N: '234',
52+
},
5153
},
5254
},
53-
},
54-
mapToString: {
55-
M: {
56-
mapTo: {
57-
S: 'foo',
58-
},
59-
str: {
60-
S: 'bar',
55+
mapToString: {
56+
M: {
57+
mapTo: {
58+
S: 'foo',
59+
},
60+
str: {
61+
S: 'bar',
62+
},
6163
},
6264
},
63-
},
64-
mapToBytes: {
65-
M: {
66-
mapTo: {
67-
B: new TextEncoder().encode('foo'),
68-
},
69-
bytes: {
70-
B: new TextEncoder().encode('bar'),
65+
mapToBytes: {
66+
M: {
67+
mapTo: {
68+
B: new TextEncoder().encode('foo'),
69+
},
70+
bytes: {
71+
B: new TextEncoder().encode('bar'),
72+
},
7173
},
7274
},
73-
},
74-
mapToBool: {
75-
M: {
76-
mapTo: {
77-
BOOL: true,
78-
},
79-
bool: {
80-
BOOL: false,
75+
mapToBool: {
76+
M: {
77+
mapTo: {
78+
BOOL: true,
79+
},
80+
bool: {
81+
BOOL: false,
82+
},
8183
},
8284
},
83-
},
84-
mapToNull: {
85-
M: {
86-
mapTo: {
87-
NULL: false,
88-
},
89-
null: {
90-
NULL: true,
85+
mapToNull: {
86+
M: {
87+
mapTo: {
88+
NULL: false,
89+
},
90+
null: {
91+
NULL: true,
92+
},
9193
},
9294
},
93-
},
94-
kvMapToNum: {
95-
M: {
96-
kvMapTo: {
97-
N: '123',
98-
},
99-
num: {
100-
N: '234',
95+
kvMapToNum: {
96+
M: {
97+
kvMapTo: {
98+
N: '123',
99+
},
100+
num: {
101+
N: '234',
102+
},
101103
},
102104
},
103-
},
104-
kvMapToString: {
105-
M: {
106-
kvMapTo: {
107-
S: 'foo',
108-
},
109-
str: {
110-
S: 'bar',
105+
kvMapToString: {
106+
M: {
107+
kvMapTo: {
108+
S: 'foo',
109+
},
110+
str: {
111+
S: 'bar',
112+
},
111113
},
112114
},
113-
},
114-
kvMapToBytes: {
115-
M: {
116-
kvMapTo: {
117-
B: new TextEncoder().encode('foo'),
118-
},
119-
bytes: {
120-
B: new TextEncoder().encode('bar'),
115+
kvMapToBytes: {
116+
M: {
117+
kvMapTo: {
118+
B: new TextEncoder().encode('foo'),
119+
},
120+
bytes: {
121+
B: new TextEncoder().encode('bar'),
122+
},
121123
},
122124
},
123-
},
124-
kvMapToBool: {
125-
M: {
126-
kvMapTo: {
127-
BOOL: true,
128-
},
129-
bool: {
130-
BOOL: false,
125+
kvMapToBool: {
126+
M: {
127+
kvMapTo: {
128+
BOOL: true,
129+
},
130+
bool: {
131+
BOOL: false,
132+
},
131133
},
132134
},
133-
},
134-
kvMapToNull: {
135-
M: {
136-
kvMapTo: {
137-
NULL: false,
138-
},
139-
null: {
140-
NULL: true,
135+
kvMapToNull: {
136+
M: {
137+
kvMapTo: {
138+
NULL: false,
139+
},
140+
null: {
141+
NULL: true,
142+
},
141143
},
142144
},
143145
},
144-
});
146+
true,
147+
);
145148

146149
expect(got.publicNum).toEqual(123);
147150
expect(got.readonlyNum).toEqual(234);
@@ -215,14 +218,17 @@ describe('from dynamodb record transform', () => {
215218
readonly kvMapToBigint: { [key: string]: BigInt };
216219
}
217220

218-
const got = fromDynamodbRecord<Interface>({
219-
bigint: { N: '123' },
220-
bigintSlice: { L: [{ N: '123' }, { N: '234' }] },
221-
bigintArray: { L: [{ N: '345' }, { N: '456' }] },
222-
bigintSet: { NS: ['567', '678'] },
223-
mapToBigint: { M: { bigint: { N: '789' } } },
224-
kvMapToBigint: { M: { bigint: { N: '890' } } },
225-
});
221+
const got = fromDynamodbRecord<Interface>(
222+
{
223+
bigint: { N: '123' },
224+
bigintSlice: { L: [{ N: '123' }, { N: '234' }] },
225+
bigintArray: { L: [{ N: '345' }, { N: '456' }] },
226+
bigintSet: { NS: ['567', '678'] },
227+
mapToBigint: { M: { bigint: { N: '789' } } },
228+
kvMapToBigint: { M: { bigint: { N: '890' } } },
229+
},
230+
true,
231+
);
226232
expect(got.bigint).toEqual(BigInt(123));
227233
expect(got.bigintSlice).toEqual([BigInt(123), BigInt(234)]);
228234
expect(got.bigintArray).toEqual(new Array<BigInt>(BigInt(345), BigInt(456)));

tests/from_dynamodb_record_transformer_error.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ describe('from dynamodb record transformation errors', () => {
66
test('should raise error when the type parameter is missing', async () => {
77
const err = await new Promise<ExecException | null>(resolve => {
88
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',
9+
'NODE_NO_WARNINGS=true npx ts-node -C ttypescript ./tests/from_dynamodb_record_transformer_error_src/no_type_parameter.ts',
1010
err => {
1111
resolve(err);
1212
},
@@ -22,7 +22,7 @@ describe('from dynamodb record transformation errors', () => {
2222
test('should raise error when the type parameter is class', async () => {
2323
const err = await new Promise<ExecException | null>(resolve => {
2424
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',
25+
'NODE_NO_WARNINGS=true npx ts-node -C ttypescript ./tests/from_dynamodb_record_transformer_error_src/class_type_parameter.ts',
2626
err => {
2727
resolve(err);
2828
},
@@ -38,7 +38,7 @@ describe('from dynamodb record transformation errors', () => {
3838
test('should raise error when the type parameter is not class and interface', async () => {
3939
const err = await new Promise<ExecException | null>(resolve => {
4040
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',
41+
'NODE_NO_WARNINGS=true npx ts-node -C ttypescript ./tests/from_dynamodb_record_transformer_error_src/object_literal_type_parameter.ts',
4242
err => {
4343
resolve(err);
4444
},

0 commit comments

Comments
 (0)