axiosのエラーかを判別する(axios.isAxiosError)

axiosでREST APIにアクセスするコードを含む複数のコードをtry-catchで囲んでいるときに、axiosのエラーをログなどに出力する際、JSON.stringifyしていたのですが、axios以外が原因でエラーになっている場合、JSON.stringifyでは文字列を得られなかったので対処方法を検討していました。

axios.isAxiosError

最初objectかどうかで判定しようとしていましたがうまく判定できませんでした。axiosではaxiosのエラーかどうかを判別するためのaxios.isAxiosErrorが提供されているため、これを使う方が良さそうです。

async function test() {
  try {
    const response = axios.get("http://httpstat.us/200");
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.log(JSON.stringify(error, null, 2));
    }
  }

動作確認

この動作を確認するために以下のようなプログラムで確認しました。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js"></script>
    <script>
      async function test() {
        try {
          const response = await axios.get("http://httpstat.us/200");
          const a = [];
          console.log(a[1].b);
        } catch (error) {
          if (axios.isAxiosError(error)) {
            console.log("Axios Error:", JSON.stringify(error, null, 2));
          } else {
            console.log("Error:", String(error));
          }
        }
      }

      test();
    </script>
  </body>
</html>

このプログラムでは、httpstat.usという指定したHTTPステータスのレスポンスを返してくれるサービスで任意のHTTPステータスを返すようにしています。HTTPステータスが200の場合は例外が発生しないため、後続の処理でundefinedなオブジェクトのプロパティにアクセスしているため例外が発生します。

上記のHTMLにブラウザからアクセスすると、コンソールログに

Error: TypeError: Cannot read properties of undefined (reading 'b')

と表示されます。

一方、

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js"></script>
    <script>
      async function test() {
        try {
          const response = await axios.get("http://httpstat.us/404");
          const a = [];
          console.log(a[1].b);
        } catch (error) {
          if (axios.isAxiosError(error)) {
            console.log("Axios Error:", JSON.stringify(error, null, 2));
          } else {
            console.log("Error:", String(error));
          }
        }
      }

      test();
    </script>
  </body>
</html>

のようにURLの200部分を404に変えてアクセスすると、

Axios Error: {
  "message": "Request failed with status code 404",
  "name": "AxiosError",
  "stack": "AxiosError: Request failed with status code 404\n    at Qe (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:31804)\n    at XMLHttpRequest.y (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:36641)\n    at e.<anonymous> (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:48621)\n    at p (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:3448)\n    at Generator.<anonymous> (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:4779)\n    at Generator.throw (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:3858)\n    at p (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:9996)\n    at u (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:10235)",
  "config": {
    "transitional": {
      "silentJSONParsing": true,
      "forcedJSONParsing": true,
      "clarifyTimeoutError": false
    },
    "adapter": [
      "xhr",
      "http",
      "fetch"
    ],
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "timeout": 0,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1,
    "env": {},
    "headers": {
      "Accept": "application/json, text/plain, */*"
    },
    "method": "get",
    "url": "http://httpstat.us/404"
  },
  "code": "ERR_BAD_REQUEST",
  "status": 404
}

と表示され、axios.isAxiosErroraxiosのエラーかどうかを判別できていることが確認できます。

まとめ

例外が発生したときにエラーページに遷移するという動作をするアプリケーションがあり、コンソールにログを出力しても記録に残らないため、サーバーにログが送るという対処を行っていました。実際に試してみると、axiosのエラーだと、エラーの内容がサーバーのログで確認できましたが、それ以外のエラーの場合は{}になって何も確認できないという事象があり、これを確認できるようにするために、このような検証をしてみました。

try-catchで例外をcatchしていることまでははっきりしていても、axiosのエラーかそれ以外のエラーかを判定する場合は型ではなく、axios.isAxiosErrorで判定するのが確実だということが確認できました。

[Spring Boot]IntelliJ IDEAでLombokを使っていてエラーになる場合の対処方法

以下の記事に解決方法が載っていました。

https://stackoverflow.com/questions/72583645/compile-error-with-lombok-in-intellij-only-when-running-build

遭遇した事象

Spring Initializrからダウンロードした時点でLombokは依存関係に追加されています。

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.4'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.t0k0sh1'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.projectlombok:lombok:1.18.22'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

IntelliJ IDEAでLombokを使用可能にするためには、設定のビルド、実行、デプロイ>コンパイラー>アノテーションプロセッサーで、「アノテーション処理を有効にする」にチェックをつけ、「プロジェクトクラスパスからプロセッサーを取得する」にチェックがついている状態にします。

ここまでがよく知られているLombokの導入方法になります。

lombok.Dataアノテーションを定義したクラスを作成し、

package com.t0k0sh1.tutorial.entity;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String name;
    private String email;
    private String password;
}

自動生成されているであろうgetterを使ってみます。

package com.t0k0sh1.tutorial.controller;

import com.t0k0sh1.tutorial.entity.User;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
public class UserController {
    private final List<User> users = new ArrayList<>();

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return users.stream().filter(a -> a.getId().equals(id)).findFirst().orElse(null);
    }

}

すると、getterが見つからずにコンパイルエラーとなります。

/Users/t0k0sh1/Workspace/tutorial/src/main/java/com/t0k0sh1/tutorial/controller/UserController.java:15: エラー: シンボルを見つけられません
        return users.stream().filter(a -> a.getId().equals(id)).findFirst().orElse(null);
                                           ^
  シンボル:   メソッド getId()
  場所: タイプUserの変数 a

対処方法

build.gradleを以下のように書き換えることで問題を解消することができます。

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.4'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.t0k0sh1'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.projectlombok:lombok:1.18.22'
	implementation 'org.modelmapper:modelmapper:3.1.1'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	// 以下の1行を追加する
	annotationProcessor 'org.projectlombok:lombok:1.18.22'
}

tasks.named('test') {
	useJUnitPlatform()
}

追加でannotationProcessor 'org.projectlombok:lombok:1.18.22'を追記します。(バージョン部分は元の記述に合わせてください)

Gradleプロジェクトの再ロード(右端のGradleタブを開いて更新ボタンをクリックする)し、プロジェクトのビルドを行なってください。

すると、先ほどまでエラーとなっていましたが、今度はビルドに成功します。

モバイルバージョンを終了