UNPKG

@coding-blocks/jsonapi-server

Version:

A config driven NodeJS framework implementing json:api

138 lines (105 loc) 4.39 kB
'use strict' const filter = module.exports = { } const FILTER_OPERATORS = ['<', '>', '~', ':'] const STRING_ONLY_OPERATORS = ['~', ':'] const FILTER_SEPERATOR = ',' filter._resourceDoesNotHaveProperty = (resourceConfig, key) => { if (resourceConfig.attributes[key]) return null return { status: '403', code: 'EFORBIDDEN', title: 'Invalid filter', detail: `${resourceConfig.resource} do not have attribute or relationship '${key}'` } } filter._relationshipIsForeign = (resourceConfig, key) => { const relationSettings = resourceConfig.attributes[key]._settings if (!relationSettings || !relationSettings.__as) return null return { status: '403', code: 'EFORBIDDEN', title: 'Invalid filter', detail: `Filter relationship '${key}' is a foreign reference and does not exist on ${resourceConfig.resource}` } } filter._splitElement = element => { if (!element) return null if (FILTER_OPERATORS.indexOf(element[0]) !== -1) { return { operator: element[0], value: element.substring(1) } } return { operator: null, value: element } } filter._stringOnlyOperator = (operator, attributeConfig) => { if (!operator || !attributeConfig) return null if (STRING_ONLY_OPERATORS.indexOf(operator) !== -1 && attributeConfig._type !== 'string') { return `operator ${operator} can only be applied to string attributes` } return null } filter._parseScalarFilterElement = (attributeConfig, scalarElement) => { if (!scalarElement) return { error: 'invalid or empty filter element' } const splitElement = filter._splitElement(scalarElement) if (!splitElement) return { error: 'empty filter' } const error = filter._stringOnlyOperator(splitElement.operator, attributeConfig) if (error) return { error } if (attributeConfig._settings) { // relationship attribute: no further validation return { result: splitElement } } const validateResult = attributeConfig.validate(splitElement.value) if (validateResult.error) { return { error: validateResult.error.message } } const validatedElement = { operator: splitElement.operator, value: validateResult.value } return { result: validatedElement } } filter._parseFilterElementHelper = (attributeConfig, filterElement) => { if (!filterElement) return { error: 'invalid or empty filter element' } const parsedElements = [].concat(filterElement).map(scalarElement => filter._parseScalarFilterElement(attributeConfig, scalarElement)) if (parsedElements.length === 1) return parsedElements[0] const errors = parsedElements.reduce((combined, element) => { if (!combined) { if (!element.error) return combined return [ element.error ] } return combined.concat(element.error) }, null) if (errors) return { error: errors } const results = parsedElements.map(element => element.result) return { result: results } } filter._parseFilterElement = (attributeName, attributeConfig, filterElement) => { const helperResult = filter._parseFilterElementHelper(attributeConfig, filterElement) if (helperResult.error) { return { error: { status: '403', code: 'EFORBIDDEN', title: 'Invalid filter', detail: `Filter value for key '${attributeName}' is invalid: ${helperResult.error}` } } } return { result: helperResult.result } } filter.parseAndValidate = request => { if (!request.params.filter) return null const resourceConfig = request.resourceConfig const processedFilter = { } let error let filterElement let parsedFilterElement for (const key in request.params.filter) { filterElement = request.params.filter[key] if (typeof filterElement === 'string') request.params.filter[key] = filterElement = filterElement.split(FILTER_SEPERATOR) if (!Array.isArray(filterElement) && filterElement instanceof Object) continue // skip deep filters error = filter._resourceDoesNotHaveProperty(resourceConfig, key) if (error) return error error = filter._relationshipIsForeign(resourceConfig, key) if (error) return error parsedFilterElement = filter._parseFilterElement(key, resourceConfig.attributes[key], filterElement) if (parsedFilterElement.error) return parsedFilterElement.error processedFilter[key] = [].concat(parsedFilterElement.result) } request.processedFilter = processedFilter return null }