diff --git a/bin/run.ts b/bin/run.ts index 81dbc240..25693445 100644 --- a/bin/run.ts +++ b/bin/run.ts @@ -7,8 +7,7 @@ import type { GraphQLMockServerConfig, RestMockServerConfig } from '@/utils/type import type { MockServerConfig, MockServerConfigArgv } from '../src'; import { validateApiMockServerConfig } from './validateMockServerConfig/validateApiMockServerConfig'; -// TODO: add loggers validation -// import { validateMockServerConfig } from './validateMockServerConfig/validateMockServerConfig'; +import { validateMockServerConfig } from './validateMockServerConfig/validateMockServerConfig'; export const run = ( mockConfig: MockServerConfig, @@ -54,7 +53,7 @@ export const run = ( return startRestMockServer(mergedApiMockServerConfig as RestMockServerConfig); } - // validateMockServerConfig(mergedMockServerConfig); + validateMockServerConfig(mergedMockServerConfig); return startMockServer(mergedMockServerConfig); } catch (error: any) { console.error(error.message); diff --git a/src/core/graphql/createGraphQLRoutes/createGraphQLRoutes.ts b/src/core/graphql/createGraphQLRoutes/createGraphQLRoutes.ts index 16f56f12..c85d3e1b 100644 --- a/src/core/graphql/createGraphQLRoutes/createGraphQLRoutes.ts +++ b/src/core/graphql/createGraphQLRoutes/createGraphQLRoutes.ts @@ -4,9 +4,7 @@ import { flatten } from 'flat'; import { asyncHandler, callRequestInterceptor, - callRequestLogger, callResponseInterceptors, - callResponseLogger, convertToEntityDescriptor, getGraphQLInput, isEntityDescriptor, @@ -20,7 +18,6 @@ import type { GraphQLEntitiesByEntityName, GraphQLEntity, Interceptors, - Loggers, TopLevelPlainEntityArray, TopLevelPlainEntityDescriptor } from '@/utils/types'; @@ -31,14 +28,12 @@ interface CreateGraphQLRoutesParams { router: IRouter; graphqlConfig: GraphqlConfig; serverResponseInterceptor?: Interceptors['response']; - loggers?: Loggers; } export const createGraphQLRoutes = ({ router, graphqlConfig, - serverResponseInterceptor, - loggers + serverResponseInterceptor }: CreateGraphQLRoutesParams) => { const preparedGraphQLRequestConfig = prepareGraphQLRequestConfigs(graphqlConfig.configs); @@ -133,11 +128,6 @@ export const createGraphQLRoutes = ({ if (!matchedRouteConfig) return next(); - const requestLogger = loggers?.request; - if (requestLogger) { - callRequestLogger({ request, logger: requestLogger }); - } - if (matchedRouteConfig.interceptors?.request) { await callRequestInterceptor({ request, @@ -217,16 +207,6 @@ export const createGraphQLRoutes = ({ await sleep(matchedRouteConfig.settings.delay); } - const responseLogger = loggers?.response; - if (responseLogger) { - callResponseLogger({ - request, - response, - logger: responseLogger, - data - }); - } - return response.json(data); }; diff --git a/src/core/middlewares/notFoundMiddleware/notFoundMiddleware.ts b/src/core/middlewares/notFoundMiddleware/notFoundMiddleware.ts index 2211df54..8418e712 100644 --- a/src/core/middlewares/notFoundMiddleware/notFoundMiddleware.ts +++ b/src/core/middlewares/notFoundMiddleware/notFoundMiddleware.ts @@ -1,6 +1,6 @@ import type { Express } from 'express'; -import { callRequestLogger, callResponseLogger, parseGraphQLRequest } from '@/utils/helpers'; +import { parseGraphQLRequest } from '@/utils/helpers'; import type { MockServerConfig, OperationNameGraphQLRequestConfig, @@ -12,7 +12,7 @@ import { getGraphqlUrlSuggestions, getRestUrlSuggestions } from './helpers'; export const notFoundMiddleware = ( server: Express, - mockServerConfig: Pick + mockServerConfig: Pick ) => { const { baseUrl: serverBaseUrl, rest, graphql } = mockServerConfig; @@ -35,11 +35,6 @@ export const notFoundMiddleware = ( })) ?? []; server.use((request, response) => { - const requestLogger = mockServerConfig.loggers?.request; - if (requestLogger) { - callRequestLogger({ request, logger: requestLogger }); - } - const url = new URL(`${request.protocol}://${request.get('host')}${request.originalUrl}`); let restRequestSuggestions: RestRequestSuggestionConfigs = []; @@ -59,10 +54,6 @@ export const notFoundMiddleware = ( } response.status(404); - const responseLogger = mockServerConfig.loggers?.response; - if (responseLogger) { - callResponseLogger({ request, response, logger: responseLogger, data: undefined }); - } const isRequestSupportHtml = request.headers.accept?.includes('text/html') || request.headers.accept?.includes('*/*'); diff --git a/src/core/rest/createRestRoutes/createRestRoutes.ts b/src/core/rest/createRestRoutes/createRestRoutes.ts index 6e78dcf1..6d68483b 100644 --- a/src/core/rest/createRestRoutes/createRestRoutes.ts +++ b/src/core/rest/createRestRoutes/createRestRoutes.ts @@ -5,9 +5,7 @@ import path from 'path'; import { asyncHandler, callRequestInterceptor, - callRequestLogger, callResponseInterceptors, - callResponseLogger, convertToEntityDescriptor, isEntityDescriptor, isFilePathValid, @@ -17,7 +15,6 @@ import { import type { Entries, Interceptors, - Loggers, RestConfig, RestEntitiesByEntityName, RestEntity, @@ -31,14 +28,12 @@ interface CreateRestRoutesParams { router: IRouter; restConfig: RestConfig; serverResponseInterceptor?: Interceptors['response']; - loggers?: Loggers; } export const createRestRoutes = ({ router, restConfig, - serverResponseInterceptor, - loggers + serverResponseInterceptor }: CreateRestRoutesParams) => { prepareRestRequestConfigs(restConfig.configs).forEach((requestConfig) => { router.route(requestConfig.path)[requestConfig.method]( @@ -105,11 +100,6 @@ export const createRestRoutes = ({ if (!matchedRouteConfig) return next(); - const requestLogger = loggers?.request; - if (requestLogger) { - callRequestLogger({ request, logger: requestLogger }); - } - if (matchedRouteConfig.interceptors?.request) { await callRequestInterceptor({ request, @@ -193,16 +183,6 @@ export const createRestRoutes = ({ await sleep(matchedRouteConfig.settings.delay); } - const responseLogger = loggers?.response; - if (responseLogger) { - callResponseLogger({ - request, - response, - logger: responseLogger, - data - }); - } - if ('file' in matchedRouteConfig) { return response.sendFile(path.resolve(matchedRouteConfig.file)); } diff --git a/src/server/createGraphQLMockServer/createGraphQLMockServer.ts b/src/server/createGraphQLMockServer/createGraphQLMockServer.ts index c31c00f6..1475626b 100644 --- a/src/server/createGraphQLMockServer/createGraphQLMockServer.ts +++ b/src/server/createGraphQLMockServer/createGraphQLMockServer.ts @@ -21,7 +21,7 @@ export const createGraphQLMockServer = ( graphqlMockServerConfig: Omit, server: Express = express() ) => { - const { cors, staticPath, configs, database, interceptors, loggers } = graphqlMockServerConfig; + const { cors, staticPath, configs, database, interceptors } = graphqlMockServerConfig; server.set('view engine', 'ejs'); server.set('views', urlJoin(__dirname, '../../static/views')); @@ -58,8 +58,7 @@ export const createGraphQLMockServer = ( const routerWithGraphqlRoutes = createGraphQLRoutes({ router: express.Router(), graphqlConfig: { configs: configs ?? [] }, - serverResponseInterceptor: interceptors?.response, - loggers + serverResponseInterceptor: interceptors?.response }); server.use(baseUrl, routerWithGraphqlRoutes); diff --git a/src/server/createMockServer/createMockServer.ts b/src/server/createMockServer/createMockServer.ts index afb9fbde..204f8e7c 100644 --- a/src/server/createMockServer/createMockServer.ts +++ b/src/server/createMockServer/createMockServer.ts @@ -22,7 +22,7 @@ export const createMockServer = ( mockServerConfig: Omit, server: Express = express() ) => { - const { cors, staticPath, rest, graphql, database, interceptors, loggers } = mockServerConfig; + const { cors, staticPath, rest, graphql, database, interceptors } = mockServerConfig; server.set('view engine', 'ejs'); server.set('views', urlJoin(__dirname, '../../static/views')); @@ -60,8 +60,7 @@ export const createMockServer = ( const routerWithRestRoutes = createRestRoutes({ router: express.Router(), restConfig: rest, - serverResponseInterceptor: interceptors?.response, - loggers + serverResponseInterceptor: interceptors?.response }); const restBaseUrl = urlJoin(baseUrl, rest.baseUrl ?? '/'); @@ -82,8 +81,7 @@ export const createMockServer = ( const routerWithGraphQLRoutes = createGraphQLRoutes({ router: express.Router(), graphqlConfig: graphql, - serverResponseInterceptor: interceptors?.response, - loggers + serverResponseInterceptor: interceptors?.response }); const graphqlBaseUrl = urlJoin(baseUrl, graphql.baseUrl ?? '/'); diff --git a/src/server/createRestMockServer/createRestMockServer.ts b/src/server/createRestMockServer/createRestMockServer.ts index 79cfbedc..87faeb9d 100644 --- a/src/server/createRestMockServer/createRestMockServer.ts +++ b/src/server/createRestMockServer/createRestMockServer.ts @@ -21,7 +21,7 @@ export const createRestMockServer = ( restMockServerConfig: Omit, server: Express = express() ) => { - const { cors, staticPath, configs, database, interceptors, loggers } = restMockServerConfig; + const { cors, staticPath, configs, database, interceptors } = restMockServerConfig; server.set('view engine', 'ejs'); server.set('views', urlJoin(__dirname, '../../static/views')); @@ -58,8 +58,7 @@ export const createRestMockServer = ( const routerWithRestRoutes = createRestRoutes({ router: express.Router(), restConfig: { configs: configs ?? [] }, - serverResponseInterceptor: interceptors?.response, - loggers + serverResponseInterceptor: interceptors?.response }); server.use(baseUrl, routerWithRestRoutes); diff --git a/src/utils/helpers/logger/callRequestLogger/callRequestLogger.ts b/src/utils/helpers/logger/callRequestLogger/callRequestLogger.ts index b39e09dc..73f22515 100644 --- a/src/utils/helpers/logger/callRequestLogger/callRequestLogger.ts +++ b/src/utils/helpers/logger/callRequestLogger/callRequestLogger.ts @@ -1,6 +1,6 @@ import type { Request } from 'express'; -import type { Logger, LoggerTokenFlags, LoggerTokenValues, RestMethod } from '@/utils/types'; +import type { Logger, LoggerTokenOptions, LoggerTokenValues, RestMethod } from '@/utils/types'; import { formatTimestamp } from '../../date'; import { filterTokenValues } from '../helpers'; @@ -15,7 +15,7 @@ const formatTokenValues = (tokenValues: Partial>) = }; }; -const DEFAULT_LOGGER_REQUEST_TOKEN_FLAGS: LoggerTokenFlags<'request'> = { +const DEFAULT_LOGGER_REQUEST_TOKEN_OPTIONS: LoggerTokenOptions<'request'> = { type: true, id: true, timestamp: true, @@ -29,7 +29,7 @@ interface CallRequestLoggerParams { } export const callRequestLogger = ({ logger, request }: CallRequestLoggerParams) => { - if (logger?.enabled === false) return; + if (logger?.enabled === false) return null; const rawTokenValues: LoggerTokenValues<'request'> = { type: 'request', @@ -49,15 +49,16 @@ export const callRequestLogger = ({ logger, request }: CallRequestLoggerParams) body: request.body }; - const tokenFlags = logger?.tokenFlags ?? DEFAULT_LOGGER_REQUEST_TOKEN_FLAGS; + const tokenOptions = logger?.tokenOptions ?? DEFAULT_LOGGER_REQUEST_TOKEN_OPTIONS; - const filteredTokenValues = filterTokenValues(rawTokenValues, tokenFlags); + const filteredTokenValues = filterTokenValues(rawTokenValues, tokenOptions); if (logger?.rewrite) { logger.rewrite(filteredTokenValues); - return; + return filteredTokenValues; } const formattedTokens = formatTokenValues(filteredTokenValues); console.dir(formattedTokens, { depth: null }); + return filteredTokenValues; }; diff --git a/src/utils/helpers/logger/callResponseLogger/callResponseLogger.ts b/src/utils/helpers/logger/callResponseLogger/callResponseLogger.ts index a8c234ab..9de6be95 100644 --- a/src/utils/helpers/logger/callResponseLogger/callResponseLogger.ts +++ b/src/utils/helpers/logger/callResponseLogger/callResponseLogger.ts @@ -1,6 +1,12 @@ import type { Request, Response } from 'express'; -import type { Data, Logger, LoggerTokenFlags, LoggerTokenValues, RestMethod } from '@/utils/types'; +import type { + Data, + Logger, + LoggerTokenOptions, + LoggerTokenValues, + RestMethod +} from '@/utils/types'; import { formatTimestamp } from '../../date'; import { filterTokenValues } from '../helpers'; @@ -15,7 +21,7 @@ const formatTokenValues = (tokenValues: Partial>) }; }; -const DEFAULT_LOGGER_RESPONSE_TOKEN_FLAGS: LoggerTokenFlags<'response'> = { +const DEFAULT_LOGGER_RESPONSE_TOKEN_OPTIONS: LoggerTokenOptions<'response'> = { type: true, id: true, timestamp: true, @@ -37,7 +43,7 @@ export const callResponseLogger = ({ request, response }: CallResponseLoggerParams) => { - if (logger?.enabled === false) return; + if (logger?.enabled === false) return null; const rawTokenValues: LoggerTokenValues<'response'> = { type: 'response', @@ -59,15 +65,16 @@ export const callResponseLogger = ({ data }; - const tokenFlags = logger?.tokenFlags ?? DEFAULT_LOGGER_RESPONSE_TOKEN_FLAGS; + const tokenOptions = logger?.tokenOptions ?? DEFAULT_LOGGER_RESPONSE_TOKEN_OPTIONS; - const filteredTokenValues = filterTokenValues(rawTokenValues, tokenFlags); + const filteredTokenValues = filterTokenValues(rawTokenValues, tokenOptions); if (logger?.rewrite) { logger.rewrite(filteredTokenValues); - return; + return filteredTokenValues; } const formattedTokens = formatTokenValues(filteredTokenValues); console.dir(formattedTokens, { depth: null }); + return filteredTokenValues; }; diff --git a/src/utils/helpers/logger/helpers/filterTokenValues/filterTokenValues.ts b/src/utils/helpers/logger/helpers/filterTokenValues/filterTokenValues.ts index 5f3dffb9..14a8ca6d 100644 --- a/src/utils/helpers/logger/helpers/filterTokenValues/filterTokenValues.ts +++ b/src/utils/helpers/logger/helpers/filterTokenValues/filterTokenValues.ts @@ -1,31 +1,62 @@ -import { flatten, unflatten } from 'flat'; - -import type { - LoggerAPI, - LoggerTokenFlags, - LoggerTokenValues, - LoggerType, - PlainObject -} from '@/utils/types'; - -type FilterTokenValues = ( - rawTokenValues: LoggerTokenValues, - tokenFlags: LoggerTokenFlags -) => Partial>; - -export const filterTokenValues: FilterTokenValues = (rawTokenValues, tokenFlags) => { - const flattenRawTokenValues = flatten(rawTokenValues); - const flattenTokenFlags = flatten(tokenFlags); - - const flattenFilteredTokenValues = Object.keys(flattenRawTokenValues).reduce((acc, tokenName) => { - // ✅ important: - // second case to allow get all mappedEntity properties - // e.g. query: true will return all query object keys - if (flattenTokenFlags[tokenName] || flattenTokenFlags[tokenName.split('.')[0]]) { - acc[tokenName] = flattenRawTokenValues[tokenName]; +import type { PlainObject } from '@/utils/types'; + +import { isPlainObject } from '../../../isPlainObject/isPlainObject'; + +type TokenObjectOptions = Record; + +type TokenObjectOptionsFiltering = 'whitelist' | 'blacklist'; + +type ResolveTokenObjectOptionsFiltering = ( + tokenObjectOptions: TokenObjectOptions +) => TokenObjectOptionsFiltering; + +const resolveTokenObjectOptionsFiltering: ResolveTokenObjectOptionsFiltering = ( + tokenObjectOptions +) => { + const values = Object.values(tokenObjectOptions); + if (values.some(Boolean)) return 'whitelist'; + return 'blacklist'; +}; + +type TokenOptions = Record; + +type FilterTokenValues = (rawTokenValues: PlainObject, tokenOptions: TokenOptions) => PlainObject; + +export const filterTokenValues: FilterTokenValues = (rawTokenValues, tokenOptions) => + Object.entries(tokenOptions).reduce((acc, [tokenName, tokenOption]) => { + const tokenValue = rawTokenValues[tokenName]; + + if (tokenOption === true) { + acc[tokenName] = tokenValue; + return acc; + } + + const isObjectOption = isPlainObject(tokenOption); + if (isObjectOption) { + const objectTokenOptions = tokenOption; + const objectTokenValues = tokenValue; + const tokenObjectOptionsFiltering = resolveTokenObjectOptionsFiltering(objectTokenOptions); + + if (tokenObjectOptionsFiltering === 'whitelist') { + acc[tokenName] = Object.entries(objectTokenOptions).reduce( + (acc, [objectTokenName, objectTokenOption]) => { + if (objectTokenOption) { + acc[objectTokenName] = objectTokenValues[objectTokenName]; + } + return acc; + }, + {} as PlainObject + ); + } + if (tokenObjectOptionsFiltering === 'blacklist') { + acc[tokenName] = Object.keys(objectTokenValues).reduce((acc, objectTokenName) => { + if (objectTokenOptions[objectTokenName] !== false) { + acc[objectTokenName] = objectTokenValues[objectTokenName]; + } + return acc; + }, {} as PlainObject); + } } + return acc; }, {} as PlainObject); - - return unflatten(flattenFilteredTokenValues); -}; diff --git a/src/utils/types/interceptors.ts b/src/utils/types/interceptors.ts index 2e228d09..c9e7fbfb 100644 --- a/src/utils/types/interceptors.ts +++ b/src/utils/types/interceptors.ts @@ -1,6 +1,6 @@ import type { CookieOptions, Request, Response } from 'express'; -import type { Logger } from './logger'; +import type { Logger, LoggerTokenValues } from './logger'; type RequestInterceptorCookieValue = string | undefined; type RequestInterceptorHeaderValue = string | number | string[] | undefined; @@ -10,7 +10,7 @@ export interface RequestInterceptorParams { getCookie: (name: string) => RequestInterceptorCookieValue; getHeader: (field: string) => RequestInterceptorHeaderValue; getHeaders: () => Record; - log: (logger?: Logger<'request'>) => void; + log: (logger?: Logger<'request'>) => Partial | null; } export type RequestInterceptor = (params: RequestInterceptorParams) => void | Promise; @@ -28,7 +28,7 @@ export interface ResponseInterceptorParams { getCookie: (name: string) => RequestInterceptorCookieValue; clearCookie: (name: string, options?: CookieOptions) => void; attachment: (filename: string) => void; - log: (logger?: Logger<'response'>) => void; + log: (logger?: Logger<'response'>) => Partial | null; } export type ResponseInterceptor = ( diff --git a/src/utils/types/logger.ts b/src/utils/types/logger.ts index 58b12567..f14f7057 100644 --- a/src/utils/types/logger.ts +++ b/src/utils/types/logger.ts @@ -4,7 +4,7 @@ import type { Cookies, Headers, Params, PlainObject, Query } from './values'; export type MappedEntityName = Exclude; -type LoggerValuesToFlags = { +type LoggerTokenValuesToTokenOptions = { [Key in keyof Type]?: Key extends MappedEntityName ? Record | boolean : boolean; }; @@ -60,35 +60,33 @@ export type LoggerTokenValues< : never : never; -type LoggerRestRequestTokenFlags = LoggerValuesToFlags; -type LoggerRestResponseTokenFlags = LoggerValuesToFlags; -type LoggerGraphQLRequestTokenFlags = LoggerValuesToFlags; -type LoggerGraphQLResponseTokenFlags = LoggerValuesToFlags; +type LoggerRestRequestTokenOptions = LoggerTokenValuesToTokenOptions; +type LoggerRestResponseTokenOptions = + LoggerTokenValuesToTokenOptions; +type LoggerGraphQLRequestTokenOptions = + LoggerTokenValuesToTokenOptions; +type LoggerGraphQLResponseTokenOptions = + LoggerTokenValuesToTokenOptions; -export type LoggerTokenFlags< +export type LoggerTokenOptions< Type extends LoggerType = LoggerType, API extends LoggerAPI = LoggerAPI > = Type extends 'request' ? API extends 'rest' - ? LoggerRestRequestTokenFlags + ? LoggerRestRequestTokenOptions : API extends 'graphql' - ? LoggerGraphQLRequestTokenFlags + ? LoggerGraphQLRequestTokenOptions : never : Type extends 'response' ? API extends 'rest' - ? LoggerRestResponseTokenFlags + ? LoggerRestResponseTokenOptions : API extends 'graphql' - ? LoggerGraphQLResponseTokenFlags + ? LoggerGraphQLResponseTokenOptions : never : never; export interface Logger { enabled?: boolean; - tokenFlags?: LoggerTokenFlags; + tokenOptions?: LoggerTokenOptions; rewrite?: (tokenValues: Partial>) => void; } - -export interface Loggers { - request?: Logger<'request', API>; - response?: Logger<'response', API>; -} diff --git a/src/utils/types/server.ts b/src/utils/types/server.ts index affebee3..46f20afe 100644 --- a/src/utils/types/server.ts +++ b/src/utils/types/server.ts @@ -3,7 +3,6 @@ import type { Arguments } from 'yargs'; import type { GraphQLRequestConfig } from './graphql'; import type { Interceptors } from './interceptors'; -import type { Loggers } from './logger'; import type { RestMethod, RestRequestConfig } from './rest'; type StaticPathObject = { prefix: `/${string}`; path: `/${string}` }; @@ -52,19 +51,16 @@ export interface MockServerConfig extends BaseMockServerConfig { rest?: RestConfig; graphql?: GraphqlConfig; database?: DatabaseConfig; - loggers?: Loggers; } export interface RestMockServerConfig extends BaseMockServerConfig { configs?: RestRequestConfig[]; database?: DatabaseConfig; - loggers?: Loggers<'rest'>; } export interface GraphQLMockServerConfig extends BaseMockServerConfig { configs?: GraphQLRequestConfig[]; database?: DatabaseConfig; - loggers?: Loggers<'graphql'>; } export interface DatabaseMockServerConfig extends BaseMockServerConfig {