I have an app that when the user logs in he received some data as a response, but if the response is not what I expect, the app crashes.
Let me show you the following:
I created an interface to tell Typescript the data I expect.
export interface AuthResponseData {
token: string;
expires: string;
role: number;
}
Then I created a method to send the login data to the server.
login(user: string, password: string){
return this.http.post<AuthResponseData>(
'https://localhost:44344/api/auth', { user: user, pwd: password }
).pipe(
catchError(this.handleError),
tap(resData => { this.handleAuthentication(resData.token, resData.expires, resData.role)})
);
}
I was expecting that if the response from the server didn't match the interface then the Angular would redirect to catchError
. But doesn't happen.
Now my question is: Is there any way to check if an API response is equal to the interface and throw an error if is not. Or what I'm asking isn't possible?
UPDATE:
After searching I discover that the interfaces disappear on run time, so there is no way to compare the api response with the interface (to the best of my knowledge). But I keep searching for a way to check the api response in a dynamic way. I think it is not really secure depending on api response to be always right. So I should check the API response. How can I do that?
Hope you can help me, thanks :)
I have an app that when the user logs in he received some data as a response, but if the response is not what I expect, the app crashes.
Let me show you the following:
I created an interface to tell Typescript the data I expect.
export interface AuthResponseData {
token: string;
expires: string;
role: number;
}
Then I created a method to send the login data to the server.
login(user: string, password: string){
return this.http.post<AuthResponseData>(
'https://localhost:44344/api/auth', { user: user, pwd: password }
).pipe(
catchError(this.handleError),
tap(resData => { this.handleAuthentication(resData.token, resData.expires, resData.role)})
);
}
I was expecting that if the response from the server didn't match the interface then the Angular would redirect to catchError
. But doesn't happen.
Now my question is: Is there any way to check if an API response is equal to the interface and throw an error if is not. Or what I'm asking isn't possible?
UPDATE:
After searching I discover that the interfaces disappear on run time, so there is no way to compare the api response with the interface (to the best of my knowledge). But I keep searching for a way to check the api response in a dynamic way. I think it is not really secure depending on api response to be always right. So I should check the API response. How can I do that?
Hope you can help me, thanks :)
Share Improve this question edited Sep 30, 2021 at 10:28 Guerric P 31.8k6 gold badges58 silver badges104 bronze badges asked Feb 12, 2020 at 14:49 Proz1gProz1g 1,2274 gold badges17 silver badges38 bronze badges 1- 1 Maybe this can help you out: stackoverflow.com/questions/14425568/… – Jacopo Sciampi Commented Feb 12, 2020 at 14:59
6 Answers
Reset to default 4 +100As you already noticed, an interface
is purely TypeScript, it doesn't exist in JavaScript and is not emulated in any way (just like private
, protected
, public
, and some other things). The type checking in TypeScript just allows to have a "fail fast" logic (you don't have to run the app to notice that your own code or a third party library is being misused, it fails at compilation time).
Your Angular app is just an UI, there are no critical operations involved (they should live in your backend), so I don't think it's a good idea to throw an error because of a type mismatch, instead of just trying to satisfy the user as much as possible. According to me a backend should validate a client input, and a client should try to adapt with a backend service.
That being said, there is a draft for JSON validation here https://json-schema.org/ and some libraries implement it (e.g: https://github.com/epoberezkin/ajv). This would allow you to get the desired behavior.
There are 3 questions in your issue plus one additional question by me (at the end) for a better understanding :
I'm asking this because if I have an endpoint that returns 100 attributes I'll have to check the 100 by hand, right?
This is the right way to write interface
s based on returned models from API responses. if one endpoint returns a model containing 100 properties, the right way is writing relevant Typescript interface
with the same properties perhaps by a tool like json2ts. This enables type-checking and prevents unwanted programming mistakes without any cost since interface
s do not exist in runtime.
Is there any way to check if an API response is equal to the interface and throw an error if is not. Or what I'm asking isn't possible?
You can check a key property value inside tap
to detect condition (null
).
I was expecting that if the response from the server didn't match the interface then the Angular would redirect to
catchError
. But doesn't happen.
interface
mismatch wont be considered as RxJS's catchError
. It fires when a request error occurs like 400 (bad request). Note that Type Checking means Once you declare a variable to be a certain type, it’s the compiler job to ensure that it is only ever assigned values of that type (or values that are sub-types of that type).
Is there a way to have dynamic model for all endpoints?
Some people employ bracket notation to extract the response values. Bracket notation and dot notation are functionally equivalent in JavaScript but are not the same thing in TypeScript. TypeScript is less strict with the bracket notation and this behavior is intentional to offer some kind of backdoor. I don't propose this way which makes the code dirty and fallible.
To check if an API response is valid, use a class instead of an interface and give it an isValid
function, and/or check the data in the constructor.
e.g.
interface IAuthResponseData {
token: string;
expires: string;
role: string;
}
class AuthResponseData implements IAuthResponseData {
public token: string;
public expires: string;
private __expires: Date;
public role: string;
public constructor(data: IAuthResponseData) {
this.token = data.token;
this.expires = data.expires;
this.__expires = new Date(data.expires);
this.role = data.role;
if (!this.isValid()) {
throw new Error("Invalid Parameters")
}
}
public isValid(): boolean {
// simple equals check against null is also true when the value is undefined
for (let member in this) {
if (this[member] == null) {
return false;
}
}
return true;
}
}
Remember to catch the error and act on it, or leave the check out of the constructor and instead check after you created the object and then act on it.
Use io-ts. It gives you static typing in the IDE and runtime validation from a single definition. This is tricky to get otherwise. You otherwise have to write concrete implementations and type definitions, plus the machinery to do the validation.
You can use promise and thenable() function to catch the error if your api response is not as per your requirement.
login(user: string, password: string){
return this.http.post('https://localhost:44344/api/auth', { user: user, pwd: password })
.pipe(map(response => {
this.checkForValidResponse(response)
.then( onResolve =>{
//Came here is your response is according to your requirements
})
.catch(onReject =>{
//Catch the error and treat it as error if your resposne is not according to your requirements
});
}));
}
checkForValidResponse(responseOfApi){
return new Promise((resolve, reject)=>{
//Your Code for To Check wether API response is Valid OR not .
//If YOur API reponse is valid then **
resolve(Your message to send back)
//IF your API resopnse is invalid
reject(Your message to send back)
})
}
Hope this is what you are looking for .
You can throw an error that would be catched by the catchError()
login(user: string, password: string){
return this.http.post<AuthResponseData>(
'https://localhost:44344/api/auth', { user: user, pwd: password }
).pipe(
map(res => { // Map should return an observable
if(!this.isValid()) // isValid() is a hole diffrent story - stucture checking in tyepscript -
throw new Error('Response structure is not valid.') // Would be catched by catchError
return res;
})
tap(resData => { // tap is for side effects
this.handleAuthentication(resData.token, resData.expires, resData.role)
})
catchError(this.handleError)
);
}
now for response structure you can check Interface type check with Typescript