-
Notifications
You must be signed in to change notification settings - Fork 106
/
Copy pathoperators.js
198 lines (173 loc) · 6.83 KB
/
operators.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
'use strict';
/**
* Writes values from focus object onto base object.
*
* @param {Object} obj1 Base Object
* @param {Object} obj2 Focus Object
*/
function wrap_object(original, target) {
if (!target) return;
Object.keys(target).forEach((key) => {
if (typeof target[key] == 'object') {
if (Array.isArray(target[key])) return (original[key] = target[key]); // lgtm [js/prototype-pollution-utility]
if (original[key] === null || typeof original[key] !== 'object') original[key] = {};
wrap_object(original[key], target[key]);
} else {
original[key] = target[key];
}
});
}
/**
* This method parses route pattern into an array of expected path parameters.
*
* @param {String} pattern
* @returns {Array} [[key {String}, index {Number}], ...]
*/
function parse_path_parameters(pattern) {
let results = [];
let counter = 0;
if (pattern.indexOf('/:') > -1) {
let chunks = pattern.split('/').filter((chunk) => chunk.length > 0);
for (let index = 0; index < chunks.length; index++) {
let current = chunks[index];
if (current.startsWith(':') && current.length > 2) {
results.push([current.substring(1), counter]);
counter++;
}
}
}
return results;
}
/**
* This method converts ArrayBuffers to a string.
*
* @param {ArrayBuffer} array_buffer
* @param {String} encoding
* @returns {String} String
*/
function array_buffer_to_string(array_buffer, encoding = 'utf8') {
return Buffer.from(array_buffer).toString(encoding);
}
/**
* Copies an ArrayBuffer to a Uint8Array.
* Note! This method is supposed to be extremely performant as it is used by the Body parser.
* @param {ArrayBuffer} array_buffer
*/
function copy_array_buffer_to_uint8array(array_buffer) {
const source = new Uint8Array(array_buffer);
return new Uint8Array(source.subarray(0, source.length));
}
/**
* Returns a promise which is resolved after provided delay in milliseconds.
*
* @param {Number} delay
* @returns {Promise}
*/
function async_wait(delay) {
return new Promise((resolve, reject) => setTimeout((res) => res(), delay, resolve));
}
/**
* Merges provided relative paths into a singular relative path.
*
* @param {String} base_path
* @param {String} new_path
* @returns {String} path
*/
function merge_relative_paths(base_path, new_path) {
// handle both roots merger case
if (base_path == '/' && new_path == '/') return '/';
// Inject leading slash to new_path
if (!new_path.startsWith('/')) new_path = '/' + new_path;
// handle base root merger case
if (base_path == '/') return new_path;
// handle new path root merger case
if (new_path == '/') return base_path;
// strip away leading slash from base path
if (base_path.endsWith('/')) base_path = base_path.substr(0, base_path.length - 1);
// Merge path and add a slash in between if new_path does not have a starting slash
return `${base_path}${new_path}`;
}
/**
* Returns all property descriptors of an Object including extended prototypes.
*
* @param {Object} prototype
*/
function get_all_property_descriptors(prototype) {
// Retrieve initial property descriptors
const descriptors = Object.getOwnPropertyDescriptors(prototype);
// Determine if we have a parent prototype with a custom name
const parent = Object.getPrototypeOf(prototype);
if (parent && parent.constructor.name !== 'Object') {
// Merge and return property descriptors along with parent prototype
return Object.assign(descriptors, get_all_property_descriptors(parent));
}
// Return property descriptors
return descriptors;
}
/**
* Inherits properties, getters, and setters from one prototype to another with the ability to optionally define middleman methods.
*
* @param {Object} options
* @param {Object|Array<Object>} options.from - The prototype to inherit from
* @param {Object} options.to - The prototype to inherit to
* @param {function(('FUNCTION'|'GETTER'|'SETTER'), string, function):function=} options.method - The method to inherit. Parameters are: type, name, method.
* @param {function(string):string=} options.override - The method name to override the original with. Parameters are: name.
* @param {Array<string>} options.ignore - The property names to ignore
*/
function inherit_prototype({ from, to, method, override, ignore = ['constructor'] }) {
// Recursively call self if the from prototype is an Array of prototypes
if (Array.isArray(from)) return from.forEach((f) => inherit_prototype({ from: f, to, override, method, ignore }));
// Inherit the descriptors from the "from" prototype to the "to" prototype
const to_descriptors = get_all_property_descriptors(to);
const from_descriptors = get_all_property_descriptors(from);
Object.keys(from_descriptors).forEach((name) => {
// Ignore the properties specified in the ignore array
if (ignore.includes(name)) return;
// Destructure the descriptor function properties
const { value, get, set } = from_descriptors[name];
// Determine if this descriptor name would be an override
// Override the original name with the provided name resolver for overrides
if (typeof override == 'function' && to_descriptors[name]?.value) name = override(name) || name;
// Determine if the descriptor is a method aka. a function
if (typeof value === 'function') {
// Inject a middleman method into the "to" prototype
const middleman = method('FUNCTION', name, value);
if (middleman) {
// Define the middleman method on the "to" prototype
Object.defineProperty(to, name, {
configurable: true,
enumerable: true,
writable: true,
value: middleman,
});
}
} else {
// Initialize a definition object
const definition = {};
// Initialize a middleman getter method
if (typeof get === 'function') definition.get = method('GETTER', name, get);
// Initialize a middleman setter method
if (typeof set === 'function') definition.set = method('SETTER', name, set);
// Inject the definition into the "to" prototype
if (definition.get || definition.set) Object.defineProperty(to, name, definition);
}
});
}
/**
* Converts Windows path backslashes to forward slashes.
* @param {string} string
* @returns {string}
*/
function to_forward_slashes(string) {
return string.split('\\').join('/');
}
module.exports = {
parse_path_parameters,
array_buffer_to_string,
wrap_object,
async_wait,
inherit_prototype,
merge_relative_paths,
to_forward_slashes,
copy_array_buffer_to_uint8array,
};