1 | 'use strict';
|
2 |
|
3 |
|
4 | const assert = require('assert');
|
5 | const EventEmitter = require('events');
|
6 | const path = require('path');
|
7 | const utils = require('./utils');
|
8 |
|
9 |
|
10 | const SUPPORT_ADAPTER_NAMES = ['express', 'socketio', 'rpc'];
|
11 | const REQUIRED_PARAM_KEYS = ['name'];
|
12 |
|
13 | class Method extends EventEmitter {
|
14 | static create(name, options, fn) {
|
15 | return new Method(name, options, fn);
|
16 | }
|
17 |
|
18 | constructor(name, options, fn) {
|
19 | super();
|
20 |
|
21 | this.name = name || options.name || utils.getName(fn);
|
22 | assert(typeof this.name === 'string', 'Method name must be a valid string');
|
23 | assert(typeof fn === 'function', 'Method fn must be a valid function');
|
24 |
|
25 |
|
26 | this.parent = null;
|
27 |
|
28 |
|
29 | this.stack = null;
|
30 |
|
31 |
|
32 | this.fn = fn;
|
33 |
|
34 |
|
35 | this.options = options || {};
|
36 |
|
37 |
|
38 | this._validateAndSetParams(options.params || options.accepts);
|
39 |
|
40 |
|
41 | this._validateAndSetAdapters(options.adapters || options.adapter);
|
42 |
|
43 |
|
44 | this._validateAndSetRoute(options.http || options.route);
|
45 |
|
46 |
|
47 | this._validateAndSetDocumentation(options);
|
48 |
|
49 | this._determineWhetherHooksShouldBeSkiped();
|
50 |
|
51 |
|
52 | this.multipart = options.multipart || {};
|
53 |
|
54 |
|
55 | this.extra = options.extra || {};
|
56 | }
|
57 |
|
58 |
|
59 | _validateAndSetParams(params) {
|
60 | params = params || [];
|
61 | if (!Array.isArray(params)) params = [params];
|
62 |
|
63 | let errorMessages = [];
|
64 |
|
65 | params.forEach((param, i) => {
|
66 | param = param || {};
|
67 | REQUIRED_PARAM_KEYS.forEach((key) => {
|
68 | if (!param[key]) errorMessages.push(`\`${key}\` is missing for params of \`${this.fullName()}\` method at ${i}`);
|
69 | });
|
70 | });
|
71 |
|
72 | if (errorMessages.length) {
|
73 | throw new Error(errorMessages.join('\n'));
|
74 | }
|
75 |
|
76 | this.params = params;
|
77 | }
|
78 |
|
79 |
|
80 | _validateAndSetAdapters(adapters) {
|
81 |
|
82 | if (!adapters) adapters = ['all'];
|
83 |
|
84 |
|
85 | if (!Array.isArray(adapters)) adapters = [adapters];
|
86 |
|
87 |
|
88 | if (~adapters.indexOf('all')) {
|
89 | this.adapters = SUPPORT_ADAPTER_NAMES.slice();
|
90 | }
|
91 |
|
92 | else {
|
93 | this.adapters = adapters;
|
94 |
|
95 | this.adapters.forEach((adapter) => {
|
96 | assert(
|
97 | ~SUPPORT_ADAPTER_NAMES.indexOf(adapter),
|
98 | `Invalid adapter name found: '${adapter}'`
|
99 | );
|
100 | });
|
101 | }
|
102 | }
|
103 |
|
104 |
|
105 | _validateAndSetRoute(route) {
|
106 | this.route = route || {};
|
107 |
|
108 | if (!this.route.verb) this.route.verb = 'all';
|
109 |
|
110 |
|
111 | this.route.verb = String(this.route.verb).toLowerCase();
|
112 |
|
113 |
|
114 | if (!this.route.path) this.route.path = '/';
|
115 | }
|
116 |
|
117 |
|
118 | _validateAndSetDocumentation(options) {
|
119 | this.description = options.description || options.desc || name;
|
120 | this.notes = options.notes;
|
121 | this.documented = options.documented !== false;
|
122 | }
|
123 |
|
124 |
|
125 | _determineWhetherHooksShouldBeSkiped() {
|
126 |
|
127 | this.skipHooks = this.route.verb === 'use';
|
128 | if (this.options.skipHooks === true) {
|
129 | this.skipHooks = true;
|
130 | } else if (this.options.skipHooks === false) {
|
131 | this.skipHooks = false;
|
132 | }
|
133 | }
|
134 |
|
135 |
|
136 | isSupport(adapterName) {
|
137 | return !!~this.adapters.indexOf(adapterName);
|
138 | }
|
139 |
|
140 | fullPath() {
|
141 | let segments = ['/'];
|
142 | if (this.parent) segments.push(this.parent.fullPath());
|
143 | segments.push(this.route.path);
|
144 | return path.join.apply(path, segments);
|
145 | }
|
146 |
|
147 | clone() {
|
148 | let method = new Method(
|
149 | this.name,
|
150 | Object.assign({}, this.options),
|
151 | this.fn
|
152 | );
|
153 |
|
154 | method.stack = null;
|
155 | method.adapters = this.adapters.slice();
|
156 | method.skipHooks = !!this.skipHooks;
|
157 | method.parent = this.parent;
|
158 | method.params = Object.assign([], this.params);
|
159 | method.description = this.description;
|
160 | method.notes = this.notes;
|
161 | method.documented = !!this.documented;
|
162 | method.route = Object.assign({}, this.route);
|
163 | method.multipart = Object.assign({}, this.multipart);
|
164 | method.extra = Object.assign({}, this.extra);
|
165 |
|
166 | return method;
|
167 | }
|
168 |
|
169 |
|
170 | fullName() {
|
171 | return this.parent ? `${this.parent.fullName()}.${this.name}` : this.name;
|
172 | }
|
173 |
|
174 |
|
175 | invoke() {
|
176 | assert(
|
177 | this.stack,
|
178 | `Method: '${this.fullName()}' must be composed before invoking`
|
179 | );
|
180 |
|
181 | return this.stack.apply(null, arguments);
|
182 | }
|
183 |
|
184 |
|
185 | compose(beforeStack, afterStack, afterErrorStack) {
|
186 | let stack = []
|
187 | .concat(this.skipHooks ? [] : (beforeStack || []))
|
188 | .concat(this.fn)
|
189 | .concat(this.skipHooks ? [] : (afterStack || []));
|
190 |
|
191 | let afterError = utils.compose(afterErrorStack || []);
|
192 |
|
193 | this.stack = utils.compose(stack, afterError);
|
194 | }
|
195 | }
|
196 |
|
197 | module.exports = Method;
|