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

Commit a32dce7

Browse files
alanshawdaviddias
authored andcommitted
feat: add config validation (#1239)
1 parent 9105700 commit a32dce7

File tree

4 files changed

+269
-2
lines changed

4 files changed

+269
-2
lines changed

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"./src/core/runtime/repo-nodejs.js": "./src/core/runtime/repo-browser.js",
1414
"./src/core/runtime/dns-nodejs.js": "./src/core/runtime/dns-browser.js",
1515
"./test/utils/create-repo-nodejs.js": "./test/utils/create-repo-browser.js",
16-
"stream": "readable-stream"
16+
"stream": "readable-stream",
17+
"joi": "joi-browser"
1718
},
1819
"engines": {
1920
"node": ">=6.0.0",
@@ -119,6 +120,8 @@
119120
"is-ipfs": "^0.3.2",
120121
"is-stream": "^1.1.0",
121122
"joi": "^13.1.2",
123+
"joi-browser": "^13.0.1",
124+
"joi-multiaddr": "^1.0.1",
122125
"libp2p": "~0.18.0",
123126
"libp2p-circuit": "~0.1.4",
124127
"libp2p-floodsub": "~0.14.1",

src/core/config.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict'
2+
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+
init: Joi.alternatives().try(
11+
Joi.boolean(),
12+
Joi.object().keys({ bits: Joi.number().integer() })
13+
).allow(null),
14+
start: Joi.boolean(),
15+
pass: Joi.string().allow(''),
16+
EXPERIMENTAL: Joi.object().keys({
17+
pubsub: Joi.boolean(),
18+
sharding: Joi.boolean(),
19+
dht: Joi.boolean()
20+
}).allow(null),
21+
config: Joi.object().keys({
22+
Addresses: Joi.object().keys({
23+
Swarm: Joi.array().items(Joi.multiaddr().options({ convert: false })),
24+
API: Joi.multiaddr().options({ convert: false }),
25+
Gateway: Joi.multiaddr().options({ convert: false })
26+
}).allow(null),
27+
Discovery: Joi.object().keys({
28+
MDNS: Joi.object().keys({
29+
Enabled: Joi.boolean(),
30+
Interval: Joi.number().integer()
31+
}).allow(null),
32+
webRTCStar: Joi.object().keys({
33+
Enabled: Joi.boolean()
34+
}).allow(null)
35+
}).allow(null),
36+
Bootstrap: Joi.array().items(Joi.multiaddr().IPFS().options({ convert: false }))
37+
}).allow(null),
38+
libp2p: Joi.object().keys({
39+
modules: Joi.object().allow(null) // TODO: schemas for libp2p modules?
40+
}).allow(null)
41+
}).options({ allowUnknown: true })
42+
43+
module.exports.validate = (config) => Joi.attempt(config, schema)

src/core/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const debug = require('debug')
1212
const extend = require('deep-extend')
1313
const EventEmitter = require('events')
1414

15+
const config = require('./config')
1516
const boot = require('./boot')
1617
const components = require('./components')
1718
// replaced by repo-browser when running in the browser
@@ -27,7 +28,7 @@ class IPFS extends EventEmitter {
2728
EXPERIMENTAL: {}
2829
}
2930

30-
options = options || {}
31+
options = config.validate(options || {})
3132
this._libp2pModules = options.libp2p && options.libp2p.modules
3233

3334
extend(this._options, options)

test/core/config.spec.js

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const chai = require('chai')
5+
const dirtyChai = require('dirty-chai')
6+
const expect = chai.expect
7+
chai.use(dirtyChai)
8+
9+
const config = require('../../src/core/config')
10+
11+
describe('config', () => {
12+
it('should allow empty config', () => {
13+
const cfg = {}
14+
expect(() => config.validate(cfg)).to.not.throw()
15+
})
16+
17+
it('should allow undefined config', () => {
18+
const cfg = undefined
19+
expect(() => config.validate(cfg)).to.not.throw()
20+
})
21+
22+
it('should allow unknown key at root', () => {
23+
const cfg = { [`${Date.now()}`]: 'test' }
24+
expect(() => config.validate(cfg)).to.not.throw()
25+
})
26+
27+
it('should validate valid repo', () => {
28+
const cfgs = [
29+
{ repo: { unknown: 'value' } },
30+
{ repo: '/path/to-repo' },
31+
{ repo: null },
32+
{ repo: undefined }
33+
]
34+
35+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
36+
})
37+
38+
it('should validate invalid repo', () => {
39+
const cfgs = [
40+
{ repo: 138 }
41+
]
42+
43+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
44+
})
45+
46+
it('should validate valid init', () => {
47+
const cfgs = [
48+
{ init: { bits: 138 } },
49+
{ init: { bits: 138, unknown: 'value' } },
50+
{ init: true },
51+
{ init: false },
52+
{ init: null },
53+
{ init: undefined }
54+
]
55+
56+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
57+
})
58+
59+
it('should validate invalid init', () => {
60+
const cfgs = [
61+
{ init: 138 },
62+
{ init: { bits: 'not an int' } }
63+
]
64+
65+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
66+
})
67+
68+
it('should validate valid start', () => {
69+
const cfgs = [
70+
{ start: true },
71+
{ start: false },
72+
{ start: undefined }
73+
]
74+
75+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
76+
})
77+
78+
it('should validate invalid start', () => {
79+
const cfgs = [
80+
{ start: 138 },
81+
{ start: 'make it so number 1' },
82+
{ start: null }
83+
]
84+
85+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
86+
})
87+
88+
it('should validate valid pass', () => {
89+
const cfgs = [
90+
{ pass: 'correctbatteryhorsestaple' },
91+
{ pass: '' },
92+
{ pass: undefined }
93+
]
94+
95+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
96+
})
97+
98+
it('should validate invalid pass', () => {
99+
const cfgs = [
100+
{ pass: 138 },
101+
{ pass: null }
102+
]
103+
104+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
105+
})
106+
107+
it('should validate valid EXPERIMENTAL', () => {
108+
const cfgs = [
109+
{ EXPERIMENTAL: { pubsub: true, dht: true, sharding: true } },
110+
{ EXPERIMENTAL: { pubsub: false, dht: false, sharding: false } },
111+
{ EXPERIMENTAL: { unknown: 'value' } },
112+
{ EXPERIMENTAL: null },
113+
{ EXPERIMENTAL: undefined }
114+
]
115+
116+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
117+
})
118+
119+
it('should validate invalid EXPERIMENTAL', () => {
120+
const cfgs = [
121+
{ EXPERIMENTAL: { pubsub: 138 } },
122+
{ EXPERIMENTAL: { dht: 138 } },
123+
{ EXPERIMENTAL: { sharding: 138 } }
124+
]
125+
126+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
127+
})
128+
129+
it('should validate valid config', () => {
130+
const cfgs = [
131+
{ config: { Addresses: { Swarm: ['/ip4/0.0.0.0/tcp/4002'] } } },
132+
{ config: { Addresses: { Swarm: [] } } },
133+
{ config: { Addresses: { Swarm: undefined } } },
134+
135+
{ config: { Addresses: { API: '/ip4/127.0.0.1/tcp/5002' } } },
136+
{ config: { Addresses: { API: undefined } } },
137+
138+
{ config: { Addresses: { Gateway: '/ip4/127.0.0.1/tcp/9090' } } },
139+
{ config: { Addresses: { Gateway: undefined } } },
140+
141+
{ config: { Addresses: { unknown: 'value' } } },
142+
{ config: { Addresses: null } },
143+
{ config: { Addresses: undefined } },
144+
145+
{ config: { Discovery: { MDNS: { Enabled: true } } } },
146+
{ config: { Discovery: { MDNS: { Enabled: false } } } },
147+
{ config: { Discovery: { MDNS: { Interval: 138 } } } },
148+
{ config: { Discovery: { MDNS: { unknown: 'value' } } } },
149+
{ config: { Discovery: { MDNS: null } } },
150+
{ config: { Discovery: { MDNS: undefined } } },
151+
152+
{ config: { Discovery: { webRTCStar: { Enabled: true } } } },
153+
{ config: { Discovery: { webRTCStar: { Enabled: false } } } },
154+
{ config: { Discovery: { webRTCStar: { unknown: 'value' } } } },
155+
{ config: { Discovery: { webRTCStar: null } } },
156+
{ config: { Discovery: { webRTCStar: undefined } } },
157+
158+
{ config: { Discovery: { unknown: 'value' } } },
159+
{ config: { Discovery: null } },
160+
{ config: { Discovery: undefined } },
161+
162+
{ config: { Bootstrap: ['/ip4/104.236.176.52/tcp/4001/ipfs/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z'] } },
163+
{ config: { Bootstrap: [] } },
164+
165+
{ config: { unknown: 'value' } },
166+
{ config: null },
167+
{ config: undefined }
168+
]
169+
170+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
171+
})
172+
173+
it('should validate invalid config', () => {
174+
const cfgs = [
175+
{ config: { Addresses: { Swarm: 138 } } },
176+
{ config: { Addresses: { Swarm: null } } },
177+
178+
{ config: { Addresses: { API: 138 } } },
179+
{ config: { Addresses: { API: null } } },
180+
181+
{ config: { Addresses: { Gateway: 138 } } },
182+
{ config: { Addresses: { Gateway: null } } },
183+
184+
{ config: { Discovery: { MDNS: { Enabled: 138 } } } },
185+
{ config: { Discovery: { MDNS: { Interval: true } } } },
186+
187+
{ config: { Discovery: { webRTCStar: { Enabled: 138 } } } },
188+
189+
{ config: { Bootstrap: ['/ip4/0.0.0.0/tcp/4002'] } },
190+
{ config: { Bootstrap: 138 } },
191+
192+
{ config: 138 }
193+
]
194+
195+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
196+
})
197+
198+
it('should validate valid libp2p', () => {
199+
const cfgs = [
200+
{ libp2p: { modules: {} } },
201+
{ libp2p: { modules: { unknown: 'value' } } },
202+
{ libp2p: { modules: null } },
203+
{ libp2p: { modules: undefined } },
204+
{ libp2p: { unknown: 'value' } },
205+
{ libp2p: null },
206+
{ libp2p: undefined }
207+
]
208+
209+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
210+
})
211+
212+
it('should validate invalid libp2p', () => {
213+
const cfgs = [
214+
{ libp2p: { modules: 138 } },
215+
{ libp2p: 138 }
216+
]
217+
218+
cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
219+
})
220+
})

0 commit comments

Comments
 (0)