Last active
May 13, 2021 14:35
-
-
Save unicornware/5414522bba1e348cf5da7638ef2c9008 to your computer and use it in GitHub Desktop.
NestJS - ParseQueryPipe
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Module } from '@nestjs/common' | |
import type { ConfigModuleOptions } from '@nestjs/config' | |
import { ConfigModule } from '@nestjs/config' | |
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core' | |
import configuration from './config/configuration' | |
import { AllExceptionsFilter } from './lib/filters' | |
import { PageviewInterceptor } from './lib/interceptors' | |
import { ParseQueryPipe } from './lib/pipes' | |
import { UsersModule } from './subdomains' | |
/** | |
* @file Root Application Module | |
* @module app/AppModule | |
* @see https://docs.nestjs.com/modules | |
*/ | |
const configModuleOptions: ConfigModuleOptions = { | |
cache: configuration().PROD, | |
ignoreEnvFile: true, | |
isGlobal: true, | |
load: [configuration] | |
} | |
@Module({ | |
imports: [ConfigModule.forRoot(configModuleOptions), UsersModule], | |
providers: [ | |
{ provide: APP_FILTER, useClass: AllExceptionsFilter }, | |
{ provide: APP_INTERCEPTOR, useClass: PageviewInterceptor }, | |
{ provide: APP_PIPE, useClass: ParseQueryPipe } | |
] | |
}) | |
export default class AppModule {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ExceptionStatusCode } from '@flex-development/exceptions/enums' | |
import Exception from '@flex-development/exceptions/exceptions/base.exception' | |
import type { Paramtype } from '@nestjs/common' | |
import merge from 'lodash.merge' | |
import TestSubject from '../parse-query.pipe' | |
/** | |
* @file Unit Tests - ParseQueryPipe | |
* @module app/lib/pipes/tests/ParseQueryPipe | |
*/ | |
describe('unit:app/lib/pipes/ParseQueryPipe', () => { | |
const Subject = new TestSubject() | |
const metadata = { type: 'query' as Paramtype } | |
describe('#transform', () => { | |
it("should return value if metadata.type !== 'query'", () => { | |
const value = null | |
expect(Subject.transform(value, { type: 'body' })).toBe(value) | |
}) | |
it('should throw Exception if value is not plain object', () => { | |
const value = [] | |
const pattern = `Query parameters must be an object; received ${value}` | |
const emessage = new RegExp(pattern) | |
let exception = {} as Exception | |
try { | |
Subject.transform(value, metadata) | |
} catch (error) { | |
exception = error | |
} | |
expect(exception.code).toBe(ExceptionStatusCode.BAD_REQUEST) | |
expect(exception.errors).toMatchObject({ value }) | |
expect(exception.message).toMatch(emessage) | |
}) | |
it('should parse key values that are are json strings', () => { | |
const value = { email: 'true' } | |
const query = Subject.transform(value, metadata) | |
expect(typeof query.email === 'boolean').toBeTruthy() | |
}) | |
it('should remove path parameter', () => { | |
const value = { path: 'users' } | |
expect(Subject.transform(value, metadata)).toMatchObject({}) | |
}) | |
it('should handle decorator argument', () => { | |
const data = 'updated_at' | |
const value = { [data]: '1620232058179' } | |
const this_metadata = merge({}, metadata, { data }) | |
const expected = value[this_metadata.data] | |
expect(Subject.transform(value, this_metadata)).toBe(expected) | |
}) | |
}) | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ExceptionStatusCode } from '@flex-development/exceptions/enums' | |
import Exception from '@flex-development/exceptions/exceptions/base.exception' | |
import type { ArgumentMetadata, PipeTransform } from '@nestjs/common' | |
import { Injectable } from '@nestjs/common' | |
import type { VercelRequestQuery } from '@vercel/node' | |
import isPlainObject from 'lodash.isplainobject' | |
import omit from 'lodash.omit' | |
import type { PlainObject } from 'simplytyped' | |
import isJSON from 'validator/lib/isJSON' | |
/** | |
* @file Global Pipe - ParseQueryPipe | |
* @module app/lib/pipes/ParseQueryPipe | |
* @see https://docs.nestjs.com/pipes#custom-pipes | |
*/ | |
/** | |
* Global pipe to parse query parameters. | |
* | |
* @class | |
* @implements {PipeTransform<any, PlainObject | any>} | |
*/ | |
@Injectable() | |
export default class ParseQueryPipe | |
implements PipeTransform<any, PlainObject | any> { | |
/** | |
* Uses `JSON.parse` to parse each key of {@param value} if it's a string | |
* containing JSON data. | |
* | |
* If {@param metadata.type} equals `query`, the original value will be | |
* returned without an attempt to be parsed. | |
* | |
* This is useful for methods that use query parameters, but don't take into | |
* account that queries are `VercelRequestQuery` objects, where all key values | |
* are actually strings. | |
* | |
* Additionally, {@param value.path} will be deleted. With every request to a | |
* serverless function running on Vercel, the request path is sent as a query | |
* parameter. When a search is executed, this will cause the search to yield | |
* zero results. | |
* | |
* @param {any} value - Value before it goes to route handler method | |
* @param {ArgumentMetadata} metadata - Pipe metadata about @param value | |
* @return {PlainObject} Parsed query, value of `query[metadata.data]`, or | |
* orignal value if `metadata.type !== 'query'` | |
* @throws {Exception} | |
*/ | |
transform(value: any, metadata: ArgumentMetadata): PlainObject | any { | |
if (metadata.type !== 'query') return value | |
let query: VercelRequestQuery = {} | |
if (value && !isPlainObject(value)) { | |
const message = `Query parameters must be an object; received ${value}` | |
const data = { errors: { value } } | |
throw new Exception(ExceptionStatusCode.BAD_REQUEST, message, data) | |
} | |
Object.keys((value || {}) as VercelRequestQuery).forEach(key => { | |
let key_value = value[key] | |
if (typeof key_value === 'string') { | |
// @ts-expect-error type definition is wrong | |
const json = isJSON(key_value, { allow_primitives: true }) | |
key_value = json ? JSON.parse(key_value) : key_value | |
} | |
query[key] = key_value | |
}) | |
query = omit(query, ['path']) | |
return typeof metadata.data === 'string' ? query[metadata.data] : query | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment