-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
87d89c0
commit fae840b
Showing
9 changed files
with
238 additions
and
3 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,4 +5,5 @@ | |
|
||
export * from './enums' | ||
export * from './interfaces' | ||
export * from './mixins' | ||
export * from './types' |
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import type { Document, MangoParsedUrlQuery, MangoSearchParams } from '@/types' | ||
import qsm from 'qs-to-mongo' | ||
import type { ParsedOptions } from 'qs-to-mongo/lib/query/options-to-mongo' | ||
import type { MangoParserOptions } from './mango-parser-options.interface' | ||
import type { QueryCriteriaOptions } from './query-criteria-options.interface' | ||
|
||
/** | ||
* @file Interface - IMangoParser | ||
* @module interfaces/IMangoParser | ||
*/ | ||
|
||
/** | ||
* `MangoParser` mixin interface. | ||
* | ||
* @template D - Document (collection object) | ||
*/ | ||
export interface IMangoParser<D extends Document = Document> { | ||
readonly parser: typeof qsm | ||
readonly options: MangoParserOptions | ||
|
||
params(query?: MangoParsedUrlQuery<D> | string): MangoSearchParams<D> | ||
queryCriteriaOptions(base?: Partial<ParsedOptions>): QueryCriteriaOptions<D> | ||
} |
Empty file.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { ProjectRule } from '@/enums' | ||
import type { MangoParserOptions } from '@/interfaces' | ||
import { ExceptionStatusCode } from '@flex-development/exceptions/enums' | ||
import Exception from '@flex-development/exceptions/exceptions/base.exception' | ||
import TestSubject from '../mango-parser.mixin' | ||
|
||
/** | ||
* @file Unit Tests - MangoParser | ||
* @module mixins/tests/MangoParser | ||
*/ | ||
|
||
describe('unit:mixins/MangoParser', () => { | ||
describe('constructor', () => { | ||
it('should remove options.objectIdFields', () => { | ||
const options = ({ objectIdFields: [] } as unknown) as MangoParserOptions | ||
|
||
const mparser = new TestSubject(options) | ||
|
||
// @ts-expect-error testing | ||
expect(mparser.options.objectIdFields).not.toBeDefined() | ||
}) | ||
|
||
it('should remove options.parameters', () => { | ||
const options = ({ parameters: '' } as unknown) as MangoParserOptions | ||
|
||
const mparser = new TestSubject(options) | ||
|
||
// @ts-expect-error testing | ||
expect(mparser.options.parameters).not.toBeDefined() | ||
}) | ||
}) | ||
|
||
describe('#params', () => { | ||
const Subject = new TestSubject({ fullTextFields: ['created_at', 'id'] }) | ||
|
||
const spy_parser = jest.spyOn(Subject, 'parser') | ||
|
||
it('should parse url query string', () => { | ||
const querystring = 'fields=created_at&sort=created_at,-id&limit=10' | ||
|
||
Subject.params(querystring) | ||
|
||
expect(spy_parser).toBeCalledTimes(1) | ||
expect(spy_parser.mock.calls[0][0]).toBe(querystring) | ||
}) | ||
|
||
it('should parse url query object', () => { | ||
const query = { | ||
fields: 'created_at,-updated_at', | ||
limit: '10', | ||
q: 'foo', | ||
sort: 'created_at,-id' | ||
} | ||
|
||
Subject.params(query) | ||
|
||
expect(spy_parser).toBeCalledTimes(1) | ||
expect(spy_parser.mock.calls[0][0]).toBe(query) | ||
}) | ||
|
||
it('should throw Exception if #parser throws', () => { | ||
const query = { q: 'will cause error' } | ||
|
||
let exception = {} as Exception | ||
|
||
try { | ||
new TestSubject().params(query) | ||
} catch (error) { | ||
exception = error | ||
} | ||
|
||
expect(exception.code).toBe(ExceptionStatusCode.BAD_REQUEST) | ||
expect(exception.data).toMatchObject({ parser_options: {}, query }) | ||
}) | ||
}) | ||
|
||
describe('#queryCriteriaOptions', () => { | ||
const Subject = new TestSubject() | ||
|
||
it('should convert base options into QueryCriteriaOptions object', () => { | ||
const projection = { created_at: ProjectRule.PICK } | ||
|
||
const options = Subject.queryCriteriaOptions({ projection }) | ||
|
||
expect(options.$project).toMatchObject(projection) | ||
}) | ||
}) | ||
}) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** | ||
* @file Entry Point - Mixins | ||
* @module mixins | ||
*/ | ||
|
||
export { default as MangoParser } from './mango-parser.mixin' |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import type { | ||
CustomQSMongoParser, | ||
IMangoParser, | ||
MangoParserOptions, | ||
QueryCriteriaOptions | ||
} from '@/interfaces' | ||
import type { Document, MangoParsedUrlQuery, MangoSearchParams } from '@/types' | ||
import { ExceptionStatusCode } from '@flex-development/exceptions/enums' | ||
import Exception from '@flex-development/exceptions/exceptions/base.exception' | ||
import type { OneOrMany, PlainObject } from '@flex-development/tutils' | ||
import qsm from 'qs-to-mongo' | ||
import type { ParsedOptions } from 'qs-to-mongo/lib/query/options-to-mongo' | ||
|
||
/** | ||
* @file Mixin - MangoParser | ||
* @module mixins/MangoParser | ||
*/ | ||
|
||
/** | ||
* Converts Mongo URL queries into repository search parameters objects. | ||
* | ||
* @template D - Document (collection object) | ||
* | ||
* @class | ||
* @implements {IMangoParser<D>} | ||
*/ | ||
export default class MangoParser<D extends Document = Document> | ||
implements IMangoParser<D> { | ||
/** | ||
* @readonly | ||
* @instance | ||
* @property {typeof qsm} parser - `qs-to-mongo` module | ||
* @see https://github.com/fox1t/qs-to-mongo | ||
*/ | ||
readonly parser: typeof qsm = qsm | ||
|
||
/** | ||
* @readonly | ||
* @instance | ||
* @property {MangoParserOptions} options - `qs-to-mongo` module options | ||
*/ | ||
readonly options: MangoParserOptions | ||
|
||
/** | ||
* Creates a new `MangoParser` client. | ||
* | ||
* Converts MongoDB query objects and strings into search parameters objects, | ||
* and formats query criteria objects. | ||
* | ||
* - https://github.com/fox1t/qs-to-mongo | ||
* - https://github.com/kofrasa/mingo | ||
* | ||
* @param {MangoParserOptions} [options] - Parser options | ||
* @param {OneOrMany<string>} [options.dateFields] - Fields that will be | ||
* converted to `Date`; if no fields are passed, any valid date string will be | ||
* converted to an ISO-8601 string | ||
* @param {OneOrMany<string>} [options.fullTextFields] - Fields that will be | ||
* used as criteria when passing `text` query parameter | ||
* @param {OneOrMany<string>} [options.ignoredFields] - Array of query | ||
* parameters that are ignored, in addition to the defaults (`fields`, | ||
* `limit`, `offset`, `omit`, `sort`, `text`) | ||
* @param {number} [options.maxLimit] - Max that can be passed to limit option | ||
* @param {CustomQSMongoParser} [options.parser] - Custom query parser | ||
* @param {any} [options.parserOptions] - Custom query parser options | ||
*/ | ||
constructor(options: MangoParserOptions = {}) { | ||
const parser_options = Object.assign({}, options) | ||
|
||
Reflect.deleteProperty(parser_options, 'objectIdFields') | ||
Reflect.deleteProperty(parser_options, 'parameters') | ||
|
||
this.options = parser_options | ||
} | ||
|
||
/** | ||
* Convert URL query objects and strings into a search parameters objects. | ||
* | ||
* @param {MangoParsedUrlQuery<D> | string} [query] - Query object or string | ||
* @return {MangoSearchParams<D>} Mango search parameters object | ||
* @throws {Exception} | ||
*/ | ||
params(query: MangoParsedUrlQuery<D> | string = ''): MangoSearchParams<D> { | ||
let build: PlainObject = {} | ||
|
||
try { | ||
build = this.parser(query, this.options) | ||
} catch ({ message, stack }) { | ||
const code = ExceptionStatusCode.BAD_REQUEST | ||
const data = { parser_options: this.options, query } | ||
|
||
throw new Exception(code, message, data, stack) | ||
} | ||
|
||
return { | ||
...build.criteria, | ||
options: this.queryCriteriaOptions(build.options) | ||
} as MangoSearchParams<D> | ||
} | ||
|
||
/** | ||
* Converts parsed URL query criteria options into `QueryCriteriaOptions` | ||
* objects. | ||
* | ||
* @param {Partial<ParsedOptions>} [base] - Parsed query criteria options | ||
* @return {QueryCriteriaOptions<D>} Query criteria options | ||
*/ | ||
queryCriteriaOptions( | ||
base: Partial<ParsedOptions> = {} | ||
): QueryCriteriaOptions<D> { | ||
const { projection, sort, ...rest } = base | ||
|
||
return { | ||
...rest, | ||
$project: projection as QueryCriteriaOptions<D>['$project'], | ||
sort: sort as QueryCriteriaOptions<D>['sort'] | ||
} | ||
} | ||
} |