1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const core_1 = require("@plumjs/core");
|
5 | const reflect_1 = require("@plumjs/reflect");
|
6 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
7 | const log = debug_1.default("plum:binder");
|
8 | function createConversionError(value, prop) {
|
9 | const type = Array.isArray(prop.parameterType) ? `Array<${prop.parameterType[0].name}>` : prop.parameterType.name;
|
10 | log(`[Converter] Unable to convert ${core_1.b(value)} into ${core_1.b(type)}`);
|
11 | return new core_1.ConversionError({ path: prop.path, type, value }, core_1.errorMessage.UnableToConvertValue.format(value, type, prop.path.join("->")));
|
12 | }
|
13 | function flattenConverters(converters) {
|
14 | return converters.reduce((a, b) => { a.set(b.type, b.converter); return a; }, new Map());
|
15 | }
|
16 | exports.flattenConverters = flattenConverters;
|
17 |
|
18 | function safeToString(value) {
|
19 | try {
|
20 | return value.toString();
|
21 | }
|
22 | catch (e) {
|
23 | return "[object Object]";
|
24 | }
|
25 | }
|
26 |
|
27 |
|
28 |
|
29 | function booleanConverter(rawValue, prop) {
|
30 | const value = safeToString(rawValue);
|
31 | const list = {
|
32 | on: true, true: true, "1": true, yes: true,
|
33 | off: false, false: false, "0": false, no: false
|
34 | };
|
35 | const result = list[value.toLowerCase()];
|
36 | log(`[Boolean Converter] Raw: ${core_1.b(value)} Value: ${core_1.b(result)}`);
|
37 | if (result === undefined)
|
38 | throw createConversionError(value, prop);
|
39 | return result;
|
40 | }
|
41 | exports.booleanConverter = booleanConverter;
|
42 | function numberConverter(rawValue, prop) {
|
43 | const value = safeToString(rawValue);
|
44 | const result = Number(value);
|
45 | if (isNaN(result) || value === "")
|
46 | throw createConversionError(value, prop);
|
47 | log(`[Number Converter] Raw: ${core_1.b(value)} Value: ${core_1.b(result)}`);
|
48 | return result;
|
49 | }
|
50 | exports.numberConverter = numberConverter;
|
51 | function dateConverter(rawValue, prop) {
|
52 | const value = safeToString(rawValue);
|
53 | const result = new Date(value);
|
54 | if (isNaN(result.getTime()) || value === "")
|
55 | throw createConversionError(value, prop);
|
56 | log(`[Date Converter] Raw: ${core_1.b(value)} Value: ${core_1.b(result)}`);
|
57 | return result;
|
58 | }
|
59 | exports.dateConverter = dateConverter;
|
60 | function defaultModelConverter(value, prop) {
|
61 |
|
62 | const isConvertibleToObject = (value) => {
|
63 | if (typeof value == "boolean" ||
|
64 | typeof value == "number" ||
|
65 | typeof value == "string")
|
66 | return false;
|
67 | else
|
68 | return true;
|
69 | };
|
70 |
|
71 |
|
72 |
|
73 | if (value instanceof prop.parameterType)
|
74 | return value;
|
75 |
|
76 | const reflection = reflect_1.reflect(prop.parameterType);
|
77 |
|
78 | if (!isConvertibleToObject(value))
|
79 | throw createConversionError(value, prop);
|
80 | log(`[Model Converter] converting ${core_1.b(value)} to ${core_1.b(prop.parameterType.name)}{${core_1.b(reflection.ctorParameters.map(x => x.name).join(", "))}}`);
|
81 |
|
82 |
|
83 | const sanitized = reflection.ctorParameters.map(x => ({
|
84 | name: x.name,
|
85 | value: convert(value[x.name], {
|
86 | parameterType: x.typeAnnotation,
|
87 | path: prop.path.concat(x.name),
|
88 | decorators: x.decorators,
|
89 | converters: prop.converters
|
90 | })
|
91 | })).reduce((a, b) => { a[b.name] = b.value; return a; }, {});
|
92 | log(`[Model Converter] Sanitized value ${core_1.b(sanitized)}`);
|
93 |
|
94 | try {
|
95 | return Object.assign(new prop.parameterType(), sanitized);
|
96 | }
|
97 | catch (e) {
|
98 | const message = core_1.errorMessage.UnableToInstantiateModel.format(prop.parameterType.name);
|
99 | if (e instanceof Error) {
|
100 | e.message = message + "\n" + e.message;
|
101 | throw e;
|
102 | }
|
103 | else
|
104 | throw new Error(message);
|
105 | }
|
106 | }
|
107 | exports.defaultModelConverter = defaultModelConverter;
|
108 | function defaultArrayConverter(value, prop) {
|
109 | if (!Array.isArray(value))
|
110 | throw createConversionError(value, prop);
|
111 | log(`[Array Converter] converting ${core_1.b(value)} to Array<${prop.parameterType[0].name}>`);
|
112 | return value.map(((x, i) => convert(x, {
|
113 | path: prop.path.concat(i.toString()),
|
114 | converters: prop.converters,
|
115 | decorators: [],
|
116 | parameterType: prop.parameterType[0]
|
117 | })));
|
118 | }
|
119 | exports.defaultArrayConverter = defaultArrayConverter;
|
120 | exports.DefaultConverterList = [
|
121 | { type: Number, converter: numberConverter },
|
122 | { type: Date, converter: dateConverter },
|
123 | { type: Boolean, converter: booleanConverter }
|
124 | ];
|
125 | function convert(value, prop) {
|
126 | if (value === null || value === undefined)
|
127 | return undefined;
|
128 | if (!prop.parameterType)
|
129 | return value;
|
130 | log(`[Converter] Path: ${core_1.b(prop.path.join("->"))} ` +
|
131 | `Source Type: ${core_1.b(typeof value)} ` +
|
132 | `Target Type:${core_1.b(prop.parameterType)} ` +
|
133 | `Value: ${core_1.b(value)}`);
|
134 |
|
135 | if (Array.isArray(prop.parameterType))
|
136 | return defaultArrayConverter(value, Object.assign({}, prop, { parameterType: prop.parameterType }));
|
137 |
|
138 | else if (prop.converters.has(prop.parameterType))
|
139 | return prop.converters.get(prop.parameterType)(value, Object.assign({}, prop, { parameterType: prop.parameterType }));
|
140 |
|
141 | else if (core_1.isCustomClass(prop.parameterType))
|
142 | return defaultModelConverter(value, Object.assign({}, prop, { parameterType: prop.parameterType }));
|
143 |
|
144 | else
|
145 | return value;
|
146 | }
|
147 | exports.convert = convert;
|
148 |
|
149 |
|
150 |
|
151 | function bindModel(action, request, par, converter) {
|
152 | if (!par.typeAnnotation)
|
153 | return;
|
154 | if (!core_1.isCustomClass(par.typeAnnotation))
|
155 | return;
|
156 | log(`[Model Binder] Action: ${core_1.b(action.name)} Parameter: ${core_1.b(par.name)} Parameter Type: ${core_1.b(par.typeAnnotation.name)}`);
|
157 | log(`[Model Binder] Request Body: ${core_1.b(request.body)}`);
|
158 | return converter(request.body);
|
159 | }
|
160 | function bindDecorator(action, request, par, converter) {
|
161 | const decorator = par.decorators.find((x) => x.type == "ParameterBinding");
|
162 | if (!decorator)
|
163 | return;
|
164 | log(`[Decorator Binder] Action: ${core_1.b(action.name)} Parameter: ${core_1.b(par.name)} Decorator: ${core_1.b(decorator.name)} Part: ${core_1.b(decorator.part)}`);
|
165 | const getDataOrPart = (data) => decorator.part ? data && data[decorator.part] : data;
|
166 | switch (decorator.name) {
|
167 | case "Body":
|
168 | return converter(getDataOrPart(request.body));
|
169 | case "Query":
|
170 | return converter(getDataOrPart(request.query));
|
171 | case "Header":
|
172 | return converter(getDataOrPart(request.headers));
|
173 | case "Request":
|
174 | return converter(getDataOrPart(request));
|
175 | }
|
176 | }
|
177 | function bindArrayDecorator(action, request, par, converter) {
|
178 | if (!Array.isArray(par.typeAnnotation))
|
179 | return;
|
180 | log(`[Array Binder] Action: ${core_1.b(action.name)} Parameter: ${core_1.b(par.name)} Type: ${core_1.b(par.typeAnnotation)}`);
|
181 | return converter(request.body, par.typeAnnotation);
|
182 | }
|
183 | function bindRegular(action, request, par, converter) {
|
184 | log(`[Regular Binder] Action: ${core_1.b(action.name)} Parameter: ${core_1.b(par.name)} Value: ${core_1.b(request.query[par.name])}`);
|
185 | return converter(request.query[par.name.toLowerCase()]);
|
186 | }
|
187 | function bindParameter(request, action, converter) {
|
188 | const mergedConverters = flattenConverters(exports.DefaultConverterList.concat(converter || []));
|
189 | return action.parameters.map(((x, i) => {
|
190 | const converter = (result, type) => convert(result, {
|
191 | path: [x.name], parameterType: type || x.typeAnnotation,
|
192 | converters: mergedConverters, decorators: x.decorators
|
193 | });
|
194 | return bindArrayDecorator(action, request, x, converter) ||
|
195 | bindDecorator(action, request, x, converter) ||
|
196 | bindModel(action, request, x, converter) ||
|
197 | bindRegular(action, request, x, converter);
|
198 | }));
|
199 | }
|
200 | exports.bindParameter = bindParameter;
|