Skip to content

Commit 5856fb7

Browse files
wardpeetDSchau
authored andcommitted
fix(gatsby): use joi validation result instead of payload (#15379)
* fix(gatsby): use joi validation result instead of payload * move leading & trailing slash checks to joi * fix tests * remove debug info
1 parent c7e2361 commit 5856fb7

File tree

4 files changed

+96
-59
lines changed

4 files changed

+96
-59
lines changed
Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,103 @@
11
const { gatsbyConfigSchema } = require(`../joi`)
22

33
describe(`gatsby config`, () => {
4-
it(`strips trailing slashes from url fields`, () => {
4+
it(`returns empty pathPrefix when not set`, async () => {
5+
const config = {}
6+
7+
const result = await gatsbyConfigSchema.validate(config)
8+
expect(result).toEqual(
9+
expect.objectContaining({
10+
pathPrefix: ``,
11+
})
12+
)
13+
})
14+
15+
it(`strips trailing slashes from url fields`, async () => {
516
const config = {
617
pathPrefix: `/blog///`,
718
assetPrefix: `https://cdn.example.com/`,
819
}
920

10-
expect(gatsbyConfigSchema.validate(config)).resolves.toEqual({
11-
pathPrefix: `/blog`,
12-
assetPrefix: `https://cdn.example.com`,
13-
})
21+
const result = await gatsbyConfigSchema.validate(config)
22+
expect(result).toEqual(
23+
expect.objectContaining({
24+
pathPrefix: `/blog`,
25+
assetPrefix: `https://cdn.example.com`,
26+
})
27+
)
1428
})
1529

16-
it(`allows assetPrefix to be full URL`, () => {
30+
it(`allows assetPrefix to be full URL`, async () => {
1731
const config = {
18-
assetPrefix: `https://cdn.example.com`,
32+
assetPrefix: `https://cdn.example.com/`,
1933
}
2034

21-
expect(gatsbyConfigSchema.validate(config)).resolves.toEqual(config)
35+
const result = await gatsbyConfigSchema.validate(config)
36+
expect(result).toEqual(
37+
expect.objectContaining({
38+
assetPrefix: `https://cdn.example.com`,
39+
})
40+
)
2241
})
2342

24-
it(`allows assetPrefix to be a URL with nested paths`, () => {
43+
it(`allows assetPrefix to be a URL with nested paths`, async () => {
2544
const config = {
2645
assetPrefix: `https://cdn.example.com/some/nested/path`,
2746
}
2847

29-
expect(gatsbyConfigSchema.validate(config)).resolves.toEqual(config)
48+
const result = await gatsbyConfigSchema.validate(config)
49+
expect(result).toEqual(expect.objectContaining(config))
3050
})
3151

32-
it(`allows relative paths for url fields`, () => {
52+
it(`allows relative paths for url fields`, async () => {
3353
const config = {
3454
pathPrefix: `/blog`,
3555
assetPrefix: `https://cdn.example.com`,
3656
}
3757

38-
expect(gatsbyConfigSchema.validate(config)).resolves.toEqual(config)
58+
const result = await gatsbyConfigSchema.validate(config)
59+
expect(result).toEqual(expect.objectContaining(config))
3960
})
4061

41-
it(`does not allow pathPrefix to be full URL`, () => {
62+
it(`strips trailing slash and add leading slash to pathPrefix`, async () => {
63+
const config = {
64+
pathPrefix: `blog/`,
65+
assetPrefix: `https://cdn.example.com/`,
66+
}
67+
68+
const result = await gatsbyConfigSchema.validate(config)
69+
expect(result).toEqual(
70+
expect.objectContaining({
71+
pathPrefix: `/blog`,
72+
assetPrefix: `https://cdn.example.com`,
73+
})
74+
)
75+
})
76+
77+
it(`does not allow pathPrefix to be full URL`, async () => {
78+
expect.assertions(1)
4279
const config = {
4380
pathPrefix: `https://google.com`,
4481
}
4582

46-
expect(
47-
gatsbyConfigSchema.validate(config)
48-
).rejects.toThrowErrorMatchingSnapshot()
83+
try {
84+
await gatsbyConfigSchema.validate(config)
85+
} catch (err) {
86+
expect(err.message).toMatchSnapshot()
87+
}
4988
})
5089

51-
it(`throws when relative path used for both assetPrefix and pathPrefix`, () => {
90+
it(`throws when relative path used for both assetPrefix and pathPrefix`, async () => {
91+
expect.assertions(1)
5292
const config = {
5393
assetPrefix: `/assets`,
5494
pathPrefix: `/blog`,
5595
}
5696

57-
expect(
58-
gatsbyConfigSchema.validate(config)
59-
).rejects.toThrowErrorMatchingSnapshot()
97+
try {
98+
await gatsbyConfigSchema.validate(config)
99+
} catch (err) {
100+
expect(err.message).toMatchSnapshot()
101+
}
60102
})
61103
})

packages/gatsby/src/joi-schemas/joi.js

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
const Joi = require(`@hapi/joi`)
22

33
const stripTrailingSlash = chain => chain.replace(/(\w)\/+$/, `$1`)
4+
// only add leading slash on relative urls
5+
const addLeadingSlash = chain =>
6+
chain.when(Joi.string().uri({ relativeOnly: true }), {
7+
then: chain.replace(/^([^/])/, `/$1`),
8+
})
49

510
export const gatsbyConfigSchema = Joi.object()
611
.keys({
712
__experimentalThemes: Joi.array(),
8-
polyfill: Joi.boolean(),
13+
polyfill: Joi.boolean().default(true),
914
assetPrefix: stripTrailingSlash(
1015
Joi.string().uri({
1116
allowRelative: true,
1217
})
1318
),
14-
pathPrefix: stripTrailingSlash(
15-
Joi.string().uri({
16-
allowRelative: true,
17-
relativeOnly: true,
18-
})
19+
pathPrefix: addLeadingSlash(
20+
stripTrailingSlash(
21+
Joi.string()
22+
.uri({
23+
allowRelative: true,
24+
relativeOnly: true,
25+
})
26+
.default(``)
27+
// removes single / value
28+
.allow(``)
29+
.replace(/^\/$/, ``)
30+
)
1931
),
2032
siteMetadata: Joi.object({
2133
siteUrl: stripTrailingSlash(Joi.string()).uri(),
@@ -35,10 +47,12 @@ export const gatsbyConfigSchema = Joi.object()
3547
allowRelative: true,
3648
relativeOnly: true,
3749
}),
38-
pathPrefix: Joi.string().uri({
39-
allowRelative: true,
40-
relativeOnly: true,
41-
}),
50+
pathPrefix: Joi.string()
51+
.uri({
52+
allowRelative: true,
53+
relativeOnly: true,
54+
})
55+
.default(``),
4256
}),
4357
{
4458
then: Joi.object({

packages/gatsby/src/redux/reducers/__tests__/config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
const reducer = require(`../config`)
22

33
describe(`config reducer`, () => {
4+
beforeEach(() => {
5+
jest.resetModules()
6+
})
7+
48
it(`let's you add a config`, () => {
59
const action = {
610
type: `SET_SITE_CONFIG`,

packages/gatsby/src/redux/reducers/config.js

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
const Joi = require(`@hapi/joi`)
21
const chalk = require(`chalk`)
3-
const _ = require(`lodash`)
42

53
const { gatsbyConfigSchema } = require(`../../joi-schemas/joi`)
64

75
module.exports = (state = {}, action) => {
86
switch (action.type) {
97
case `SET_SITE_CONFIG`: {
108
// Validate the config.
11-
const result = Joi.validate(action.payload, gatsbyConfigSchema)
9+
const result = gatsbyConfigSchema.validate(action.payload || {})
10+
11+
const normalizedPayload = result.value
12+
1213
// TODO use Redux for capturing errors from different
1314
// parts of Gatsby so a) can capture richer errors and b) be
1415
// more flexible how to display them.
@@ -17,37 +18,13 @@ module.exports = (state = {}, action) => {
1718
chalk.blue.bgYellow(`The site's gatsby-config.js failed validation`)
1819
)
1920
console.log(chalk.bold.red(result.error))
20-
if (action.payload.linkPrefix) {
21+
if (normalizedPayload.linkPrefix) {
2122
console.log(`"linkPrefix" should be changed to "pathPrefix"`)
2223
}
2324
throw new Error(`The site's gatsby-config.js failed validation`)
2425
}
2526

26-
// Ensure that the pathPrefix (if set) starts with a forward slash
27-
// and doesn't end with a slash.
28-
if (action.payload && action.payload.pathPrefix) {
29-
if (!_.startsWith(action.payload.pathPrefix, `/`)) {
30-
action.payload.pathPrefix = `/${action.payload.pathPrefix}`
31-
}
32-
if (_.endsWith(action.payload.pathPrefix, `/`)) {
33-
action.payload.pathPrefix = action.payload.pathPrefix.slice(0, -1)
34-
}
35-
}
36-
37-
// If pathPrefix isn't set, set it to an empty string
38-
// to avoid it showing up as undefined elsewhere.
39-
if (!_.has(action, [`payload`, `pathPrefix`])) {
40-
action = _.set(action, [`payload`, `pathPrefix`], ``)
41-
}
42-
43-
// Default polyfill to true.
44-
if (!_.has(action, [`payload`, `polyfill`])) {
45-
action = _.set(action, [`payload`, `polyfill`], true)
46-
}
47-
48-
return {
49-
...action.payload,
50-
}
27+
return normalizedPayload
5128
}
5229
default:
5330
return state

0 commit comments

Comments
 (0)