diff --git a/lib/features/redis/redis_connection_form.dart b/lib/features/redis/redis_connection_form.dart index 3c0a4711..ad68a504 100644 --- a/lib/features/redis/redis_connection_form.dart +++ b/lib/features/redis/redis_connection_form.dart @@ -1,3 +1,5 @@ +import 'package:querya_desktop/shared/report_error.dart'; +import 'package:querya_desktop/core/database/database_error_mapper.dart'; import 'package:flutter/widgets.dart'; import 'dart:async'; @@ -220,8 +222,8 @@ class _RedisConnectionFormContentState if (mounted) { _showTestResult(result.ok ? 'success' : (result.error ?? 'failed')); } - } catch (e) { - if (mounted) _showTestResult('error: $e'); + } catch (e, st) { + if (mounted) _showTestResult(failureMessage('error', e, where: 'RedisConnectionForm', stack: st, driver: DatabaseDriver.redis)); } } diff --git a/lib/features/redis/redis_key_editor.dart b/lib/features/redis/redis_key_editor.dart index 7a33c449..37ca2cfc 100644 --- a/lib/features/redis/redis_key_editor.dart +++ b/lib/features/redis/redis_key_editor.dart @@ -1,3 +1,5 @@ +import 'package:querya_desktop/shared/report_error.dart'; +import 'package:querya_desktop/core/database/database_error_mapper.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/theme/querya_ui_colors.dart'; @@ -315,11 +317,11 @@ class _RedisKeyEditorState extends material.State { } if (!mounted) return; setState(() => _loadingMore = false); - } catch (e) { + } catch (e, st) { if (!mounted) return; setState(() { _loadingMore = false; - _error = 'Load failed: $e'; + _error = failureMessage('Load failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis); }); } } @@ -336,9 +338,9 @@ class _RedisKeyEditorState extends material.State { if (!mounted) return; setState(() => _success = 'Value saved'); _clearSuccessAfterDelay(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'Save failed: $e'); + setState(() => _error = failureMessage('Save failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -377,9 +379,9 @@ class _RedisKeyEditorState extends material.State { }); _clearSuccessAfterDelay(); widget.onKeyRenamed?.call(newBulk); - } catch (e) { + } catch (e, st) { if (mounted) { - setState(() => _error = 'Rename failed: $e'); + setState(() => _error = failureMessage('Rename failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } } @@ -397,9 +399,9 @@ class _RedisKeyEditorState extends material.State { try { await widget.connection.del(_cmdKey); widget.onKeyDeleted?.call(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'Delete failed: $e'); + setState(() => _error = failureMessage('Delete failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -417,9 +419,9 @@ class _RedisKeyEditorState extends material.State { _success = seconds > 0 ? 'TTL set to $seconds seconds' : 'TTL removed'; }); _clearSuccessAfterDelay(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'TTL failed: $e'); + setState(() => _error = failureMessage('TTL failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -429,9 +431,9 @@ class _RedisKeyEditorState extends material.State { try { await widget.connection.hset(_cmdKey, field, value); await _load(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'HSET failed: $e'); + setState(() => _error = failureMessage('HSET failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -448,9 +450,9 @@ class _RedisKeyEditorState extends material.State { try { await widget.connection.hdel(_cmdKey, field); await _load(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'HDEL failed: $e'); + setState(() => _error = failureMessage('HDEL failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -460,9 +462,9 @@ class _RedisKeyEditorState extends material.State { try { await widget.connection.rpush(_cmdKey, value); await _load(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'RPUSH failed: $e'); + setState(() => _error = failureMessage('RPUSH failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -485,9 +487,9 @@ class _RedisKeyEditorState extends material.State { } await widget.connection.lset(_cmdKey, index, newValue); await _load(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'LSET failed: $e'); + setState(() => _error = failureMessage('LSET failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -522,9 +524,9 @@ class _RedisKeyEditorState extends material.State { rethrow; } await _load(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'LREM failed: $e'); + setState(() => _error = failureMessage('LREM failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -541,9 +543,9 @@ class _RedisKeyEditorState extends material.State { try { await widget.connection.sadd(_cmdKey, member); await _load(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'SADD failed: $e'); + setState(() => _error = failureMessage('SADD failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -560,9 +562,9 @@ class _RedisKeyEditorState extends material.State { try { await widget.connection.srem(_cmdKey, member); await _load(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'SREM failed: $e'); + setState(() => _error = failureMessage('SREM failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -572,9 +574,9 @@ class _RedisKeyEditorState extends material.State { try { await widget.connection.zadd(_cmdKey, score, member); await _load(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'ZADD failed: $e'); + setState(() => _error = failureMessage('ZADD failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } @@ -591,9 +593,9 @@ class _RedisKeyEditorState extends material.State { try { await widget.connection.zrem(_cmdKey, member); await _load(); - } catch (e) { + } catch (e, st) { if (!mounted) return; - setState(() => _error = 'ZREM failed: $e'); + setState(() => _error = failureMessage('ZREM failed', e, where: 'RedisKeyEditor', stack: st, driver: DatabaseDriver.redis)); } } diff --git a/lib/features/redis/redis_keys_view.dart b/lib/features/redis/redis_keys_view.dart index cd932867..e46d7900 100644 --- a/lib/features/redis/redis_keys_view.dart +++ b/lib/features/redis/redis_keys_view.dart @@ -1,3 +1,5 @@ +import 'package:querya_desktop/shared/report_error.dart'; +import 'package:querya_desktop/core/database/database_error_mapper.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart' as material; import 'package:querya_desktop/core/theme/querya_ui_colors.dart'; @@ -130,13 +132,13 @@ class _RedisKeysViewState extends material.State { ttl: metas[i].ttl, ), ]; - } catch (e) { + } catch (e, st) { // Still show keys with unknown type/TTL; surface the failure non-blocking. infos = [ for (final name in accumulatedKeys) RedisKeyInfo(name: name, type: 'unknown', ttl: -1), ]; - typeTtlError = 'Failed to load key types/TTLs: $e'; + typeTtlError = failureMessage('Failed to load key types/TTLs', e, where: 'RedisKeysView', stack: st, driver: DatabaseDriver.redis); } } else { infos = []; @@ -193,8 +195,8 @@ class _RedisKeysViewState extends material.State { _keys.removeWhere((k) => k.name == keyInfo.name); _dbSize = (_dbSize - 1).clamp(0, _dbSize); }); - } catch (e) { - if (mounted) setState(() => _error = 'Delete failed: $e'); + } catch (e, st) { + if (mounted) setState(() => _error = failureMessage('Delete failed', e, where: 'RedisKeysView', stack: st, driver: DatabaseDriver.redis)); } } @@ -222,11 +224,11 @@ class _RedisKeysViewState extends material.State { _keys.removeWhere((k) => toRemove.contains(k.name)); _dbSize = (_dbSize - count).clamp(0, _dbSize); }); - } catch (e) { + } catch (e, st) { if (!mounted) return; // Some chunks may have been removed: show what is really left. await _load(); - if (mounted) setState(() => _error = 'Delete folder failed: $e'); + if (mounted) setState(() => _error = failureMessage('Delete folder failed', e, where: 'RedisKeysView', stack: st, driver: DatabaseDriver.redis)); } } diff --git a/lib/shared/report_error.dart b/lib/shared/report_error.dart index c5251f67..ccc073c9 100644 --- a/lib/shared/report_error.dart +++ b/lib/shared/report_error.dart @@ -36,3 +36,17 @@ void reportError( message: '$action. ${describeError(error, driver: driver)}', ); } + +/// Logs [error] and returns `: ` for an inline error line (a +/// view's own error text), where a toast is not the surface. [action] is a +/// short phrase (`'Delete failed'`); [where] names the place for the log. +String failureMessage( + String action, + Object error, { + required String where, + StackTrace? stack, + DatabaseDriver? driver, +}) { + QueryaLog.instance.error(where, action, error: error, stack: stack); + return '$action: ${describeError(error, driver: driver)}'; +} diff --git a/test/shared/report_error_test.dart b/test/shared/report_error_test.dart index 71ef67c3..aaad46c7 100644 --- a/test/shared/report_error_test.dart +++ b/test/shared/report_error_test.dart @@ -48,6 +48,21 @@ void main() { expect(text, contains('Cannot reach the database server')); }); + test('failureMessage logs and returns the inline error line', () async { + final text = failureMessage( + 'Delete failed', + StateError('connection reset'), + where: 'RedisKeysView', + driver: DatabaseDriver.redis, + ); + await QueryaLog.instance.flush(); + + expect(text, startsWith('Delete failed: ')); + final log = + await File('${logDir.path}/${QueryaLog.fileName}').readAsString(); + expect(log, contains('[RedisKeysView] Delete failed')); + }); + testWidgets('reportError shows the toast and writes the log', (tester) async { final host = await pumpHost(tester);