I_Jemin

Validate Input

Showing 49 changed files with 2987 additions and 11 deletions
1 +const Joi = require('joi');
1 const express = require('express'); 2 const express = require('express');
2 const app = express(); 3 const app = express();
3 4
4 app.use(express.json()); 5 app.use(express.json());
5 6
6 const courses = [ 7 const courses = [
7 - { id: 1, name: 'course1'}, 8 + { id: 1, name: 'course1' },
8 - { id: 2, name: 'course2'}, 9 + { id: 2, name: 'course2' },
9 - { id: 3, name: 'course3'}, 10 + { id: 3, name: 'course3' },
10 ]; 11 ];
11 12
12 // Corespond to HTTP 13 // Corespond to HTTP
...@@ -15,21 +16,34 @@ const courses = [ ...@@ -15,21 +16,34 @@ const courses = [
15 // app.put(); 16 // app.put();
16 // app.delete() 17 // app.delete()
17 18
18 -app.get('/',(req,res)=> { 19 +app.get('/', (req, res) => {
19 res.send('Hello World!!!'); 20 res.send('Hello World!!!');
20 }); 21 });
21 22
22 -app.get('/api/courses',(req,res) => { 23 +app.get('/api/courses', (req, res) => {
23 res.send(courses); 24 res.send(courses);
24 }); 25 });
25 26
26 -app.get('/api/courses/:id',(req,res) => { 27 +app.get('/api/courses/:id', (req, res) => {
27 - const course = courses.find(c=> c.id === parseInt(req.params.id)); 28 + const course = courses.find(c => c.id === parseInt(req.params.id));
28 - if(!course) res.status(404).send('The course with the given ID was not found'); 29 + if (!course) res.status(404).send('The course with the given ID was not found');
29 res.send(course); 30 res.send(course);
30 }); 31 });
31 32
32 -app.post('/api/courses',(req,res)=> { 33 +app.post('/api/courses', (req, res) => {
34 + // Define Schema
35 + const schema = {
36 + name: Joi.string().min(3).required()
37 + };
38 +
39 + const result = Joi.validate(req.body, schema);
40 +
41 + if (result.error) {
42 + // 400 Bad Request
43 + res.status(400).send(result.error.details[0].message);
44 + return;
45 + }
46 +
33 const course = { 47 const course = {
34 id: courses.length + 1, 48 id: courses.length + 1,
35 name: req.body.name 49 name: req.body.name
...@@ -42,4 +56,4 @@ app.post('/api/courses',(req,res)=> { ...@@ -42,4 +56,4 @@ app.post('/api/courses',(req,res)=> {
42 // PORT 56 // PORT
43 const port = process.env.PORT || 3000; 57 const port = process.env.PORT || 3000;
44 58
45 -app.listen(port,()=> console.log(`Listening on port ${port}...`)); 59 +app.listen(port, () => console.log(`Listening on port ${port}...`));
......
1 +Copyright (c) 2011-2017, Project contributors
2 +Copyright (c) 2011-2014, Walmart
3 +Copyright (c) 2011, Yahoo Inc.
4 +All rights reserved.
5 +
6 +Redistribution and use in source and binary forms, with or without
7 +modification, are permitted provided that the following conditions are met:
8 + * Redistributions of source code must retain the above copyright
9 + notice, this list of conditions and the following disclaimer.
10 + * Redistributions in binary form must reproduce the above copyright
11 + notice, this list of conditions and the following disclaimer in the
12 + documentation and/or other materials provided with the distribution.
13 + * The names of any contributors may not be used to endorse or promote
14 + products derived from this software without specific prior written
15 + permission.
16 +
17 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
21 +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 +
28 + * * *
29 +
30 +The complete list of contributors can be found at: https://github.com/hapijs/hapi/graphs/contributors
31 +Portions of this project were initially based on the Yahoo! Inc. Postmile project,
32 +published at https://github.com/yahoo/postmile.
1 +![hoek Logo](https://raw.github.com/hapijs/hoek/master/images/hoek.png)
2 +
3 +Utility methods for the hapi ecosystem. This module is not intended to solve every problem for everyone, but rather as a central place to store hapi-specific methods. If you're looking for a general purpose utility module, check out [lodash](https://github.com/lodash/lodash) or [underscore](https://github.com/jashkenas/underscore).
4 +
5 +[![Build Status](https://secure.travis-ci.org/hapijs/hoek.svg)](http://travis-ci.org/hapijs/hoek)
6 +
7 +<a href="https://andyet.com"><img src="https://s3.amazonaws.com/static.andyet.com/images/%26yet-logo.svg" align="right" /></a>
8 +
9 +Lead Maintainer: [Nathan LaFreniere](https://github.com/nlf)
10 +
11 +**hoek** is sponsored by [&yet](https://andyet.com)
12 +
13 +## Usage
14 +
15 +The *Hoek* library contains some common functions used within the hapi ecosystem. It comes with useful methods for Arrays (clone, merge, applyToDefaults), Objects (removeKeys, copy), Asserting and more.
16 +
17 +For example, to use Hoek to set configuration with default options:
18 +```javascript
19 +const Hoek = require('hoek');
20 +
21 +const default = {url : "www.github.com", port : "8000", debug : true};
22 +
23 +const config = Hoek.applyToDefaults(default, {port : "3000", admin : true});
24 +
25 +// In this case, config would be { url: 'www.github.com', port: '3000', debug: true, admin: true }
26 +```
27 +
28 +## Documentation
29 +
30 +[**API Reference**](API.md)
1 +'use strict';
2 +
3 +// Declare internals
4 +
5 +const internals = {};
6 +
7 +
8 +exports.escapeJavaScript = function (input) {
9 +
10 + if (!input) {
11 + return '';
12 + }
13 +
14 + let escaped = '';
15 +
16 + for (let i = 0; i < input.length; ++i) {
17 +
18 + const charCode = input.charCodeAt(i);
19 +
20 + if (internals.isSafe(charCode)) {
21 + escaped += input[i];
22 + }
23 + else {
24 + escaped += internals.escapeJavaScriptChar(charCode);
25 + }
26 + }
27 +
28 + return escaped;
29 +};
30 +
31 +
32 +exports.escapeHtml = function (input) {
33 +
34 + if (!input) {
35 + return '';
36 + }
37 +
38 + let escaped = '';
39 +
40 + for (let i = 0; i < input.length; ++i) {
41 +
42 + const charCode = input.charCodeAt(i);
43 +
44 + if (internals.isSafe(charCode)) {
45 + escaped += input[i];
46 + }
47 + else {
48 + escaped += internals.escapeHtmlChar(charCode);
49 + }
50 + }
51 +
52 + return escaped;
53 +};
54 +
55 +
56 +exports.escapeJson = function (input) {
57 +
58 + if (!input) {
59 + return '';
60 + }
61 +
62 + const lessThan = 0x3C;
63 + const greaterThan = 0x3E;
64 + const andSymbol = 0x26;
65 + const lineSeperator = 0x2028;
66 +
67 + // replace method
68 + let charCode;
69 + return input.replace(/[<>&\u2028\u2029]/g, (match) => {
70 +
71 + charCode = match.charCodeAt(0);
72 +
73 + if (charCode === lessThan) {
74 + return '\\u003c';
75 + }
76 + else if (charCode === greaterThan) {
77 + return '\\u003e';
78 + }
79 + else if (charCode === andSymbol) {
80 + return '\\u0026';
81 + }
82 + else if (charCode === lineSeperator) {
83 + return '\\u2028';
84 + }
85 + return '\\u2029';
86 + });
87 +};
88 +
89 +
90 +internals.escapeJavaScriptChar = function (charCode) {
91 +
92 + if (charCode >= 256) {
93 + return '\\u' + internals.padLeft('' + charCode, 4);
94 + }
95 +
96 + const hexValue = Buffer.from(String.fromCharCode(charCode), 'ascii').toString('hex');
97 + return '\\x' + internals.padLeft(hexValue, 2);
98 +};
99 +
100 +
101 +internals.escapeHtmlChar = function (charCode) {
102 +
103 + const namedEscape = internals.namedHtml[charCode];
104 + if (typeof namedEscape !== 'undefined') {
105 + return namedEscape;
106 + }
107 +
108 + if (charCode >= 256) {
109 + return '&#' + charCode + ';';
110 + }
111 +
112 + const hexValue = Buffer.from(String.fromCharCode(charCode), 'ascii').toString('hex');
113 + return '&#x' + internals.padLeft(hexValue, 2) + ';';
114 +};
115 +
116 +
117 +internals.padLeft = function (str, len) {
118 +
119 + while (str.length < len) {
120 + str = '0' + str;
121 + }
122 +
123 + return str;
124 +};
125 +
126 +
127 +internals.isSafe = function (charCode) {
128 +
129 + return (typeof internals.safeCharCodes[charCode] !== 'undefined');
130 +};
131 +
132 +
133 +internals.namedHtml = {
134 + '38': '&amp;',
135 + '60': '&lt;',
136 + '62': '&gt;',
137 + '34': '&quot;',
138 + '160': '&nbsp;',
139 + '162': '&cent;',
140 + '163': '&pound;',
141 + '164': '&curren;',
142 + '169': '&copy;',
143 + '174': '&reg;'
144 +};
145 +
146 +
147 +internals.safeCharCodes = (function () {
148 +
149 + const safe = {};
150 +
151 + for (let i = 32; i < 123; ++i) {
152 +
153 + if ((i >= 97) || // a-z
154 + (i >= 65 && i <= 90) || // A-Z
155 + (i >= 48 && i <= 57) || // 0-9
156 + i === 32 || // space
157 + i === 46 || // .
158 + i === 44 || // ,
159 + i === 45 || // -
160 + i === 58 || // :
161 + i === 95) { // _
162 +
163 + safe[i] = null;
164 + }
165 + }
166 +
167 + return safe;
168 +}());
This diff is collapsed. Click to expand it.
1 +{
2 + "_from": "hoek@5.x.x",
3 + "_id": "hoek@5.0.3",
4 + "_inBundle": false,
5 + "_integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw==",
6 + "_location": "/hoek",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "hoek@5.x.x",
12 + "name": "hoek",
13 + "escapedName": "hoek",
14 + "rawSpec": "5.x.x",
15 + "saveSpec": null,
16 + "fetchSpec": "5.x.x"
17 + },
18 + "_requiredBy": [
19 + "/joi",
20 + "/topo"
21 + ],
22 + "_resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz",
23 + "_shasum": "b71d40d943d0a95da01956b547f83c4a5b4a34ac",
24 + "_spec": "hoek@5.x.x",
25 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo/node_modules/joi",
26 + "bugs": {
27 + "url": "https://github.com/hapijs/hoek/issues"
28 + },
29 + "bundleDependencies": false,
30 + "dependencies": {},
31 + "deprecated": false,
32 + "description": "General purpose node utilities",
33 + "devDependencies": {
34 + "code": "5.x.x",
35 + "lab": "15.x.x"
36 + },
37 + "engines": {
38 + "node": ">=8.9.0"
39 + },
40 + "homepage": "https://github.com/hapijs/hoek#readme",
41 + "keywords": [
42 + "utilities"
43 + ],
44 + "license": "BSD-3-Clause",
45 + "main": "lib/index.js",
46 + "name": "hoek",
47 + "repository": {
48 + "type": "git",
49 + "url": "git://github.com/hapijs/hoek.git"
50 + },
51 + "scripts": {
52 + "test": "lab -a code -t 100 -L",
53 + "test-cov-html": "lab -a code -t 100 -L -r html -o coverage.html"
54 + },
55 + "version": "5.0.3"
56 +}
1 +Copyright (c) 2014-2015, Eli Skeggs and Project contributors
2 +Copyright (c) 2013-2014, GlobeSherpa
3 +Copyright (c) 2008-2011, Dominic Sayers
4 +All rights reserved.
5 +
6 +Redistribution and use in source and binary forms, with or without
7 +modification, are permitted provided that the following conditions are met:
8 + * Redistributions of source code must retain the above copyright
9 + notice, this list of conditions and the following disclaimer.
10 + * Redistributions in binary form must reproduce the above copyright
11 + notice, this list of conditions and the following disclaimer in the
12 + documentation and/or other materials provided with the distribution.
13 + * The names of any contributors may not be used to endorse or promote
14 + products derived from this software without specific prior written
15 + permission.
16 +
17 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
21 +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 +
28 + * * *
29 +
30 +The complete list of contributors can be found at: https://github.com/hapijs/isemail/graphs/contributors
31 +Previously published under the 2-Clause-BSD license published here: https://github.com/hapijs/isemail/blob/v1.2.0/LICENSE
32 +
1 +# isemail
2 +
3 +Node email address validation library
4 +
5 +[![Build Status](https://travis-ci.org/hapijs/isemail.svg?branch=master)](https://travis-ci.org/hapijs/isemail)<a href="#footnote-1"><sup>&#91;1&#93;</sup></a>
6 +
7 +Lead Maintainer: [Eli Skeggs][skeggse]
8 +
9 +This library is a port of the PHP `is_email` function by Dominic Sayers.
10 +
11 +Install
12 +=======
13 +
14 +```sh
15 +$ npm install isemail
16 +```
17 +
18 +Test
19 +====
20 +
21 +The tests were pulled from `is_email`'s extensive [test suite][tests] on October 15, 2013. Many thanks to the contributors! Additional tests have been added to increase code coverage and verify edge-cases.
22 +
23 +Run any of the following.
24 +
25 +```sh
26 +$ lab
27 +$ npm test
28 +$ make test
29 +```
30 +
31 +_remember to_ `npm install` to get the development dependencies!
32 +
33 +API
34 +===
35 +
36 +validate(email, [options])
37 +--------------------------
38 +
39 +Determines whether the `email` is valid or not, for various definitions thereof. Optionally accepts an `options` object. Options may include `errorLevel`.
40 +
41 +Use `errorLevel` to specify the type of result for `validate()`. Passing a `false` literal will result in a true or false boolean indicating whether the email address is sufficiently defined for use in sending an email. Passing a `true` literal will result in a more granular numeric status, with zero being a perfectly valid email address. Passing a number will return `0` if the numeric status is below the `errorLevel` and the numeric status otherwise.
42 +
43 +The `tldBlacklist` option can be either an object lookup table or an array of invalid top-level domains. If the email address has a top-level domain that is in the whitelist, the email will be marked as invalid.
44 +
45 +The `tldWhitelist` option can be either an object lookup table or an array of valid top-level domains. If the email address has a top-level domain that is not in the whitelist, the email will be marked as invalid.
46 +
47 +The `allowUnicode` option governs whether non-ASCII characters are allowed. Defaults to `true` per RFC 6530.
48 +
49 +Only one of `tldBlacklist` and `tldWhitelist` will be consulted for TLD validity.
50 +
51 +The `minDomainAtoms` option is an optional positive integer that specifies the minimum number of domain atoms that must be included for the email address to be considered valid. Be careful with the option, as some top-level domains, like `io`, directly support email addresses.
52 +
53 +As of `3.1.1`, the `callback` parameter is deprecated, and will be removed in `4.0.0`.
54 +
55 +### Examples
56 +
57 +```js
58 +$ node
59 +> var Isemail = require('isemail');
60 +undefined
61 +> Isemail.validate('test@iana.org');
62 +true
63 +> Isemail.validate('test@iana.org', {errorLevel: true});
64 +0
65 +> Isemail.validate('test@e.com', {errorLevel: true});
66 +6
67 +> Isemail.validate('test@e.com', {errorLevel: 7});
68 +0
69 +> Isemail.validate('test@e.com', {errorLevel: 6});
70 +6
71 +```
72 +
73 +<sup name="footnote-1">&#91;1&#93;</sup>: if this badge indicates the build is passing, then isemail has 100% code coverage.
74 +
75 +[skeggse]: https://github.com/skeggse "Eli Skeggs"
76 +[tests]: http://isemail.info/_system/is_email/test/?all‎ "is_email test suite"
1 +// Code shows a string is valid,
2 +// but docs require string array or object.
3 +type TLDList = string | string[] | { [topLevelDomain: string]: any };
4 +
5 +type BaseOptions = {
6 + tldWhitelist?: TLDList;
7 + tldBlacklist?: TLDList;
8 + minDomainAtoms?: number;
9 + allowUnicode?: boolean;
10 +};
11 +
12 +type OptionsWithBool = BaseOptions & {
13 + errorLevel?: false;
14 +};
15 +
16 +type OptionsWithNumThreshold = BaseOptions & {
17 + errorLevel?: true | number;
18 +};
19 +
20 +interface Validator {
21 + /**
22 + * Check that an email address conforms to RFCs 5321, 5322, 6530 and others.
23 + *
24 + * The callback function will always be called
25 + * with the result of the operation.
26 + *
27 + * ```
28 + * import * as IsEmail from "isemail";
29 + *
30 + * const log = result => console.log(`Result: ${result}`);
31 + * IsEmail.validate("test@e.com");
32 + * // => true
33 + * ```
34 + */
35 + validate(email: string): boolean;
36 +
37 + /**
38 + * Check that an email address conforms to RFCs 5321, 5322, 6530 and others.
39 + *
40 + * The callback function will always be called
41 + * with the result of the operation.
42 + *
43 + * ```
44 + * import * as IsEmail from "isemail";
45 + *
46 + * IsEmail.validate("test@iana.org", { errorLevel: false });
47 + * // => true
48 + * ```
49 + */
50 + validate(email: string, options: OptionsWithBool): boolean;
51 +
52 + /**
53 + * Check that an email address conforms to RFCs 5321, 5322, 6530 and others.
54 + *
55 + * The callback function will always be called
56 + * with the result of the operation.
57 + *
58 + * ```
59 + * import * as IsEmail from "isemail";
60 + *
61 + * IsEmail.validate("test@iana.org", { errorLevel: true });
62 + * // => 0
63 + * IsEmail.validate("test @e.com", { errorLevel: 50 });
64 + * // => 0
65 + * IsEmail.validate('test @e.com', { errorLevel: true })
66 + * // => 49
67 + * ```
68 + */
69 + validate(email: string, options: OptionsWithNumThreshold): number;
70 +}
71 +
72 +declare const IsEmail: Validator;
73 +
74 +export = IsEmail;
This diff is collapsed. Click to expand it.
1 +{
2 + "_from": "isemail@3.x.x",
3 + "_id": "isemail@3.1.1",
4 + "_inBundle": false,
5 + "_integrity": "sha512-mVjAjvdPkpwXW61agT2E9AkGoegZO7SdJGCezWwxnETL58f5KwJ4vSVAMBUL5idL6rTlYAIGkX3n4suiviMLNw==",
6 + "_location": "/isemail",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "isemail@3.x.x",
12 + "name": "isemail",
13 + "escapedName": "isemail",
14 + "rawSpec": "3.x.x",
15 + "saveSpec": null,
16 + "fetchSpec": "3.x.x"
17 + },
18 + "_requiredBy": [
19 + "/joi"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.1.tgz",
22 + "_shasum": "e8450fe78ff1b48347db599122adcd0668bd92b5",
23 + "_spec": "isemail@3.x.x",
24 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo/node_modules/joi",
25 + "bugs": {
26 + "url": "https://github.com/hapijs/isemail/issues"
27 + },
28 + "bundleDependencies": false,
29 + "dependencies": {
30 + "punycode": "2.x.x"
31 + },
32 + "deprecated": false,
33 + "description": "Validate an email address according to RFCs 5321, 5322, and others",
34 + "devDependencies": {
35 + "code": "3.x.x",
36 + "lab": "15.x.x"
37 + },
38 + "engines": {
39 + "node": ">=4.0.0"
40 + },
41 + "homepage": "https://github.com/hapijs/isemail#readme",
42 + "keywords": [
43 + "isemail",
44 + "validation",
45 + "check",
46 + "checking",
47 + "verification",
48 + "email",
49 + "address",
50 + "email address"
51 + ],
52 + "license": "BSD-3-Clause",
53 + "main": "lib/index.js",
54 + "name": "isemail",
55 + "repository": {
56 + "type": "git",
57 + "url": "git://github.com/hapijs/isemail.git"
58 + },
59 + "scripts": {
60 + "test": "lab -a code -t 100 -L -m 5000",
61 + "test-cov-html": "lab -a code -r html -o coverage.html -m 5000"
62 + },
63 + "types": "lib/index.d.ts",
64 + "version": "3.1.1"
65 +}
1 +Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/joi/issues?q=is%3Aissue+label%3A%22release+notes%22).
2 +
3 +If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/joi/milestones?state=closed&direction=asc&sort=due_date).
1 +Copyright (c) 2012-2017, Project contributors
2 +Copyright (c) 2012-2014, Walmart
3 +All rights reserved.
4 +
5 +Redistribution and use in source and binary forms, with or without
6 +modification, are permitted provided that the following conditions are met:
7 + * Redistributions of source code must retain the above copyright
8 + notice, this list of conditions and the following disclaimer.
9 + * Redistributions in binary form must reproduce the above copyright
10 + notice, this list of conditions and the following disclaimer in the
11 + documentation and/or other materials provided with the distribution.
12 + * The names of any contributors may not be used to endorse or promote
13 + products derived from this software without specific prior written
14 + permission.
15 +
16 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
20 +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 +
27 + * * *
28 +
29 +The complete list of contributors can be found at: https://github.com/hapijs/joi/graphs/contributors
1 +![joi Logo](https://raw.github.com/hapijs/joi/master/images/joi.png)
2 +
3 +Object schema description language and validator for JavaScript objects.
4 +
5 +[![npm version](https://badge.fury.io/js/joi.svg)](http://badge.fury.io/js/joi)
6 +[![Build Status](https://travis-ci.org/hapijs/joi.svg?branch=master)](https://travis-ci.org/hapijs/joi)
7 +[![NSP Status](https://nodesecurity.io/orgs/hapijs/projects/0394bf83-b5bc-410b-878c-e8cf1b92033e/badge)](https://nodesecurity.io/orgs/hapijs/projects/0394bf83-b5bc-410b-878c-e8cf1b92033e)
8 +[![Known Vulnerabilities](https://snyk.io/test/github/hapijs/joi/badge.svg)](https://snyk.io/test/github/hapijs/joi)
9 +
10 +Lead Maintainer: [Nicolas Morel](https://github.com/marsup)
11 +
12 +# Introduction
13 +
14 +Imagine you run facebook and you want visitors to sign up on the website with real names and not something like `l337_p@nda` in the first name field. How would you define the limitations of what can be inputted and validate it against the set rules?
15 +
16 +This is joi, joi allows you to create *blueprints* or *schemas* for JavaScript objects (an object that stores information) to ensure *validation* of key information.
17 +
18 +# API
19 +See the detailed [API Reference](https://github.com/hapijs/joi/blob/v13.1.2/API.md).
20 +
21 +# Example
22 +
23 +```javascript
24 +const Joi = require('joi');
25 +
26 +const schema = Joi.object().keys({
27 + username: Joi.string().alphanum().min(3).max(30).required(),
28 + password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
29 + access_token: [Joi.string(), Joi.number()],
30 + birthyear: Joi.number().integer().min(1900).max(2013),
31 + email: Joi.string().email()
32 +}).with('username', 'birthyear').without('password', 'access_token');
33 +
34 +// Return result.
35 +const result = Joi.validate({ username: 'abc', birthyear: 1994 }, schema);
36 +// result.error === null -> valid
37 +
38 +// You can also pass a callback which will be called synchronously with the validation result.
39 +Joi.validate({ username: 'abc', birthyear: 1994 }, schema, function (err, value) { }); // err === null -> valid
40 +
41 +```
42 +
43 +The above schema defines the following constraints:
44 +* `username`
45 + * a required string
46 + * must contain only alphanumeric characters
47 + * at least 3 characters long but no more than 30
48 + * must be accompanied by `birthyear`
49 +* `password`
50 + * an optional string
51 + * must satisfy the custom regex
52 + * cannot appear together with `access_token`
53 +* `access_token`
54 + * an optional, unconstrained string or number
55 +* `birthyear`
56 + * an integer between 1900 and 2013
57 +* `email`
58 + * a valid email address string
59 +
60 +# Usage
61 +
62 +Usage is a two steps process. First, a schema is constructed using the provided types and constraints:
63 +
64 +```javascript
65 +const schema = {
66 + a: Joi.string()
67 +};
68 +```
69 +
70 +Note that **joi** schema objects are immutable which means every additional rule added (e.g. `.min(5)`) will return a
71 +new schema object.
72 +
73 +Then the value is validated against the schema:
74 +
75 +```javascript
76 +const {error, value} = Joi.validate({ a: 'a string' }, schema);
77 +
78 +// or
79 +
80 +Joi.validate({ a: 'a string' }, schema, function (err, value) { });
81 +```
82 +
83 +If the input is valid, then the error will be `null`, otherwise it will be an Error object.
84 +
85 +The schema can be a plain JavaScript object where every key is assigned a **joi** type, or it can be a **joi** type directly:
86 +
87 +```javascript
88 +const schema = Joi.string().min(10);
89 +```
90 +
91 +If the schema is a **joi** type, the `schema.validate(value, callback)` can be called directly on the type. When passing a non-type schema object,
92 +the module converts it internally to an object() type equivalent to:
93 +
94 +```javascript
95 +const schema = Joi.object().keys({
96 + a: Joi.string()
97 +});
98 +```
99 +
100 +When validating a schema:
101 +
102 +* Values (or keys in case of objects) are optional by default.
103 +
104 + ```javascript
105 + Joi.validate(undefined, Joi.string()); // validates fine
106 + ```
107 +
108 + To disallow this behavior, you can either set the schema as `required()`, or set `presence` to `"required"` when passing `options`:
109 +
110 + ```javascript
111 + Joi.validate(undefined, Joi.string().required());
112 + // or
113 + Joi.validate(undefined, Joi.string(), /* options */ { presence: "required" });
114 + ```
115 +
116 +* Strings are utf-8 encoded by default.
117 +* Rules are defined in an additive fashion and evaluated in order after whitelist and blacklist checks.
118 +
119 +# Browsers
120 +
121 +Joi doesn't directly support browsers, but you could use [joi-browser](https://github.com/jeffbski/joi-browser) for an ES5 build of Joi that works in browsers, or as a source of inspiration for your own builds.
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const Ref = require('./ref');
7 +
8 +// Type modules are delay-loaded to prevent circular dependencies
9 +
10 +
11 +// Declare internals
12 +
13 +const internals = {};
14 +
15 +
16 +exports.schema = function (Joi, config) {
17 +
18 + if (config !== undefined && config !== null && typeof config === 'object') {
19 +
20 + if (config.isJoi) {
21 + return config;
22 + }
23 +
24 + if (Array.isArray(config)) {
25 + return Joi.alternatives().try(config);
26 + }
27 +
28 + if (config instanceof RegExp) {
29 + return Joi.string().regex(config);
30 + }
31 +
32 + if (config instanceof Date) {
33 + return Joi.date().valid(config);
34 + }
35 +
36 + return Joi.object().keys(config);
37 + }
38 +
39 + if (typeof config === 'string') {
40 + return Joi.string().valid(config);
41 + }
42 +
43 + if (typeof config === 'number') {
44 + return Joi.number().valid(config);
45 + }
46 +
47 + if (typeof config === 'boolean') {
48 + return Joi.boolean().valid(config);
49 + }
50 +
51 + if (Ref.isRef(config)) {
52 + return Joi.valid(config);
53 + }
54 +
55 + Hoek.assert(config === null, 'Invalid schema content:', config);
56 +
57 + return Joi.valid(null);
58 +};
59 +
60 +
61 +exports.ref = function (id) {
62 +
63 + return Ref.isRef(id) ? id : Ref.create(id);
64 +};
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +
6 +// Declare internals
7 +
8 +const internals = {};
9 +
10 +
11 +exports.errors = {
12 + root: 'value',
13 + key: '"{{!label}}" ',
14 + messages: {
15 + wrapArrays: true
16 + },
17 + any: {
18 + unknown: 'is not allowed',
19 + invalid: 'contains an invalid value',
20 + empty: 'is not allowed to be empty',
21 + required: 'is required',
22 + allowOnly: 'must be one of {{valids}}',
23 + default: 'threw an error when running default method'
24 + },
25 + alternatives: {
26 + base: 'not matching any of the allowed alternatives',
27 + child: null
28 + },
29 + array: {
30 + base: 'must be an array',
31 + includes: 'at position {{pos}} does not match any of the allowed types',
32 + includesSingle: 'single value of "{{!label}}" does not match any of the allowed types',
33 + includesOne: 'at position {{pos}} fails because {{reason}}',
34 + includesOneSingle: 'single value of "{{!label}}" fails because {{reason}}',
35 + includesRequiredUnknowns: 'does not contain {{unknownMisses}} required value(s)',
36 + includesRequiredKnowns: 'does not contain {{knownMisses}}',
37 + includesRequiredBoth: 'does not contain {{knownMisses}} and {{unknownMisses}} other required value(s)',
38 + excludes: 'at position {{pos}} contains an excluded value',
39 + excludesSingle: 'single value of "{{!label}}" contains an excluded value',
40 + min: 'must contain at least {{limit}} items',
41 + max: 'must contain less than or equal to {{limit}} items',
42 + length: 'must contain {{limit}} items',
43 + ordered: 'at position {{pos}} fails because {{reason}}',
44 + orderedLength: 'at position {{pos}} fails because array must contain at most {{limit}} items',
45 + ref: 'references "{{ref}}" which is not a positive integer',
46 + sparse: 'must not be a sparse array',
47 + unique: 'position {{pos}} contains a duplicate value'
48 + },
49 + boolean: {
50 + base: 'must be a boolean'
51 + },
52 + binary: {
53 + base: 'must be a buffer or a string',
54 + min: 'must be at least {{limit}} bytes',
55 + max: 'must be less than or equal to {{limit}} bytes',
56 + length: 'must be {{limit}} bytes'
57 + },
58 + date: {
59 + base: 'must be a number of milliseconds or valid date string',
60 + format: 'must be a string with one of the following formats {{format}}',
61 + strict: 'must be a valid date',
62 + min: 'must be larger than or equal to "{{limit}}"',
63 + max: 'must be less than or equal to "{{limit}}"',
64 + isoDate: 'must be a valid ISO 8601 date',
65 + timestamp: {
66 + javascript: 'must be a valid timestamp or number of milliseconds',
67 + unix: 'must be a valid timestamp or number of seconds'
68 + },
69 + ref: 'references "{{ref}}" which is not a date'
70 + },
71 + function: {
72 + base: 'must be a Function',
73 + arity: 'must have an arity of {{n}}',
74 + minArity: 'must have an arity greater or equal to {{n}}',
75 + maxArity: 'must have an arity lesser or equal to {{n}}',
76 + ref: 'must be a Joi reference',
77 + class: 'must be a class'
78 + },
79 + lazy: {
80 + base: '!!schema error: lazy schema must be set',
81 + schema: '!!schema error: lazy schema function must return a schema'
82 + },
83 + object: {
84 + base: 'must be an object',
85 + child: '!!child "{{!child}}" fails because {{reason}}',
86 + min: 'must have at least {{limit}} children',
87 + max: 'must have less than or equal to {{limit}} children',
88 + length: 'must have {{limit}} children',
89 + allowUnknown: '!!"{{!child}}" is not allowed',
90 + with: '!!"{{mainWithLabel}}" missing required peer "{{peerWithLabel}}"',
91 + without: '!!"{{mainWithLabel}}" conflict with forbidden peer "{{peerWithLabel}}"',
92 + missing: 'must contain at least one of {{peersWithLabels}}',
93 + xor: 'contains a conflict between exclusive peers {{peersWithLabels}}',
94 + or: 'must contain at least one of {{peersWithLabels}}',
95 + and: 'contains {{presentWithLabels}} without its required peers {{missingWithLabels}}',
96 + nand: '!!"{{mainWithLabel}}" must not exist simultaneously with {{peersWithLabels}}',
97 + assert: '!!"{{ref}}" validation failed because "{{ref}}" failed to {{message}}',
98 + rename: {
99 + multiple: 'cannot rename child "{{from}}" because multiple renames are disabled and another key was already renamed to "{{to}}"',
100 + override: 'cannot rename child "{{from}}" because override is disabled and target "{{to}}" exists',
101 + regex: {
102 + multiple: 'cannot rename children {{from}} because multiple renames are disabled and another key was already renamed to "{{to}}"',
103 + override: 'cannot rename children {{from}} because override is disabled and target "{{to}}" exists'
104 + }
105 + },
106 + type: 'must be an instance of "{{type}}"',
107 + schema: 'must be a Joi instance'
108 + },
109 + number: {
110 + base: 'must be a number',
111 + min: 'must be larger than or equal to {{limit}}',
112 + max: 'must be less than or equal to {{limit}}',
113 + less: 'must be less than {{limit}}',
114 + greater: 'must be greater than {{limit}}',
115 + float: 'must be a float or double',
116 + integer: 'must be an integer',
117 + negative: 'must be a negative number',
118 + positive: 'must be a positive number',
119 + precision: 'must have no more than {{limit}} decimal places',
120 + ref: 'references "{{ref}}" which is not a number',
121 + multiple: 'must be a multiple of {{multiple}}'
122 + },
123 + string: {
124 + base: 'must be a string',
125 + min: 'length must be at least {{limit}} characters long',
126 + max: 'length must be less than or equal to {{limit}} characters long',
127 + length: 'length must be {{limit}} characters long',
128 + alphanum: 'must only contain alpha-numeric characters',
129 + token: 'must only contain alpha-numeric and underscore characters',
130 + regex: {
131 + base: 'with value "{{!value}}" fails to match the required pattern: {{pattern}}',
132 + name: 'with value "{{!value}}" fails to match the {{name}} pattern',
133 + invert: {
134 + base: 'with value "{{!value}}" matches the inverted pattern: {{pattern}}',
135 + name: 'with value "{{!value}}" matches the inverted {{name}} pattern'
136 + }
137 + },
138 + email: 'must be a valid email',
139 + uri: 'must be a valid uri',
140 + uriRelativeOnly: 'must be a valid relative uri',
141 + uriCustomScheme: 'must be a valid uri with a scheme matching the {{scheme}} pattern',
142 + isoDate: 'must be a valid ISO 8601 date',
143 + guid: 'must be a valid GUID',
144 + hex: 'must only contain hexadecimal characters',
145 + base64: 'must be a valid base64 string',
146 + hostname: 'must be a valid hostname',
147 + normalize: 'must be unicode normalized in the {{form}} form',
148 + lowercase: 'must only contain lowercase characters',
149 + uppercase: 'must only contain uppercase characters',
150 + trim: 'must not have leading or trailing whitespace',
151 + creditCard: 'must be a credit card',
152 + ref: 'references "{{ref}}" which is not a number',
153 + ip: 'must be a valid ip address with a {{cidr}} CIDR',
154 + ipVersion: 'must be a valid ip address of one of the following versions {{version}} with a {{cidr}} CIDR'
155 + }
156 +};
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {};
11 +
12 +
13 +exports.create = function (key, options) {
14 +
15 + Hoek.assert(typeof key === 'string', 'Invalid reference key:', key);
16 +
17 + const settings = Hoek.clone(options); // options can be reused and modified
18 +
19 + const ref = function (value, validationOptions) {
20 +
21 + return Hoek.reach(ref.isContext ? validationOptions.context : value, ref.key, settings);
22 + };
23 +
24 + ref.isContext = (key[0] === ((settings && settings.contextPrefix) || '$'));
25 + ref.key = (ref.isContext ? key.slice(1) : key);
26 + ref.path = ref.key.split((settings && settings.separator) || '.');
27 + ref.depth = ref.path.length;
28 + ref.root = ref.path[0];
29 + ref.isJoi = true;
30 +
31 + ref.toString = function () {
32 +
33 + return (ref.isContext ? 'context:' : 'ref:') + ref.key;
34 + };
35 +
36 + return ref;
37 +};
38 +
39 +
40 +exports.isRef = function (ref) {
41 +
42 + return typeof ref === 'function' && ref.isJoi;
43 +};
44 +
45 +
46 +exports.push = function (array, ref) {
47 +
48 + if (exports.isRef(ref) &&
49 + !ref.isContext) {
50 +
51 + array.push(ref.root);
52 + }
53 +};
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Joi = require('../');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {};
11 +
12 +exports.options = Joi.object({
13 + abortEarly: Joi.boolean(),
14 + convert: Joi.boolean(),
15 + allowUnknown: Joi.boolean(),
16 + skipFunctions: Joi.boolean(),
17 + stripUnknown: [Joi.boolean(), Joi.object({ arrays: Joi.boolean(), objects: Joi.boolean() }).or('arrays', 'objects')],
18 + language: Joi.object(),
19 + presence: Joi.string().only('required', 'optional', 'forbidden', 'ignore'),
20 + raw: Joi.boolean(),
21 + context: Joi.object(),
22 + strip: Joi.boolean(),
23 + noDefaults: Joi.boolean(),
24 + escapeHtml: Joi.boolean()
25 +}).strict();
1 +'use strict';
2 +
3 +const Ref = require('./ref');
4 +
5 +module.exports = class Set {
6 +
7 + constructor() {
8 +
9 + this._set = [];
10 + }
11 +
12 + add(value, refs) {
13 +
14 + if (!Ref.isRef(value) && this.has(value, null, null, false)) {
15 +
16 + return;
17 + }
18 +
19 + if (refs !== undefined) { // If it's a merge, we don't have any refs
20 + Ref.push(refs, value);
21 + }
22 +
23 + this._set.push(value);
24 + return this;
25 + }
26 +
27 + merge(add, remove) {
28 +
29 + for (let i = 0; i < add._set.length; ++i) {
30 + this.add(add._set[i]);
31 + }
32 +
33 + for (let i = 0; i < remove._set.length; ++i) {
34 + this.remove(remove._set[i]);
35 + }
36 +
37 + return this;
38 + }
39 +
40 + remove(value) {
41 +
42 + this._set = this._set.filter((item) => value !== item);
43 + return this;
44 + }
45 +
46 + has(value, state, options, insensitive) {
47 +
48 + for (let i = 0; i < this._set.length; ++i) {
49 + let items = this._set[i];
50 +
51 + if (state && Ref.isRef(items)) { // Only resolve references if there is a state, otherwise it's a merge
52 + items = items(state.reference || state.parent, options);
53 + }
54 +
55 + if (!Array.isArray(items)) {
56 + items = [items];
57 + }
58 +
59 + for (let j = 0; j < items.length; ++j) {
60 + const item = items[j];
61 + if (typeof value !== typeof item) {
62 + continue;
63 + }
64 +
65 + if (value === item ||
66 + (value instanceof Date && item instanceof Date && value.getTime() === item.getTime()) ||
67 + (insensitive && typeof value === 'string' && value.toLowerCase() === item.toLowerCase()) ||
68 + (Buffer.isBuffer(value) && Buffer.isBuffer(item) && value.length === item.length && value.toString('binary') === item.toString('binary'))) {
69 +
70 + return true;
71 + }
72 + }
73 + }
74 +
75 + return false;
76 + }
77 +
78 + values(options) {
79 +
80 + if (options && options.stripUndefined) {
81 + const values = [];
82 +
83 + for (let i = 0; i < this._set.length; ++i) {
84 + const item = this._set[i];
85 + if (item !== undefined) {
86 + values.push(item);
87 + }
88 + }
89 +
90 + return values;
91 + }
92 +
93 + return this._set.slice();
94 + }
95 +
96 + slice() {
97 +
98 + const newSet = new Set();
99 + newSet._set = this._set.slice();
100 +
101 + return newSet;
102 + }
103 +
104 + concat(source) {
105 +
106 + const newSet = new Set();
107 + newSet._set = this._set.concat(source._set);
108 +
109 + return newSet;
110 + }
111 +};
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const Any = require('../any');
7 +const Cast = require('../../cast');
8 +const Ref = require('../../ref');
9 +
10 +
11 +// Declare internals
12 +
13 +const internals = {};
14 +
15 +
16 +internals.Alternatives = class extends Any {
17 +
18 + constructor() {
19 +
20 + super();
21 + this._type = 'alternatives';
22 + this._invalids.remove(null);
23 + this._inner.matches = [];
24 + }
25 +
26 + _base(value, state, options) {
27 +
28 + let errors = [];
29 + const il = this._inner.matches.length;
30 + const baseType = this._baseType;
31 +
32 + for (let i = 0; i < il; ++i) {
33 + const item = this._inner.matches[i];
34 + if (!item.schema) {
35 + const schema = item.peek || item.is;
36 + const input = item.is ? item.ref(state.reference || state.parent, options) : value;
37 + const failed = schema._validate(input, null, options, state.parent).errors;
38 +
39 + if (failed) {
40 + if (item.otherwise) {
41 + return item.otherwise._validate(value, state, options);
42 + }
43 + }
44 + else if (item.then) {
45 + return item.then._validate(value, state, options);
46 + }
47 +
48 + if (i === (il - 1) && baseType) {
49 + return baseType._validate(value, state, options);
50 + }
51 +
52 + continue;
53 + }
54 +
55 + const result = item.schema._validate(value, state, options);
56 + if (!result.errors) { // Found a valid match
57 + return result;
58 + }
59 +
60 + errors = errors.concat(result.errors);
61 + }
62 +
63 + if (errors.length) {
64 + return { errors: this.createError('alternatives.child', { reason: errors }, state, options) };
65 + }
66 +
67 + return { errors: this.createError('alternatives.base', null, state, options) };
68 + }
69 +
70 + try(...schemas) {
71 +
72 + schemas = Hoek.flatten(schemas);
73 + Hoek.assert(schemas.length, 'Cannot add other alternatives without at least one schema');
74 +
75 + const obj = this.clone();
76 +
77 + for (let i = 0; i < schemas.length; ++i) {
78 + const cast = Cast.schema(this._currentJoi, schemas[i]);
79 + if (cast._refs.length) {
80 + obj._refs = obj._refs.concat(cast._refs);
81 + }
82 + obj._inner.matches.push({ schema: cast });
83 + }
84 +
85 + return obj;
86 + }
87 +
88 + when(condition, options) {
89 +
90 + let schemaCondition = false;
91 + Hoek.assert(Ref.isRef(condition) || typeof condition === 'string' || (schemaCondition = condition instanceof Any), 'Invalid condition:', condition);
92 + Hoek.assert(options, 'Missing options');
93 + Hoek.assert(typeof options === 'object', 'Invalid options');
94 + if (schemaCondition) {
95 + Hoek.assert(!options.hasOwnProperty('is'), '"is" can not be used with a schema condition');
96 + }
97 + else {
98 + Hoek.assert(options.hasOwnProperty('is'), 'Missing "is" directive');
99 + }
100 + Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"');
101 +
102 + const obj = this.clone();
103 + let is;
104 + if (!schemaCondition) {
105 + is = Cast.schema(this._currentJoi, options.is);
106 +
107 + if (options.is === null || !(Ref.isRef(options.is) || options.is instanceof Any)) {
108 +
109 + // Only apply required if this wasn't already a schema or a ref, we'll suppose people know what they're doing
110 + is = is.required();
111 + }
112 + }
113 +
114 + const item = {
115 + ref: schemaCondition ? null : Cast.ref(condition),
116 + peek: schemaCondition ? condition : null,
117 + is,
118 + then: options.then !== undefined ? Cast.schema(this._currentJoi, options.then) : undefined,
119 + otherwise: options.otherwise !== undefined ? Cast.schema(this._currentJoi, options.otherwise) : undefined
120 + };
121 +
122 + if (obj._baseType) {
123 +
124 + item.then = item.then && obj._baseType.concat(item.then);
125 + item.otherwise = item.otherwise && obj._baseType.concat(item.otherwise);
126 + }
127 +
128 + if (!schemaCondition) {
129 + Ref.push(obj._refs, item.ref);
130 + obj._refs = obj._refs.concat(item.is._refs);
131 + }
132 +
133 + if (item.then && item.then._refs) {
134 + obj._refs = obj._refs.concat(item.then._refs);
135 + }
136 +
137 + if (item.otherwise && item.otherwise._refs) {
138 + obj._refs = obj._refs.concat(item.otherwise._refs);
139 + }
140 +
141 + obj._inner.matches.push(item);
142 +
143 + return obj;
144 + }
145 +
146 + describe() {
147 +
148 + const description = Any.prototype.describe.call(this);
149 + const alternatives = [];
150 + for (let i = 0; i < this._inner.matches.length; ++i) {
151 + const item = this._inner.matches[i];
152 + if (item.schema) {
153 +
154 + // try()
155 +
156 + alternatives.push(item.schema.describe());
157 + }
158 + else {
159 +
160 + // when()
161 +
162 + const when = item.is ? {
163 + ref: item.ref.toString(),
164 + is: item.is.describe()
165 + } : {
166 + peek: item.peek.describe()
167 + };
168 +
169 + if (item.then) {
170 + when.then = item.then.describe();
171 + }
172 +
173 + if (item.otherwise) {
174 + when.otherwise = item.otherwise.describe();
175 + }
176 +
177 + alternatives.push(when);
178 + }
179 + }
180 +
181 + description.alternatives = alternatives;
182 + return description;
183 + }
184 +
185 +};
186 +
187 +
188 +module.exports = new internals.Alternatives();
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Hoek = require('hoek');
7 +
8 +
9 +// Declare internals
10 +
11 +const internals = {};
12 +
13 +
14 +internals.Binary = class extends Any {
15 +
16 + constructor() {
17 +
18 + super();
19 + this._type = 'binary';
20 + }
21 +
22 + _base(value, state, options) {
23 +
24 + const result = {
25 + value
26 + };
27 +
28 + if (typeof value === 'string' &&
29 + options.convert) {
30 +
31 + try {
32 + result.value = new Buffer(value, this._flags.encoding);
33 + }
34 + catch (e) {
35 + }
36 + }
37 +
38 + result.errors = Buffer.isBuffer(result.value) ? null : this.createError('binary.base', null, state, options);
39 + return result;
40 + }
41 +
42 + encoding(encoding) {
43 +
44 + Hoek.assert(Buffer.isEncoding(encoding), 'Invalid encoding:', encoding);
45 +
46 + if (this._flags.encoding === encoding) {
47 + return this;
48 + }
49 +
50 + const obj = this.clone();
51 + obj._flags.encoding = encoding;
52 + return obj;
53 + }
54 +
55 + min(limit) {
56 +
57 + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
58 +
59 + return this._test('min', limit, function (value, state, options) {
60 +
61 + if (value.length >= limit) {
62 + return value;
63 + }
64 +
65 + return this.createError('binary.min', { limit, value }, state, options);
66 + });
67 + }
68 +
69 + max(limit) {
70 +
71 + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
72 +
73 + return this._test('max', limit, function (value, state, options) {
74 +
75 + if (value.length <= limit) {
76 + return value;
77 + }
78 +
79 + return this.createError('binary.max', { limit, value }, state, options);
80 + });
81 + }
82 +
83 + length(limit) {
84 +
85 + Hoek.assert(Number.isSafeInteger(limit) && limit >= 0, 'limit must be a positive integer');
86 +
87 + return this._test('length', limit, function (value, state, options) {
88 +
89 + if (value.length === limit) {
90 + return value;
91 + }
92 +
93 + return this.createError('binary.length', { limit, value }, state, options);
94 + });
95 + }
96 +
97 +};
98 +
99 +
100 +module.exports = new internals.Binary();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Hoek = require('hoek');
7 +
8 +
9 +// Declare internals
10 +
11 +const internals = {
12 + Set: require('../../set')
13 +};
14 +
15 +
16 +internals.Boolean = class extends Any {
17 + constructor() {
18 +
19 + super();
20 + this._type = 'boolean';
21 + this._flags.insensitive = true;
22 + this._inner.truthySet = new internals.Set();
23 + this._inner.falsySet = new internals.Set();
24 + }
25 +
26 + _base(value, state, options) {
27 +
28 + const result = {
29 + value
30 + };
31 +
32 + if (typeof value === 'string' &&
33 + options.convert) {
34 +
35 + const normalized = this._flags.insensitive ? value.toLowerCase() : value;
36 + result.value = (normalized === 'true' ? true
37 + : (normalized === 'false' ? false : value));
38 + }
39 +
40 + if (typeof result.value !== 'boolean') {
41 + result.value = (this._inner.truthySet.has(value, null, null, this._flags.insensitive) ? true
42 + : (this._inner.falsySet.has(value, null, null, this._flags.insensitive) ? false : value));
43 + }
44 +
45 + result.errors = (typeof result.value === 'boolean') ? null : this.createError('boolean.base', null, state, options);
46 + return result;
47 + }
48 +
49 + truthy(...values) {
50 +
51 + const obj = this.clone();
52 + values = Hoek.flatten(values);
53 + for (let i = 0; i < values.length; ++i) {
54 + const value = values[i];
55 +
56 + Hoek.assert(value !== undefined, 'Cannot call truthy with undefined');
57 + obj._inner.truthySet.add(value);
58 + }
59 + return obj;
60 + }
61 +
62 + falsy(...values) {
63 +
64 + const obj = this.clone();
65 + values = Hoek.flatten(values);
66 + for (let i = 0; i < values.length; ++i) {
67 + const value = values[i];
68 +
69 + Hoek.assert(value !== undefined, 'Cannot call falsy with undefined');
70 + obj._inner.falsySet.add(value);
71 + }
72 + return obj;
73 + }
74 +
75 + insensitive(enabled) {
76 +
77 + const insensitive = enabled === undefined ? true : !!enabled;
78 +
79 + if (this._flags.insensitive === insensitive) {
80 + return this;
81 + }
82 +
83 + const obj = this.clone();
84 + obj._flags.insensitive = insensitive;
85 + return obj;
86 + }
87 +
88 + describe() {
89 +
90 + const description = Any.prototype.describe.call(this);
91 + description.truthy = [true].concat(this._inner.truthySet.values());
92 + description.falsy = [false].concat(this._inner.falsySet.values());
93 + return description;
94 + }
95 +};
96 +
97 +
98 +module.exports = new internals.Boolean();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Ref = require('../../ref');
7 +const Hoek = require('hoek');
8 +
9 +
10 +// Declare internals
11 +
12 +const internals = {};
13 +
14 +internals.isoDate = /^(?:[-+]\d{2})?(?:\d{4}(?!\d{2}\b))(?:(-?)(?:(?:0[1-9]|1[0-2])(?:\1(?:[12]\d|0[1-9]|3[01]))?|W(?:[0-4]\d|5[0-2])(?:-?[1-7])?|(?:00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[1-6])))(?![T]$|[T][\d]+Z$)(?:[T\s](?:(?:(?:[01]\d|2[0-3])(?:(:?)[0-5]\d)?|24\:?00)(?:[.,]\d+(?!:))?)(?:\2[0-5]\d(?:[.,]\d+)?)?(?:[Z]|(?:[+-])(?:[01]\d|2[0-3])(?::?[0-5]\d)?)?)?)?$/;
15 +internals.invalidDate = new Date('');
16 +internals.isIsoDate = (() => {
17 +
18 + const isoString = internals.isoDate.toString();
19 +
20 + return (date) => {
21 +
22 + return date && (date.toString() === isoString);
23 + };
24 +})();
25 +
26 +internals.Date = class extends Any {
27 +
28 + constructor() {
29 +
30 + super();
31 + this._type = 'date';
32 + }
33 +
34 + _base(value, state, options) {
35 +
36 + const result = {
37 + value: (options.convert && internals.Date.toDate(value, this._flags.format, this._flags.timestamp, this._flags.multiplier)) || value
38 + };
39 +
40 + if (result.value instanceof Date && !isNaN(result.value.getTime())) {
41 + result.errors = null;
42 + }
43 + else if (!options.convert) {
44 + result.errors = this.createError('date.strict', null, state, options);
45 + }
46 + else {
47 + let type;
48 + if (internals.isIsoDate(this._flags.format)) {
49 + type = 'isoDate';
50 + }
51 + else if (this._flags.timestamp) {
52 + type = `timestamp.${this._flags.timestamp}`;
53 + }
54 + else {
55 + type = 'base';
56 + }
57 +
58 + result.errors = this.createError(`date.${type}`, null, state, options);
59 + }
60 +
61 + return result;
62 + }
63 +
64 + static toDate(value, format, timestamp, multiplier) {
65 +
66 + if (value instanceof Date) {
67 + return value;
68 + }
69 +
70 + if (typeof value === 'string' ||
71 + (typeof value === 'number' && !isNaN(value) && isFinite(value))) {
72 +
73 + if (typeof value === 'string' &&
74 + /^[+-]?\d+(\.\d+)?$/.test(value)) {
75 +
76 + value = parseFloat(value);
77 + }
78 +
79 + let date;
80 + if (format && internals.isIsoDate(format)) {
81 + date = format.test(value) ? new Date(value) : internals.invalidDate;
82 + }
83 + else if (timestamp && multiplier) {
84 + date = /^\s*$/.test(value) ? internals.invalidDate : new Date(value * multiplier);
85 + }
86 + else {
87 + date = new Date(value);
88 + }
89 +
90 + if (!isNaN(date.getTime())) {
91 + return date;
92 + }
93 + }
94 +
95 + return null;
96 + }
97 +
98 + iso() {
99 +
100 + if (this._flags.format === internals.isoDate) {
101 + return this;
102 + }
103 +
104 + const obj = this.clone();
105 + obj._flags.format = internals.isoDate;
106 + return obj;
107 + }
108 +
109 + timestamp(type = 'javascript') {
110 +
111 + const allowed = ['javascript', 'unix'];
112 + Hoek.assert(allowed.includes(type), '"type" must be one of "' + allowed.join('", "') + '"');
113 +
114 + if (this._flags.timestamp === type) {
115 + return this;
116 + }
117 +
118 + const obj = this.clone();
119 + obj._flags.timestamp = type;
120 + obj._flags.multiplier = type === 'unix' ? 1000 : 1;
121 + return obj;
122 + }
123 +
124 + _isIsoDate(value) {
125 +
126 + return internals.isoDate.test(value);
127 + }
128 +
129 +};
130 +
131 +internals.compare = function (type, compare) {
132 +
133 + return function (date) {
134 +
135 + const isNow = date === 'now';
136 + const isRef = Ref.isRef(date);
137 +
138 + if (!isNow && !isRef) {
139 + date = internals.Date.toDate(date);
140 + }
141 +
142 + Hoek.assert(date, 'Invalid date format');
143 +
144 + return this._test(type, date, function (value, state, options) {
145 +
146 + let compareTo;
147 + if (isNow) {
148 + compareTo = Date.now();
149 + }
150 + else if (isRef) {
151 + compareTo = internals.Date.toDate(date(state.reference || state.parent, options));
152 +
153 + if (!compareTo) {
154 + return this.createError('date.ref', { ref: date.key }, state, options);
155 + }
156 +
157 + compareTo = compareTo.getTime();
158 + }
159 + else {
160 + compareTo = date.getTime();
161 + }
162 +
163 + if (compare(value.getTime(), compareTo)) {
164 + return value;
165 + }
166 +
167 + return this.createError('date.' + type, { limit: new Date(compareTo) }, state, options);
168 + });
169 + };
170 +};
171 +internals.Date.prototype.min = internals.compare('min', (value, date) => value >= date);
172 +internals.Date.prototype.max = internals.compare('max', (value, date) => value <= date);
173 +
174 +
175 +module.exports = new internals.Date();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +const ObjectType = require('../object');
7 +const Ref = require('../../ref');
8 +
9 +
10 +// Declare internals
11 +
12 +const internals = {};
13 +
14 +
15 +internals.Func = class extends ObjectType.constructor {
16 +
17 + constructor() {
18 +
19 + super();
20 + this._flags.func = true;
21 + }
22 +
23 + arity(n) {
24 +
25 + Hoek.assert(Number.isSafeInteger(n) && n >= 0, 'n must be a positive integer');
26 +
27 + return this._test('arity', n, function (value, state, options) {
28 +
29 + if (value.length === n) {
30 + return value;
31 + }
32 +
33 + return this.createError('function.arity', { n }, state, options);
34 + });
35 + }
36 +
37 + minArity(n) {
38 +
39 + Hoek.assert(Number.isSafeInteger(n) && n > 0, 'n must be a strict positive integer');
40 +
41 + return this._test('minArity', n, function (value, state, options) {
42 +
43 + if (value.length >= n) {
44 + return value;
45 + }
46 +
47 + return this.createError('function.minArity', { n }, state, options);
48 + });
49 + }
50 +
51 + maxArity(n) {
52 +
53 + Hoek.assert(Number.isSafeInteger(n) && n >= 0, 'n must be a positive integer');
54 +
55 + return this._test('maxArity', n, function (value, state, options) {
56 +
57 + if (value.length <= n) {
58 + return value;
59 + }
60 +
61 + return this.createError('function.maxArity', { n }, state, options);
62 + });
63 + }
64 +
65 + ref() {
66 +
67 + return this._test('ref', null, function (value, state, options) {
68 +
69 + if (Ref.isRef(value)) {
70 + return value;
71 + }
72 +
73 + return this.createError('function.ref', null, state, options);
74 + });
75 + }
76 +
77 + class() {
78 +
79 + return this._test('class', null, function (value, state, options) {
80 +
81 + if ((/^\s*class\s/).test(value.toString())) {
82 + return value;
83 + }
84 +
85 + return this.createError('function.class', null, state, options);
86 + });
87 + }
88 +};
89 +
90 +module.exports = new internals.Func();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Hoek = require('hoek');
7 +
8 +
9 +// Declare internals
10 +
11 +const internals = {};
12 +
13 +
14 +internals.Lazy = class extends Any {
15 +
16 + constructor() {
17 +
18 + super();
19 + this._type = 'lazy';
20 + }
21 +
22 + _base(value, state, options) {
23 +
24 + const result = { value };
25 + const lazy = this._flags.lazy;
26 +
27 + if (!lazy) {
28 + result.errors = this.createError('lazy.base', null, state, options);
29 + return result;
30 + }
31 +
32 + const schema = lazy();
33 +
34 + if (!(schema instanceof Any)) {
35 + result.errors = this.createError('lazy.schema', null, state, options);
36 + return result;
37 + }
38 +
39 + return schema._validate(value, state, options);
40 + }
41 +
42 + set(fn) {
43 +
44 + Hoek.assert(typeof fn === 'function', 'You must provide a function as first argument');
45 +
46 + const obj = this.clone();
47 + obj._flags.lazy = fn;
48 + return obj;
49 + }
50 +
51 +};
52 +
53 +module.exports = new internals.Lazy();
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Any = require('../any');
6 +const Ref = require('../../ref');
7 +const Hoek = require('hoek');
8 +
9 +
10 +// Declare internals
11 +
12 +const internals = {
13 + precisionRx: /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/
14 +};
15 +
16 +
17 +internals.Number = class extends Any {
18 +
19 + constructor() {
20 +
21 + super();
22 + this._type = 'number';
23 + this._invalids.add(Infinity);
24 + this._invalids.add(-Infinity);
25 + }
26 +
27 + _base(value, state, options) {
28 +
29 + const result = {
30 + errors: null,
31 + value
32 + };
33 +
34 + if (typeof value === 'string' &&
35 + options.convert) {
36 +
37 + const number = parseFloat(value);
38 + result.value = (isNaN(number) || !isFinite(value)) ? NaN : number;
39 + }
40 +
41 + const isNumber = typeof result.value === 'number' && !isNaN(result.value);
42 +
43 + if (options.convert && 'precision' in this._flags && isNumber) {
44 +
45 + // This is conceptually equivalent to using toFixed but it should be much faster
46 + const precision = Math.pow(10, this._flags.precision);
47 + result.value = Math.round(result.value * precision) / precision;
48 + }
49 +
50 + result.errors = isNumber ? null : this.createError('number.base', null, state, options);
51 + return result;
52 + }
53 +
54 + multiple(base) {
55 +
56 + const isRef = Ref.isRef(base);
57 +
58 + if (!isRef) {
59 + Hoek.assert(typeof base === 'number' && isFinite(base), 'multiple must be a number');
60 + Hoek.assert(base > 0, 'multiple must be greater than 0');
61 + }
62 +
63 + return this._test('multiple', base, function (value, state, options) {
64 +
65 + const divisor = isRef ? base(state.reference || state.parent, options) : base;
66 +
67 + if (isRef && (typeof divisor !== 'number' || !isFinite(divisor))) {
68 + return this.createError('number.ref', { ref: base.key }, state, options);
69 + }
70 +
71 + if (value % divisor === 0) {
72 + return value;
73 + }
74 +
75 + return this.createError('number.multiple', { multiple: base, value }, state, options);
76 + });
77 + }
78 +
79 + integer() {
80 +
81 + return this._test('integer', undefined, function (value, state, options) {
82 +
83 + return Number.isSafeInteger(value) ? value : this.createError('number.integer', { value }, state, options);
84 + });
85 + }
86 +
87 + negative() {
88 +
89 + return this._test('negative', undefined, function (value, state, options) {
90 +
91 + if (value < 0) {
92 + return value;
93 + }
94 +
95 + return this.createError('number.negative', { value }, state, options);
96 + });
97 + }
98 +
99 + positive() {
100 +
101 + return this._test('positive', undefined, function (value, state, options) {
102 +
103 + if (value > 0) {
104 + return value;
105 + }
106 +
107 + return this.createError('number.positive', { value }, state, options);
108 + });
109 + }
110 +
111 + precision(limit) {
112 +
113 + Hoek.assert(Number.isSafeInteger(limit), 'limit must be an integer');
114 + Hoek.assert(!('precision' in this._flags), 'precision already set');
115 +
116 + const obj = this._test('precision', limit, function (value, state, options) {
117 +
118 + const places = value.toString().match(internals.precisionRx);
119 + const decimals = Math.max((places[1] ? places[1].length : 0) - (places[2] ? parseInt(places[2], 10) : 0), 0);
120 + if (decimals <= limit) {
121 + return value;
122 + }
123 +
124 + return this.createError('number.precision', { limit, value }, state, options);
125 + });
126 +
127 + obj._flags.precision = limit;
128 + return obj;
129 + }
130 +
131 +};
132 +
133 +
134 +internals.compare = function (type, compare) {
135 +
136 + return function (limit) {
137 +
138 + const isRef = Ref.isRef(limit);
139 + const isNumber = typeof limit === 'number' && !isNaN(limit);
140 +
141 + Hoek.assert(isNumber || isRef, 'limit must be a number or reference');
142 +
143 + return this._test(type, limit, function (value, state, options) {
144 +
145 + let compareTo;
146 + if (isRef) {
147 + compareTo = limit(state.reference || state.parent, options);
148 +
149 + if (!(typeof compareTo === 'number' && !isNaN(compareTo))) {
150 + return this.createError('number.ref', { ref: limit.key }, state, options);
151 + }
152 + }
153 + else {
154 + compareTo = limit;
155 + }
156 +
157 + if (compare(value, compareTo)) {
158 + return value;
159 + }
160 +
161 + return this.createError('number.' + type, { limit: compareTo, value }, state, options);
162 + });
163 + };
164 +};
165 +
166 +
167 +internals.Number.prototype.min = internals.compare('min', (value, limit) => value >= limit);
168 +internals.Number.prototype.max = internals.compare('max', (value, limit) => value <= limit);
169 +internals.Number.prototype.greater = internals.compare('greater', (value, limit) => value > limit);
170 +internals.Number.prototype.less = internals.compare('less', (value, limit) => value < limit);
171 +
172 +
173 +module.exports = new internals.Number();
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const RFC3986 = require('./rfc3986');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {
11 + Ip: {
12 + cidrs: {
13 + ipv4: {
14 + required: '\\/(?:' + RFC3986.ipv4Cidr + ')',
15 + optional: '(?:\\/(?:' + RFC3986.ipv4Cidr + '))?',
16 + forbidden: ''
17 + },
18 + ipv6: {
19 + required: '\\/' + RFC3986.ipv6Cidr,
20 + optional: '(?:\\/' + RFC3986.ipv6Cidr + ')?',
21 + forbidden: ''
22 + },
23 + ipvfuture: {
24 + required: '\\/' + RFC3986.ipv6Cidr,
25 + optional: '(?:\\/' + RFC3986.ipv6Cidr + ')?',
26 + forbidden: ''
27 + }
28 + },
29 + versions: {
30 + ipv4: RFC3986.IPv4address,
31 + ipv6: RFC3986.IPv6address,
32 + ipvfuture: RFC3986.IPvFuture
33 + }
34 + }
35 +};
36 +
37 +
38 +internals.Ip.createIpRegex = function (versions, cidr) {
39 +
40 + let regex;
41 + for (let i = 0; i < versions.length; ++i) {
42 + const version = versions[i];
43 + if (!regex) {
44 + regex = '^(?:' + internals.Ip.versions[version] + internals.Ip.cidrs[version][cidr];
45 + }
46 + else {
47 + regex += '|' + internals.Ip.versions[version] + internals.Ip.cidrs[version][cidr];
48 + }
49 + }
50 +
51 + return new RegExp(regex + ')$');
52 +};
53 +
54 +module.exports = internals.Ip;
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +
6 +// Delcare internals
7 +
8 +const internals = {
9 + rfc3986: {}
10 +};
11 +
12 +
13 +internals.generate = function () {
14 +
15 + /**
16 + * elements separated by forward slash ("/") are alternatives.
17 + */
18 + const or = '|';
19 +
20 + /**
21 + * Rule to support zero-padded addresses.
22 + */
23 + const zeroPad = '0?';
24 +
25 + /**
26 + * DIGIT = %x30-39 ; 0-9
27 + */
28 + const digit = '0-9';
29 + const digitOnly = '[' + digit + ']';
30 +
31 + /**
32 + * ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
33 + */
34 + const alpha = 'a-zA-Z';
35 + const alphaOnly = '[' + alpha + ']';
36 +
37 + /**
38 + * IPv4
39 + * cidr = DIGIT ; 0-9
40 + * / %x31-32 DIGIT ; 10-29
41 + * / "3" %x30-32 ; 30-32
42 + */
43 + internals.rfc3986.ipv4Cidr = digitOnly + or + '[1-2]' + digitOnly + or + '3' + '[0-2]';
44 +
45 + /**
46 + * IPv6
47 + * cidr = DIGIT ; 0-9
48 + * / %x31-39 DIGIT ; 10-99
49 + * / "1" %x0-1 DIGIT ; 100-119
50 + * / "12" %x0-8 ; 120-128
51 + */
52 + internals.rfc3986.ipv6Cidr = '(?:' + zeroPad + zeroPad + digitOnly + or + zeroPad + '[1-9]' + digitOnly + or + '1' + '[01]' + digitOnly + or + '12[0-8])';
53 +
54 + /**
55 + * HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
56 + */
57 + const hexDigit = digit + 'A-Fa-f';
58 + const hexDigitOnly = '[' + hexDigit + ']';
59 +
60 + /**
61 + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
62 + */
63 + const unreserved = alpha + digit + '-\\._~';
64 +
65 + /**
66 + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
67 + */
68 + const subDelims = '!\\$&\'\\(\\)\\*\\+,;=';
69 +
70 + /**
71 + * pct-encoded = "%" HEXDIG HEXDIG
72 + */
73 + const pctEncoded = '%' + hexDigit;
74 +
75 + /**
76 + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
77 + */
78 + const pchar = unreserved + pctEncoded + subDelims + ':@';
79 + const pcharOnly = '[' + pchar + ']';
80 +
81 + /**
82 + * dec-octet = DIGIT ; 0-9
83 + * / %x31-39 DIGIT ; 10-99
84 + * / "1" 2DIGIT ; 100-199
85 + * / "2" %x30-34 DIGIT ; 200-249
86 + * / "25" %x30-35 ; 250-255
87 + */
88 + const decOctect = '(?:' + zeroPad + zeroPad + digitOnly + or + zeroPad + '[1-9]' + digitOnly + or + '1' + digitOnly + digitOnly + or + '2' + '[0-4]' + digitOnly + or + '25' + '[0-5])';
89 +
90 + /**
91 + * IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
92 + */
93 + internals.rfc3986.IPv4address = '(?:' + decOctect + '\\.){3}' + decOctect;
94 +
95 + /**
96 + * h16 = 1*4HEXDIG ; 16 bits of address represented in hexadecimal
97 + * ls32 = ( h16 ":" h16 ) / IPv4address ; least-significant 32 bits of address
98 + * IPv6address = 6( h16 ":" ) ls32
99 + * / "::" 5( h16 ":" ) ls32
100 + * / [ h16 ] "::" 4( h16 ":" ) ls32
101 + * / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
102 + * / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
103 + * / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
104 + * / [ *4( h16 ":" ) h16 ] "::" ls32
105 + * / [ *5( h16 ":" ) h16 ] "::" h16
106 + * / [ *6( h16 ":" ) h16 ] "::"
107 + */
108 + const h16 = hexDigitOnly + '{1,4}';
109 + const ls32 = '(?:' + h16 + ':' + h16 + '|' + internals.rfc3986.IPv4address + ')';
110 + const IPv6SixHex = '(?:' + h16 + ':){6}' + ls32;
111 + const IPv6FiveHex = '::(?:' + h16 + ':){5}' + ls32;
112 + const IPv6FourHex = '(?:' + h16 + ')?::(?:' + h16 + ':){4}' + ls32;
113 + const IPv6ThreeHex = '(?:(?:' + h16 + ':){0,1}' + h16 + ')?::(?:' + h16 + ':){3}' + ls32;
114 + const IPv6TwoHex = '(?:(?:' + h16 + ':){0,2}' + h16 + ')?::(?:' + h16 + ':){2}' + ls32;
115 + const IPv6OneHex = '(?:(?:' + h16 + ':){0,3}' + h16 + ')?::' + h16 + ':' + ls32;
116 + const IPv6NoneHex = '(?:(?:' + h16 + ':){0,4}' + h16 + ')?::' + ls32;
117 + const IPv6NoneHex2 = '(?:(?:' + h16 + ':){0,5}' + h16 + ')?::' + h16;
118 + const IPv6NoneHex3 = '(?:(?:' + h16 + ':){0,6}' + h16 + ')?::';
119 + internals.rfc3986.IPv6address = '(?:' + IPv6SixHex + or + IPv6FiveHex + or + IPv6FourHex + or + IPv6ThreeHex + or + IPv6TwoHex + or + IPv6OneHex + or + IPv6NoneHex + or + IPv6NoneHex2 + or + IPv6NoneHex3 + ')';
120 +
121 + /**
122 + * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
123 + */
124 + internals.rfc3986.IPvFuture = 'v' + hexDigitOnly + '+\\.[' + unreserved + subDelims + ':]+';
125 +
126 + /**
127 + * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
128 + */
129 + internals.rfc3986.scheme = alphaOnly + '[' + alpha + digit + '+-\\.]*';
130 +
131 + /**
132 + * userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
133 + */
134 + const userinfo = '[' + unreserved + pctEncoded + subDelims + ':]*';
135 +
136 + /**
137 + * IP-literal = "[" ( IPv6address / IPvFuture ) "]"
138 + */
139 + const IPLiteral = '\\[(?:' + internals.rfc3986.IPv6address + or + internals.rfc3986.IPvFuture + ')\\]';
140 +
141 + /**
142 + * reg-name = *( unreserved / pct-encoded / sub-delims )
143 + */
144 + const regName = '[' + unreserved + pctEncoded + subDelims + ']{0,255}';
145 +
146 + /**
147 + * host = IP-literal / IPv4address / reg-name
148 + */
149 + const host = '(?:' + IPLiteral + or + internals.rfc3986.IPv4address + or + regName + ')';
150 +
151 + /**
152 + * port = *DIGIT
153 + */
154 + const port = digitOnly + '*';
155 +
156 + /**
157 + * authority = [ userinfo "@" ] host [ ":" port ]
158 + */
159 + const authority = '(?:' + userinfo + '@)?' + host + '(?::' + port + ')?';
160 +
161 + /**
162 + * segment = *pchar
163 + * segment-nz = 1*pchar
164 + * path = path-abempty ; begins with "/" or is empty
165 + * / path-absolute ; begins with "/" but not "//"
166 + * / path-noscheme ; begins with a non-colon segment
167 + * / path-rootless ; begins with a segment
168 + * / path-empty ; zero characters
169 + * path-abempty = *( "/" segment )
170 + * path-absolute = "/" [ segment-nz *( "/" segment ) ]
171 + * path-rootless = segment-nz *( "/" segment )
172 + */
173 + const segment = pcharOnly + '*';
174 + const segmentNz = pcharOnly + '+';
175 + const segmentNzNc = '[' + unreserved + pctEncoded + subDelims + '@' + ']+';
176 + const pathEmpty = '';
177 + const pathAbEmpty = '(?:\\/' + segment + ')*';
178 + const pathAbsolute = '\\/(?:' + segmentNz + pathAbEmpty + ')?';
179 + const pathRootless = segmentNz + pathAbEmpty;
180 + const pathNoScheme = segmentNzNc + pathAbEmpty;
181 +
182 + /**
183 + * hier-part = "//" authority path
184 + */
185 + internals.rfc3986.hierPart = '(?:' + '(?:\\/\\/' + authority + pathAbEmpty + ')' + or + pathAbsolute + or + pathRootless + ')';
186 +
187 + /**
188 + * relative-part = "//" authority path-abempty
189 + * / path-absolute
190 + * / path-noscheme
191 + * / path-empty
192 + */
193 + internals.rfc3986.relativeRef = '(?:' + '(?:\\/\\/' + authority + pathAbEmpty + ')' + or + pathAbsolute + or + pathNoScheme + or + pathEmpty + ')';
194 +
195 + /**
196 + * query = *( pchar / "/" / "?" )
197 + */
198 + internals.rfc3986.query = '[' + pchar + '\\/\\?]*(?=#|$)'; //Finish matching either at the fragment part or end of the line.
199 +
200 + /**
201 + * fragment = *( pchar / "/" / "?" )
202 + */
203 + internals.rfc3986.fragment = '[' + pchar + '\\/\\?]*';
204 +};
205 +
206 +
207 +internals.generate();
208 +
209 +module.exports = internals.rfc3986;
1 +'use strict';
2 +
3 +// Load Modules
4 +
5 +const RFC3986 = require('./rfc3986');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {
11 + Uri: {
12 + createUriRegex: function (optionalScheme, allowRelative, relativeOnly) {
13 +
14 + let scheme = RFC3986.scheme;
15 + let prefix;
16 +
17 + if (relativeOnly) {
18 + prefix = '(?:' + RFC3986.relativeRef + ')';
19 + }
20 + else {
21 + // If we were passed a scheme, use it instead of the generic one
22 + if (optionalScheme) {
23 +
24 + // Have to put this in a non-capturing group to handle the OR statements
25 + scheme = '(?:' + optionalScheme + ')';
26 + }
27 +
28 + const withScheme = '(?:' + scheme + ':' + RFC3986.hierPart + ')';
29 +
30 + prefix = allowRelative ? '(?:' + withScheme + '|' + RFC3986.relativeRef + ')' : withScheme;
31 + }
32 +
33 + /**
34 + * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
35 + *
36 + * OR
37 + *
38 + * relative-ref = relative-part [ "?" query ] [ "#" fragment ]
39 + */
40 + return new RegExp('^' + prefix + '(?:\\?' + RFC3986.query + ')?' + '(?:#' + RFC3986.fragment + ')?$');
41 + }
42 + }
43 +};
44 +
45 +
46 +module.exports = internals.Uri;
1 +{
2 + "_from": "joi",
3 + "_id": "joi@13.1.2",
4 + "_inBundle": false,
5 + "_integrity": "sha512-bZZSQYW5lPXenOfENvgCBPb9+H6E6MeNWcMtikI04fKphj5tvFL9TOb+H2apJzbCrRw/jebjTH8z6IHLpBytGg==",
6 + "_location": "/joi",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "tag",
10 + "registry": true,
11 + "raw": "joi",
12 + "name": "joi",
13 + "escapedName": "joi",
14 + "rawSpec": "",
15 + "saveSpec": null,
16 + "fetchSpec": "latest"
17 + },
18 + "_requiredBy": [
19 + "#USER",
20 + "/"
21 + ],
22 + "_resolved": "https://registry.npmjs.org/joi/-/joi-13.1.2.tgz",
23 + "_shasum": "b2db260323cc7f919fafa51e09e2275bd089a97e",
24 + "_spec": "joi",
25 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo",
26 + "bugs": {
27 + "url": "https://github.com/hapijs/joi/issues"
28 + },
29 + "bundleDependencies": false,
30 + "dependencies": {
31 + "hoek": "5.x.x",
32 + "isemail": "3.x.x",
33 + "topo": "3.x.x"
34 + },
35 + "deprecated": false,
36 + "description": "Object schema validation",
37 + "devDependencies": {
38 + "code": "5.x.x",
39 + "hapitoc": "1.x.x",
40 + "lab": "15.x.x"
41 + },
42 + "engines": {
43 + "node": ">=8.9.0"
44 + },
45 + "homepage": "https://github.com/hapijs/joi",
46 + "keywords": [
47 + "hapi",
48 + "schema",
49 + "validation"
50 + ],
51 + "license": "BSD-3-Clause",
52 + "main": "lib/index.js",
53 + "name": "joi",
54 + "repository": {
55 + "type": "git",
56 + "url": "git://github.com/hapijs/joi.git"
57 + },
58 + "scripts": {
59 + "test": "lab -t 100 -a code -L",
60 + "test-cov-html": "lab -r html -o coverage.html -a code",
61 + "test-debug": "lab -a code",
62 + "toc": "hapitoc",
63 + "version": "npm run toc && git add API.md README.md"
64 + },
65 + "version": "13.1.2"
66 +}
1 +Copyright Mathias Bynens <https://mathiasbynens.be/>
2 +
3 +Permission is hereby granted, free of charge, to any person obtaining
4 +a copy of this software and associated documentation files (the
5 +"Software"), to deal in the Software without restriction, including
6 +without limitation the rights to use, copy, modify, merge, publish,
7 +distribute, sublicense, and/or sell copies of the Software, and to
8 +permit persons to whom the Software is furnished to do so, subject to
9 +the following conditions:
10 +
11 +The above copyright notice and this permission notice shall be
12 +included in all copies or substantial portions of the Software.
13 +
14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1 +# Punycode.js [![Build status](https://travis-ci.org/bestiejs/punycode.js.svg?branch=master)](https://travis-ci.org/bestiejs/punycode.js) [![Code coverage status](http://img.shields.io/codecov/c/github/bestiejs/punycode.js.svg)](https://codecov.io/gh/bestiejs/punycode.js) [![Dependency status](https://gemnasium.com/bestiejs/punycode.js.svg)](https://gemnasium.com/bestiejs/punycode.js)
2 +
3 +Punycode.js is a robust Punycode converter that fully complies to [RFC 3492](https://tools.ietf.org/html/rfc3492) and [RFC 5891](https://tools.ietf.org/html/rfc5891).
4 +
5 +This JavaScript library is the result of comparing, optimizing and documenting different open-source implementations of the Punycode algorithm:
6 +
7 +* [The C example code from RFC 3492](https://tools.ietf.org/html/rfc3492#appendix-C)
8 +* [`punycode.c` by _Markus W. Scherer_ (IBM)](http://opensource.apple.com/source/ICU/ICU-400.42/icuSources/common/punycode.c)
9 +* [`punycode.c` by _Ben Noordhuis_](https://github.com/bnoordhuis/punycode/blob/master/punycode.c)
10 +* [JavaScript implementation by _some_](http://stackoverflow.com/questions/183485/can-anyone-recommend-a-good-free-javascript-for-punycode-to-unicode-conversion/301287#301287)
11 +* [`punycode.js` by _Ben Noordhuis_](https://github.com/joyent/node/blob/426298c8c1c0d5b5224ac3658c41e7c2a3fe9377/lib/punycode.js) (note: [not fully compliant](https://github.com/joyent/node/issues/2072))
12 +
13 +This project was [bundled](https://github.com/joyent/node/blob/master/lib/punycode.js) with Node.js from [v0.6.2+](https://github.com/joyent/node/compare/975f1930b1...61e796decc) until [v7](https://github.com/nodejs/node/pull/7941) (soft-deprecated).
14 +
15 +The current version supports recent versions of Node.js only. It provides a CommonJS module and an ES6 module. For the old version that offers the same functionality with broader support, including Rhino, Ringo, Narwhal, and web browsers, see [v1.4.1](https://github.com/bestiejs/punycode.js/releases/tag/v1.4.1).
16 +
17 +## Installation
18 +
19 +Via [npm](https://www.npmjs.com/):
20 +
21 +```bash
22 +npm install punycode --save
23 +```
24 +
25 +In [Node.js](https://nodejs.org/):
26 +
27 +```js
28 +const punycode = require('punycode');
29 +```
30 +
31 +## API
32 +
33 +### `punycode.decode(string)`
34 +
35 +Converts a Punycode string of ASCII symbols to a string of Unicode symbols.
36 +
37 +```js
38 +// decode domain name parts
39 +punycode.decode('maana-pta'); // 'mañana'
40 +punycode.decode('--dqo34k'); // '☃-⌘'
41 +```
42 +
43 +### `punycode.encode(string)`
44 +
45 +Converts a string of Unicode symbols to a Punycode string of ASCII symbols.
46 +
47 +```js
48 +// encode domain name parts
49 +punycode.encode('mañana'); // 'maana-pta'
50 +punycode.encode('☃-⌘'); // '--dqo34k'
51 +```
52 +
53 +### `punycode.toUnicode(input)`
54 +
55 +Converts a Punycode string representing a domain name or an email address to Unicode. Only the Punycoded parts of the input will be converted, i.e. it doesn’t matter if you call it on a string that has already been converted to Unicode.
56 +
57 +```js
58 +// decode domain names
59 +punycode.toUnicode('xn--maana-pta.com');
60 +// → 'mañana.com'
61 +punycode.toUnicode('xn----dqo34k.com');
62 +// → '☃-⌘.com'
63 +
64 +// decode email addresses
65 +punycode.toUnicode('джумла@xn--p-8sbkgc5ag7bhce.xn--ba-lmcq');
66 +// → 'джумла@джpумлатест.bрфa'
67 +```
68 +
69 +### `punycode.toASCII(input)`
70 +
71 +Converts a lowercased Unicode string representing a domain name or an email address to Punycode. Only the non-ASCII parts of the input will be converted, i.e. it doesn’t matter if you call it with a domain that’s already in ASCII.
72 +
73 +```js
74 +// encode domain names
75 +punycode.toASCII('mañana.com');
76 +// → 'xn--maana-pta.com'
77 +punycode.toASCII('☃-⌘.com');
78 +// → 'xn----dqo34k.com'
79 +
80 +// encode email addresses
81 +punycode.toASCII('джумла@джpумлатест.bрфa');
82 +// → 'джумла@xn--p-8sbkgc5ag7bhce.xn--ba-lmcq'
83 +```
84 +
85 +### `punycode.ucs2`
86 +
87 +#### `punycode.ucs2.decode(string)`
88 +
89 +Creates an array containing the numeric code point values of each Unicode symbol in the string. While [JavaScript uses UCS-2 internally](https://mathiasbynens.be/notes/javascript-encoding), this function will convert a pair of surrogate halves (each of which UCS-2 exposes as separate characters) into a single code point, matching UTF-16.
90 +
91 +```js
92 +punycode.ucs2.decode('abc');
93 +// → [0x61, 0x62, 0x63]
94 +// surrogate pair for U+1D306 TETRAGRAM FOR CENTRE:
95 +punycode.ucs2.decode('\uD834\uDF06');
96 +// → [0x1D306]
97 +```
98 +
99 +#### `punycode.ucs2.encode(codePoints)`
100 +
101 +Creates a string based on an array of numeric code point values.
102 +
103 +```js
104 +punycode.ucs2.encode([0x61, 0x62, 0x63]);
105 +// → 'abc'
106 +punycode.ucs2.encode([0x1D306]);
107 +// → '\uD834\uDF06'
108 +```
109 +
110 +### `punycode.version`
111 +
112 +A string representing the current Punycode.js version number.
113 +
114 +## Author
115 +
116 +| [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
117 +|---|
118 +| [Mathias Bynens](https://mathiasbynens.be/) |
119 +
120 +## License
121 +
122 +Punycode.js is available under the [MIT](https://mths.be/mit) license.
1 +{
2 + "_from": "punycode@2.x.x",
3 + "_id": "punycode@2.1.0",
4 + "_inBundle": false,
5 + "_integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=",
6 + "_location": "/punycode",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "punycode@2.x.x",
12 + "name": "punycode",
13 + "escapedName": "punycode",
14 + "rawSpec": "2.x.x",
15 + "saveSpec": null,
16 + "fetchSpec": "2.x.x"
17 + },
18 + "_requiredBy": [
19 + "/isemail"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz",
22 + "_shasum": "5f863edc89b96db09074bad7947bf09056ca4e7d",
23 + "_spec": "punycode@2.x.x",
24 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo/node_modules/isemail",
25 + "author": {
26 + "name": "Mathias Bynens",
27 + "url": "https://mathiasbynens.be/"
28 + },
29 + "bugs": {
30 + "url": "https://github.com/bestiejs/punycode.js/issues"
31 + },
32 + "bundleDependencies": false,
33 + "contributors": [
34 + {
35 + "name": "Mathias Bynens",
36 + "url": "https://mathiasbynens.be/"
37 + }
38 + ],
39 + "deprecated": false,
40 + "description": "A robust Punycode converter that fully complies to RFC 3492 and RFC 5891, and works on nearly all JavaScript platforms.",
41 + "devDependencies": {
42 + "codecov": "^1.0.1",
43 + "istanbul": "^0.4.1",
44 + "mocha": "^2.5.3"
45 + },
46 + "engines": {
47 + "node": ">=6"
48 + },
49 + "files": [
50 + "LICENSE-MIT.txt",
51 + "punycode.js",
52 + "punycode.es6.js"
53 + ],
54 + "homepage": "https://mths.be/punycode",
55 + "jsnext:main": "punycode.es6.js",
56 + "jspm": {
57 + "map": {
58 + "./punycode.js": {
59 + "node": "@node/punycode"
60 + }
61 + }
62 + },
63 + "keywords": [
64 + "punycode",
65 + "unicode",
66 + "idn",
67 + "idna",
68 + "dns",
69 + "url",
70 + "domain"
71 + ],
72 + "license": "MIT",
73 + "main": "punycode.js",
74 + "name": "punycode",
75 + "repository": {
76 + "type": "git",
77 + "url": "git+https://github.com/bestiejs/punycode.js.git"
78 + },
79 + "scripts": {
80 + "prepublish": "node scripts/prepublish.js",
81 + "test": "mocha tests"
82 + },
83 + "version": "2.1.0"
84 +}
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 +Copyright (c) 2012-2016, Project contributors
2 +Copyright (c) 2012-2014, Walmart
3 +All rights reserved.
4 +
5 +Redistribution and use in source and binary forms, with or without
6 +modification, are permitted provided that the following conditions are met:
7 + * Redistributions of source code must retain the above copyright
8 + notice, this list of conditions and the following disclaimer.
9 + * Redistributions in binary form must reproduce the above copyright
10 + notice, this list of conditions and the following disclaimer in the
11 + documentation and/or other materials provided with the distribution.
12 + * The names of any contributors may not be used to endorse or promote
13 + products derived from this software without specific prior written
14 + permission.
15 +
16 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
20 +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 +
27 + * * *
28 +
29 +The complete list of contributors can be found at: https://github.com/hapijs/topo/graphs/contributors
1 +# topo
2 +
3 +Topological sorting with grouping support.
4 +
5 +[![Build Status](https://secure.travis-ci.org/hapijs/topo.svg?branch=master)](http://travis-ci.org/hapijs/topo)
6 +
7 +Lead Maintainer: [Devin Ivy](https://github.com/devinivy)
8 +
9 +## Usage
10 +
11 +See the [API Reference](API.md)
12 +
13 +**Example**
14 +```node
15 +const Topo = require('topo');
16 +
17 +const morning = new Topo();
18 +
19 +morning.add('Nap', { after: ['breakfast', 'prep'] });
20 +
21 +morning.add([
22 + 'Make toast',
23 + 'Pour juice'
24 +], { before: 'breakfast', group: 'prep' });
25 +
26 +morning.add('Eat breakfast', { group: 'breakfast' });
27 +
28 +morning.nodes; // ['Make toast', 'Pour juice', 'Eat breakfast', 'Nap']
29 +```
1 +'use strict';
2 +
3 +// Load modules
4 +
5 +const Hoek = require('hoek');
6 +
7 +
8 +// Declare internals
9 +
10 +const internals = {};
11 +
12 +
13 +exports = module.exports = internals.Topo = function () {
14 +
15 + this._items = [];
16 + this.nodes = [];
17 +};
18 +
19 +
20 +internals.Topo.prototype.add = function (nodes, options) {
21 +
22 + options = options || {};
23 +
24 + // Validate rules
25 +
26 + const before = [].concat(options.before || []);
27 + const after = [].concat(options.after || []);
28 + const group = options.group || '?';
29 + const sort = options.sort || 0; // Used for merging only
30 +
31 + Hoek.assert(before.indexOf(group) === -1, 'Item cannot come before itself:', group);
32 + Hoek.assert(before.indexOf('?') === -1, 'Item cannot come before unassociated items');
33 + Hoek.assert(after.indexOf(group) === -1, 'Item cannot come after itself:', group);
34 + Hoek.assert(after.indexOf('?') === -1, 'Item cannot come after unassociated items');
35 +
36 + ([].concat(nodes)).forEach((node, i) => {
37 +
38 + const item = {
39 + seq: this._items.length,
40 + sort,
41 + before,
42 + after,
43 + group,
44 + node
45 + };
46 +
47 + this._items.push(item);
48 + });
49 +
50 + // Insert event
51 +
52 + const error = this._sort();
53 + Hoek.assert(!error, 'item', (group !== '?' ? 'added into group ' + group : ''), 'created a dependencies error');
54 +
55 + return this.nodes;
56 +};
57 +
58 +
59 +internals.Topo.prototype.merge = function (others) {
60 +
61 + others = [].concat(others);
62 + for (let i = 0; i < others.length; ++i) {
63 + const other = others[i];
64 + if (other) {
65 + for (let j = 0; j < other._items.length; ++j) {
66 + const item = Hoek.shallow(other._items[j]);
67 + this._items.push(item);
68 + }
69 + }
70 + }
71 +
72 + // Sort items
73 +
74 + this._items.sort(internals.mergeSort);
75 + for (let i = 0; i < this._items.length; ++i) {
76 + this._items[i].seq = i;
77 + }
78 +
79 + const error = this._sort();
80 + Hoek.assert(!error, 'merge created a dependencies error');
81 +
82 + return this.nodes;
83 +};
84 +
85 +
86 +internals.mergeSort = function (a, b) {
87 +
88 + return a.sort === b.sort ? 0 : (a.sort < b.sort ? -1 : 1);
89 +};
90 +
91 +
92 +internals.Topo.prototype._sort = function () {
93 +
94 + // Construct graph
95 +
96 + const graph = {};
97 + const graphAfters = Object.create(null); // A prototype can bungle lookups w/ false positives
98 + const groups = Object.create(null);
99 +
100 + for (let i = 0; i < this._items.length; ++i) {
101 + const item = this._items[i];
102 + const seq = item.seq; // Unique across all items
103 + const group = item.group;
104 +
105 + // Determine Groups
106 +
107 + groups[group] = groups[group] || [];
108 + groups[group].push(seq);
109 +
110 + // Build intermediary graph using 'before'
111 +
112 + graph[seq] = item.before;
113 +
114 + // Build second intermediary graph with 'after'
115 +
116 + const after = item.after;
117 + for (let j = 0; j < after.length; ++j) {
118 + graphAfters[after[j]] = (graphAfters[after[j]] || []).concat(seq);
119 + }
120 + }
121 +
122 + // Expand intermediary graph
123 +
124 + let graphNodes = Object.keys(graph);
125 + for (let i = 0; i < graphNodes.length; ++i) {
126 + const node = graphNodes[i];
127 + const expandedGroups = [];
128 +
129 + const graphNodeItems = Object.keys(graph[node]);
130 + for (let j = 0; j < graphNodeItems.length; ++j) {
131 + const group = graph[node][graphNodeItems[j]];
132 + groups[group] = groups[group] || [];
133 +
134 + for (let k = 0; k < groups[group].length; ++k) {
135 + expandedGroups.push(groups[group][k]);
136 + }
137 + }
138 + graph[node] = expandedGroups;
139 + }
140 +
141 + // Merge intermediary graph using graphAfters into final graph
142 +
143 + const afterNodes = Object.keys(graphAfters);
144 + for (let i = 0; i < afterNodes.length; ++i) {
145 + const group = afterNodes[i];
146 +
147 + if (groups[group]) {
148 + for (let j = 0; j < groups[group].length; ++j) {
149 + const node = groups[group][j];
150 + graph[node] = graph[node].concat(graphAfters[group]);
151 + }
152 + }
153 + }
154 +
155 + // Compile ancestors
156 +
157 + let children;
158 + const ancestors = {};
159 + graphNodes = Object.keys(graph);
160 + for (let i = 0; i < graphNodes.length; ++i) {
161 + const node = graphNodes[i];
162 + children = graph[node];
163 +
164 + for (let j = 0; j < children.length; ++j) {
165 + ancestors[children[j]] = (ancestors[children[j]] || []).concat(node);
166 + }
167 + }
168 +
169 + // Topo sort
170 +
171 + const visited = {};
172 + const sorted = [];
173 +
174 + for (let i = 0; i < this._items.length; ++i) { // Really looping thru item.seq values out of order
175 + let next = i;
176 +
177 + if (ancestors[i]) {
178 + next = null;
179 + for (let j = 0; j < this._items.length; ++j) { // As above, these are item.seq values
180 + if (visited[j] === true) {
181 + continue;
182 + }
183 +
184 + if (!ancestors[j]) {
185 + ancestors[j] = [];
186 + }
187 +
188 + const shouldSeeCount = ancestors[j].length;
189 + let seenCount = 0;
190 + for (let k = 0; k < shouldSeeCount; ++k) {
191 + if (visited[ancestors[j][k]]) {
192 + ++seenCount;
193 + }
194 + }
195 +
196 + if (seenCount === shouldSeeCount) {
197 + next = j;
198 + break;
199 + }
200 + }
201 + }
202 +
203 + if (next !== null) {
204 + visited[next] = true;
205 + sorted.push(next);
206 + }
207 + }
208 +
209 + if (sorted.length !== this._items.length) {
210 + return new Error('Invalid dependencies');
211 + }
212 +
213 + const seqIndex = {};
214 + for (let i = 0; i < this._items.length; ++i) {
215 + const item = this._items[i];
216 + seqIndex[item.seq] = item;
217 + }
218 +
219 + const sortedNodes = [];
220 + this._items = sorted.map((value) => {
221 +
222 + const sortedItem = seqIndex[value];
223 + sortedNodes.push(sortedItem.node);
224 + return sortedItem;
225 + });
226 +
227 + this.nodes = sortedNodes;
228 +};
1 +{
2 + "_from": "topo@3.x.x",
3 + "_id": "topo@3.0.0",
4 + "_inBundle": false,
5 + "_integrity": "sha512-Tlu1fGlR90iCdIPURqPiufqAlCZYzLjHYVVbcFWDMcX7+tK8hdZWAfsMrD/pBul9jqHHwFjNdf1WaxA9vTRRhw==",
6 + "_location": "/topo",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "topo@3.x.x",
12 + "name": "topo",
13 + "escapedName": "topo",
14 + "rawSpec": "3.x.x",
15 + "saveSpec": null,
16 + "fetchSpec": "3.x.x"
17 + },
18 + "_requiredBy": [
19 + "/joi"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz",
22 + "_shasum": "37e48c330efeac784538e0acd3e62ca5e231fe7a",
23 + "_spec": "topo@3.x.x",
24 + "_where": "/Users/jeminlee/git-projects/node-practice/express-demo/node_modules/joi",
25 + "bugs": {
26 + "url": "https://github.com/hapijs/topo/issues"
27 + },
28 + "bundleDependencies": false,
29 + "dependencies": {
30 + "hoek": "5.x.x"
31 + },
32 + "deprecated": false,
33 + "description": "Topological sorting with grouping support",
34 + "devDependencies": {
35 + "code": "5.x.x",
36 + "lab": "14.x.x"
37 + },
38 + "engines": {
39 + "node": ">=8.0.0"
40 + },
41 + "homepage": "https://github.com/hapijs/topo#readme",
42 + "keywords": [
43 + "topological",
44 + "sort",
45 + "toposort",
46 + "topsort"
47 + ],
48 + "license": "BSD-3-Clause",
49 + "main": "lib/index.js",
50 + "name": "topo",
51 + "repository": {
52 + "type": "git",
53 + "url": "git://github.com/hapijs/topo.git"
54 + },
55 + "scripts": {
56 + "test": "lab -a code -t 100 -L",
57 + "test-cov-html": "lab -a code -t 100 -L -r html -o coverage.html"
58 + },
59 + "version": "3.0.0"
60 +}
...@@ -159,6 +159,11 @@ ...@@ -159,6 +159,11 @@
159 "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 159 "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
160 "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 160 "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
161 }, 161 },
162 + "hoek": {
163 + "version": "5.0.3",
164 + "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz",
165 + "integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw=="
166 + },
162 "http-errors": { 167 "http-errors": {
163 "version": "1.6.2", 168 "version": "1.6.2",
164 "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 169 "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
...@@ -197,6 +202,24 @@ ...@@ -197,6 +202,24 @@
197 "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", 202 "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz",
198 "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" 203 "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs="
199 }, 204 },
205 + "isemail": {
206 + "version": "3.1.1",
207 + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.1.tgz",
208 + "integrity": "sha512-mVjAjvdPkpwXW61agT2E9AkGoegZO7SdJGCezWwxnETL58f5KwJ4vSVAMBUL5idL6rTlYAIGkX3n4suiviMLNw==",
209 + "requires": {
210 + "punycode": "2.1.0"
211 + }
212 + },
213 + "joi": {
214 + "version": "13.1.2",
215 + "resolved": "https://registry.npmjs.org/joi/-/joi-13.1.2.tgz",
216 + "integrity": "sha512-bZZSQYW5lPXenOfENvgCBPb9+H6E6MeNWcMtikI04fKphj5tvFL9TOb+H2apJzbCrRw/jebjTH8z6IHLpBytGg==",
217 + "requires": {
218 + "hoek": "5.0.3",
219 + "isemail": "3.1.1",
220 + "topo": "3.0.0"
221 + }
222 + },
200 "media-typer": { 223 "media-typer": {
201 "version": "0.3.0", 224 "version": "0.3.0",
202 "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 225 "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
...@@ -267,6 +290,11 @@ ...@@ -267,6 +290,11 @@
267 "ipaddr.js": "1.6.0" 290 "ipaddr.js": "1.6.0"
268 } 291 }
269 }, 292 },
293 + "punycode": {
294 + "version": "2.1.0",
295 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz",
296 + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0="
297 + },
270 "qs": { 298 "qs": {
271 "version": "6.5.1", 299 "version": "6.5.1",
272 "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 300 "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
...@@ -334,6 +362,14 @@ ...@@ -334,6 +362,14 @@
334 "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 362 "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
335 "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 363 "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
336 }, 364 },
365 + "topo": {
366 + "version": "3.0.0",
367 + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz",
368 + "integrity": "sha512-Tlu1fGlR90iCdIPURqPiufqAlCZYzLjHYVVbcFWDMcX7+tK8hdZWAfsMrD/pBul9jqHHwFjNdf1WaxA9vTRRhw==",
369 + "requires": {
370 + "hoek": "5.0.3"
371 + }
372 + },
337 "type-is": { 373 "type-is": {
338 "version": "1.6.16", 374 "version": "1.6.16",
339 "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 375 "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
10 "author": "", 10 "author": "",
11 "license": "ISC", 11 "license": "ISC",
12 "dependencies": { 12 "dependencies": {
13 - "express": "^4.16.3" 13 + "express": "^4.16.3",
14 + "joi": "^13.1.2"
14 } 15 }
15 } 16 }
......