Picsur/backend/src/layers/success/success.interceptor.ts

100 lines
2.5 KiB
TypeScript
Raw Normal View History

2022-02-24 20:32:31 +00:00
import {
2022-03-28 10:50:39 +00:00
CallHandler,
ExecutionContext,
2022-08-26 18:40:16 +00:00
Injectable,
Logger,
2022-04-04 08:36:59 +00:00
NestInterceptor,
2022-09-06 14:32:16 +00:00
Optional,
2022-02-24 20:32:31 +00:00
} from '@nestjs/common';
2022-04-04 08:36:59 +00:00
import { Reflector } from '@nestjs/core';
2022-08-26 18:40:16 +00:00
import { FastifyReply } from 'fastify';
2022-04-05 18:37:25 +00:00
import { ApiAnySuccessResponse } from 'picsur-shared/dist/dto/api/api.dto';
2022-07-19 12:54:02 +00:00
import { Fail, FT } from 'picsur-shared/dist/types';
2022-04-04 08:36:59 +00:00
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
2022-03-06 11:34:33 +00:00
import { map, Observable } from 'rxjs';
2022-02-24 20:32:31 +00:00
2022-03-28 10:50:39 +00:00
// This interceptor will neatly wrap any json response made within nest
2022-04-04 08:36:59 +00:00
export interface ZodValidationInterceptorOptions {
strict?: boolean;
}
2022-02-24 20:32:31 +00:00
@Injectable()
export class SuccessInterceptor<T> implements NestInterceptor {
2022-04-04 08:36:59 +00:00
private readonly logger = new Logger();
// TODO: make work
private strict: boolean;
constructor(
2022-06-27 15:37:37 +00:00
private readonly reflector: Reflector,
2022-04-04 08:36:59 +00:00
@Optional() options?: ZodValidationInterceptorOptions,
) {
this.strict = options?.strict ?? true;
}
2022-04-05 18:37:25 +00:00
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
2022-02-24 20:32:31 +00:00
return next.handle().pipe(
map((data) => {
if (data instanceof Buffer) {
return data;
} else if (typeof data === 'object') {
2022-04-04 08:36:59 +00:00
const validated = this.validate(context, data);
2022-02-25 11:22:00 +00:00
2022-04-04 08:36:59 +00:00
return this.createResponse(context, validated);
2022-02-24 20:32:31 +00:00
} else {
return data;
}
}),
);
}
2022-04-04 08:36:59 +00:00
2022-04-05 18:37:25 +00:00
private validate(context: ExecutionContext, data: unknown): unknown {
2022-04-04 08:36:59 +00:00
const schemaStatic = this.reflector.get<ZodDtoStatic>(
'returns',
context.getHandler(),
);
if (!schemaStatic) {
2022-07-19 12:54:02 +00:00
throw Fail(
FT.Internal,
"Couldn't find schema",
2022-04-04 08:36:59 +00:00
`No zodSchema found on handler ${context.getHandler().name}`,
);
}
let schema = schemaStatic.zodSchema;
const parseResult = schema.safeParse(data);
if (!parseResult.success) {
2022-07-19 12:54:02 +00:00
throw Fail(
FT.Internal,
'Server produced invalid response',
2022-06-05 10:20:16 +00:00
`Function ${context.getHandler().name} failed validation: ${
parseResult.error
}`,
2022-04-04 08:36:59 +00:00
);
}
return parseResult.data;
}
2022-06-05 10:20:16 +00:00
private createResponse(
context: ExecutionContext,
data: unknown,
): ApiAnySuccessResponse {
2022-08-26 18:40:16 +00:00
const response = context.switchToHttp().getResponse<FastifyReply>();
const newResponse: ApiAnySuccessResponse = {
2022-04-05 18:37:25 +00:00
success: true as true, // really typescript
2022-08-26 18:40:16 +00:00
statusCode: response.statusCode,
2022-04-04 08:36:59 +00:00
timestamp: new Date().toISOString(),
2022-08-26 18:40:16 +00:00
timeMs: Math.round(response.getResponseTime()),
2022-04-04 08:36:59 +00:00
data,
};
2022-08-26 18:40:16 +00:00
return newResponse;
2022-04-04 08:36:59 +00:00
}
2022-02-24 20:32:31 +00:00
}