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

Commit

Permalink
feat: add config validation (#1239)
Browse files Browse the repository at this point in the history
  • Loading branch information
alanshaw authored and daviddias committed Mar 12, 2018
1 parent 9105700 commit a32dce7
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 2 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"./src/core/runtime/repo-nodejs.js": "./src/core/runtime/repo-browser.js",
"./src/core/runtime/dns-nodejs.js": "./src/core/runtime/dns-browser.js",
"./test/utils/create-repo-nodejs.js": "./test/utils/create-repo-browser.js",
"stream": "readable-stream"
"stream": "readable-stream",
"joi": "joi-browser"
},
"engines": {
"node": ">=6.0.0",
Expand Down Expand Up @@ -119,6 +120,8 @@
"is-ipfs": "^0.3.2",
"is-stream": "^1.1.0",
"joi": "^13.1.2",
"joi-browser": "^13.0.1",
"joi-multiaddr": "^1.0.1",
"libp2p": "~0.18.0",
"libp2p-circuit": "~0.1.4",
"libp2p-floodsub": "~0.14.1",
Expand Down
43 changes: 43 additions & 0 deletions src/core/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict'

const Joi = require('joi').extend(require('joi-multiaddr'))

const schema = Joi.object().keys({
repo: Joi.alternatives().try(
Joi.object(), // TODO: schema for IPFS repo
Joi.string()
).allow(null),
init: Joi.alternatives().try(
Joi.boolean(),
Joi.object().keys({ bits: Joi.number().integer() })
).allow(null),
start: Joi.boolean(),
pass: Joi.string().allow(''),
EXPERIMENTAL: Joi.object().keys({
pubsub: Joi.boolean(),
sharding: Joi.boolean(),
dht: Joi.boolean()
}).allow(null),
config: Joi.object().keys({
Addresses: Joi.object().keys({
Swarm: Joi.array().items(Joi.multiaddr().options({ convert: false })),
API: Joi.multiaddr().options({ convert: false }),
Gateway: Joi.multiaddr().options({ convert: false })
}).allow(null),
Discovery: Joi.object().keys({
MDNS: Joi.object().keys({
Enabled: Joi.boolean(),
Interval: Joi.number().integer()
}).allow(null),
webRTCStar: Joi.object().keys({
Enabled: Joi.boolean()
}).allow(null)
}).allow(null),
Bootstrap: Joi.array().items(Joi.multiaddr().IPFS().options({ convert: false }))
}).allow(null),
libp2p: Joi.object().keys({
modules: Joi.object().allow(null) // TODO: schemas for libp2p modules?
}).allow(null)
}).options({ allowUnknown: true })

module.exports.validate = (config) => Joi.attempt(config, schema)
3 changes: 2 additions & 1 deletion src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const debug = require('debug')
const extend = require('deep-extend')
const EventEmitter = require('events')

const config = require('./config')
const boot = require('./boot')
const components = require('./components')
// replaced by repo-browser when running in the browser
Expand All @@ -27,7 +28,7 @@ class IPFS extends EventEmitter {
EXPERIMENTAL: {}
}

options = options || {}
options = config.validate(options || {})
this._libp2pModules = options.libp2p && options.libp2p.modules

extend(this._options, options)
Expand Down
220 changes: 220 additions & 0 deletions test/core/config.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/* eslint-env mocha */
'use strict'

const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)

const config = require('../../src/core/config')

describe('config', () => {
it('should allow empty config', () => {
const cfg = {}
expect(() => config.validate(cfg)).to.not.throw()
})

it('should allow undefined config', () => {
const cfg = undefined
expect(() => config.validate(cfg)).to.not.throw()
})

it('should allow unknown key at root', () => {
const cfg = { [`${Date.now()}`]: 'test' }
expect(() => config.validate(cfg)).to.not.throw()
})

it('should validate valid repo', () => {
const cfgs = [
{ repo: { unknown: 'value' } },
{ repo: '/path/to-repo' },
{ repo: null },
{ repo: undefined }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
})

it('should validate invalid repo', () => {
const cfgs = [
{ repo: 138 }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
})

it('should validate valid init', () => {
const cfgs = [
{ init: { bits: 138 } },
{ init: { bits: 138, unknown: 'value' } },
{ init: true },
{ init: false },
{ init: null },
{ init: undefined }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
})

it('should validate invalid init', () => {
const cfgs = [
{ init: 138 },
{ init: { bits: 'not an int' } }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
})

it('should validate valid start', () => {
const cfgs = [
{ start: true },
{ start: false },
{ start: undefined }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
})

it('should validate invalid start', () => {
const cfgs = [
{ start: 138 },
{ start: 'make it so number 1' },
{ start: null }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
})

it('should validate valid pass', () => {
const cfgs = [
{ pass: 'correctbatteryhorsestaple' },
{ pass: '' },
{ pass: undefined }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
})

it('should validate invalid pass', () => {
const cfgs = [
{ pass: 138 },
{ pass: null }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
})

it('should validate valid EXPERIMENTAL', () => {
const cfgs = [
{ EXPERIMENTAL: { pubsub: true, dht: true, sharding: true } },
{ EXPERIMENTAL: { pubsub: false, dht: false, sharding: false } },
{ EXPERIMENTAL: { unknown: 'value' } },
{ EXPERIMENTAL: null },
{ EXPERIMENTAL: undefined }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
})

it('should validate invalid EXPERIMENTAL', () => {
const cfgs = [
{ EXPERIMENTAL: { pubsub: 138 } },
{ EXPERIMENTAL: { dht: 138 } },
{ EXPERIMENTAL: { sharding: 138 } }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
})

it('should validate valid config', () => {
const cfgs = [
{ config: { Addresses: { Swarm: ['/ip4/0.0.0.0/tcp/4002'] } } },
{ config: { Addresses: { Swarm: [] } } },
{ config: { Addresses: { Swarm: undefined } } },

{ config: { Addresses: { API: '/ip4/127.0.0.1/tcp/5002' } } },
{ config: { Addresses: { API: undefined } } },

{ config: { Addresses: { Gateway: '/ip4/127.0.0.1/tcp/9090' } } },
{ config: { Addresses: { Gateway: undefined } } },

{ config: { Addresses: { unknown: 'value' } } },
{ config: { Addresses: null } },
{ config: { Addresses: undefined } },

{ config: { Discovery: { MDNS: { Enabled: true } } } },
{ config: { Discovery: { MDNS: { Enabled: false } } } },
{ config: { Discovery: { MDNS: { Interval: 138 } } } },
{ config: { Discovery: { MDNS: { unknown: 'value' } } } },
{ config: { Discovery: { MDNS: null } } },
{ config: { Discovery: { MDNS: undefined } } },

{ config: { Discovery: { webRTCStar: { Enabled: true } } } },
{ config: { Discovery: { webRTCStar: { Enabled: false } } } },
{ config: { Discovery: { webRTCStar: { unknown: 'value' } } } },
{ config: { Discovery: { webRTCStar: null } } },
{ config: { Discovery: { webRTCStar: undefined } } },

{ config: { Discovery: { unknown: 'value' } } },
{ config: { Discovery: null } },
{ config: { Discovery: undefined } },

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

{ config: { unknown: 'value' } },
{ config: null },
{ config: undefined }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
})

it('should validate invalid config', () => {
const cfgs = [
{ config: { Addresses: { Swarm: 138 } } },
{ config: { Addresses: { Swarm: null } } },

{ config: { Addresses: { API: 138 } } },
{ config: { Addresses: { API: null } } },

{ config: { Addresses: { Gateway: 138 } } },
{ config: { Addresses: { Gateway: null } } },

{ config: { Discovery: { MDNS: { Enabled: 138 } } } },
{ config: { Discovery: { MDNS: { Interval: true } } } },

{ config: { Discovery: { webRTCStar: { Enabled: 138 } } } },

{ config: { Bootstrap: ['/ip4/0.0.0.0/tcp/4002'] } },
{ config: { Bootstrap: 138 } },

{ config: 138 }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
})

it('should validate valid libp2p', () => {
const cfgs = [
{ libp2p: { modules: {} } },
{ libp2p: { modules: { unknown: 'value' } } },
{ libp2p: { modules: null } },
{ libp2p: { modules: undefined } },
{ libp2p: { unknown: 'value' } },
{ libp2p: null },
{ libp2p: undefined }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw())
})

it('should validate invalid libp2p', () => {
const cfgs = [
{ libp2p: { modules: 138 } },
{ libp2p: 138 }
]

cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw())
})
})

0 comments on commit a32dce7

Please sign in to comment.