-
-
Notifications
You must be signed in to change notification settings - Fork 8k
/
validationSchemas.ts
178 lines (162 loc) · 5.57 KB
/
validationSchemas.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {isValidPathname, DEFAULT_PLUGIN_ID, type Tag} from '@docusaurus/utils';
import {addLeadingSlash} from '@docusaurus/utils-common';
import Joi from './Joi';
import {JoiFrontMatter} from './JoiFrontMatter';
export const PluginIdSchema = Joi.string()
.regex(/^[\w-]+$/)
.message(
'Illegal plugin ID value "{#value}": it should only contain alphanumerics, underscores, and dashes.',
)
.default(DEFAULT_PLUGIN_ID);
const MarkdownPluginsSchema = Joi.array()
.items(
Joi.array().ordered(Joi.function().required(), Joi.any().required()),
Joi.function(),
Joi.object(),
)
.messages({
'array.includes': `{#label} does not look like a valid MDX plugin config. A plugin config entry should be one of:
- A tuple, like \`[require("rehype-katex"), \\{ strict: false \\}]\`, or
- A simple module, like \`require("remark-math")\``,
})
.default([]);
export const RemarkPluginsSchema = MarkdownPluginsSchema;
export const RehypePluginsSchema = MarkdownPluginsSchema;
export const AdmonitionsSchema = JoiFrontMatter.alternatives()
.try(
JoiFrontMatter.boolean().required(),
JoiFrontMatter.object({
keywords: JoiFrontMatter.array().items(
JoiFrontMatter.string(),
// Apparently this is how we tell job to accept empty arrays...
// .required(),
),
extendDefaults: JoiFrontMatter.boolean(),
// TODO Remove before 2024
tag: Joi.any().forbidden().messages({
'any.unknown': `It is not possible anymore to use a custom admonition tag. The only admonition tag supported is ':::' (Markdown Directive syntax)`,
}),
}).required(),
)
.default(true)
.messages({
'alternatives.types':
'{{#label}} does not look like a valid admonitions config',
});
// TODO how can we make this emit a custom error message :'(
// Joi is such a pain, good luck to annoying trying to improve this
export const URISchema = Joi.alternatives(
Joi.string().uri({allowRelative: true}),
// This custom validation logic is required notably because Joi does not
// accept paths like /a/b/c ...
Joi.custom((val: unknown, helpers) => {
if (typeof val !== 'string') {
return helpers.error('any.invalid');
}
try {
// eslint-disable-next-line no-new
new URL(String(val));
return val;
} catch {
return helpers.error('any.invalid');
}
}),
).messages({
'alternatives.match':
"{{#label}} does not look like a valid url (value='{{.value}}')",
});
export const PathnameSchema = Joi.string()
.custom((val: string) => {
if (!isValidPathname(val)) {
throw new Error();
}
return val;
})
.message(
'{{#label}} ({{#value}}) is not a valid pathname. Pathname should start with slash and not contain any domain or query string.',
);
// Normalized schema for url path segments: baseUrl + routeBasePath...
// Note we only add a leading slash
// we don't always want to enforce a trailing slash on urls such as /docs
//
// Examples:
// '' => '/'
// 'docs' => '/docs'
// '/docs' => '/docs'
// 'docs/' => '/docs'
// 'prefix/docs' => '/prefix/docs'
// TODO tighter validation: not all strings are valid path segments
export const RouteBasePathSchema = Joi
// Weird Joi trick needed, otherwise value '' is not normalized...
.alternatives()
.try(Joi.string().required().allow(''))
.custom((value: string) =>
// /!\ do not add trailing slash here
addLeadingSlash(value),
);
const FrontMatterTagSchema = JoiFrontMatter.alternatives()
.try(
JoiFrontMatter.string().required(),
JoiFrontMatter.object<Tag>({
label: JoiFrontMatter.string().required(),
permalink: JoiFrontMatter.string().required(),
}).required(),
)
.messages({
'alternatives.match': '{{#label}} does not look like a valid tag',
'alternatives.types': '{{#label}} does not look like a valid tag',
});
export const FrontMatterTagsSchema = JoiFrontMatter.array()
.items(FrontMatterTagSchema)
.messages({
'array.base':
'{{#label}} does not look like a valid front matter Yaml array.',
});
export const FrontMatterTOCHeadingLevels = {
toc_min_heading_level: JoiFrontMatter.number().when('toc_max_heading_level', {
is: JoiFrontMatter.exist(),
then: JoiFrontMatter.number()
.min(2)
.max(JoiFrontMatter.ref('toc_max_heading_level')),
otherwise: JoiFrontMatter.number().min(2).max(6),
}),
toc_max_heading_level: JoiFrontMatter.number().min(2).max(6),
};
export type ContentVisibility = {
draft: boolean;
unlisted: boolean;
};
export const ContentVisibilitySchema = JoiFrontMatter.object<ContentVisibility>(
{
draft: JoiFrontMatter.boolean(),
unlisted: JoiFrontMatter.boolean(),
},
)
.custom((frontMatter: ContentVisibility, helpers) => {
if (frontMatter.draft && frontMatter.unlisted) {
return helpers.error('frontMatter.draftAndUnlistedError');
}
return frontMatter;
})
.messages({
'frontMatter.draftAndUnlistedError':
"Can't be draft and unlisted at the same time.",
})
.unknown();
export const FrontMatterLastUpdateErrorMessage =
'{{#label}} does not look like a valid last update object. Please use an author key with a string or a date with a string or Date.';
export const FrontMatterLastUpdateSchema = Joi.object({
author: Joi.string(),
date: Joi.date().raw(),
})
.or('author', 'date')
.messages({
'object.missing': FrontMatterLastUpdateErrorMessage,
'object.base': FrontMatterLastUpdateErrorMessage,
});