NestJS access response object in pipes - node.js

I am using pipes for request validation. If the request fails, I want to redirect to a page but don't want to throw error. The question is, how can I access the response object in validation?
This is my validation pipe.
#Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
// in here i need to response with res.redirect('') function
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
Instead of throwing exception i need to access res.redirect() function

The response object is not available from within the context of a pipe. What you could do is A) use an interceptor instead or B) throw an exception and use a filter to catch this specific exception and redirect to the correct location.
Pipes are only used for validation or object transformation and as such immediately return successes (with the possibly transformed object) or throw errors about why the transformation/validation failed.

Related

NESTJS Get the execution context within the exception filter

I store user identification data in the execution context with my authentication guard. I would like to retrieve this context in the exception filter so I can set sentry metadata from user identification data. Exception filter however receives ArgumentsHost from the constructor, not the execution context where user identification data are stored. Can I somehow retrieve the execution context within the exception filter?
Have you tried it like it is specified in the docs?
#Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp()
let request = ctx.getRequest<Request>()
/* ... */
}
}
So I found that identification data are in the execution context only for a short time being and vanish when accessed with paramDecorator. So I store my identification data (ReqUser) in a request object specific to each context type. Now I access those contexts with help of the Helper class from guards, interceptors, and exception filters.
export class Helpers {
public static getReqUserDestinationFromHost(host: ArgumentsHost): Record<string, unknown> {
switch (host.getType<ContextType | 'graphql'>()) {
case RequestType.HTTP:
return host.switchToHttp().getRequest();
case RequestType.GRAPHQL:
return GqlArgumentsHost.create(host).getArgByIndex(2);
case RequestType.WEB_SOCKET:
throw new NotImplementedException('WEB_SOCKET ReqUser destination is not reachable');
case RequestType.RPC:
throw new NotImplementedException('RPC ReqUser destination is not reachable');
}
throw new NotImplementedException('Unable to identify ReqUser destination');
}
public static getReqUserDestinationFromContext(
context: ExecutionContext
): Record<string, unknown> {
switch (context.getType<ContextType | 'graphql'>()) {
case RequestType.HTTP:
return context.switchToHttp().getRequest();
case RequestType.GRAPHQL:
return context.getArgByIndex(2);
case RequestType.WEB_SOCKET:
throw new NotImplementedException('WEB_SOCKET ReqUser destination is not reachable');
case RequestType.RPC:
throw new NotImplementedException('RPC ReqUser destination is not reachable');
}
throw new NotImplementedException('Unable to identify ReqUser destination');
}
}

target.prototype is an empty object on class decorator

I am trying to implement decorators with express (i dont want to download any package to do this). I wrote a Get decorator and a Controller decorator, following a guide. But the target in the controller decorator is an empty object.
Controller Decorator
export function Controller(prefix: string) {
return function (target: Function) {
for (let key in target.prototype) {
const routeHandler = target.prototype[key];
const path = Reflect.getMetadata("path", target.prototype, key);
const method = Reflect.getMetadata("method", target.prototype, key);
const middlewares = Reflect.getMetadata(
"middlewares",
target.prototype,
key
);
if (path) {
console.log("has path"); //never logs out
router[method](`${prefix}${path}`, ...middlewares, routeHandler);
}
}
};
}
Get Decorator
export function Get(path: string) {
return function (target: any, key: string, desc: PropertyDescriptor) {
Reflect.defineMetadata("path", path, target, key);
Reflect.defineMetadata("method", "get", target, key);
};
}
The methods/properties created through the class syntax are non-enumerable. Thus, you would need to use Object.getOwnPropertyNames()
which returns an array of all properties (including non-enumerable properties)
Then in your case, you might want to convert your existing for loop into;
for(let key of Object.getOwnPropertyNames(target.prototype))
since now Object.getOwnPropertyNames return array.

Use global nest module in decorator

I have a global logger module in nest, that logs to a cloud logging service. I am trying to create a class method decorator that adds logging functionality. But I am struggling how to inject the service of a global nest module inside a decorator, since all dependency injection mechanisms I found in the docs depend are class or class property based injection.
export function logDecorator() {
// I would like to inject a LoggerService that is a provider of a global logger module
let logger = ???
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = function(...args: any[]) {
logger.log(`${propertyKey} method called with args.`);
//attach original method implementation
const result = originalMethod.apply(this, args);
//log result of method
logger.log(`${propertyKey} method return value`);
};
};
}
UPDATE: Per reqest a simple example
Basic example would be to log calls to a service method using my custom logger (which in my case logs to a cloud service):
class MyService {
#logDecorator()
someMethod(name: string) {
// calls to this method as well as method return values would be logged to CloudWatch
return `Hello ${name}`
}
}
Another extended use case would be to catch some errors, then log them. I have a lot of this kind of logic that get reused across all my services.
Okay, found a solution. In case anyone else stumbles upon this. First please keep in mind how decorators work – they are class constructor based, not instance based.
In my case I wanted to have my logger service injected in the class instance. So the solution is to tell Nest in the decorator to inject the LoggerService into the instance of the class that contains the decorated method.
import { Inject } from '#nestjs/common';
import { LoggerService } from '../../logger/logger.service';
export function logErrorDecorator(bubble = true) {
const injectLogger = Inject(LoggerService);
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
injectLogger(target, 'logger'); // this is the same as using constructor(private readonly logger: LoggerService) in a class
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = async function(...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
const logger: LoggerService = this.logger;
logger.setContext(target.constructor.name);
logger.error(error.message, error.stack);
// rethrow error, so it can bubble up
if (bubble) {
throw error;
}
}
};
};
}
This gives the possibility to catch errors in a method, log them within the service context, and either re-throw them (so your controllers can handle user resp) or not. In my case I also had to implement some transaction-related logic here.
export class FoobarService implements OnModuleInit {
onModuleInit() {
this.test();
}
#logErrorDecorator()
test() {
throw new Error('Oh my');
}
}

Typescript object property incorrect type

I'm trying to write a simple Discord bot in TypeScript, using discord.js and clime.
I'm running into an issue where I'm trying to access an object property of a context object that I pass around, but it's always null. When I check the properties using either vscode's debugger or console.log, the object seems to have all of the properties that I would expect, except they're all nested one layer too deep.
export class DiscordCommandContext extends Context {
public message:Message;
public client:Client;
constructor (options:ContextOptions, message:Message, client:Client) {
super(options);
this.message = message;
this.client = client;
}
}
When I try accessing it the message property, it's always falsy (if block is skipped over).
if (context.message.guild) {
var settings = await repo.getRealmSettings(+context.message.guild.id);
if (key) {
embed.fields.push({name:key,value:settings[key]});
} else {
Object.keys(settings).forEach(property => {
embed.fields.push({name:property,value:settings[property]});
});
}
}
But in the console, I see this:
DiscordCommandContext appears to have nested "message" objects, one of the wrong type
I cannot access context.message.message, I get "Property 'message' does not exist on type 'Message'", which is as I would expect.
EDIT 1
My instantiation code looked like this:
var options:ContextOptions = {
commands: argArr,
cwd: ""
};
var context = new DiscordCommandContext(options, this.message, this.client );
Where argArr is a split string passed into the method and both this.message and this.client are populated in the constructor of the calling class (none are null)
I managed to get DiscordCommandContext to function properly by changing it to this:
export class DiscordCommandContext extends Context {
public message:Message;
public client:Client;
public realmSettings: RealmSettings;
constructor (options:ContextOptions, contextExtension:DiscordCommandContextValues) {
super(options);
this.message = contextExtension.message;
this.client = contextExtension.client;
this.realmSettings = contextExtension.realmSettings
}
}
export interface DiscordCommandContextValues {
message:Message;
client:Client;
realmSettings: RealmSettings;
}
And calling it like this:
var context = new DiscordCommandContext(options, {message:this.message, client:this.client, realmSettings: settings} );
I'm not sure if that's the right way or not... but it works.

HTTP Get Json and Cast To Dart Map

I've started to learn Dart, but I stuck.
I follow this tutorial but something goes wrong.
Here is the problem: I would like to reach out to an API and fetch data from it. Nice!
I've imported packages for doing requests and converting. API returns the data correctly. HTTP GET is working.
The troubles came when I tried to assign json.decode(response.body) to Map().
It always says: The argument type dynamic cannot be assigned to the parameter type Map<String, dynamic>.
Could someone explain why that it's happening and how to handle it?
I'm using Android Studio. I'm invoking the function in the StatefulWidget:
var trends = fetchData('getAll', params);
where params is a Map().
The code:
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<ApiResponse> fetchData(String command, Map params) async {
final String url =
'https://example.com/api/v2/....';
final response = await http.get(url);
if (response.statusCode == 200) {
return ApiResponse.fromJson(json.decode(response.body));
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
}
class ApiResponse{
final String result;
final String error;
final String error_number;
final String response_code;
PopnableResponse(
{this.result, this.error, this.error_number, this.response_code});
factory ApiResponse.fromJson(Map<String, dynamic> json) {
return ApiResponse(
result: json['result'] as String,
error: json['error'] as String,
error_number: json['error_number'] as String,
response_code: json['response_code'] as String,
);
}
}
JSON Example:
{
"error":"",
"error_number":"",
"response_code":200,
"result":[
{
"id":1,
"name":"Great Deal",
"day_aired":"2015-07-05 11:06:09",
"trend":"Noone"
},
{
"id":2,
....
}
]
}
Try to cast it
json.decode(response.body) as Map<String, dynamic>

Resources