Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 8fb5825

Browse files
hugomrdiasAlan Shaw
authored andcommitted
refactor: swap joi-browser with superstruct (#1961)
Relay config is removed because libp2p already validates it. A bug was detected and fixed because the validation is stricter. This should improve the overall bundle size and safety handling the config. BREAKING CHANGE: Constructor config validation is now a bit more strict - it does not allow `null` values or unknown properties.
1 parent 1c07779 commit 8fb5825

File tree

4 files changed

+88
-116
lines changed

4 files changed

+88
-116
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,6 @@
125125
"is-stream": "^1.1.0",
126126
"iso-url": "~0.4.6",
127127
"joi": "^14.3.0",
128-
"joi-browser": "^13.4.0",
129-
"joi-multiaddr": "^4.0.0",
130128
"just-flatten-it": "^2.1.0",
131129
"just-safe-set": "^2.1.0",
132130
"libp2p": "~0.25.0-rc.5",
@@ -172,6 +170,7 @@
172170
"readable-stream": "^3.1.1",
173171
"receptacle": "^1.3.2",
174172
"stream-to-pull-stream": "^1.7.3",
173+
"superstruct": "~0.6.0",
175174
"tar-stream": "^2.0.0",
176175
"temp": "~0.9.0",
177176
"update-notifier": "^2.5.0",

src/core/config.js

Lines changed: 84 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,86 @@
11
'use strict'
22

3-
const Joi = require('joi').extend(require('joi-multiaddr'))
4-
5-
const schema = Joi.object().keys({
6-
repo: Joi.alternatives().try(
7-
Joi.object(), // TODO: schema for IPFS repo
8-
Joi.string()
9-
).allow(null),
10-
repoOwner: Joi.boolean().default(true),
11-
preload: Joi.object().keys({
12-
enabled: Joi.boolean().default(true),
13-
addresses: Joi.array().items(Joi.multiaddr().options({ convert: false })),
14-
interval: Joi.number().integer().default(30 * 1000)
15-
}).allow(null),
16-
init: Joi.alternatives().try(
17-
Joi.boolean(),
18-
Joi.object().keys({ bits: Joi.number().integer() })
19-
).allow(null),
20-
start: Joi.boolean(),
21-
offline: Joi.boolean(),
22-
pass: Joi.string().allow(''),
23-
relay: Joi.object().keys({
24-
enabled: Joi.boolean(),
25-
hop: Joi.object().keys({
26-
enabled: Joi.boolean(),
27-
active: Joi.boolean()
28-
}).allow(null)
29-
}).allow(null),
30-
EXPERIMENTAL: Joi.object().keys({
31-
pubsub: Joi.boolean(),
32-
ipnsPubsub: Joi.boolean(),
33-
sharding: Joi.boolean(),
34-
dht: Joi.boolean()
35-
}).allow(null),
36-
connectionManager: Joi.object().allow(null),
37-
config: Joi.object().keys({
38-
Addresses: Joi.object().keys({
39-
Swarm: Joi.array().items(Joi.multiaddr().options({ convert: false })),
40-
API: Joi.multiaddr().options({ convert: false }),
41-
Gateway: Joi.multiaddr().options({ convert: false })
42-
}).allow(null),
43-
Discovery: Joi.object().keys({
44-
MDNS: Joi.object().keys({
45-
Enabled: Joi.boolean(),
46-
Interval: Joi.number().integer()
47-
}).allow(null),
48-
webRTCStar: Joi.object().keys({
49-
Enabled: Joi.boolean()
50-
}).allow(null)
51-
}).allow(null),
52-
Bootstrap: Joi.array().items(Joi.multiaddr().IPFS().options({ convert: false }))
53-
}).allow(null),
54-
libp2p: Joi.alternatives().try(
55-
Joi.func(),
56-
Joi.object().keys({
57-
modules: Joi.object().allow(null) // TODO: schemas for libp2p modules?
58-
})
59-
).allow(null)
60-
}).options({ allowUnknown: true })
61-
62-
module.exports.validate = (config) => Joi.attempt(config, schema)
3+
const Multiaddr = require('multiaddr')
4+
const mafmt = require('mafmt')
5+
const { struct, superstruct } = require('superstruct')
6+
7+
const { optional, union } = struct
8+
const s = superstruct({
9+
types: {
10+
multiaddr: v => {
11+
if (v === null) {
12+
return `multiaddr invalid, value must be a string, Buffer, or another Multiaddr got ${v}`
13+
}
14+
15+
try {
16+
Multiaddr(v)
17+
} catch (err) {
18+
return `multiaddr invalid, ${err.message}`
19+
}
20+
21+
return true
22+
},
23+
'multiaddr-ipfs': v => mafmt.IPFS.matches(v) ? true : `multiaddr IPFS invalid`
24+
}
25+
})
26+
27+
const configSchema = s({
28+
repo: optional(s('object|string')),
29+
repoOwner: 'boolean?',
30+
preload: s({
31+
enabled: 'boolean?',
32+
addresses: optional(s(['multiaddr'])),
33+
interval: 'number?'
34+
}, { enabled: true, interval: 30 * 1000 }),
35+
init: optional(union(['boolean', s({
36+
bits: 'number?',
37+
emptyRepo: 'boolean?',
38+
privateKey: optional(s('object|string')), // object should be a custom type for PeerId using 'kind-of'
39+
pass: 'string?'
40+
})])),
41+
start: 'boolean?',
42+
offline: 'boolean?',
43+
pass: 'string?',
44+
silent: 'boolean?',
45+
relay: 'object?', // relay validates in libp2p
46+
EXPERIMENTAL: optional(s({
47+
pubsub: 'boolean?',
48+
ipnsPubsub: 'boolean?',
49+
sharding: 'boolean?',
50+
dht: 'boolean?'
51+
})),
52+
connectionManager: 'object?',
53+
config: optional(s({
54+
API: 'object?',
55+
Addresses: optional(s({
56+
Swarm: optional(s(['multiaddr'])),
57+
API: 'multiaddr?',
58+
Gateway: 'multiaddr'
59+
})),
60+
Discovery: optional(s({
61+
MDNS: optional(s({
62+
Enabled: 'boolean?',
63+
Interval: 'number?'
64+
})),
65+
webRTCStar: optional(s({
66+
Enabled: 'boolean?'
67+
}))
68+
})),
69+
Bootstrap: optional(s(['multiaddr-ipfs']))
70+
})),
71+
libp2p: optional(union(['function', 'object'])) // libp2p validates this
72+
}, {
73+
repoOwner: true
74+
})
75+
76+
const validate = (opts) => {
77+
const [err, options] = configSchema.validate(opts)
78+
79+
if (err) {
80+
throw err
81+
}
82+
83+
return options
84+
}
85+
86+
module.exports = { validate }

test/core/bitswap.spec.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,8 @@ describe('bitswap', function () {
113113

114114
if (isNode) {
115115
config = Object.assign({}, config, {
116-
config: {
117-
Addresses: {
118-
Swarm: ['/ip4/127.0.0.1/tcp/0']
119-
}
116+
Addresses: {
117+
Swarm: ['/ip4/127.0.0.1/tcp/0']
120118
}
121119
})
122120
}

test/core/config.spec.js

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,10 @@ describe('config', () => {
1919
expect(() => config.validate(cfg)).to.not.throw()
2020
})
2121

22-
it('should allow unknown key at root', () => {
23-
const cfg = { [`${Date.now()}`]: 'test' }
24-
expect(() => config.validate(cfg)).to.not.throw()
25-
})
26-
2722
it('should validate valid repo', () => {
2823
const cfgs = [
2924
{ repo: { unknown: 'value' } },
3025
{ repo: '/path/to-repo' },
31-
{ repo: null },
3226
{ repo: undefined }
3327
]
3428

@@ -46,10 +40,8 @@ describe('config', () => {
4640
it('should validate valid init', () => {
4741
const cfgs = [
4842
{ init: { bits: 138 } },
49-
{ init: { bits: 138, unknown: 'value' } },
5043
{ init: true },
5144
{ init: false },
52-
{ init: null },
5345
{ init: undefined }
5446
]
5547

@@ -104,36 +96,10 @@ describe('config', () => {
10496
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
10597
})
10698

107-
it('should validate valid relay', () => {
108-
const cfgs = [
109-
{ relay: { enabled: true, hop: { enabled: true } } },
110-
{ relay: { enabled: false, hop: { enabled: false } } },
111-
{ relay: { enabled: false, hop: null } },
112-
{ relay: { enabled: false } },
113-
{ relay: null },
114-
{ relay: undefined }
115-
]
116-
117-
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
118-
})
119-
120-
it('should validate invalid relay', () => {
121-
const cfgs = [
122-
{ relay: 138 },
123-
{ relay: { enabled: 138 } },
124-
{ relay: { enabled: true, hop: 138 } },
125-
{ relay: { enabled: true, hop: { enabled: 138 } } }
126-
]
127-
128-
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
129-
})
130-
13199
it('should validate valid EXPERIMENTAL', () => {
132100
const cfgs = [
133101
{ EXPERIMENTAL: { pubsub: true, dht: true, sharding: true } },
134102
{ EXPERIMENTAL: { pubsub: false, dht: false, sharding: false } },
135-
{ EXPERIMENTAL: { unknown: 'value' } },
136-
{ EXPERIMENTAL: null },
137103
{ EXPERIMENTAL: undefined }
138104
]
139105

@@ -162,32 +128,22 @@ describe('config', () => {
162128
{ config: { Addresses: { Gateway: '/ip4/127.0.0.1/tcp/9090' } } },
163129
{ config: { Addresses: { Gateway: undefined } } },
164130

165-
{ config: { Addresses: { unknown: 'value' } } },
166-
{ config: { Addresses: null } },
167131
{ config: { Addresses: undefined } },
168132

169133
{ config: { Discovery: { MDNS: { Enabled: true } } } },
170134
{ config: { Discovery: { MDNS: { Enabled: false } } } },
171135
{ config: { Discovery: { MDNS: { Interval: 138 } } } },
172-
{ config: { Discovery: { MDNS: { unknown: 'value' } } } },
173-
{ config: { Discovery: { MDNS: null } } },
174136
{ config: { Discovery: { MDNS: undefined } } },
175137

176138
{ config: { Discovery: { webRTCStar: { Enabled: true } } } },
177139
{ config: { Discovery: { webRTCStar: { Enabled: false } } } },
178-
{ config: { Discovery: { webRTCStar: { unknown: 'value' } } } },
179-
{ config: { Discovery: { webRTCStar: null } } },
180140
{ config: { Discovery: { webRTCStar: undefined } } },
181141

182-
{ config: { Discovery: { unknown: 'value' } } },
183-
{ config: { Discovery: null } },
184142
{ config: { Discovery: undefined } },
185143

186144
{ config: { Bootstrap: ['/ip4/104.236.176.52/tcp/4001/ipfs/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z'] } },
187145
{ config: { Bootstrap: [] } },
188146

189-
{ config: { unknown: 'value' } },
190-
{ config: null },
191147
{ config: undefined }
192148
]
193149

@@ -222,12 +178,7 @@ describe('config', () => {
222178
it('should validate valid libp2p', () => {
223179
const cfgs = [
224180
{ libp2p: { modules: {} } },
225-
{ libp2p: { modules: { unknown: 'value' } } },
226-
{ libp2p: { modules: null } },
227-
{ libp2p: { modules: undefined } },
228-
{ libp2p: { unknown: 'value' } },
229181
{ libp2p: () => {} },
230-
{ libp2p: null },
231182
{ libp2p: undefined }
232183
]
233184

@@ -236,7 +187,7 @@ describe('config', () => {
236187

237188
it('should validate invalid libp2p', () => {
238189
const cfgs = [
239-
{ libp2p: { modules: 138 } },
190+
{ libp2p: 'error' },
240191
{ libp2p: 138 }
241192
]
242193

0 commit comments

Comments
 (0)