Skip to content

Commit

Permalink
feat(v2): replace yup validation by joi validation (#2962)
Browse files Browse the repository at this point in the history
* replace yup with joi

* update test snapshots with new  error messages
  • Loading branch information
anshulrgoyal authored Jun 22, 2020
1 parent ee5e59f commit 2b4b6f7
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 76 deletions.
2 changes: 2 additions & 0 deletions packages/docusaurus-plugin-client-redirects/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"dependencies": {
"@docusaurus/types": "^2.0.0-alpha.58",
"@docusaurus/utils": "^2.0.0-alpha.58",
"@hapi/joi": "^17.1.1",
"@types/hapi__joi": "^17.1.2",
"chalk": "^3.0.0",
"eta": "^1.1.1",
"fs-extra": "^8.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ Valid paths you can redirect to:

exports[`collectRedirects should throw if redirect creator creates array of array redirect 1`] = `
"Some created redirects are invalid:
- {\\"from\\":[\\"/fromPath\\"],\\"to\\":\\"/\\"} => Validation error: from must be a \`string\` type, but the final value was: \`[
\\"\\\\\\"/fromPath\\\\\\"\\"
]\`.
- {\\"from\\":[\\"/fromPath\\"],\\"to\\":\\"/\\"} => Validation error: \\"from\\" must be a string
"
`;

exports[`collectRedirects should throw if redirect creator creates invalid redirects 1`] = `
"Some created redirects are invalid:
- {\\"from\\":\\"https://google.com/\\",\\"to\\":\\"/\\"} => Validation error: from is not a valid pathname. Pathname should start with / and not contain any domain or query string
- {\\"from\\":\\"//abc\\",\\"to\\":\\"/\\"} => Validation error: from is not a valid pathname. Pathname should start with / and not contain any domain or query string
- {\\"from\\":\\"/def?queryString=toto\\",\\"to\\":\\"/\\"} => Validation error: from is not a valid pathname. Pathname should start with / and not contain any domain or query string
- {\\"from\\":\\"https://google.com/\\",\\"to\\":\\"/\\"} => Validation error: \\"from\\" is not a valid pathname. Pathname should start with / and not contain any domain or query string
- {\\"from\\":\\"//abc\\",\\"to\\":\\"/\\"} => Validation error: \\"from\\" is not a valid pathname. Pathname should start with / and not contain any domain or query string
- {\\"from\\":\\"/def?queryString=toto\\",\\"to\\":\\"/\\"} => Validation error: \\"from\\" is not a valid pathname. Pathname should start with / and not contain any domain or query string
"
`;
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`normalizePluginOptions should reject bad createRedirects user inputs 1`] = `
"Invalid @docusaurus/plugin-client-redirects options: createRedirects should be a function
{
"Invalid @docusaurus/plugin-client-redirects options: \\"createRedirects\\" must be of type function
{
\\"createRedirects\\": [
\\"bad\\",
\\"value\\"
Expand All @@ -11,9 +11,8 @@ exports[`normalizePluginOptions should reject bad createRedirects user inputs 1`
`;

exports[`normalizePluginOptions should reject bad fromExtensions user inputs 1`] = `
"Invalid @docusaurus/plugin-client-redirects options: fromExtensions[0] must be a \`string\` type, but the final value was: \`null\`.
If \\"null\\" is intended as an empty value be sure to mark the schema as \`.nullable()\`
{
"Invalid @docusaurus/plugin-client-redirects options: \\"fromExtensions[0]\\" contains an invalid value
{
\\"fromExtensions\\": [
null,
null,
Expand All @@ -24,9 +23,8 @@ exports[`normalizePluginOptions should reject bad fromExtensions user inputs 1`]
`;

exports[`normalizePluginOptions should reject bad toExtensions user inputs 1`] = `
"Invalid @docusaurus/plugin-client-redirects options: toExtensions[0] must be a \`string\` type, but the final value was: \`null\`.
If \\"null\\" is intended as an empty value be sure to mark the schema as \`.nullable()\`
{
"Invalid @docusaurus/plugin-client-redirects options: \\"toExtensions[0]\\" contains an invalid value
{
\\"toExtensions\\": [
null,
null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`validateRedirect throw for bad redirects 1`] = `"{\\"from\\":\\"https://fb.com/fromSomePath\\",\\"to\\":\\"/toSomePath\\"} => Validation error: from is not a valid pathname. Pathname should start with / and not contain any domain or query string"`;
exports[`validateRedirect throw for bad redirects 1`] = `"{\\"from\\":\\"https://fb.com/fromSomePath\\",\\"to\\":\\"/toSomePath\\"} => Validation error: \\"from\\" is not a valid pathname. Pathname should start with / and not contain any domain or query string"`;

exports[`validateRedirect throw for bad redirects 2`] = `"{\\"from\\":\\"/fromSomePath\\",\\"to\\":\\"https://fb.com/toSomePath\\"} => Validation error: to is not a valid pathname. Pathname should start with / and not contain any domain or query string"`;
exports[`validateRedirect throw for bad redirects 2`] = `"{\\"from\\":\\"/fromSomePath\\",\\"to\\":\\"https://fb.com/toSomePath\\"} => Validation error: \\"to\\" is not a valid pathname. Pathname should start with / and not contain any domain or query string"`;

exports[`validateRedirect throw for bad redirects 3`] = `"{\\"from\\":\\"/fromSomePath\\",\\"to\\":\\"/toSomePath?queryString=xyz\\"} => Validation error: to is not a valid pathname. Pathname should start with / and not contain any domain or query string"`;
exports[`validateRedirect throw for bad redirects 3`] = `"{\\"from\\":\\"/fromSomePath\\",\\"to\\":\\"/toSomePath?queryString=xyz\\"} => Validation error: \\"to\\" is not a valid pathname. Pathname should start with / and not contain any domain or query string"`;

exports[`validateRedirect throw for bad redirects 4`] = `"{\\"from\\":null,\\"to\\":\\"/toSomePath?queryString=xyz\\"} => Validation error: to is not a valid pathname. Pathname should start with / and not contain any domain or query string"`;
exports[`validateRedirect throw for bad redirects 4`] = `"{\\"from\\":null,\\"to\\":\\"/toSomePath?queryString=xyz\\"} => Validation error: \\"from\\" must be a string"`;

exports[`validateRedirect throw for bad redirects 5`] = `"{\\"from\\":[\\"heyho\\"],\\"to\\":\\"/toSomePath?queryString=xyz\\"} => Validation error: to is not a valid pathname. Pathname should start with / and not contain any domain or query string"`;
exports[`validateRedirect throw for bad redirects 5`] = `"{\\"from\\":[\\"heyho\\"],\\"to\\":\\"/toSomePath?queryString=xyz\\"} => Validation error: \\"from\\" must be a string"`;
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/

import {
PluginOptions,
RedirectOption,
CreateRedirectsFnOption,
UserPluginOptions,
} from './types';
import * as Yup from 'yup';
import {PluginOptions, RedirectOption, UserPluginOptions} from './types';
import * as Joi from '@hapi/joi';
import {PathnameValidator} from './redirectValidation';

export const DefaultPluginOptions: PluginOptions = {
Expand All @@ -20,46 +15,32 @@ export const DefaultPluginOptions: PluginOptions = {
redirects: [],
};

function isRedirectsCreator(
value: unknown,
): value is CreateRedirectsFnOption | undefined {
if (value === null || typeof value === 'undefined') {
return true;
}
return value instanceof Function;
}

const RedirectPluginOptionValidation = Yup.object<RedirectOption>({
const RedirectPluginOptionValidation = Joi.object<RedirectOption>({
to: PathnameValidator.required(),
// See https://stackoverflow.com/a/62177080/82609
from: Yup.lazy<string | string[]>((from) => {
return Array.isArray(from)
? Yup.array().of(PathnameValidator.required()).required()
: PathnameValidator.required();
}),
from: Joi.alternatives().try(
PathnameValidator.required(),
Joi.array().items(PathnameValidator.required()),
),
});

const UserOptionsSchema = Yup.object().shape<UserPluginOptions>({
fromExtensions: Yup.array().of(Yup.string().required().min(0)),
toExtensions: Yup.array().of(Yup.string().required().min(0)),
redirects: Yup.array().of(RedirectPluginOptionValidation) as any, // TODO Yup expect weird typing here
createRedirects: Yup.mixed().test(
'createRedirects',
'createRedirects should be a function',
isRedirectsCreator,
),
const isString = Joi.string().required().not(null);

const UserOptionsSchema = Joi.object<UserPluginOptions>({
fromExtensions: Joi.array().items(isString),
toExtensions: Joi.array().items(isString),
redirects: Joi.array().items(RedirectPluginOptionValidation),
createRedirects: Joi.function().arity(1),
});

function validateUserOptions(userOptions: UserPluginOptions) {
try {
UserOptionsSchema.validateSync(userOptions, {
strict: true,
abortEarly: true,
});
} catch (e) {
const {error} = UserOptionsSchema.validate(userOptions, {
abortEarly: true,
allowUnknown: false,
});
if (error) {
throw new Error(
`Invalid @docusaurus/plugin-client-redirects options: ${e.message}
${JSON.stringify(userOptions, null, 2)}`,
`Invalid @docusaurus/plugin-client-redirects options: ${error.message}
${JSON.stringify(userOptions, null, 2)}`,
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,34 @@
* LICENSE file in the root directory of this source tree.
*/

import * as Joi from '@hapi/joi';
import {isValidPathname} from '@docusaurus/utils';
import * as Yup from 'yup';
import {RedirectMetadata} from './types';

export const PathnameValidator = Yup.string().test({
name: 'isValidPathname',
message:
// Yup requires this format.
// eslint-disable-next-line no-template-curly-in-string
'${path} is not a valid pathname. Pathname should start with / and not contain any domain or query string',
test: isValidPathname,
});
export const PathnameValidator = Joi.string()
.custom((val) => {
if (!isValidPathname(val)) throw new Error();
else return val;
})
.message(
'{{#label}} is not a valid pathname. Pathname should start with / and not contain any domain or query string',
);

const RedirectSchema = Yup.object<RedirectMetadata>({
const RedirectSchema = Joi.object<RedirectMetadata>({
from: PathnameValidator.required(),
to: PathnameValidator.required(),
});

export function validateRedirect(redirect: RedirectMetadata): void {
try {
RedirectSchema.validateSync(redirect, {
strict: true,
abortEarly: true,
});
} catch (e) {
const {error} = RedirectSchema.validate(redirect, {
abortEarly: true,
convert: false,
});

if (error) {
// Tells the user which redirect is the problem!
throw new Error(
`${JSON.stringify(redirect)} => Validation error: ${e.message}`,
`${JSON.stringify(redirect)} => Validation error: ${error.message}`,
);
}
}
45 changes: 45 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1349,16 +1349,33 @@
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==

"@hapi/address@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-4.0.1.tgz#267301ddf7bc453718377a6fb3832a2f04a721dd"
integrity sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==
dependencies:
"@hapi/hoek" "^9.0.0"

"@hapi/bourne@1.x.x":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a"
integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==

"@hapi/formula@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128"
integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==

"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0":
version "8.5.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.0.tgz#2f9ce301c8898e1c3248b0a8564696b24d1a9a5a"
integrity sha512-7XYT10CZfPsH7j9F1Jmg1+d0ezOux2oM2GfArAzLwWe4mE2Dr3hVjsAL6+TFY49RRJlCdJDMw3nJsLFroTc8Kw==

"@hapi/hoek@^9.0.0":
version "9.0.4"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.0.4.tgz#e80ad4e8e8d2adc6c77d985f698447e8628b6010"
integrity sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==

"@hapi/joi@^15.1.0":
version "15.1.1"
resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7"
Expand All @@ -1369,13 +1386,36 @@
"@hapi/hoek" "8.x.x"
"@hapi/topo" "3.x.x"

"@hapi/joi@^17.1.1":
version "17.1.1"
resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-17.1.1.tgz#9cc8d7e2c2213d1e46708c6260184b447c661350"
integrity sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==
dependencies:
"@hapi/address" "^4.0.1"
"@hapi/formula" "^2.0.0"
"@hapi/hoek" "^9.0.0"
"@hapi/pinpoint" "^2.0.0"
"@hapi/topo" "^5.0.0"

"@hapi/pinpoint@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df"
integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==

"@hapi/topo@3.x.x":
version "3.1.6"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29"
integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==
dependencies:
"@hapi/hoek" "^8.3.0"

"@hapi/topo@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7"
integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==
dependencies:
"@hapi/hoek" "^9.0.0"

"@istanbuljs/load-nyc-config@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b"
Expand Down Expand Up @@ -2795,6 +2835,11 @@
"@types/minimatch" "*"
"@types/node" "*"

"@types/hapi__joi@^17.1.2":
version "17.1.2"
resolved "https://registry.yarnpkg.com/@types/hapi__joi/-/hapi__joi-17.1.2.tgz#f547d45b5d33677d1807ec217aeee832dc7e6334"
integrity sha512-2S6+hBISRQ5Ca6/9zfQi7zPueWMGyZxox6xicqJuW1/aC/6ambLyh+gDqY5fi8JBuHmGKMHldSfEpIXJtTmGKQ==

"@types/history@*":
version "4.7.6"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.6.tgz#ed8fc802c45b8e8f54419c2d054e55c9ea344356"
Expand Down

0 comments on commit 2b4b6f7

Please sign in to comment.