mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-31 23:54:25 +00:00
feat(v2): replace yup validation by joi validation (#2962)
* replace yup with joi * update test snapshots with new error messages
This commit is contained in:
parent
ee5e59f633
commit
2b4b6f73b7
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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\\"
|
||||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"`;
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
@ -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();
|
||||
}),
|
||||
});
|
||||
|
||||
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,
|
||||
from: Joi.alternatives().try(
|
||||
PathnameValidator.required(),
|
||||
Joi.array().items(PathnameValidator.required()),
|
||||
),
|
||||
});
|
||||
|
||||
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)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
yarn.lock
45
yarn.lock
|
|
@ -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"
|
||||
|
|
@ -1369,6 +1386,22 @@
|
|||
"@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"
|
||||
|
|
@ -1376,6 +1409,13 @@
|
|||
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"
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue