UNPKG

9.33 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const core_1 = require("@plumjs/core");
5const reflect_1 = require("@plumjs/reflect");
6const debug_1 = tslib_1.__importDefault(require("debug"));
7const log = debug_1.default("plum:binder");
8function 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}
13function flattenConverters(converters) {
14 return converters.reduce((a, b) => { a.set(b.type, b.converter); return a; }, new Map());
15}
16exports.flattenConverters = flattenConverters;
17//some object can't simply convertible to string https://github.com/emberjs/ember.js/issues/14922#issuecomment-278986178
18function safeToString(value) {
19 try {
20 return value.toString();
21 }
22 catch (e) {
23 return "[object Object]";
24 }
25}
26/* ------------------------------------------------------------------------------- */
27/* ------------------------------- CONVERTER ------------------------------------- */
28/* ------------------------------------------------------------------------------- */
29function 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}
41exports.booleanConverter = booleanConverter;
42function 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}
50exports.numberConverter = numberConverter;
51function 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}
59exports.dateConverter = dateConverter;
60function defaultModelConverter(value, prop) {
61 //--- helper functions
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 //if the value already instance of the type then return immediately
72 //this is possible when using decorator binding such as @bind.request("req")
73 if (value instanceof prop.parameterType)
74 return value;
75 //get reflection metadata of the class
76 const reflection = reflect_1.reflect(prop.parameterType);
77 //check if the value is possible to convert to model
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 //sanitize excess property to prevent object having properties that doesn't match with declaration
82 //traverse through the object properties and convert to appropriate property's type
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 //crete new instance of the type and assigned the sanitized values
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}
107exports.defaultModelConverter = defaultModelConverter;
108function 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}
119exports.defaultArrayConverter = defaultArrayConverter;
120exports.DefaultConverterList = [
121 { type: Number, converter: numberConverter },
122 { type: Date, converter: dateConverter },
123 { type: Boolean, converter: booleanConverter }
124];
125function 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 //check if the parameter contains @array()
135 if (Array.isArray(prop.parameterType))
136 return defaultArrayConverter(value, Object.assign({}, prop, { parameterType: prop.parameterType }));
137 //check if parameter is native value that has default converter (Number, Date, Boolean) or if user provided converter
138 else if (prop.converters.has(prop.parameterType))
139 return prop.converters.get(prop.parameterType)(value, Object.assign({}, prop, { parameterType: prop.parameterType }));
140 //if type of model and has no converter, use DefaultObject converter
141 else if (core_1.isCustomClass(prop.parameterType))
142 return defaultModelConverter(value, Object.assign({}, prop, { parameterType: prop.parameterType }));
143 //no converter, return the value
144 else
145 return value;
146}
147exports.convert = convert;
148/* ------------------------------------------------------------------------------- */
149/* ----------------------------- BINDER FUNCTIONS -------------------------------- */
150/* ------------------------------------------------------------------------------- */
151function 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}
160function 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}
177function 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}
183function 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}
187function 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}
200exports.bindParameter = bindParameter;