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

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.

Related

Typescript check propery for unknow type

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.

Why is the `message` for my Jest custom matcher not being displayed?

I've created a Jest custom matcher. It works (meaning, it passes/fails when it should), but I don't see the message anywhere in Jest's output.
What am I doing wrong? Do I have to do something to "enable" messages? Am I totally misunderstanding where the message is supposed to show up?
Environment: NestJS, Prisma
Execution command: jest --watch
Simplified code:
declare global {
namespace jest {
interface Matchers<R> {
toMatchHash(received: string, expected: string): R;
}
}
}
expect.extend({
toMatchJsonHash(received, expected) {
return {
pass: false,
message: () => `Why doesn't this work?!`,
};
},
});
expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
Output:
● MyService › should pass a test using a custom matcher
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: toMatchJsonHash<db0110285c148c77943f996a17cbaf27>
Received: {<Big ol' object redacted for conciseness>}
Number of calls: 1
178 |
179 | // #ts-ignore
> 180 | expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
| ^
181 | // expect(prisma.name.findMany).toHaveBeenCalledWith({
182 | // select: { type: true, name: true },
183 | // where: {
at Object.<anonymous> (my/my.service.spec.ts:180:32)
I'm expecting to see "Why doesn't this work?!" somewhere in the output, but I don't. What am I missing?
As suggested by #jonsharpe, the reason was that Jest was showing the message from the "outer" matcher, .toHaveBeenCalledWith().
To fix this, I found the source that defines the .toHaveBeenCalledWith() matcher and "merged" its code into my custom matcher.
This enabled my custom matcher to effectively "extend" the functionality of the .toHaveBeenCalledWith() matcher, including my own custom code and messages.
In case it helps someone, the code I ended up with for my specific use case was:
declare global {
namespace jest {
interface Matchers<R> {
toHaveBeenCalledWithObjectMatchingHash(expected: string): CustomMatcherResult;
}
}
}
expect.extend({toHaveBeenCalledWithObjectMatchingHash(received, expected) {
const isSpy = (received: any) =>
received != null &&
received.calls != null &&
typeof received.calls.all === 'function' &&
typeof received.calls.count === 'function';
const receivedIsSpy = isSpy(received);
const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
const calls = receivedIsSpy
? received.calls.all().map((x: any) => x.args)
: received.mock.calls;
if(calls.length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was not called.`,
};
}
if(calls[0].length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was called, but not with any arguments.`,
};
}
const md5Hash = crypto.createHash('md5');
const receivedHash = md5Hash.update(JSON.stringify(calls[0][0])).digest('hex');
const pass = receivedHash === expected;
if(pass) {
return {
pass: true,
message: () => `expected the function to not be called with an object that hashes to '${expected}'. Instead, the passed object hashes to the same value.`,
};
} else {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the passed object hashes to '${receivedHash}'.`,
};
}
}});

How can I convert from a firebase document to a custom class in Node JS

In node.js I'm getting the error below. Any ideas why?
Conversion of type 'Promise' to type 'Member[]' may be a mistake
because neither type sufficiently overlaps with the other. If this was
intentional, convert the expression to 'unknown' first. Type
'Promise' is missing the following properties from type
'Member[]': length, pop, push, concat, and 26 more.
export async function getFamilyMembers(tenantId: string, familyCode: string): Promise<Member[]> {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
}) as Member[];
}
EDIT:
If I remove the types and change it to
export async function getFamilyMembers(tenantId: string, familyCode: string) {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
});
}
I just have to deal with the problem later.
I get the error
Property 'length' does not exist on type 'void'.
const familyMembers: Member[] | void = await getFamilyMembers(tenantId, familyCode);
if (familyMembers === null) {
isVerified = false;
verificationFailMessage = `Sorry we can't find this code. Please check it is correct.`;
} else if (familyMembers.length === 0) {
I needed to add Promise in front of Member.
export async function getFamilyMembers(tenantId: string, familyCode: string): Promise<Member[]> {
return db.collection(`tenants/${tenantId}/members`)
.where('familyCode', '==', familyCode)
.get()
.then(snaps => {
snaps.docs.forEach(doc => {
return { id: doc.id, ...doc.data()}
});
}) as Promise<Member[]>;
}

This expression is not callable. Type 'string' has no call signatures

I'm trying to create a template file from an object where the key can be either a string or a function that returns a string:
export const createDynamicTemplate = (
templateParams: CreateDynamicTemplateParams
) => {
const { template, projectPath = '', param = '' } = templateParams
const updatedTemplateArr = Object.keys(template).map((key: string) => {
return {
[key]: {
filePath: `${projectPath}/${key}`,
template: typeof template[key] === 'function' ?
template[key](param) : template[key],
},
}
})
const updatedTemplate = Object.assign({}, ...updatedTemplateArr)
return updatedTemplate
}
My interfaces are:
export interface TemplateObject {
[key: string]: string
}
export interface FunctionalTemplateObject {
[key: string]: (param: string) => void
}
export interface CreateDynamicTemplateParams {
template: FunctionalTemplateObject | TemplateObject
projectPath: string
param: string
}
It keeps throwing this error in createDynamicTemplate though:
This expression is not callable.
Not all constituents of type 'string | ((param: string) => void)' are callable.
Type 'string' has no call signatures.
What am I doing wrong here?
Checking the type of a child property of a variable which references an object will not narrow the type of the variable-object. You can save the value at the key in a separate variable first, then check that value to narrow its type:
const updatedTemplateArr = Object.keys(template).map((key: string) => {
const item = template[key];
if (typeof item === 'function') {
return {
[key]: {
filePath: `${projectPath}/${key}`,
template: item(param),
},
}
}
})
Or, even better, use Object.entries to get the key and value at once. (Also note that there's no need to note the type of the .map parameters - TS can infer it automatically just fine)
const updatedTemplateArr = Object.entries(template).map(([key, value]) => {
if (typeof value === 'function') {
return {
[key]: {
filePath: `${projectPath}/${key}`,
template: value(param),
},
}
}
})

GraphQL Resolver returns error "Cannot read forEach of Undefined"

I have a graphql endpoint that I'm running a query against, and I'm building my resolver that is massaging the data before returning to the client. My query is this:
query getTransactions($transID: String!, $confidence: Float) {
transactions(parentID: $transID, confidence: $confidence) {
id
childrens {
id
name
email
phone
age
connectionInfo {
type
confidence
}
}
name
email
phone
age
}
}
and my resolver is currently looking like this:
const getTransactions = (args: any): any => {
const { parentID, confidence } = args;
const trxs = transactions.filter(t => {
return t.id === parentID;
});
let finalChildrens: any[] = [];
trxs.forEach(t => {
finalChildrens.concat(filteredChildren(t));
});
trxs.concat(finalChildrens);
return trxs;
};
const filteredChildren = (t: any): any[] => {
log.debug({ typeCheck: typeof t.childrens, children: t.childrens });
let outputChildren: any[] = [];
if (typeof t.childrens !== undefined) {
t.childrens.forEach((c1: any) => {
if (typeof c1.childrens !== undefined) {
outputChildren.concat(filteredChildren(c1));
outputChildren.push(c1);
} else {
outputChildren.push(c1);
}
});
return outputChildren;
} else {
return ['no child'] as any[];
}
};
The issue I'm facing is that I'm continually getting this error either in the client or graphiql is this:
"Cannot read property 'forEach' of undefined"
I want to say that it has to do with either the forEach in filteredChildren or inside the resolver itself. I'm going through these "gymnastics" in order to get a flat array that is retrieved recursively from the underlying data. How would someone check the array to see if it's filled or not? (or in this case, if the array exists at all?)
The condition typeof t.childrens !== undefined is always true. You should either use typeof t.childrens !== "undefined" or t.childrens !== undefined.

Resources