Typescript check propery for unknow type - node.js

I have an api request and for error statement. I want to return message from payload.
But message object can change depending on error. For example, payload object can be
{ message: 'Not Authorized', type: 'service.not_authorized' }
or
{
"errors": [
{
"category": "AUTHENTICATION_ERROR",
"code": "UNAUTHORIZED",
"detail": "Authorization code is expired.
}
]
}
I can't create interfaces for all possible payloads.
In JS, basically I can write this :
if(payload && payload.message){
return payload.message
}
if(payload && payload.errors){
const message = payload.errors.length>0 ? payload.errors[0].detail : 'Error'
return message;
}
Now I can try to do this with Typescript
import axios from "axios";
interface IApiError{
category:string,
code:string,
detail:string
}
const CustomError=<T>(e:T):string=>{
let message="";
if(axios.isAxiosError(e)){
const payload = e.response?.data; // payload:unknown
if(payload){
if(typeof payload === 'object'){
if(payload.hasOwnProperty('message')){
message=payload.message;
// Error : Property 'message' does not exist on type 'object'.
}
else if(payload.hasOwnProperty('errors')){
const errorsArray:IApiError[]=payload.errors;
message=errorsArray[0].detail;
// Error : Property 'errors' does not exist on type 'object'.
}
}
}
}
return message;
}
How can I solve this?
Edit
I found this topic. But according this we should create interfaces and typeguard for every possible payload option.

I guess, I found a solution. Based on this answer I created a function.
// Basically it takes 3 arguments
// o -> Object that we don't know what is look like
// prop -> The name of prop that we are looking for
// type -> A variable with the same type with our prop
// Because I didn't find to send type to method in typescript
// (for example string,number,boolean)
const getProp = (o: unknown, prop: string, type:any): any => {
// Checks whether the object contains the requested prop
const p = (o as any)[prop]
// If this field instance of requested type
if (typeof p === typeof type) {
return p;
}
return undefined;
}
Let's test this for different scenarios.
let obj:unknown={ message: 'Not Authorized', type: 'service.not_authorized' }
let obj2:unknown={
"errors": [
{
"category": "AUTHENTICATION_ERROR",
"code": "UNAUTHORIZED",
"detail": "Authorization code is expired."
}
]
}
// In first object I looking for message field and it should be string
let res=getProp(obj,"message","");
res ? console.log(res) : console.log("Not Found");
// console result -> Not Authorized
// In second object I'm intentionally sending the wrong type
// There is a field with name 'errors' but it's type is array
res=getProp(obj2,"errors","");
res && res.length>0 ? console.log(res) : console.log("Not Found");
// console result -> Not Found
// I send true types
res=getProp(obj2,"errors",[]);
res && res.length>0 ? console.log(res[0].detail) : console.log("Not Found");
// console result -> Authorization code is expired.

Related

How to dynamically assign a name and type to unknown object property in Typescript

I'm using Shopify's rest client for node and its request and response look something like this:
request
client.get({
path: 'orders/count.json',
query: { fulfillment_status: 'unfulfilled' }
})
If there's an error:
{
"errors": "[API] Invalid API key or access...",
"code": 2342,
"statusText": "Authentication Error",
"Headers": "..."
}
If there's no error:
{
"body": { "count": 8 },
"code": 2342,
"statusText": "Authentication Error",
"Headers": "..."
}
I'd like to add some boilerplate over this client library so that I can get the typings of the response. This is what I'm trying to do but it's not working too well:
const customClient = {
get: async <T, K extends string>(params: GetRequestParams) => {
const response = (await client.get(params));
if (response.body.errors) return { errors: response.body.errors };
// somehow index it. obviously not with the type declaration???
return { [K]: response.body[K] as T };
},
}
With the hopes that I can use it as.
const { count, error } = customClient.get<number, "count">({ ... });
Any help would be appreciated. I have an entire file of the Shopify API types that I would like to leverage. A solution to this would be perfect!
A possible implementation can look like this:
const customClient = {
get: async <T, K extends string>(params: GetRequestParams):
Promise<Partial<Record<K, T> & { errors: string }>> =>
{
const response = (await client.get(params));
if (response.body.errors) return { errors: response.body.errors } as any;
return {
[Object.keys(response)[0]]: response[Object.keys(response)[0]]
} as any
},
}
As you correctly noted, we can't use the TypeScript generic types when constructing the returning object. We need to use JavaScript features instead. In this case I just took the first key of the response and used it for the key of the returning object as well as to index the response object.
The return type of the function is a Promise consisting of both a Record with K and T as keys and values and the error type. I used Partial here since they are not both required.
Destructing the response leads to the correct types now:
async function main(){
const { count, errors } = await customClient.get<number, "count">({} as any);
// count: number | undefined
// errors: string | undefined
}
Playground

Azure Function disabled setting "user" field to HTTP request object on the 26th of April 2022

(!) Issue cannot be reproduced locally.
Azure Function Version ~4
Node Version 14.18.1
Creating a simple HTTP triggered Azure Function and setting just two simple properties we get the following code:
module.exports = async function (context, req) {
req.auth = {authField: 'some value'}
req.user = {userField: 'some value'}
context.log(`${JSON.stringify(req,null,2)}`);
context.res = {
body: 'responseMessage'
};
}
The logger prints the following object:
{
"method": "POST",
"url": "xxx",
"originalUrl": "xxx",
"headers": {
/// ...
},
"query": {},
"params": {},
"body": { "name": "Azure" },
"rawBody": "{\"name\":\"Azure\"}",
"auth": { "authField": "some value" }
}
As you see only auth is set and not user.
The same failing behavior can be seen in version 4.2.0.
When I test with Azure Function ~3 the output looks like this:
{
"method": "POST",
"url": "xxx",
"originalUrl": "xxx",
"headers": {
// ...
},
"query": {},
"params": {},
"body": { "name": "Azure" },
"rawBody": "{\"name\":\"Azure\"}",
"auth": { "authField": "some value" },
"user": { "userField": "some value" }
}
As you see the field is set.
The following custom v4 4.1.0-17156 also sets the user field.
The field user was used by us through the express-jwt (v6.1.0) which is using it when no value is provided for requestProperty.
I could not yet reproduce it but in out transpiled from Typescript project, we get the following runtime error:
FailureException: Cannot set property user of [object Object] which has only a getterStack: TypeError: Cannot set property user of [object Object] which has only a getterat Object.run
The issue started at the beginning of the day of 26th of April 2022.
Questions:
what is the reason?
is a quick roll back to the functioning Azure Function custom version possible?
I found the culprit in this PR, which was added for Azure Function runtime v4.2.0.
The user field was added with only a getter.
We took the code and made the minimal example:
class Request {
#cachedUser?: string | null;
constructor() {
}
get user(): string | null {
if (this.#cachedUser === undefined) {
this.#cachedUser = "some value";
}
return this.#cachedUser;
}
}
and got the following transpilied version:
var __classPrivateFieldGet =
(this && this.__classPrivateFieldGet) ||
function (receiver, state, kind, f) {
if (kind === 'a' && !f) throw new TypeError('Private accessor was defined without a getter')
if (typeof state === 'function' ? receiver !== state || !f : !state.has(receiver))
throw new TypeError('Cannot read private member from an object whose class did not declare it')
return kind === 'm' ? f : kind === 'a' ? f.call(receiver) : f ? f.value : state.get(receiver)
}
var __classPrivateFieldSet =
(this && this.__classPrivateFieldSet) ||
function (receiver, state, value, kind, f) {
if (kind === 'm') throw new TypeError('Private method is not writable')
if (kind === 'a' && !f) throw new TypeError('Private accessor was defined without a setter')
if (typeof state === 'function' ? receiver !== state || !f : !state.has(receiver))
throw new TypeError('Cannot write private member to an object whose class did not declare it')
return kind === 'a' ? f.call(receiver, value) : f ? (f.value = value) : state.set(receiver, value), value
}
var _Request_cachedUser
class Request {
constructor() {
_Request_cachedUser.set(this, void 0)
}
get user() {
if (__classPrivateFieldGet(this, _Request_cachedUser, 'f') === undefined) {
__classPrivateFieldSet(this, _Request_cachedUser, 'some value', 'f')
}
return __classPrivateFieldGet(this, _Request_cachedUser, 'f')
}
}
_Request_cachedUser = new WeakMap()
// this section is added for testing
const request = new Request()
request.user = 'new value'
console.log(JSON.stringify(request.user))
So, it always returns the the initial value, which in our example is "some value" but in the original code is simply undefined and does not allow to set it.

NODE.JS(TYPESCRIPT) Property 'req' does not exist on type 'NodeModule'

I am having issues using this.status and this.req in my subfile
I initialize the route index like this
router.use(response);
my index file is below
import {Request,Response,NextFunction} from 'express'
module.exports = function(req:Request, res:Response, next:NextFunction){
const responseTypes = {
unprocessable: require('./unprocessable')
};
res = {...res, ...responseTypes};
next();
};
here is my unprocessable.ts file
import log from '../logger'
import queue from '../queue'
module.exports = function (data, message) {
log.warn('Sending unprocessable entity response: ', data, message || 'unprocessable entity');
const req = this.req;
const res = this;
// Dump it in the queue
const response = { response: { status: 'error', data: data, message: message ? message : 'unprocessable entity' } };
response.requestId = req.requestId;
queue.add('logResponse', response);
if (data !== undefined && data !== null) {
if (Object.keys(data).length === 0 && JSON.stringify(data) === JSON.stringify({})) {
data = data.toString();
}
}
if (data) {
this.status(422).json({ status: 'error', data: data, message: message ? message : 'unprocessable entity' });
} else {
this.status(422).json({ status: 'error', message: message ? message : 'unprocessable entity' });
}
};
It complains about the following in the unprocessable.ts file
Property 'status' does not exist on type 'NodeModule' if I use this.status
Property 'req' does not exist on type 'NodeModule' if I use this.req
I have no idea how to solve it as I am new to typescript
Typescript does for the most part not know what you refer to when you are using the this keyword.
You can however tell typescript what you mean by this, e.g:
function someFunction(this: object) {
// do something with this
}
In your case, this refers to an object that extends Response from express so what you could do is:
const { Response } = require('express');
interface IModifiedResponse extends Response {
// define your properties here
unprocessable: (data: object, message: string) => void
}
function unprocessable(this: IModifiedResponse, data: object, message: string) {
// use this as in your function
}
However I do not know what this.req refers to as Response does not have a req property. See ExpressJS docs
Hope this answers helps :).

can't use a member function inside fetch call

I get a response from a fetch call and i'm trying to check if the respond correspond to a kind of error my server is likely to respond me if the username or the email sent already exist in my database. If this is the case, i try to use a member function of my class to show a error message to the user. But when i try to use my member function i get the error:
Unhandled Rejection (TypeError): Cannot read property 'showValidationErr' of undefined.
I think that i can't use a member function in that case, how can i call my member function when i catch a error from my server ?
class RegisterBox extends React.Component {
constructor(props) {
super(props);
this.state = {username: "",
email: "",
password: "",
confirmPassword: "",
errors: [],
pwdState: null};
}
showValidationErr(elm, msg) {
this.setState((prevState) => ( {errors: [...prevState.errors, {elm, msg} ] } ) );
}
submitRegister(e) {
fetch("http://localhost:8080/register?user=" + this.state.username + "&psw=" + this.state.password + "&email=" + this.state.email)
.then(function (respond) {
return respond.text()
.then(function(text) {
if (text === "RegisterError : username") {
this.showValidationErr("username", "username allready taken.");
}
if (text === "RegisterError : email") {
this.showValidationErr("email", "email allready used.");
}
})
});
}
}
I found how to correct it. i needed to add the line "var that = this;" at the beginning of my function and use "that.showValidationErr();" instead.
In this context, this refers to the anonymous callback function() that you're passing to the then(). Try using the arrow function syntax instead which keeps the this as the surrounding context.
.then((respond) => {
return respond.text()
.then((text) => {
if (text === "RegisterError : username") {
this.showValidationErr("username", "username allready taken.");
}
if (text === "RegisterError : email") {
this.showValidationErr("email", "email allready used.");
}
})

How to fix TypeScript error Property 'isBoom' does not exist on type 'Boom<any> | ResponseObject'

The following source code returns TypeScript errors:
this.hapi.ext({
type: 'onPreResponse',
method: async (request, handler) => {
if (request.response.isBoom && request.response.message !== 'Invalid request payload input') {
if (request.response.isServer) {
logger.captureException(request.response, null, {
digest: this.requestDigest(request)
});
} else {
logger.captureMessage(request.response.message, 'info', null, {
digest: this.requestDigest(request)
});
}
}
return handler.continue;
}
});
Property 'isBoom' does not exist on type 'Boom | ResponseObject'.
Property 'isBoom' does not exist on type 'ResponseObject'.
Property 'isServer' does not exist on type 'Boom |
ResponseObject'. Property 'isServer' does not exist on type
'ResponseObject'.
Argument of type 'string | ((httpMessage: string) => ResponseObject)'
is not assignable to parameter of type 'string'. Type '(httpMessage:
string) => ResponseObject' is not assignable to type 'string'.
How can I fix them? Is there a problem with #types/hapi?
Since the response is a union ResponseObject | Boom | null we can only access common members of a union unless we use a type-guard.
There are several types of type-guards and you ca read more about the here .Below I use an in type guard to discriminated based on the existence of the property
import { Server } from 'hapi';
const hapi = new Server({})
hapi.ext({
type: 'onPreResponse',
method: async (request, handler) => {
if (request.response && 'isBoom' in request.response && request.response.message !== 'Invalid request payload input') {
if (request.response.isServer) {
request.response.message // string
}
return handler.continue;
}
}
});
Since the type Boom is a class an instanceof typeguard should also work:
hapi.ext({
type: 'onPreResponse',
method: async (request, handler) => {
if (request.response && request.response instanceof Boom && request.response.message !== 'Invalid request payload input') {
if (request.response.isServer) {
request.response.message // string
}
return handler.continue;
}
}
});
Note in both cases I added a check for request.response to exclude null from the union. This is only required by the compiler if strictNullChecks are enabled, but might be a good idea anyway.

Resources