Skip to content

Commit

Permalink
docs: add server example (#17)
Browse files Browse the repository at this point in the history
* refactor: better error handling

* refactor: add ERR for errors
  • Loading branch information
Kikobeats authored Apr 29, 2024
1 parent 8841f0f commit eb902b7
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 53 deletions.
17 changes: 12 additions & 5 deletions examples/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

const { styleText } = require('node:util')
const { json } = require('http-body')
const send = require('send-http')

const Redis = require('ioredis')
const http = require('http')

const redis = new Redis()
const openkey = require('../..')({ redis })

const createSend = req => (res, statusCode, body) => {
console.log(`~> ${req.method} ${req.url} (${statusCode})`)
return require('send-http')(res, statusCode, body)
}

const server = http.createServer(async (req, res) => {
try {
console.log(`~> ${req.method} ${req.url} `)
const send = createSend(req)

try {
if (req.url === '/keys/create') {
const options = await json(req)
const result = await openkey.keys.create(options)
Expand Down Expand Up @@ -49,9 +54,11 @@ const server = http.createServer(async (req, res) => {

return send(res, 400)
} catch (error) {
if (error.name === 'OpenKeyError') {
return send(res, 400, { code: error.code, message: error.message })
}
console.log(' ' + styleText('red', `ERROR: ${error.message}`))
res.statusCode = 500
res.end()
return send(res, 500)
}
})

Expand Down
24 changes: 12 additions & 12 deletions src/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ class OpenKeyError extends Error {
}

const errors = [
['KEY_NOT_EXIST', key => `The key \`${key}\` does not exist.`],
['KEY_IS_ASSOCIATED', (id, value) => `The plan \`${id}\` is associated with the key \`${value}\`.`],
['PLAN_NOT_EXIST', plan => `The plan \`${plan}\` does not exist.`],
['PLAN_ID_REQUIRED', () => 'The argument `id` must be a string.'],
['PLAN_INVALID_ID', () => 'The argument `id` cannot contain whitespace.'],
['PLAN_INVALID_LIMIT', () => 'The argument `limit` must be a positive number.'],
['PLAN_INVALID_PERIOD', () => 'The argument `period` must be a string.'],
['PLAN_ALREADY_EXIST', plan => `The plan \`${plan}\` already exists.`],
['METADATA_NOT_FLAT_OBJECT', () => 'The metadata must be a flat object.'],
['METADATA_INVALID', key => `The metadata field '${key}' can't be an object.`]
['ERR_KEY_NOT_EXIST', key => `The key \`${key}\` does not exist.`],
['ERR_KEY_IS_ASSOCIATED', (id, value) => `The plan \`${id}\` is associated with the key \`${value}\`.`],
['ERR_PLAN_NOT_EXIST', plan => `The plan \`${plan}\` does not exist.`],
['ERR_PLAN_ID_REQUIRED', () => 'The argument `id` must be a string.'],
['ERR_PLAN_INVALID_ID', () => 'The argument `id` cannot contain whitespace.'],
['ERR_PLAN_INVALID_LIMIT', () => 'The argument `limit` must be a positive number.'],
['ERR_PLAN_INVALID_PERIOD', () => 'The argument `period` must be a string.'],
['ERR_PLAN_ALREADY_EXIST', plan => `The plan \`${plan}\` already exists.`],
['ERR_METADATA_NOT_FLAT_OBJECT', () => 'The metadata must be a flat object.'],
['ERR_METADATA_INVALID', key => `The metadata field '${key}' can't be an object.`]
].reduce((acc, [code, message]) => {
acc[code] = args => new OpenKeyError({ code, message: message.apply(null, args()) })
return acc
Expand All @@ -37,9 +37,9 @@ const assert = (condition, code, args = () => []) => {

const assertMetadata = metadata => {
if (metadata) {
assert(isPlainObject(metadata), 'METADATA_NOT_FLAT_OBJECT')
assert(isPlainObject(metadata), 'ERR_METADATA_NOT_FLAT_OBJECT')
Object.keys(metadata).forEach(key => {
assert(!isPlainObject(metadata[key]), 'METADATA_INVALID', () => [key])
assert(!isPlainObject(metadata[key]), 'ERR_METADATA_INVALID', () => [key])
if (metadata[key] === undefined) delete metadata[key]
})
return Object.keys(metadata).length ? metadata : undefined
Expand Down
4 changes: 2 additions & 2 deletions src/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ module.exports = ({ serialize, deserialize, plans, redis, prefix } = {}) => {
*/
const retrieve = async (value, { throwError = false } = {}) => {
const key = await redis.get(prefixKey(value))
if (throwError) assert(key !== null, 'KEY_NOT_EXIST', () => [value])
if (throwError) assert(key !== null, 'ERR_KEY_NOT_EXIST', () => [value])
else if (key === null) return null
return Object.assign({ value }, deserialize(key))
}
Expand All @@ -55,7 +55,7 @@ module.exports = ({ serialize, deserialize, plans, redis, prefix } = {}) => {
*/
const del = async value => {
const isDeleted = (await redis.del(prefixKey(value))) === 1
assert(isDeleted, 'KEY_NOT_EXIST', () => [value])
assert(isDeleted, 'ERR_KEY_NOT_EXIST', () => [value])
return isDeleted
}

Expand Down
23 changes: 13 additions & 10 deletions src/plans.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,23 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
* @returns {Object} The plan object.
*/
const create = async (opts = {}) => {
assert(typeof opts.id === 'string' && opts.id.length > 0, 'PLAN_ID_REQUIRED')
assert(!/\s/.test(opts.id), 'PLAN_INVALID_ID')
assert(typeof opts.id === 'string' && opts.id.length > 0, 'ERR_PLAN_ID_REQUIRED')
assert(!/\s/.test(opts.id), 'ERR_PLAN_INVALID_ID')
const plan = Object.assign(
{
limit: assert(typeof opts.limit === 'number' && opts.limit > 0 && opts.limit, 'PLAN_INVALID_LIMIT'),
period: assert(typeof opts.period === 'string' && opts.period.length > 0 && opts.period, 'PLAN_INVALID_PERIOD')
limit: assert(typeof opts.limit === 'number' && opts.limit > 0 && opts.limit, 'ERR_PLAN_INVALID_LIMIT'),
period: assert(
typeof opts.period === 'string' && opts.period.length > 0 && opts.period,
'ERR_PLAN_INVALID_PERIOD'
)
},
pick(opts, ['rate', 'burst'])
)
const metadata = assertMetadata(opts.metadata)
if (metadata) plan.metadata = metadata
plan.createdAt = plan.updatedAt = Date.now()
const isCreated = (await redis.set(prefixKey(opts.id), serialize(plan), 'NX')) === 'OK'
assert(isCreated, 'PLAN_ALREADY_EXIST', () => [opts.id])
assert(isCreated, 'ERR_PLAN_ALREADY_EXIST', () => [opts.id])
return Object.assign({ id: opts.id }, plan)
}

Expand All @@ -47,7 +50,7 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
*/
const retrieve = async (id, { throwError = false } = {}) => {
const plan = await redis.get(prefixKey(id))
if (throwError) assert(plan !== null, 'PLAN_NOT_EXIST', () => [id])
if (throwError) assert(plan !== null, 'ERR_PLAN_NOT_EXIST', () => [id])
else if (plan === null) return null
return Object.assign({ id }, deserialize(plan))
}
Expand All @@ -63,9 +66,9 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
const del = async id => {
const allKeys = await keys().list()
const key = allKeys.find(key => key.plan === id)
assert(key === undefined, 'KEY_IS_ASSOCIATED', () => [id, key.value])
assert(key === undefined, 'ERR_KEY_IS_ASSOCIATED', () => [id, key.value])
const isDeleted = (await redis.del(prefixKey(id))) === 1
assert(isDeleted, 'PLAN_NOT_EXIST', () => [id])
assert(isDeleted, 'ERR_PLAN_NOT_EXIST', () => [id])
return isDeleted
}

Expand Down Expand Up @@ -95,13 +98,13 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
)

if (opts.limit) {
plan.limit = assert(typeof opts.limit === 'number' && opts.limit > 0 && opts.limit, 'PLAN_INVALID_LIMIT')
plan.limit = assert(typeof opts.limit === 'number' && opts.limit > 0 && opts.limit, 'ERR_PLAN_INVALID_LIMIT')
}

if (opts.period) {
plan.period = assert(
typeof opts.period === 'string' && opts.period.length > 0 && opts.period,
'PLAN_INVALID_PERIOD'
'ERR_PLAN_INVALID_PERIOD'
)
}

Expand Down
12 changes: 6 additions & 6 deletions test/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ test('.create # error if `metadata` is not a flat object', async t => {
const error = await t.throwsAsync(openkey.keys.create({ metadata: { tier: { type: 'new' } } }))
t.is(error.message, "The metadata field 'tier' can't be an object.")
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'METADATA_INVALID')
t.is(error.code, 'ERR_METADATA_INVALID')
})

test('.create # `metadata` as undefined is omitted', async t => {
Expand All @@ -33,7 +33,7 @@ test('.create # error if plan does not exist', async t => {
const error = await t.throwsAsync(openkey.keys.create({ plan: '123' }))
t.is(error.message, 'The plan `123` does not exist.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_NOT_EXIST')
t.is(error.code, 'ERR_PLAN_NOT_EXIST')
})

test('.create', async t => {
Expand Down Expand Up @@ -97,15 +97,15 @@ test('.update # error if key does not exist', async t => {
const error = await t.throwsAsync(openkey.keys.update('value'))
t.is(error.message, 'The key `value` does not exist.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'KEY_NOT_EXIST')
t.is(error.code, 'ERR_KEY_NOT_EXIST')
})

test('.update # error if plan does not exist', async t => {
const { value } = await openkey.keys.create()
const error = await t.throwsAsync(openkey.keys.update(value, { plan: 'id' }))
t.is(error.message, 'The plan `id` does not exist.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_NOT_EXIST')
t.is(error.code, 'ERR_PLAN_NOT_EXIST')
})

test('.update # add a plan', async t => {
Expand Down Expand Up @@ -140,7 +140,7 @@ test('.update # error is metadata is not a flat object', async t => {
const error = await t.throwsAsync(openkey.keys.update(value, { metadata: { email: { cc: '[email protected]' } } }))
t.is(error.message, "The metadata field 'email' can't be an object.")
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'METADATA_INVALID')
t.is(error.code, 'ERR_METADATA_INVALID')
})

test('.update # metadata as undefined is omitted', async t => {
Expand Down Expand Up @@ -198,5 +198,5 @@ test('.del # error if key does not exist', async t => {

t.is(error.message, 'The key `key_id` does not exist.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'KEY_NOT_EXIST')
t.is(error.code, 'ERR_KEY_NOT_EXIST')
})
32 changes: 16 additions & 16 deletions test/plans.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,37 @@ test('.create # error is `id` is not provided', async t => {
const error = await t.throwsAsync(openkey.plans.create())
t.is(error.message, 'The argument `id` must be a string.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_ID_REQUIRED')
t.is(error.code, 'ERR_PLAN_ID_REQUIRED')
}
{
const error = await t.throwsAsync(openkey.plans.create({ id: null }))
t.is(error.message, 'The argument `id` must be a string.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_ID_REQUIRED')
t.is(error.code, 'ERR_PLAN_ID_REQUIRED')
}
{
const error = await t.throwsAsync(openkey.plans.create({ id: undefined }))
t.is(error.message, 'The argument `id` must be a string.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_ID_REQUIRED')
t.is(error.code, 'ERR_PLAN_ID_REQUIRED')
}
{
const error = await t.throwsAsync(openkey.plans.create({ id: 0 }))
t.is(error.message, 'The argument `id` must be a string.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_ID_REQUIRED')
t.is(error.code, 'ERR_PLAN_ID_REQUIRED')
}
{
const error = await t.throwsAsync(openkey.plans.create({ id: NaN }))
t.is(error.message, 'The argument `id` must be a string.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_ID_REQUIRED')
t.is(error.code, 'ERR_PLAN_ID_REQUIRED')
}
{
const error = await t.throwsAsync(openkey.plans.create({ id: false }))
t.is(error.message, 'The argument `id` must be a string.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_ID_REQUIRED')
t.is(error.code, 'ERR_PLAN_ID_REQUIRED')
}
})

Expand All @@ -64,28 +64,28 @@ test('.create # error if `id` already exist', async t => {
const error = await t.throwsAsync(openkey.plans.create(props))
t.is(error.message, `The plan \`${id}\` already exists.`)
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_ALREADY_EXIST')
t.is(error.code, 'ERR_PLAN_ALREADY_EXIST')
})

test('.create # error if `id` contains whitespaces', async t => {
const error = await t.throwsAsync(openkey.plans.create({ id: 'free tier' }))
t.is(error.message, 'The argument `id` cannot contain whitespace.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_INVALID_ID')
t.is(error.code, 'ERR_PLAN_INVALID_ID')
})

test('.create # error if `limit` is not provided', async t => {
const error = await t.throwsAsync(openkey.plans.create({ id: randomUUID() }))
t.is(error.message, 'The argument `limit` must be a positive number.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_INVALID_LIMIT')
t.is(error.code, 'ERR_PLAN_INVALID_LIMIT')
})

test('.create # error if `period` is not provided', async t => {
const error = await t.throwsAsync(openkey.plans.create({ id: randomUUID(), limit: 3 }))
t.is(error.message, 'The argument `period` must be a string.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_INVALID_PERIOD')
t.is(error.code, 'ERR_PLAN_INVALID_PERIOD')
})

test('.create # error if `metadata` is not a flat object', async t => {
Expand All @@ -100,7 +100,7 @@ test('.create # error if `metadata` is not a flat object', async t => {
)
t.is(error.message, "The metadata field 'tier' can't be an object.")
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'METADATA_INVALID')
t.is(error.code, 'ERR_METADATA_INVALID')
}
{
const error = await t.throwsAsync(
Expand All @@ -113,7 +113,7 @@ test('.create # error if `metadata` is not a flat object', async t => {
)
t.is(error.message, 'The metadata must be a flat object.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'METADATA_NOT_FLAT_OBJECT')
t.is(error.code, 'ERR_METADATA_NOT_FLAT_OBJECT')
}
})

Expand Down Expand Up @@ -286,7 +286,7 @@ test('.update # error is metadata is not a flat object', async t => {
const error = await t.throwsAsync(openkey.plans.update(id, { metadata: { tier: { type: 'new' } } }))
t.is(error.message, "The metadata field 'tier' can't be an object.")
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'METADATA_INVALID')
t.is(error.code, 'ERR_METADATA_INVALID')
})

test('.update # metadata as undefined is omitted', async t => {
Expand Down Expand Up @@ -340,7 +340,7 @@ test('.update # error if plan does not exist', async t => {
const error = await t.throwsAsync(openkey.plans.update('plan_id', { foo: 'bar' }))
t.is(error.message, 'The plan `plan_id` does not exist.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_NOT_EXIST')
t.is(error.code, 'ERR_PLAN_NOT_EXIST')
})

test.serial('.list', async t => {
Expand Down Expand Up @@ -382,7 +382,7 @@ test('.del # error if plan does not exist', async t => {
const error = await t.throwsAsync(openkey.plans.del('id'))
t.is(error.message, 'The plan `id` does not exist.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_NOT_EXIST')
t.is(error.code, 'ERR_PLAN_NOT_EXIST')
})

test.serial('.del # error if a key is associated with the plan', async t => {
Expand All @@ -397,5 +397,5 @@ test.serial('.del # error if a key is associated with the plan', async t => {

t.is(error.message, `The plan \`${plan.id}\` is associated with the key \`${key.value}\`.`)
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'KEY_IS_ASSOCIATED')
t.is(error.code, 'ERR_KEY_IS_ASSOCIATED')
})
4 changes: 2 additions & 2 deletions test/usage.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ test('.get # error if key does not exist', async t => {
const error = await t.throwsAsync(openkey.usage('value'))
t.is(error.message, 'The key `value` does not exist.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'KEY_NOT_EXIST')
t.is(error.code, 'ERR_KEY_NOT_EXIST')
})

test('.get # error if plan does not exist', async t => {
const key = await openkey.keys.create()
const error = await t.throwsAsync(openkey.usage(key.value))
t.is(error.message, 'The plan `undefined` does not exist.')
t.is(error.name, 'OpenKeyError')
t.is(error.code, 'PLAN_NOT_EXIST')
t.is(error.code, 'ERR_PLAN_NOT_EXIST')
})

test('.increment', async t => {
Expand Down

0 comments on commit eb902b7

Please sign in to comment.