1 | /**
|
2 | * @file Defines the Request class.
|
3 | *
|
4 | * @author Luke Chavers <luke@c2cschools.com>
|
5 | * @author Kevin Sanders <kevin@c2cschools.com>
|
6 | * @since 5.0.0
|
7 | * @license See LICENSE.md for details about licensing.
|
8 | * @copyright 2017 C2C Schools, LLC
|
9 | */
|
10 |
|
11 | ;
|
12 |
|
13 | const BaseClass = require( "@corefw/common" ).common.BaseClass;
|
14 | const ERRORS = require( "../errors" );
|
15 |
|
16 | /**
|
17 | * Represents an API request.
|
18 | *
|
19 | * @memberOf Request
|
20 | * @extends Common.BaseClass
|
21 | */
|
22 | class Request extends BaseClass {
|
23 |
|
24 | /**
|
25 | * @inheritDoc
|
26 | */
|
27 | _initialize( cfg ) {
|
28 |
|
29 | // const me = this;
|
30 |
|
31 | // Call parent
|
32 | super._initialize( cfg );
|
33 | }
|
34 |
|
35 | /**
|
36 | * Validate the request.
|
37 | *
|
38 | * @returns {Promise<Request.Request>} This request.
|
39 | */
|
40 | validate() {
|
41 |
|
42 | const me = this;
|
43 |
|
44 | // Dependencies
|
45 | const BB = me.$dep( "bluebird" );
|
46 |
|
47 | return BB.try( function () {
|
48 |
|
49 | return me._validateParameters();
|
50 |
|
51 | } ).then( function () {
|
52 |
|
53 | return me._validateBody();
|
54 |
|
55 | } ).then( function () {
|
56 |
|
57 | return me;
|
58 | } );
|
59 | }
|
60 |
|
61 | /**
|
62 | * The {@link ExecutionContext.BaseExecutionContext} that built this
|
63 | * Request object.
|
64 | *
|
65 | * @public
|
66 | * @type {?ExecutionContext.BaseExecutionContext}
|
67 | * @default null
|
68 | */
|
69 | get context() {
|
70 |
|
71 | const me = this;
|
72 |
|
73 | return me.getConfigValue( "context", null );
|
74 | }
|
75 |
|
76 | set context( /** ?ExecutionContext.BaseExecutionContext */ val ) {
|
77 |
|
78 | const me = this;
|
79 |
|
80 | val.$adopt( me );
|
81 | me.setConfigValue( "context", val );
|
82 | }
|
83 |
|
84 | /**
|
85 | * The request body data passed in by the client.
|
86 | *
|
87 | * @public
|
88 | * @returns {Object|null} Request body.
|
89 | */
|
90 | get body() {
|
91 |
|
92 | const me = this;
|
93 |
|
94 | return me.getConfigValue( "body", null );
|
95 | }
|
96 |
|
97 | set body( val ) {
|
98 |
|
99 | const me = this;
|
100 |
|
101 | me.setConfigValue( "body", val );
|
102 | }
|
103 |
|
104 | /**
|
105 | * Get the raw parameter data passed in by the client, which does not
|
106 | * include any schema information about the parameter.
|
107 | *
|
108 | * @public
|
109 | * @returns {Object|null} Raw parameters.
|
110 | */
|
111 | getRawParameters() {
|
112 |
|
113 | const me = this;
|
114 |
|
115 | return me.getConfigValue( "rawParameters", null );
|
116 | }
|
117 |
|
118 | /**
|
119 | * Set the raw parameter data passed in by the client, which does not
|
120 | * include any schema information about the parameter.
|
121 | *
|
122 | * @public
|
123 | * @param {Object|null} rawParameters - Raw parameters.
|
124 | * @returns {Promise<void>} Empty promise.
|
125 | */
|
126 | setRawParameters( rawParameters ) {
|
127 |
|
128 | const me = this;
|
129 |
|
130 | // Dependencies
|
131 | const BB = me.$dep( "bluebird" );
|
132 |
|
133 | me.setConfigValue( "rawParameters", rawParameters );
|
134 |
|
135 | // FIXME: breaks response formatting for error responses
|
136 | // return BB.try( function () {
|
137 | //
|
138 | // return me._validateParameters();
|
139 | //
|
140 | // } ).then( function () {
|
141 | //
|
142 | // return BB.resolve();
|
143 | // } );
|
144 |
|
145 | return BB.resolve();
|
146 | }
|
147 |
|
148 | /**
|
149 | * The fully resolved parameter information, which includes schema
|
150 | * information and the current value of each parameter.
|
151 | *
|
152 | * This method will return ALL parameters, even if the client did not
|
153 | * specify a value for it and the parameter does not have a default
|
154 | * value.
|
155 | *
|
156 | * @public
|
157 | * @type {?Object}
|
158 | * @readonly
|
159 | * @default null
|
160 | */
|
161 | get parameters() {
|
162 |
|
163 | const me = this;
|
164 |
|
165 | return me.getConfigValue( "parameters", null );
|
166 | }
|
167 |
|
168 | /**
|
169 | * The fully resolved parameter information, which includes schema
|
170 | * information and the current value of each parameter.
|
171 | *
|
172 | * This method will only return parameters that have values, which includes
|
173 | * parameters that had values passed in by the client OR those that were
|
174 | * not passed in by the client but have default values defined.
|
175 | *
|
176 | * @public
|
177 | * @type {?Object}
|
178 | * @readonly
|
179 | * @default null
|
180 | */
|
181 | get parametersWithValues() {
|
182 |
|
183 | const me = this;
|
184 |
|
185 | // Dependencies
|
186 | const _ = me.$dep( "lodash" );
|
187 |
|
188 | let params = me.parameters;
|
189 | let ret = {};
|
190 |
|
191 | if ( params === null ) {
|
192 |
|
193 | return null;
|
194 | }
|
195 |
|
196 | _.each( params, function ( p, key ) {
|
197 |
|
198 | if ( p.hasValue ) {
|
199 |
|
200 | ret[ key ] = p;
|
201 | }
|
202 | } );
|
203 |
|
204 | return ret;
|
205 | }
|
206 |
|
207 | /**
|
208 | * A plain object containing parameters and their values.
|
209 | *
|
210 | * This method will only return parameters that have values, which includes
|
211 | * parameters that had values passed in by the client OR those that were
|
212 | * not passed in by the client but have default values defined.
|
213 | *
|
214 | * @public
|
215 | * @type {Object}
|
216 | * @readonly
|
217 | * @default {}
|
218 | */
|
219 | get parameterValues() {
|
220 |
|
221 | const me = this;
|
222 |
|
223 | // Dependencies
|
224 | const _ = me.$dep( "lodash" );
|
225 |
|
226 | let params = me.parametersWithValues;
|
227 | let ret = {};
|
228 |
|
229 | _.each( params, function ( v, k ) {
|
230 |
|
231 | ret[ k ] = v.value;
|
232 | } );
|
233 |
|
234 | return ret;
|
235 | }
|
236 |
|
237 | /**
|
238 | * Returns the value of the specified parameter or NULL.
|
239 | *
|
240 | * @param {string} name - Parameter name.
|
241 | * @returns {*} The parameter value or NULL if the parameter does not exist.
|
242 | */
|
243 | getParameterValue( name ) {
|
244 |
|
245 | const me = this;
|
246 |
|
247 | let params = me.parameters;
|
248 |
|
249 | let param = params[ name ];
|
250 |
|
251 | if ( param ) {
|
252 |
|
253 | return param.value;
|
254 | }
|
255 |
|
256 | return null;
|
257 | }
|
258 |
|
259 | /**
|
260 | * Request offset parameter value.
|
261 | *
|
262 | * @readonly
|
263 | * @returns {number} Request offset parameter value.
|
264 | */
|
265 | get offset() {
|
266 |
|
267 | const me = this;
|
268 |
|
269 | let pageNumber = me.pageNumber;
|
270 | let pageSize = me.pageSize;
|
271 |
|
272 | let offset = ( pageNumber - 1 ) * pageSize;
|
273 |
|
274 | if ( offset < 0 ) {
|
275 |
|
276 | offset = 0;
|
277 | }
|
278 |
|
279 | return offset;
|
280 | }
|
281 |
|
282 | /**
|
283 | * Request limit parameter value.
|
284 | *
|
285 | * @readonly
|
286 | * @returns {number} Request limit parameter value.
|
287 | */
|
288 | get limit() {
|
289 |
|
290 | const me = this;
|
291 |
|
292 | return me.pageSize;
|
293 | }
|
294 |
|
295 | /**
|
296 | * Page number parameter value.
|
297 | *
|
298 | * @readonly
|
299 | * @returns {number} Page number parameter value.
|
300 | */
|
301 | get pageNumber() {
|
302 |
|
303 | const me = this;
|
304 |
|
305 | return me.getParameterValue( "pageNumber" );
|
306 | }
|
307 |
|
308 | /**
|
309 | * Page size parameter value.
|
310 | *
|
311 | * @readonly
|
312 | * @returns {number} Page size parameter value.
|
313 | */
|
314 | get pageSize() {
|
315 |
|
316 | const me = this;
|
317 |
|
318 | return me.getParameterValue( "pageSize" );
|
319 | }
|
320 |
|
321 | /**
|
322 | * Sort parameter value.
|
323 | *
|
324 | * @readonly
|
325 | * @returns {number} Sort parameter value.
|
326 | */
|
327 | get sort() {
|
328 |
|
329 | const me = this;
|
330 |
|
331 | return me.getParameterValue( "sort" );
|
332 | }
|
333 |
|
334 | /**
|
335 | * The endpoint that this Request was submitted to.
|
336 | *
|
337 | * @public
|
338 | * @type {?Endpoint.BaseEndpoint}
|
339 | * @default null
|
340 | * @readonly
|
341 | */
|
342 | get endpoint() {
|
343 |
|
344 | const me = this;
|
345 |
|
346 | if ( me.context === null ) {
|
347 |
|
348 | return null;
|
349 | }
|
350 |
|
351 | return me.context.endpoint;
|
352 | }
|
353 |
|
354 | /**
|
355 | * The request schema for the endpoint that this request was submitted to.
|
356 | *
|
357 | * @public
|
358 | * @returns {Promise<Object|null>} Request schema.
|
359 | */
|
360 | getSchema() {
|
361 |
|
362 | const me = this;
|
363 |
|
364 | // Dependencies
|
365 | const BB = require( "bluebird" );
|
366 |
|
367 | if ( me.endpoint === null ) {
|
368 |
|
369 | return BB.resolve( null );
|
370 | }
|
371 |
|
372 | return me.endpoint.getRequestSchema();
|
373 | }
|
374 |
|
375 | /**
|
376 | * The parameter portion of the request schema.
|
377 | *
|
378 | * @public
|
379 | * @returns {Promise<Object|null>} Parameter schema.
|
380 | */
|
381 | getParameterSchema() {
|
382 |
|
383 | const me = this;
|
384 |
|
385 | // Dependencies
|
386 | const BB = require( "bluebird" );
|
387 |
|
388 | return BB.try( function () {
|
389 |
|
390 | return me.getSchema();
|
391 |
|
392 | } ).then( function ( schema ) {
|
393 |
|
394 | if ( schema && schema.properties && schema.properties.parameters ) {
|
395 |
|
396 | return schema.properties.parameters;
|
397 | }
|
398 |
|
399 | return BB.resolve( null );
|
400 | } );
|
401 | }
|
402 |
|
403 | /**
|
404 | * The body portion of the request schema.
|
405 | *
|
406 | * @public
|
407 | * @returns {Promise<Object|null>} Body schema.
|
408 | */
|
409 | getBodySchema() {
|
410 |
|
411 | const me = this;
|
412 |
|
413 | // Dependencies
|
414 | const BB = require( "bluebird" );
|
415 |
|
416 | return BB.try( function () {
|
417 |
|
418 | return me.getSchema();
|
419 |
|
420 | } ).then( function ( schema ) {
|
421 |
|
422 | if ( schema && schema.properties && schema.properties.body ) {
|
423 |
|
424 | return schema.properties.body;
|
425 | }
|
426 |
|
427 | return BB.resolve( null );
|
428 | } );
|
429 | }
|
430 |
|
431 | /**
|
432 | * The primary model of the endpoint that this request was submitted to.
|
433 | *
|
434 | * @public
|
435 | * @type {?Model.BaseModel}
|
436 | * @readonly
|
437 | * @default null
|
438 | */
|
439 | get model() {
|
440 |
|
441 | const me = this;
|
442 |
|
443 | if ( me.endpoint === null ) {
|
444 |
|
445 | return null;
|
446 | }
|
447 |
|
448 | return me.endpoint.model;
|
449 | }
|
450 |
|
451 | /**
|
452 | * The encoded session token used in this Request. This will either be
|
453 | * passed in by the client or generated (for some special purpose) by the
|
454 | * {@link Session.SessionManager}.
|
455 | *
|
456 | * @public
|
457 | * @type {?string}
|
458 | * @default null
|
459 | */
|
460 | get token() {
|
461 |
|
462 | const me = this;
|
463 |
|
464 | return me.getConfigValue( "token", null );
|
465 | }
|
466 |
|
467 | set token( /** ?string */ val ) {
|
468 |
|
469 | const me = this;
|
470 |
|
471 | me.setConfigValue( "token", val );
|
472 | }
|
473 |
|
474 | /**
|
475 | * The decoded data from the session token used in this Request. This will
|
476 | * either be passed in by the client or generated (for some special purpose)
|
477 | * by the {@link Session.SessionManager}.
|
478 | *
|
479 | * @public
|
480 | * @type {?string}
|
481 | * @default null
|
482 | */
|
483 | get tokenData() {
|
484 |
|
485 | const me = this;
|
486 |
|
487 | return me.getConfigValue( "tokenData", null );
|
488 | }
|
489 |
|
490 | set tokenData( /** ?string */ val ) {
|
491 |
|
492 | const me = this;
|
493 |
|
494 | me.setConfigValue( "tokenData", val );
|
495 | }
|
496 |
|
497 | /**
|
498 | * The username of the client making the request, which is extracted from
|
499 | * the session token.
|
500 | *
|
501 | * @public
|
502 | * @type {?string}
|
503 | * @default null
|
504 | * @readonly
|
505 | */
|
506 | get username() {
|
507 |
|
508 | const me = this;
|
509 |
|
510 | if ( me.tokenData === null ) {
|
511 |
|
512 | return null;
|
513 | }
|
514 |
|
515 | return me.tokenData.data.username;
|
516 | }
|
517 |
|
518 | /**
|
519 | * The userId (which is a UUID) of the client making the request, which is
|
520 | * extracted from the session token.
|
521 | *
|
522 | * @public
|
523 | * @type {?string}
|
524 | * @default null
|
525 | * @readonly
|
526 | */
|
527 | get userId() {
|
528 |
|
529 | const me = this;
|
530 |
|
531 | if ( me.tokenData === null ) {
|
532 |
|
533 | return null;
|
534 | }
|
535 |
|
536 | return me.tokenData.userId;
|
537 | }
|
538 |
|
539 | /**
|
540 | * The personId (which is a UUID) of the client making the request, which is
|
541 | * extracted from the session token.
|
542 | *
|
543 | * @public
|
544 | * @type {?string}
|
545 | * @default null
|
546 | * @readonly
|
547 | */
|
548 | get personId() {
|
549 |
|
550 | const me = this;
|
551 |
|
552 | if ( me.tokenData === null ) {
|
553 |
|
554 | return null;
|
555 | }
|
556 |
|
557 | return me.tokenData.data.personId;
|
558 | }
|
559 |
|
560 | /**
|
561 | * The sessionId (which is a UUID) of the client's session, which is
|
562 | * extracted from the session token.
|
563 | *
|
564 | * @public
|
565 | * @type {?string}
|
566 | * @default null
|
567 | * @readonly
|
568 | */
|
569 | get sessionId() {
|
570 |
|
571 | const me = this;
|
572 |
|
573 | if ( me.tokenData === null ) {
|
574 |
|
575 | return null;
|
576 | }
|
577 |
|
578 | return me.tokenData.data.sessionId;
|
579 | }
|
580 |
|
581 | /**
|
582 | * The namespace of the client's session, which is extracted from the
|
583 | * session token.
|
584 | *
|
585 | * Currently, this is not used, but exists for future compatibility with
|
586 | * a planned extension of the MSA architecture that will make it more
|
587 | * versatile and reusable.
|
588 | *
|
589 | * @public
|
590 | * @type {?string}
|
591 | * @default null
|
592 | * @readonly
|
593 | */
|
594 | get namespace() {
|
595 |
|
596 | const me = this;
|
597 |
|
598 | if ( me.tokenData === null ) {
|
599 |
|
600 | return "default";
|
601 | }
|
602 |
|
603 | return me.tokenData.data.ns;
|
604 | }
|
605 |
|
606 | // noinspection JSUnusedGlobalSymbols
|
607 | /**
|
608 | * The version of the session token's format, which is extracted from the
|
609 | * session token.
|
610 | *
|
611 | * @public
|
612 | * @type {?number}
|
613 | * @default null
|
614 | * @readonly
|
615 | */
|
616 | get tokenVersion() {
|
617 |
|
618 | const me = this;
|
619 |
|
620 | if ( me.tokenData === null ) {
|
621 |
|
622 | return null;
|
623 | }
|
624 |
|
625 | return me.tokenData.data.v;
|
626 | }
|
627 |
|
628 | // noinspection JSUnusedGlobalSymbols
|
629 | /**
|
630 | * The ip address of the requesting client, which is extracted from the
|
631 | * session token.
|
632 | *
|
633 | * Because this value is extracted from the session token, the ip address
|
634 | * will be that of the client that created the session, which is not
|
635 | * necessarily the same ip address as the client making this, specific,
|
636 | * request.
|
637 | *
|
638 | * @public
|
639 | * @type {?string}
|
640 | * @default null
|
641 | * @readonly
|
642 | */
|
643 | get tokenClientIp() {
|
644 |
|
645 | const me = this;
|
646 |
|
647 | if ( me.tokenData === null ) {
|
648 |
|
649 | return null;
|
650 | }
|
651 |
|
652 | return me.tokenData.sourceIp;
|
653 | }
|
654 |
|
655 | // noinspection JSUnusedGlobalSymbols
|
656 | /**
|
657 | * Session flags for the current session, which is extracted from the
|
658 | * session token.
|
659 | *
|
660 | * Session flags are used in special processing logic, especially logic
|
661 | * related to access control. For example, tokens with the "system" flag
|
662 | * will (for the most part) bypass ACM checks.
|
663 | *
|
664 | * Session flags are also persisted to each log event emitted by endpoints,
|
665 | * which can be useful for various types of analysis.
|
666 | *
|
667 | * @public
|
668 | * @type {string[]}
|
669 | * @default []
|
670 | * @readonly
|
671 | */
|
672 | get tokenFlags() {
|
673 |
|
674 | const me = this;
|
675 |
|
676 | if ( me.tokenData === null ) {
|
677 |
|
678 | return [ "no-token" ];
|
679 | }
|
680 |
|
681 | return me.tokenData.data.flags;
|
682 | }
|
683 |
|
684 | /**
|
685 | * This method will convert the rawParameters, which is a plain one
|
686 | * dimensional object, into a more standardized object that includes
|
687 | * schema information. The information added by this process may
|
688 | * be useful to other validators and parameter parsers that exist
|
689 | * in other parts of the application.
|
690 | *
|
691 | * @private
|
692 | * @returns {Promise<void>} This method stores its results internally as
|
693 | * the 'parameters' property.
|
694 | */
|
695 | _normalizeRawParameters() {
|
696 |
|
697 | const me = this;
|
698 |
|
699 | // Dependencies
|
700 | const BB = me.$dep( "bluebird" );
|
701 | const TIPE = me.$dep( "tipe" );
|
702 | const _ = me.$dep( "lodash" );
|
703 |
|
704 | return BB.try( function () {
|
705 |
|
706 | return BB.all( [
|
707 | me.getParameterSchema(),
|
708 | me.getRawParameters(),
|
709 | ] );
|
710 |
|
711 | } ).then( function ( [ schema, params ] ) {
|
712 |
|
713 | let normalized = {};
|
714 |
|
715 | // Skip normalization if we're missing the
|
716 | // parameters or the schema...
|
717 | if ( params === null || schema === null ) {
|
718 |
|
719 | return;
|
720 | }
|
721 |
|
722 | if ( schema.properties === undefined ) {
|
723 |
|
724 | return;
|
725 | }
|
726 |
|
727 | // Iterate over the parameters defined within the schema.
|
728 | _.each( schema.properties, function ( paramSchema, paramKey ) {
|
729 |
|
730 | let p = normalized[ paramKey ] = {
|
731 | hasValue : false,
|
732 | value : null,
|
733 | provided : false,
|
734 | hasDefault : false,
|
735 | defaultValue : null,
|
736 | key : paramKey,
|
737 | schema : paramSchema,
|
738 | };
|
739 |
|
740 | // Create a few meta fields related to defaults
|
741 | if ( p.schema.default !== undefined ) {
|
742 |
|
743 | p.hasDefault = true;
|
744 | p.defaultValue = paramSchema.default;
|
745 | }
|
746 |
|
747 | // Apply the param value, if it was provided...
|
748 | if ( params[ paramKey ] !== undefined ) {
|
749 |
|
750 | p.provided = true;
|
751 | p.value = params[ paramKey ];
|
752 | p.hasValue = true;
|
753 | }
|
754 |
|
755 | // Apply defaults, as applicable
|
756 | if ( !p.hasValue && p.hasDefault ) {
|
757 |
|
758 | p.value = p.defaultValue;
|
759 | p.hasValue = true;
|
760 | }
|
761 |
|
762 | // Additional parsing for certain field types...
|
763 | if ( p.hasValue ) {
|
764 |
|
765 | if ( TIPE( p.schema.format ) === "string" ) {
|
766 |
|
767 | switch ( p.schema.format.toLowerCase() ) {
|
768 |
|
769 | case "uuid":
|
770 | case "uuid-plus":
|
771 | me._normalizeUuidParamValue( p );
|
772 | break;
|
773 |
|
774 | default:
|
775 | break;
|
776 | }
|
777 | }
|
778 |
|
779 | if ( TIPE( p.schema.type ) === "string" ) {
|
780 |
|
781 | switch ( p.schema.type.toLowerCase() ) {
|
782 |
|
783 | case "array":
|
784 | me._normalizeArrayParamValue( p );
|
785 | break;
|
786 |
|
787 | case "integer":
|
788 | me._normalizeIntegerParamValue( p );
|
789 | break;
|
790 |
|
791 | default:
|
792 | break;
|
793 | }
|
794 | }
|
795 | }
|
796 | } );
|
797 |
|
798 | // Persist
|
799 | me.setConfigValue( "parameters", normalized );
|
800 | } );
|
801 | }
|
802 |
|
803 | _normalizeArrayParamValue( paramInfoObject ) {
|
804 |
|
805 | const me = this;
|
806 |
|
807 | // Dependencies
|
808 | const TIPE = me.$dep( "tipe" );
|
809 | const _ = me.$dep( "lodash" );
|
810 |
|
811 | // FIXME: should parameters already be desearialized by the API gateway before being processed by the endpoint?
|
812 | //
|
813 | // let val = paramInfoObject.value;
|
814 | //
|
815 | // console.log(paramInfoObject);
|
816 | //
|
817 | // switch ( TIPE( val ) ) {
|
818 | //
|
819 | // case "string":
|
820 | //
|
821 | // // FIXME: how to handle escaped commas? CSV format?
|
822 | // val = val.split( "," );
|
823 | // break;
|
824 | //
|
825 | // default:
|
826 | //
|
827 | // // Do nothing
|
828 | //
|
829 | // break;
|
830 | // }
|
831 | //
|
832 | // paramInfoObject.value = val;
|
833 |
|
834 | let items = paramInfoObject.schema.items;
|
835 |
|
836 | _.each( items, function ( item ) {
|
837 |
|
838 | let format = item.format;
|
839 |
|
840 | if ( TIPE( format ) === "string" ) {
|
841 |
|
842 | switch ( format.toLowerCase() ) {
|
843 |
|
844 | case "uuid":
|
845 | case "uuid-plus":
|
846 | me._normalizeUuidParamValue( paramInfoObject );
|
847 | break;
|
848 |
|
849 | default:
|
850 | break;
|
851 | }
|
852 | }
|
853 | } );
|
854 | }
|
855 |
|
856 | /**
|
857 | * This is a helper method for the `#_normalizeRawParameters()` method,
|
858 | * which normalizes the value for UUID fields, when they'r provided.
|
859 | *
|
860 | * @private
|
861 | * @param {Object} paramInfoObject - An object describing a parameter, its
|
862 | * schema, and its value.
|
863 | * @returns {void} All modifications are made ByRef
|
864 | */
|
865 | _normalizeUuidParamValue( paramInfoObject ) {
|
866 |
|
867 | const me = this;
|
868 |
|
869 | // Dependencies
|
870 | const _ = me.$dep( "lodash" );
|
871 | // const ERRORS = me.$dep( "errors" );
|
872 |
|
873 | let val = paramInfoObject.value;
|
874 | let final = [];
|
875 | let isUuidPlus = false;
|
876 |
|
877 | // Check for "plus" designation, which
|
878 | // will loosen the validation rules to
|
879 | // allow for non-uuid values.
|
880 | if ( paramInfoObject.schema.format === "uuid-plus" ) {
|
881 |
|
882 | isUuidPlus = true;
|
883 |
|
884 | // For UUID plus fields, it may also be helpful
|
885 | // for us to keep track of the values that are
|
886 | // valid UUIDs.
|
887 | paramInfoObject.uuidsAt = [];
|
888 | }
|
889 |
|
890 | // Check for commas...
|
891 | if ( val.indexOf( "," ) !== -1 ) {
|
892 |
|
893 | val = val.split( "," );
|
894 |
|
895 | } else {
|
896 |
|
897 | // No commas, but...
|
898 | // We're going to force it to an array anyway,
|
899 | // so that the next steps will be easier.
|
900 | val = [ val ];
|
901 | }
|
902 |
|
903 | // Make all values unique...
|
904 | val = _.uniq( val );
|
905 |
|
906 | // Normalize each UUID
|
907 | _.each( val, function ( unparsed ) {
|
908 |
|
909 | // Trimming couldn't hurt...
|
910 | unparsed = _.trim( unparsed );
|
911 |
|
912 | // Ignore blanks...
|
913 | if ( unparsed !== "" ) {
|
914 |
|
915 | // Create a parsed value,
|
916 | // ...which should be all lower case
|
917 | let parsed = unparsed.toLowerCase();
|
918 |
|
919 | // ...and without non-uuid characters.
|
920 | parsed = parsed.replace( /[^a-f0-9]/g, "" );
|
921 |
|
922 | // Validate...
|
923 | if ( parsed.length !== 32 ) {
|
924 |
|
925 | // This isn't a valid UUID.
|
926 | // Let's see if we're allowing them.
|
927 |
|
928 | if ( isUuidPlus ) {
|
929 |
|
930 | // We're allowing non-uuids, so, we'll
|
931 | // just try to preserve this value as it
|
932 | // was before we parsed anything.
|
933 | final.push( unparsed );
|
934 |
|
935 | } else {
|
936 |
|
937 | // We're not allowing non-UUID values, so
|
938 | // this is a validation error...
|
939 | throw new ERRORS.common.InvalidParameterError(
|
940 | "Invalid request parameter '" + paramInfoObject.key + "': " +
|
941 | "Invalid string format (expected UUID)"
|
942 | );
|
943 | }
|
944 |
|
945 | } else {
|
946 |
|
947 | // This looks like a proper UUID (though there is,
|
948 | // admittedly, a small chance for screw-ups here,
|
949 | // I will address it later...)
|
950 |
|
951 | // todo: scrutinize the value more closely to ensure that it is a UUID
|
952 |
|
953 | // Insert dashes...
|
954 | parsed =
|
955 | parsed.substr( 0, 8 ) + "-" +
|
956 | parsed.substr( 8, 4 ) + "-" +
|
957 | parsed.substr( 12, 4 ) + "-" +
|
958 | parsed.substr( 16, 4 ) + "-" +
|
959 | parsed.substr( 20 );
|
960 |
|
961 | // Track this as a valid UUID
|
962 | if ( isUuidPlus ) {
|
963 |
|
964 | paramInfoObject.uuidsAt.push( final.length );
|
965 | }
|
966 |
|
967 | final.push( parsed );
|
968 | }
|
969 | }
|
970 | } );
|
971 |
|
972 | // If we have an empty array after processing, then we'll
|
973 | // count this parameter as not being provided...
|
974 | if ( final.length === 0 ) {
|
975 |
|
976 | paramInfoObject.hasValue = false;
|
977 | paramInfoObject.value = null;
|
978 | paramInfoObject.provided = false;
|
979 | paramInfoObject.uuidsAt = [];
|
980 |
|
981 | } else if ( final.length === 1 ) {
|
982 |
|
983 | // If there's only one value, we'll convert it back to a string
|
984 | paramInfoObject.value = final[ 0 ];
|
985 |
|
986 | } else {
|
987 |
|
988 | // Otherwise, just store it...
|
989 | paramInfoObject.value = final;
|
990 | }
|
991 | }
|
992 |
|
993 | // noinspection JSMethodCanBeStatic
|
994 | /**
|
995 | * This is a helper method for the `#_normalizeRawParameters()` method,
|
996 | * which normalizes the value for integer fields, when they'r provided.
|
997 | *
|
998 | * @private
|
999 | * @param {Object} paramInfoObject - An object describing a parameter, its
|
1000 | * schema, and its value.
|
1001 | * @returns {void} All modifications are made ByRef
|
1002 | */
|
1003 | _normalizeIntegerParamValue( paramInfoObject ) {
|
1004 |
|
1005 | let val = paramInfoObject.value;
|
1006 |
|
1007 | // First, cast to a string...
|
1008 | val = String( val );
|
1009 |
|
1010 | // Remove decimals (if they exist)
|
1011 | if ( val.indexOf( "." ) !== -1 ) {
|
1012 |
|
1013 | let spl = val.split( "." );
|
1014 |
|
1015 | val = spl[ 0 ];
|
1016 | }
|
1017 |
|
1018 | // Remove invalid characters
|
1019 | val = val.replace( /[^0-9]/g, "" );
|
1020 |
|
1021 | // Cast back to a number
|
1022 | val = parseInt( val, 10 );
|
1023 |
|
1024 | // Save it
|
1025 | paramInfoObject.value = val;
|
1026 | }
|
1027 |
|
1028 | /**
|
1029 | * This is the main entry point for parameter validation
|
1030 | * against a predefined request schema. This method will be called
|
1031 | * whenever the 'rawParameters' of this Request object are updated.
|
1032 | *
|
1033 | * @private
|
1034 | * @throws UnrecognizedParameterError, MissingParameterError,
|
1035 | * InvalidParameterError
|
1036 | * @see http://json-schema.org/latest/json-schema-validation.html
|
1037 | * @returns {Promise<void>} This method (or its subsidiaries) will throw
|
1038 | * errors if parameter validation fails; otherwise, nothing is returned.
|
1039 | */
|
1040 | _validateParameters() {
|
1041 |
|
1042 | const me = this;
|
1043 |
|
1044 | // Dependencies
|
1045 | const BB = me.$dep( "bluebird" );
|
1046 | const Validator = me.$dep( "util/Validator" );
|
1047 |
|
1048 | return BB.try( function () {
|
1049 |
|
1050 | // Normalize first...
|
1051 | return me._normalizeRawParameters();
|
1052 |
|
1053 | } ).then( function () {
|
1054 |
|
1055 | return me.getParameterSchema();
|
1056 |
|
1057 | } ).then( function ( schema ) {
|
1058 |
|
1059 | if ( !schema ) {
|
1060 |
|
1061 | return;
|
1062 | }
|
1063 |
|
1064 | // Create a Validator object
|
1065 | let validator = new Validator(
|
1066 | {
|
1067 | data : me.parameterValues,
|
1068 | schema : schema,
|
1069 | skipIfNull : true,
|
1070 | }
|
1071 | );
|
1072 |
|
1073 | // Execute it...
|
1074 | validator.validate();
|
1075 | } );
|
1076 | }
|
1077 |
|
1078 | /**
|
1079 | * This is the main entry point for body validation against a predefined
|
1080 | * request schema.
|
1081 | *
|
1082 | * @private
|
1083 | * @see http://json-schema.org/latest/json-schema-validation.html
|
1084 | * @returns {Promise<void>} This method (or its subsidiaries) will throw
|
1085 | * errors if body validation fails; otherwise, nothing is returned.
|
1086 | */
|
1087 | _validateBody() {
|
1088 |
|
1089 | const me = this;
|
1090 |
|
1091 | // Dependencies
|
1092 | const BB = me.$dep( "bluebird" );
|
1093 | const Validator = me.$dep( "util/Validator" );
|
1094 |
|
1095 | return BB.try( function () {
|
1096 |
|
1097 | return me.getBodySchema();
|
1098 |
|
1099 | } ).then( function ( schema ) {
|
1100 |
|
1101 | if ( !schema ) {
|
1102 |
|
1103 | return;
|
1104 | }
|
1105 |
|
1106 | // Create a Validator object
|
1107 | let validator = new Validator(
|
1108 | {
|
1109 | data : me.body,
|
1110 | schema : schema,
|
1111 | skipIfNull : true,
|
1112 | }
|
1113 | );
|
1114 |
|
1115 | // Execute it...
|
1116 | validator.validate();
|
1117 | } );
|
1118 | }
|
1119 | }
|
1120 |
|
1121 | module.exports = Request;
|