Skip to content

fix: graceful degradation when plugin not found - #63

Open
windy664 wants to merge 1 commit into
ikafly144:masterfrom
windy664:fix/plugin-not-found-graceful-degradation
Open

fix: graceful degradation when plugin not found#63
windy664 wants to merge 1 commit into
ikafly144:masterfrom
windy664:fix/plugin-not-found-graceful-degradation

Conversation

@windy664

@windy664 windy664 commented Jul 19, 2026

Copy link
Copy Markdown

Problem

When a plugin that registered a PluginMailUser is not loaded (e.g., disabled or uninstalled), MailBox throws IllegalStateException: Plugin not found for namespace: xxx and crashes when trying to view mails/templates.

Solution

Gracefully degrade to DummyMailUser instead of crashing:

  1. UserConverter.readUser: Catch IllegalStateException from PluginUser.toUser() and return null
  2. Base.getMails/getMail: Handle null sender/receiver by creating fallback DummyMailUser
  3. Base.getMailTemplate(s): Handle null sender by creating fallback DummyMailUser

Use Case

This commonly happens when:

  • A plugin is temporarily disabled
  • A plugin is uninstalled but its data remains in the database
  • Server operators switch between different plugins

Testing

Tested with TransferStation plugin disabled - MailBox now shows "Unknown Sender/Receiver" instead of crashing.

Summary by CodeRabbit

  • バグ修正
    • 関連ユーザーが見つからないメールやテンプレートを取得する際に、エラーが発生する問題を修正しました。
    • 状態不整合や利用できない連携先がある場合も、処理が中断せず安全に扱えるようになりました。
    • 該当ユーザー情報が存在しない場合は、代替ユーザー情報を用いてメールやテンプレートを表示できるようになりました。

When a plugin that registered a PluginMailUser is not loaded (e.g., disabled or uninstalled), MailBox now gracefully degrades to DummyMailUser instead of throwing IllegalStateException.

Changes:
- UserConverter.readUser: catch IllegalStateException from PluginUser.toUser and return null
- Base.getMails/getMail: handle null sender/receiver by creating fallback DummyMailUser
- Base.getMailTemplate(s): handle null sender by creating fallback DummyMailUser

This prevents crashes when viewing mails/templates that reference users from plugins that are not currently loaded.
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

ユーザー変換時のIllegalStateExceptionnullとして扱い、メールおよびメールテンプレート取得時に不足ユーザーをダミーユーザーへ置換するよう変更されています。

Changes

メールユーザーフォールバック

Layer / File(s) Summary
ユーザー変換失敗のnull化
paper/src/main/java/net/sabafly/mailBox/database/UserConverter.java
readUser(...)IllegalStateException発生時にnullを返すよう変更されています。
メール取得時のダミーユーザー適用
paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java
メール、単一メール、メールテンプレートの取得処理でnullユーザーをDummyMailUserへ置換し、非null強制を除去しています。

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

うさぎが跳ねて、nullを越え
ダミーの耳でメールを受け
テンプレートも安心運び
例外の森をそっと抜け
月夜の箱が開きます 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 変更内容と一致しており、プラグイン未検出時の例外回避とフォールバック導入を適切に要約しています。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces graceful error handling when converting users and loading mails from the database. Specifically, it catches IllegalStateException during user conversion to return null and provides fallback DummyMailUser instances when a sender or receiver is not found in the database. The review feedback highlights two critical issues: first, using the hardcoded key "unknown" for fallback users will trigger database unique constraint violations, so using the unique UUID string as the key is recommended; second, returning null from user conversion can cause downstream NullPointerExceptions in Base.getAllUsers() because the returned nulls are added to a list expected to contain only non-null elements.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +326 to +333
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", "unknown", null);
}
UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUser(receiverId);
if (receiver == null) {
receiver = DummyMailUser.createUser(receiverId, "Unknown Receiver", "unknown", null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using a hardcoded key value of "unknown" for all fallback DummyMailUsers will cause a database constraint violation.

Why this is an issue:

  1. The mailbox_users table has a UNIQUE constraint on the address column (which stores the user's key):
    CREATE TABLE IF NOT EXISTS mailbox_users (
        ...
        address TEXT DEFAULT NULL UNIQUE
    )
  2. When mail.sender() or mail.receiver() is called (e.g., when rendering mails in the GUI), it triggers database().getOrCreateUser(user).
  3. If the plugin is missing, getUser(uuid) returns null, so getOrCreateUser attempts to update/insert the fallback user into the database.
  4. If multiple fallback users are created with the same key "unknown", the database will attempt to set address = "mailbox:unknown" for multiple rows, violating the UNIQUE constraint and throwing a SQLException. This will crash the mail loading process entirely.

Solution:

Use the user's unique UUID string (e.g., senderId.toString() or receiverId.toString()) as the key value instead of "unknown". Since UUIDs are unique and valid key patterns (containing only lowercase letters, numbers, and hyphens), this completely avoids any database conflicts.

Suggested change
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", "unknown", null);
}
UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUser(receiverId);
if (receiver == null) {
receiver = DummyMailUser.createUser(receiverId, "Unknown Receiver", "unknown", null);
}
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", senderId.toString(), null);
}
UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUser(receiverId);
if (receiver == null) {
receiver = DummyMailUser.createUser(receiverId, "Unknown Receiver", receiverId.toString(), null);
}

Comment on lines +353 to +360
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", "unknown", null);
}
UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUser(receiverId);
if (receiver == null) {
receiver = DummyMailUser.createUser(receiverId, "Unknown Receiver", "unknown", null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using a hardcoded key value of "unknown" for all fallback DummyMailUsers will cause a database constraint violation. Please use the unique UUID string as the key value instead.

Suggested change
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", "unknown", null);
}
UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUser(receiverId);
if (receiver == null) {
receiver = DummyMailUser.createUser(receiverId, "Unknown Receiver", "unknown", null);
}
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", senderId.toString(), null);
}
UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUser(receiverId);
if (receiver == null) {
receiver = DummyMailUser.createUser(receiverId, "Unknown Receiver", receiverId.toString(), null);
}

Comment on lines +411 to +418
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", "unknown", null);
}
UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUser(receiverId);
if (receiver == null) {
receiver = DummyMailUser.createUser(receiverId, "Unknown Receiver", "unknown", null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using a hardcoded key value of "unknown" for all fallback DummyMailUsers will cause a database constraint violation. Please use the unique UUID string as the key value instead.

Suggested change
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", "unknown", null);
}
UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUser(receiverId);
if (receiver == null) {
receiver = DummyMailUser.createUser(receiverId, "Unknown Receiver", "unknown", null);
}
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", senderId.toString(), null);
}
UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUser(receiverId);
if (receiver == null) {
receiver = DummyMailUser.createUser(receiverId, "Unknown Receiver", receiverId.toString(), null);
}

Comment on lines +511 to +513
if (sender == null) {
sender = DummyMailUser.createUser(senderId != null ? senderId : DummyMailUser.SYSTEM_UUID, "Unknown Sender", "unknown", null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using a hardcoded key value of "unknown" for all fallback DummyMailUsers will cause a database constraint violation. Please use the unique UUID string as the key value instead.

Suggested change
if (sender == null) {
sender = DummyMailUser.createUser(senderId != null ? senderId : DummyMailUser.SYSTEM_UUID, "Unknown Sender", "unknown", null);
}
if (sender == null) {
UUID fallbackId = senderId != null ? senderId : DummyMailUser.SYSTEM_UUID;
sender = DummyMailUser.createUser(fallbackId, "Unknown Sender", fallbackId.toString(), null);
}

Comment on lines +544 to +546
if (sender == null) {
sender = DummyMailUser.createUser(senderId != null ? senderId : DummyMailUser.SYSTEM_UUID, "Unknown Sender", "unknown", null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using a hardcoded key value of "unknown" for all fallback DummyMailUsers will cause a database constraint violation. Please use the unique UUID string as the key value instead.

Suggested change
if (sender == null) {
sender = DummyMailUser.createUser(senderId != null ? senderId : DummyMailUser.SYSTEM_UUID, "Unknown Sender", "unknown", null);
}
if (sender == null) {
UUID fallbackId = senderId != null ? senderId : DummyMailUser.SYSTEM_UUID;
sender = DummyMailUser.createUser(fallbackId, "Unknown Sender", fallbackId.toString(), null);
}

Comment on lines +577 to +579
if (sender1 == null) {
sender1 = DummyMailUser.createUser(senderId != null ? senderId : DummyMailUser.SYSTEM_UUID, "Unknown Sender", "unknown", null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using a hardcoded key value of "unknown" for all fallback DummyMailUsers will cause a database constraint violation. Please use the unique UUID string as the key value instead.

Suggested change
if (sender1 == null) {
sender1 = DummyMailUser.createUser(senderId != null ? senderId : DummyMailUser.SYSTEM_UUID, "Unknown Sender", "unknown", null);
}
if (sender1 == null) {
UUID fallbackId = senderId != null ? senderId : DummyMailUser.SYSTEM_UUID;
sender1 = DummyMailUser.createUser(fallbackId, "Unknown Sender", fallbackId.toString(), null);
}

Comment on lines +610 to +612
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", "unknown", null);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using a hardcoded key value of "unknown" for all fallback DummyMailUsers will cause a database constraint violation. Please use the unique UUID string as the key value instead.

Suggested change
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", "unknown", null);
}
if (sender == null) {
sender = DummyMailUser.createUser(senderId, "Unknown Sender", senderId.toString(), null);
}

Comment on lines +33 to +38
try {
return baseUser.toUser(uuid, key);
} catch (IllegalStateException e) {
// Plugin not found or other state issues - return null gracefully
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Returning null from readUser when a plugin is not found is a great way to handle graceful degradation. However, please note that Base.getAllUsers() (line 304 in Base.java) directly adds the result of readUser(rs) to a list of type List<@NotNull User> without checking for null:

while (rs.next()) {
    users.add(readUser(rs));
}

This will result in null elements being added to a list that is contractually specified to contain only non-null Users, which can lead to unexpected NullPointerExceptions downstream when iterating over the list.

Recommendation:
Please update Base.getAllUsers() to filter out null users before adding them to the list:

User user = readUser(rs);
if (user != null) {
    users.add(user);
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java (1)

326-338: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

重複するダミーユーザー生成処理の共通化

ユーザー取得時の null チェックと DummyMailUser へのフォールバック処理が複数の箇所で重複しています。一部の処理(例: getMailTemplatesenderId != null ? senderId : DummyMailUser.SYSTEM_UUID)の冗長な記述も含め、一貫したヘルパーメソッドへ抽出することでコードの見通しを良くすることができます。

以下のようなヘルパーメソッドを追加し、それぞれの箇所を置き換えることをご検討ください。

💡 ヘルパーメソッドの追加例
private `@NotNull` User getUserOrDefault(`@Nullable` UUID userId, `@NotNull` String defaultName) {
    UUID id = userId != null ? userId : DummyMailUser.SYSTEM_UUID;
    User user = getUser(id);
    if (user == null) {
        return DummyMailUser.createUser(id, defaultName, "unknown", null);
    }
    return user;
}

呼び出し側の例:

UUID senderId = Optional.ofNullable(rs.getString("sender")).map(UUID::fromString).orElse(null);
User sender = getUserOrDefault(senderId, "Unknown Sender");

UUID receiverId = UUID.fromString(rs.getString("receiver"));
User receiver = getUserOrDefault(receiverId, "Unknown Receiver");
  • paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java#L326-L338: 追加したヘルパーメソッドを使用するようリファクタリングします。
  • paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java#L353-L365: 追加したヘルパーメソッドを使用するようリファクタリングします。
  • paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java#L411-L423: 追加したヘルパーメソッドを使用するようリファクタリングします。
  • paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java#L511-L518: 追加したヘルパーメソッドを使用するようリファクタリングします。
  • paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java#L544-L551: 追加したヘルパーメソッドを使用するようリファクタリングします。
  • paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java#L577-L584: 追加したヘルパーメソッドを使用するようリファクタリングします。
  • paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java#L610-L617: 追加したヘルパーメソッドを使用するようリファクタリングします。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java` around lines
326 - 338, Base.java の各所で重複しているユーザー取得と DummyMailUser フォールバック処理を共通化してください。Base に
getUserOrDefault 相当のヘルパーメソッドを追加し、null の UUID には SYSTEM_UUID
を使い、取得できない場合は指定名のダミーユーザーを返すようにします。paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java
の326-338、353-365、411-423、511-518、544-551、577-584、610-617では、それぞれ既存の個別処理をこのヘルパー呼び出しへ置き換え、getMailTemplate
を含む冗長な senderId 処理も統一してください。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java`:
- Around line 326-338: Base.java の各所で重複しているユーザー取得と DummyMailUser
フォールバック処理を共通化してください。Base に getUserOrDefault 相当のヘルパーメソッドを追加し、null の UUID には
SYSTEM_UUID
を使い、取得できない場合は指定名のダミーユーザーを返すようにします。paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java
の326-338、353-365、411-423、511-518、544-551、577-584、610-617では、それぞれ既存の個別処理をこのヘルパー呼び出しへ置き換え、getMailTemplate
を含む冗長な senderId 処理も統一してください。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc77934b-647e-4440-a785-92b5fa35e239

📥 Commits

Reviewing files that changed from the base of the PR and between de9adac and 1bb5b11.

📒 Files selected for processing (2)
  • paper/src/main/java/net/sabafly/mailBox/database/UserConverter.java
  • paper/src/main/java/net/sabafly/mailBox/database/impl/Base.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant