Skip to content

Commit

Permalink
fix: remove metadata fields on update (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kikobeats committed May 18, 2024
1 parent 020b6b8 commit 6404b62
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 40 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The **options** accepted are:
- `id`<span class="type">string</span>: The id of the plan, it cannot contain whitespaces.
- `period`<span class="type">string</span>: The time window which the limit applies. It accepts [ms](https://www.npmjs.com/package/ms) syntax.
- `limit`<span class="type">number</span>: The target maximum number of requests that can be made in a given time period.
- `metadata`<span class="type">object</span>: A flat object containing additional information.
- `metadata`<span class="type">object</span>: A flat object containing additional information. Pass `null` or `''` to remove all the metadata fields.

Any other field provided will be omitted.

Expand Down Expand Up @@ -142,7 +142,7 @@ The **options** accepted are:

- `value`<span class="type">string</span>: The value of the key, being a base58 16 length key generated by default.
- `enabled`<span class="type">string</span>: It determines if the key is active, being `true` by default.
- `metadata`<span class="type">object</span>: A flat object containing additional information.
- `metadata`<span class="type">object</span>: A flat object containing additional information. Pass `null` or `''` to remove all the metadata fields.

Any other field provided will be omitted.

Expand Down
10 changes: 10 additions & 0 deletions src/assert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { errors } = require('./error')

module.exports = (condition, code, args = () => []) => {
return (
condition ||
(() => {
throw errors[code](args)
})()
)
}
24 changes: 1 addition & 23 deletions src/error.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict'

const { isPlainObject } = require('./util')

class OpenKeyError extends Error {
constructor (props) {
super()
Expand All @@ -26,24 +24,4 @@ const errors = [
return acc
}, {})

const assert = (condition, code, args = () => []) => {
return (
condition ||
(() => {
throw errors[code](args)
})()
)
}

const assertMetadata = metadata => {
if (metadata) {
assert(isPlainObject(metadata), 'ERR_METADATA_NOT_FLAT_OBJECT')
Object.keys(metadata).forEach(key => {
assert(!isPlainObject(metadata[key]), 'ERR_METADATA_INVALID', () => [key])
if (metadata[key] === undefined) delete metadata[key]
})
return Object.keys(metadata).length ? metadata : false
}
}

module.exports = { errors, assert, assertMetadata }
module.exports = { errors }
11 changes: 5 additions & 6 deletions src/keys.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict'

const { pick, uid } = require('./util')
const { assert, assertMetadata } = require('./error')
const metadata = require('./metadata')
const assert = require('./assert')

module.exports = ({ serialize, deserialize, plans, redis, prefix } = {}) => {
/**
Expand All @@ -17,8 +18,8 @@ module.exports = ({ serialize, deserialize, plans, redis, prefix } = {}) => {
*/
const create = async (opts = {}) => {
const key = { enabled: opts.enabled ?? true }
const metadata = assertMetadata(opts.metadata)
if (metadata) key.metadata = metadata
metadata(key, opts)
key.createdAt = key.updatedAt = Date.now()
const value = opts.value ?? (await uid({ redis, size: 16 }))
if (opts.plan) {
Expand Down Expand Up @@ -70,10 +71,8 @@ module.exports = ({ serialize, deserialize, plans, redis, prefix } = {}) => {
* @returns {Object} The updated plan.
*/
const update = async (value, opts) => {
const currentKey = await retrieve(value, { throwError: true })
const metadata = Object.assign({}, currentKey.metadata, assertMetadata(opts.metadata))
const key = Object.assign(currentKey, { updatedAt: Date.now() }, pick(opts, ['enabled', 'value', 'plan']))
if (Object.keys(metadata).length) key.metadata = metadata
let key = await retrieve(value, { throwError: true })
key = Object.assign(metadata(key, opts), { updatedAt: Date.now() }, pick(opts, ['enabled', 'value', 'plan']))
if (key.plan) await plans.retrieve(key.plan, { throwError: true })
return (await redis.set(prefixKey(value), await serialize(key))) && key
}
Expand Down
38 changes: 38 additions & 0 deletions src/metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict'

const { isPlainObject } = require('./util')
const assert = require('./assert')

const assertMetadata = metadata => {
if (metadata) {
assert(isPlainObject(metadata), 'ERR_METADATA_NOT_FLAT_OBJECT')
Object.keys(metadata).forEach(key => {
assert(!isPlainObject(metadata[key]), 'ERR_METADATA_INVALID', () => [key])
if (metadata[key] === undefined) delete metadata[key]
})

return Object.keys(metadata).length ? metadata : false
}
}

const clean = metadata => {
for (const [key, value] of Object.entries(metadata)) {
if ([null, ''].includes(value)) delete metadata[key]
}
return metadata
}

const merge = (metadata, newMetadata) => {
if (newMetadata === null) return null
const mergedMetadata = Object.assign({}, metadata, assertMetadata(newMetadata))
return clean(mergedMetadata)
}

const mutate = (obj, opts) => {
const metadata = merge(obj.metadata, opts.metadata)
if (metadata === null || Object.keys(metadata).length === 0) delete obj.metadata
else obj.metadata = metadata
return obj
}

module.exports = mutate
12 changes: 5 additions & 7 deletions src/plans.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const { assert, assertMetadata } = require('./error')
const assert = require('./assert')
const metadata = require('./metadata')

module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
/**
Expand All @@ -24,8 +25,7 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
'ERR_PLAN_INVALID_PERIOD'
)
}
const metadata = assertMetadata(opts.metadata)
if (metadata) plan.metadata = metadata
metadata(plan, opts)
plan.createdAt = plan.updatedAt = Date.now()
const isCreated = (await redis.set(prefixKey(opts.id), await serialize(plan), 'NX')) === 'OK'
assert(isCreated, 'ERR_PLAN_ALREADY_EXIST', () => [opts.id])
Expand Down Expand Up @@ -78,8 +78,7 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
* @returns {Object} The updated plan.
*/
const update = async (id, opts) => {
const plan = await retrieve(id, { throwError: true })
const metadata = Object.assign({}, plan.metadata, assertMetadata(opts.metadata))
let plan = await retrieve(id, { throwError: true })

if (opts.limit) {
plan.limit = assert(typeof opts.limit === 'number' && opts.limit > 0 && opts.limit, 'ERR_PLAN_INVALID_LIMIT')
Expand All @@ -92,9 +91,8 @@ module.exports = ({ serialize, deserialize, redis, keys, prefix } = {}) => {
)
}

plan.updatedAt = Date.now()
plan = Object.assign(metadata(plan, opts), { updatedAt: Date.now() })

if (Object.keys(metadata).length) plan.metadata = metadata
return (await redis.set(prefixKey(id), await serialize(plan))) && plan
}

Expand Down
17 changes: 15 additions & 2 deletions test/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ test('.update # error if plan does not exist', async t => {
t.is(error.code, 'ERR_PLAN_NOT_EXIST')
})

test('.update # add a plan', async t => {
test('.update # plan', async t => {
const plan = await openkey.plans.create({
id: randomUUID(),
limit: 3,
Expand All @@ -120,7 +120,7 @@ test('.update # add a plan', async t => {
t.is(key.plan, plan.id)
})

test('.update # add metadata', async t => {
test('.update # metadata', async t => {
{
const { value } = await openkey.keys.create()
const key = await openkey.keys.update(value, { metadata: { cc: 'hello@microlink.io' } })
Expand All @@ -134,6 +134,19 @@ test('.update # add metadata', async t => {
t.is(key.metadata.cc, 'hello@microlink.io')
t.is(key.metadata.version, 2)
}
{
const { value } = await openkey.keys.create()
const metadata = { tier: 'free', null: 'null', undefined: 'undefined', empty: '' }
let key = await openkey.keys.update(value, { metadata })
t.is(key.metadata.null, 'null')
t.is(key.metadata.undefined, 'undefined')
t.is(key.metadata.empty, undefined)
key = await openkey.keys.update(value, { metadata: { ...metadata, null: null, undefined: '' } })
t.is(key.metadata.null, undefined)
t.is(key.metadata.undefined, undefined)
key = await openkey.keys.update(value, { metadata: null })
t.is(key.metadata, undefined)
}
})

test('.update # error is metadata is not a flat object', async t => {
Expand Down
18 changes: 18 additions & 0 deletions test/plans.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,24 @@ test('.update # metadata', async t => {
t.is(plan.metadata.tier, 'free')
t.is(plan.metadata.version, 2)
}
{
const { id } = await openkey.plans.create({
id: randomUUID(),
limit: 1,
period: '1s'
})

const metadata = { tier: 'free', null: 'null', undefined: 'undefined', empty: '' }
let plan = await openkey.plans.update(id, { metadata })
t.is(plan.metadata.null, 'null')
t.is(plan.metadata.undefined, 'undefined')
t.is(plan.metadata.empty, undefined)
plan = await openkey.plans.update(id, { metadata: { ...metadata, null: null, undefined: '' } })
t.is(plan.metadata.null, undefined)
t.is(plan.metadata.undefined, undefined)
plan = await openkey.plans.update(id, { metadata: null })
t.is(plan.metadata, undefined)
}
})

test('.update # error is metadata is not a flat object', async t => {
Expand Down

0 comments on commit 6404b62

Please sign in to comment.