7

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 :)

Guerric P
  • 30,447
  • 6
  • 48
  • 86
Proz1g
  • 1,177
  • 4
  • 16
  • 36

6 Answers6

4

As 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.

Guerric P
  • 30,447
  • 6
  • 48
  • 86
  • 1
    Thanks for the explanation. I think you are right, I can't verify everything that comes from the backend, instead I've to adapt like you said ;) – Proz1g Feb 21 '20 at 11:09
  • you should definitely verify everything that comes from the backend. this allows you to detect breaking api changes immediately. it is an absolute necessity. too bad it doesn't work that way. – aycanadal May 01 '20 at 09:49
3

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 interfaces 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 interfaces 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.

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
  • Wow thanks for the detailed answer :D. Just one thing: when I said `I'm asking this because if I have an endpoint that returns 100 attributes I'll have to check the 100 by hand, right?` I was not talking about the Interface because I know I have to do that manually. What I meant was that I had to write again all attributes and check the type for each one manually. What I really wanted was a way to compare the response with the (manually inserted) interface I created before. Anyway, I appreciate your answer, it helped me understand some basic things :) :) :) – Proz1g Feb 20 '20 at 14:59
  • You can specify possible responses at server-side otherwise check its key properties at client-side. In general, If a key property is enumerated, other keys should be enumerated too. unless you have invalid data returned or violation on server-side. It may solve your problem :) – Amirhossein Mehrvarzi Feb 20 '20 at 16:55
2

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.

CGundlach
  • 671
  • 4
  • 13
  • Hi, thanks for your answer. But in this case I have always to write down all the attributes by hand. There is no dynamic way to do this? I'm asking this because if I have and endpoint that returns 100 attributes I'll have to check the 100 by hand, right? – Proz1g Feb 19 '20 at 11:22
  • I've found a library that supposedly can validate this: https://www.npmjs.com/package/ts-interface-checker – CGundlach Feb 19 '20 at 17:12
  • 1
    You can use the webapp [quicktype.io](https://app.quicktype.io/) to quickly create TypeScript interfaces out of JSON data. They also have an option to include runtime validation, as I just noticed. – CGundlach Feb 19 '20 at 17:13
  • I've also modified the `isValid`-function in my example to iterate over all members in the class, although that would still require you to either assign the values from the interface manually in the constructor, or declare the variables with a default value of `undefined` and iterate over them like in the `isValid` function in the constructor, because otherwise they're not members of the object in the resulting JavaScript and won't be checked. – CGundlach Feb 19 '20 at 17:41
  • Thanks! I'll test that as soon as possible ;) – Proz1g Feb 19 '20 at 17:42
  • @Proz1g A `class` is unsuitable for declaring a *type* that represents an **HTTP** response because the *deserialized JSON values* that result from HTTP requests *will never be instances of a class*. An interface is perfect candidate for it. – Amirhossein Mehrvarzi Feb 20 '20 at 12:16
  • Ok thanks for the notice @AmirhosseinMehrvarzi. It would be great if when I specify the response type the Angular checked for me the response and throw me an error if it wasn't a match. But that don't exists and I can't check manually ( it will be anti-productive) so I'm screwed xD. – Proz1g Feb 20 '20 at 12:31
2

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.

Josh Wulf
  • 4,727
  • 2
  • 20
  • 34
2

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 .

Pushprajsinh Chudasama
  • 7,772
  • 4
  • 20
  • 43
  • Hi, thanks for your answer, but the issue here is the code to check the API response is valid or not, but I already know that there isn't a pre-build method that checks it for me, because the interface is purely Typescript and disappears on runtime. – Proz1g Feb 21 '20 at 11:12
  • yupp so therefore you can check it by yourself, and validate that your response is according to you or not. – Pushprajsinh Chudasama Feb 21 '20 at 14:22
0

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

Fahd Lihidheb
  • 671
  • 4
  • 20
  • Thanks for you answer :) Unfortunately the way to check the API response with an Interface isn't good for me. I wanted something dynamic but I think it is not possible. – Proz1g Feb 20 '20 at 12:21
  • what do you mean by dynamic ? something like `MyType boo = res` throws an error if res is not instance of `MyType` ? – Fahd Lihidheb Feb 20 '20 at 15:00
  • In that particular case I wanted to check if the `res` had the same attributes and the same type as the attributes in `AuthResponseData`. Something like (pseudo-code): `res == AuthResponseData` or `res instanceof AuthResponseData` – Proz1g Feb 20 '20 at 15:02
  • The closest thing you can do is to change `AuthResponseData` to a class and instantiate a new response from `res`. then `typedRes instanceof AuthResponseData` will always return true regardless of `original res`'s structure. which is useless i gess :p – Fahd Lihidheb Feb 20 '20 at 15:18
  • yeah :´/ . But well, I had to ask :D :D Thanks any way ;) – Proz1g Feb 20 '20 at 15:28