Skip to content

Commit 5b75fea

Browse files
committed
Extract the duplicated logic to the external layer
Signed-off-by: moznion <moznion@mail.moznion.net>
1 parent 4e4e06b commit 5b75fea

File tree

4 files changed

+255
-500
lines changed

4 files changed

+255
-500
lines changed

lib/dynamodb_record_transformer.ts

Lines changed: 9 additions & 249 deletions
Original file line numberDiff line numberDiff line change
@@ -1,237 +1,30 @@
11
import ts from 'typescript';
2-
import {
3-
DynamodbPrimitiveTypes,
4-
dynamodbPrimitiveTypeFromTypeFlag,
5-
dynamodbPrimitiveTypeFromName,
6-
} from './dynamodb_primitive_types';
7-
import {
8-
ArrayField,
9-
DynamodbItemField,
10-
KeyValuePairMapField,
11-
MapField,
12-
PrimitiveField,
13-
SetField,
14-
} from './dynamodb_item_field';
15-
import { warn } from './logger';
2+
import { FieldsCollector } from './fields_collector';
163

174
export class DynamodbRecordTransformer {
18-
public static readonly dynamodbRecordFuncName = 'dynamodbRecord'; // TODO
5+
public static readonly funcName = 'dynamodbRecord';
196
private static readonly shouldLenientTypeCheck = !!process.env['TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK']; // TODO
207

218
public static visitNode(node: ts.CallExpression, typeChecker: ts.TypeChecker): ts.Node | undefined {
229
if (node.typeArguments === undefined || node.typeArguments.length !== 1 || !node.typeArguments[0]) {
2310
throw new Error(
24-
`No type argument on ${DynamodbRecordTransformer.dynamodbRecordFuncName}(). Please put a type argument on the function`,
11+
`No type argument on ${DynamodbRecordTransformer.funcName}(). Please put a type argument on the function`,
2512
);
2613
}
2714

2815
const typeName = node.typeArguments[0].getText();
29-
3016
if (node.arguments.length !== 1 || !node.arguments[0]) {
3117
throw new Error(
32-
`No argument on ${DynamodbRecordTransformer.dynamodbRecordFuncName}(). Please put an argument that has ${typeName} type on the function`,
18+
`No argument on ${DynamodbRecordTransformer.funcName}(). Please put an argument that has ${typeName} type on the function`,
3319
);
3420
}
3521

3622
const argVarNameIdent = ts.factory.createIdentifier('arg');
37-
const type = typeChecker.getTypeFromTypeNode(node.typeArguments[0]);
38-
const properties = typeChecker.getPropertiesOfType(type);
39-
const objectProps = properties
40-
.map((prop): DynamodbItemField | undefined => {
41-
if (
42-
!prop.valueDeclaration ||
43-
!ts.canHaveModifiers(prop.valueDeclaration) ||
44-
!DynamodbRecordTransformer.isPropertyModifierInArgumentSuitableForDynamodbAttr(
45-
ts.getModifiers(prop.valueDeclaration),
46-
)
47-
) {
48-
// skip it
49-
return undefined;
50-
}
51-
52-
const propName = prop.name;
53-
54-
if (
55-
prop.valueDeclaration.kind !== ts.SyntaxKind.Parameter &&
56-
prop.valueDeclaration.kind !== ts.SyntaxKind.PropertySignature
57-
) {
58-
const msg = `a property "${propName}" of the type "${typeName}" doesn't have parameter kind; maybe the ${typeName} is not class of interface`;
59-
if (DynamodbRecordTransformer.shouldLenientTypeCheck) {
60-
warn(msg);
61-
return undefined;
62-
}
63-
throw new Error(msg);
64-
}
65-
66-
const valueDeclSymbol = typeChecker.getTypeAtLocation(prop.valueDeclaration).symbol;
67-
const valueDeclSymbolName = valueDeclSymbol?.name;
68-
69-
if (valueDeclSymbolName === 'Array') {
70-
const typeArgs = typeChecker.getTypeArguments(
71-
typeChecker.getTypeAtLocation(prop.valueDeclaration) as ts.TypeReference,
72-
);
73-
const valueType = dynamodbPrimitiveTypeFromTypeFlag(typeArgs[0]?.flags);
74-
if (valueType === undefined) {
75-
const valueTypeName = typeArgs[0]?.symbol?.name;
76-
if (valueTypeName === 'Uint8Array') {
77-
return new ArrayField(propName, DynamodbPrimitiveTypes.Binary);
78-
}
79-
if (valueTypeName === 'BigInt') {
80-
return new ArrayField(propName, DynamodbPrimitiveTypes.Number);
81-
}
82-
83-
const msg = `a property "${propName}" of the type "${typeName}" has unsupported type: "${valueTypeName}"`;
84-
if (DynamodbRecordTransformer.shouldLenientTypeCheck) {
85-
warn(msg);
86-
return undefined;
87-
}
88-
throw new Error(msg);
89-
}
90-
return new ArrayField(propName, valueType);
91-
}
92-
if (valueDeclSymbolName == 'Set') {
93-
const typeArgs = typeChecker.getTypeArguments(
94-
typeChecker.getTypeAtLocation(prop.valueDeclaration) as ts.TypeReference,
95-
);
96-
const valueType = dynamodbPrimitiveTypeFromTypeFlag(typeArgs[0]?.flags);
97-
if (valueType === undefined) {
98-
const valueTypeName = typeArgs[0]?.symbol?.name;
99-
if (valueTypeName === 'Uint8Array') {
100-
return new SetField(
101-
propName,
102-
DynamodbPrimitiveTypes.Binary,
103-
DynamodbRecordTransformer.shouldLenientTypeCheck,
104-
);
105-
}
106-
if (valueTypeName === 'BigInt') {
107-
return new SetField(
108-
propName,
109-
DynamodbPrimitiveTypes.Number,
110-
DynamodbRecordTransformer.shouldLenientTypeCheck,
111-
);
112-
}
113-
114-
const msg = `a property "${propName}" of the type "${typeName}" has unsupported type: ${valueTypeName}`;
115-
if (DynamodbRecordTransformer.shouldLenientTypeCheck) {
116-
warn(msg);
117-
return undefined;
118-
}
119-
throw new Error(msg);
120-
}
121-
return new SetField(propName, valueType, DynamodbRecordTransformer.shouldLenientTypeCheck);
122-
}
123-
if (valueDeclSymbolName == 'Map') {
124-
const typeArgs = typeChecker.getTypeArguments(
125-
typeChecker.getTypeAtLocation(prop.valueDeclaration) as ts.TypeReference,
126-
);
127-
128-
const keyType = dynamodbPrimitiveTypeFromTypeFlag(typeArgs[0]?.flags);
129-
if (keyType === undefined) {
130-
const msg = `a Map type property "${propName}" of the type "${typeName}" has non-string key type`;
131-
if (DynamodbRecordTransformer.shouldLenientTypeCheck) {
132-
warn(msg);
133-
return undefined;
134-
}
135-
throw new Error(msg);
136-
}
137-
138-
const valueType = dynamodbPrimitiveTypeFromTypeFlag(typeArgs[1]?.flags);
139-
if (valueType === undefined) {
140-
const valueTypeName = typeArgs[1]?.symbol?.name;
141-
if (valueTypeName === 'Uint8Array') {
142-
return new MapField(
143-
propName,
144-
keyType,
145-
DynamodbPrimitiveTypes.Binary,
146-
DynamodbRecordTransformer.shouldLenientTypeCheck,
147-
);
148-
}
149-
if (valueTypeName === 'BigInt') {
150-
return new MapField(
151-
propName,
152-
keyType,
153-
DynamodbPrimitiveTypes.Number,
154-
DynamodbRecordTransformer.shouldLenientTypeCheck,
155-
);
156-
}
157-
158-
const msg = `a property "${propName}" of the type "${typeName}" has unsupported type: "${valueTypeName}"`;
159-
if (DynamodbRecordTransformer.shouldLenientTypeCheck) {
160-
warn(msg);
161-
return undefined;
162-
}
163-
throw new Error(msg);
164-
}
165-
166-
return new MapField(propName, keyType, valueType, DynamodbRecordTransformer.shouldLenientTypeCheck);
167-
}
168-
if ((valueDeclSymbol?.flags & ts.SymbolFlags.TypeLiteral) === ts.SymbolFlags.TypeLiteral) {
169-
// for key-value pair map notation
170-
const kvType = DynamodbRecordTransformer.extractKeyValueTypesFromKeyValuePairMapSyntax(
171-
prop.valueDeclaration.getChildren(),
172-
);
173-
174-
const keyTypeName = kvType?.[0];
175-
const keyType = dynamodbPrimitiveTypeFromName(keyTypeName);
176-
if (keyType === undefined) {
177-
const msg = `a Map type property "${propName}" of the type "${typeName}" has non-string key type: "${keyTypeName}"`;
178-
if (DynamodbRecordTransformer.shouldLenientTypeCheck) {
179-
warn(msg);
180-
return undefined;
181-
}
182-
throw new Error(msg);
183-
}
184-
185-
const valueTypeName = kvType?.[1];
186-
const valueType = dynamodbPrimitiveTypeFromName(valueTypeName);
187-
if (valueType === undefined) {
188-
const msg = `a property "${propName}" of the type "${typeName}" has unsupported type: "${valueTypeName}"`;
189-
if (DynamodbRecordTransformer.shouldLenientTypeCheck) {
190-
warn(msg);
191-
return undefined;
192-
}
193-
throw new Error(msg);
194-
}
195-
196-
return new KeyValuePairMapField(
197-
propName,
198-
keyType,
199-
valueType,
200-
DynamodbRecordTransformer.shouldLenientTypeCheck,
201-
);
202-
}
203-
204-
// primitive types
205-
if (valueDeclSymbolName === 'Uint8Array') {
206-
return new PrimitiveField(propName, DynamodbPrimitiveTypes.Binary);
207-
}
208-
209-
let colonTokenCame = false;
210-
for (const propNode of prop.valueDeclaration.getChildren()) {
211-
if (colonTokenCame) {
212-
const fieldType = dynamodbPrimitiveTypeFromName(propNode.getText());
213-
if (fieldType === undefined) {
214-
const msg = `a property "${propName}" of the type "${typeName}" has unsupported type: "${propNode.getText()}"`;
215-
if (DynamodbRecordTransformer.shouldLenientTypeCheck) {
216-
warn(msg);
217-
return undefined;
218-
}
219-
throw new Error(msg);
220-
}
221-
return new PrimitiveField(propName, fieldType);
222-
}
223-
colonTokenCame = propNode.kind == ts.SyntaxKind.ColonToken;
224-
}
225-
226-
// should never reach here
227-
228-
const msg = `unexpected error: a property "${propName}" of the type "${typeName}" has unsupported type`;
229-
if (DynamodbRecordTransformer.shouldLenientTypeCheck) {
230-
warn(msg);
231-
return undefined;
232-
}
233-
throw new Error(msg);
234-
})
23+
const objectProps = new FieldsCollector(
24+
DynamodbRecordTransformer.funcName,
25+
DynamodbRecordTransformer.shouldLenientTypeCheck,
26+
)
27+
.collectFields(node, typeChecker)
23528
.map(field => {
23629
return field?.generateCode(argVarNameIdent.text);
23730
})
@@ -250,37 +43,4 @@ export class DynamodbRecordTransformer {
25043
node.arguments[0],
25144
);
25245
}
253-
254-
private static extractKeyValueTypesFromKeyValuePairMapSyntax(nodes: ts.Node[]): [string, string] | undefined {
255-
for (const node of nodes) {
256-
if (node.kind === ts.SyntaxKind.TypeLiteral && node.getChildCount() === 3) {
257-
const kvTypeDeclNode = node.getChildAt(1);
258-
if (kvTypeDeclNode.kind === ts.SyntaxKind.SyntaxList && kvTypeDeclNode.getChildCount() === 1) {
259-
const kvTypeSignatureNode = kvTypeDeclNode.getChildAt(0);
260-
if (kvTypeSignatureNode.kind === ts.SyntaxKind.IndexSignature && kvTypeSignatureNode.getChildCount() === 5) {
261-
const valueType = kvTypeSignatureNode.getChildAt(4).getText();
262-
263-
const keyTypeDeclNode = kvTypeSignatureNode.getChildAt(1);
264-
if (keyTypeDeclNode.kind === ts.SyntaxKind.SyntaxList && keyTypeDeclNode.getChildCount() === 1) {
265-
const keyTypeSignatureNode = keyTypeDeclNode.getChildAt(0);
266-
if (keyTypeSignatureNode.kind === ts.SyntaxKind.Parameter && keyTypeSignatureNode.getChildCount() === 3) {
267-
return [keyTypeSignatureNode.getChildAt(2).getText(), valueType];
268-
}
269-
}
270-
}
271-
}
272-
break;
273-
}
274-
}
275-
return undefined;
276-
}
277-
278-
private static isPropertyModifierInArgumentSuitableForDynamodbAttr(
279-
modifiers: readonly ts.Modifier[] | undefined,
280-
): boolean {
281-
if (modifiers === undefined) {
282-
return true;
283-
}
284-
return !modifiers.map(m => m.kind).includes(ts.SyntaxKind.PrivateKeyword);
285-
}
28646
}

0 commit comments

Comments
 (0)