anyhow used

This commit is contained in:
filipriec
2025-04-18 19:04:05 +02:00
parent 09ccad2bd4
commit 5a029283a1
12 changed files with 69 additions and 74 deletions

5
Cargo.lock generated
View File

@@ -109,9 +109,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.97"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "arc-swap"
@@ -423,6 +423,7 @@ dependencies = [
name = "client"
version = "0.3.13"
dependencies = [
"anyhow",
"async-trait",
"common",
"crossterm",

View File

@@ -5,6 +5,7 @@ edition.workspace = true
license.workspace = true
[dependencies]
anyhow = "1.0.98"
async-trait = "0.1.88"
common = { path = "../common" }

View File

@@ -3,6 +3,7 @@
use serde::Deserialize;
use std::collections::HashMap;
use std::path::Path;
use anyhow::{Context, Result};
use crossterm::event::{KeyCode, KeyModifiers};
#[derive(Debug, Deserialize, Default)]
@@ -43,11 +44,11 @@ pub struct ModeKeybindings {
impl Config {
/// Loads the configuration from "config.toml" in the client crate directory.
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
pub fn load() -> Result<Self> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let config_path = Path::new(manifest_dir).join("config.toml");
let config_str = std::fs::read_to_string(&config_path)
.map_err(|e| format!("Failed to read config file at {:?}: {}", config_path, e))?;
.with_context(|| format!("Failed to read config file at {:?}", config_path))?;
let config: Config = toml::from_str(&config_str)?;
Ok(config)
}

View File

@@ -8,7 +8,7 @@ use crate::ui::handlers::rat_state::UiStateHandler;
use crate::ui::handlers::context::UiContext;
use crate::ui::handlers::context::DialogPurpose;
use crate::functions::common::buffer;
use std::error::Error;
use anyhow::{Context, Result};
use crate::tui::{
terminal::core::TerminalCore,
functions::{
@@ -68,7 +68,7 @@ pub struct EventHandler {
}
impl EventHandler {
pub async fn new(login_result_sender: mpsc::Sender<LoginResult>) -> Result<Self, Box<dyn Error + Send + Sync>> {
pub async fn new(login_result_sender: mpsc::Sender<LoginResult>) -> Result<Self> {
Ok(EventHandler {
command_mode: false,
command_input: String::new(),
@@ -100,7 +100,7 @@ impl EventHandler {
app_state: &mut AppState,
total_count: u64,
current_position: &mut u64,
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
) -> Result<EventOutcome> {
let current_mode = ModeManager::derive_mode(app_state, self);
app_state.update_mode(current_mode);
@@ -257,7 +257,9 @@ impl EventHandler {
spawn(async move {
let login_outcome = match AuthClient::new().await {
Ok(mut auth_client) => {
match auth_client.login(username, password).await {
match auth_client.login(username.clone(), password).await
.with_context(|| format!("Spawned login task failed for identifier: {}", username))
{
Ok(response) => login::LoginResult::Success(response),
Err(e) => login::LoginResult::Failure(format!("{}", e)),
}

View File

@@ -5,20 +5,22 @@ use common::proto::multieko2::auth::{
LoginRequest, LoginResponse,
RegisterRequest, AuthResponse,
};
use std::error::Error;
use anyhow::{Context, Result};
pub struct AuthClient {
client: AuthServiceClient<Channel>,
}
impl AuthClient {
pub async fn new() -> Result<Self, Box<dyn Error + Send + Sync>> {
let client = AuthServiceClient::connect("http://[::1]:50051").await?;
pub async fn new() -> Result<Self> {
let client = AuthServiceClient::connect("http://[::1]:50051")
.await
.context("Failed to connect to auth service")?;
Ok(Self { client })
}
/// Login user via gRPC.
pub async fn login(&mut self, identifier: String, password: String) -> Result<LoginResponse, Box<dyn std::error::Error>> {
pub async fn login(&mut self, identifier: String, password: String) -> Result<LoginResponse> {
let request = tonic::Request::new(LoginRequest { identifier, password });
let response = self.client.login(request).await?.into_inner();
Ok(response)
@@ -32,7 +34,7 @@ impl AuthClient {
password: Option<String>,
password_confirmation: Option<String>,
role: Option<String>,
) -> Result<AuthResponse, Box<dyn std::error::Error>> {
) -> Result<AuthResponse> {
let request = tonic::Request::new(RegisterRequest {
username,
email,

View File

@@ -10,6 +10,7 @@ use common::proto::multieko2::table_definition::{
table_definition_client::TableDefinitionClient,
ProfileTreeResponse
};
use anyhow::{Context, Result};
#[derive(Clone)]
pub struct GrpcClient {
@@ -19,7 +20,7 @@ pub struct GrpcClient {
}
impl GrpcClient {
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
pub async fn new() -> Result<Self> {
let adresar_client = AdresarClient::connect("http://[::1]:50051").await?;
let table_structure_client = TableStructureServiceClient::connect("http://[::1]:50051").await?;
let table_definition_client = TableDefinitionClient::connect("http://[::1]:50051").await?;
@@ -31,37 +32,37 @@ impl GrpcClient {
})
}
pub async fn get_adresar_count(&mut self) -> Result<u64, Box<dyn std::error::Error>> {
pub async fn get_adresar_count(&mut self) -> Result<u64> {
let request = tonic::Request::new(Empty::default());
let response: CountResponse = self.adresar_client.get_adresar_count(request).await?.into_inner();
Ok(response.count as u64)
}
pub async fn get_adresar_by_position(&mut self, position: u64) -> Result<AdresarResponse, Box<dyn std::error::Error>> {
pub async fn get_adresar_by_position(&mut self, position: u64) -> Result<AdresarResponse> {
let request = tonic::Request::new(PositionRequest { position: position as i64 });
let response: AdresarResponse = self.adresar_client.get_adresar_by_position(request).await?.into_inner();
Ok(response)
}
pub async fn post_adresar(&mut self, request: PostAdresarRequest) -> Result<tonic::Response<AdresarResponse>, Box<dyn std::error::Error>> {
pub async fn post_adresar(&mut self, request: PostAdresarRequest) -> Result<tonic::Response<AdresarResponse>> {
let request = tonic::Request::new(request);
let response = self.adresar_client.post_adresar(request).await?;
Ok(response)
}
pub async fn put_adresar(&mut self, request: PutAdresarRequest) -> Result<tonic::Response<AdresarResponse>, Box<dyn std::error::Error>> {
pub async fn put_adresar(&mut self, request: PutAdresarRequest) -> Result<tonic::Response<AdresarResponse>> {
let request = tonic::Request::new(request);
let response = self.adresar_client.put_adresar(request).await?;
Ok(response)
}
pub async fn get_table_structure(&mut self) -> Result<TableStructureResponse, Box<dyn std::error::Error>> {
pub async fn get_table_structure(&mut self) -> Result<TableStructureResponse> {
let request = tonic::Request::new(Empty::default());
let response = self.table_structure_client.get_adresar_table_structure(request).await?;
Ok(response.into_inner())
}
pub async fn get_profile_tree(&mut self) -> Result<ProfileTreeResponse, Box<dyn std::error::Error>> {
pub async fn get_profile_tree(&mut self) -> Result<ProfileTreeResponse> {
let request = tonic::Request::new(Empty::default());
let response = self.table_definition_client.get_profile_tree(request).await?;
Ok(response.into_inner())

View File

@@ -4,6 +4,7 @@ use crate::services::grpc_client::GrpcClient;
use crate::state::pages::form::FormState;
use crate::tui::functions::common::form::SaveOutcome;
use crate::state::app::state::AppState;
use anyhow::{Context, Result};
pub struct UiService;
@@ -13,7 +14,7 @@ impl UiService {
app_state: &mut AppState,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
// Fetch profile tree
let profile_tree = grpc_client.get_profile_tree().await?;
let profile_tree = grpc_client.get_profile_tree().await.context("Failed to get profile tree")?;
app_state.profile_tree = profile_tree;
// Fetch table structure
@@ -32,8 +33,8 @@ impl UiService {
pub async fn initialize_adresar_count(
grpc_client: &mut GrpcClient,
app_state: &mut AppState,
) -> Result<(), Box<dyn std::error::Error>> {
let total_count = grpc_client.get_adresar_count().await?;
) -> Result<()> {
let total_count = grpc_client.get_adresar_count().await.await.context("Failed to get adresar count")?;
app_state.update_total_count(total_count);
app_state.update_current_position(total_count.saturating_add(1)); // Start in new entry mode
Ok(())
@@ -42,8 +43,8 @@ impl UiService {
pub async fn update_adresar_count(
grpc_client: &mut GrpcClient,
app_state: &mut AppState,
) -> Result<(), Box<dyn std::error::Error>> {
let total_count = grpc_client.get_adresar_count().await?;
) -> Result<()> {
let total_count = grpc_client.get_adresar_count().await.context("Failed to get adresar by position")?;
app_state.update_total_count(total_count);
Ok(())
}
@@ -53,7 +54,7 @@ impl UiService {
_app_state: &mut AppState,
form_state: &mut FormState,
position: u64,
) -> Result<String, Box<dyn std::error::Error>> {
) -> Result<String> {
match grpc_client.get_adresar_by_position(position).await {
Ok(response) => {
// Set the ID properly
@@ -92,8 +93,8 @@ impl UiService {
save_outcome: SaveOutcome,
grpc_client: &mut GrpcClient,
app_state: &mut AppState,
form_state: &mut FormState, // Needed to potentially update position/ID
) -> Result<(), Box<dyn std::error::Error>> {
form_state: &mut FormState,
) -> Result<()> {
match save_outcome {
SaveOutcome::CreatedNew(new_id) => {
// A new record was created, update the count!

View File

@@ -4,6 +4,7 @@ use std::env;
use common::proto::multieko2::table_definition::ProfileTreeResponse;
use crate::modes::handlers::mode_manager::AppMode;
use crate::ui::handlers::context::DialogPurpose;
use anyhow::{Context, Result};
pub struct DialogState {
pub dialog_show: bool,
@@ -43,7 +44,7 @@ pub struct AppState {
}
impl AppState {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
pub fn new() -> Result<Self> {
let current_dir = env::current_dir()?
.to_string_lossy()
.to_string();

View File

@@ -9,6 +9,7 @@ use crate::state::pages::canvas_state::CanvasState;
use crate::ui::handlers::context::DialogPurpose;
use crate::modes::handlers::event::EventOutcome;
use common::proto::multieko2::auth::LoginResponse;
use anyhow::{Context, Result};
#[derive(Debug)]
pub enum LoginResult {
@@ -19,13 +20,12 @@ pub enum LoginResult {
/// Attempts to log the user in using the provided credentials via gRPC.
/// Updates AuthState and AppState on success or failure.
/// (This is your existing function - remains unchanged)
pub async fn save(
auth_state: &mut AuthState,
login_state: &mut LoginState,
auth_client: &mut AuthClient,
app_state: &mut AppState,
) -> Result<String, Box<dyn std::error::Error>> {
) -> Result<String> {
let identifier = login_state.username.clone();
let password = login_state.password.clone();
@@ -40,7 +40,7 @@ pub async fn save(
DialogPurpose::LoginFailed,
);
login_state.error_message = Some(error_message.clone());
return Ok(error_message);
return Err(anyhow::anyhow!(error_message));
}
// Clear previous error/dialog state before attempting
@@ -48,7 +48,9 @@ pub async fn save(
app_state.hide_dialog(); // Hide any previous dialog
// Call the gRPC login method
match auth_client.login(identifier, password).await {
match auth_client.login(identifier.clone(), password).await
.with_context(|| format!("gRPC login attempt failed for identifier: {}", identifier))
{
Ok(response) => {
// Store authentication details using correct field names
auth_state.auth_token = Some(response.access_token.clone());
@@ -92,28 +94,11 @@ pub async fn save(
login_state.set_has_unsaved_changes(true);
login_state.username.clear();
login_state.password.clear();
Ok(format!("Login failed: {}", error_message))
Err(e)
}
}
}
// --- Add this new function ---
/// Sets the stage for login: shows loading dialog and sets the pending flag.
/// Call this from the event handler when login is triggered.
pub async fn initiate_login(
app_state: &mut AppState,
login_state: &mut LoginState,
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
// Show the loading dialog immediately
app_state.show_loading_dialog("Logging In", "Please wait...");
// Set the flag in LoginState to indicate the actual save should run next loop
login_state.login_request_pending = true;
// Return immediately to allow redraw
Ok(EventOutcome::Ok("Login initiated.".to_string()))
}
// --- End of new function ---
/// Reverts the login form fields to empty and returns to the previous screen (Intro).
pub async fn revert(
login_state: &mut LoginState,

View File

@@ -7,13 +7,14 @@ use crossterm::{
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io::{self, stdout, Write};
use anyhow::{Context, Result};
pub struct TerminalCore {
terminal: Terminal<CrosstermBackend<io::Stdout>>,
}
impl TerminalCore {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
pub fn new() -> Result<Self> {
enable_raw_mode()?;
let mut stdout = stdout();
execute!(
@@ -27,7 +28,7 @@ impl TerminalCore {
Ok(Self { terminal })
}
pub fn draw<F>(&mut self, f: F) -> Result<(), Box<dyn std::error::Error>>
pub fn draw<F>(&mut self, f: F) -> Result<()>
where
F: FnOnce(&mut ratatui::Frame),
{
@@ -35,7 +36,7 @@ impl TerminalCore {
Ok(())
}
pub fn cleanup(&mut self) -> Result<(), Box<dyn std::error::Error>> {
pub fn cleanup(&mut self) -> Result<()> {
let backend = self.terminal.backend_mut();
execute!(
backend,
@@ -56,7 +57,7 @@ impl TerminalCore {
pub fn set_cursor_style(
&mut self,
style: SetCursorStyle,
) -> Result<(), Box<dyn std::error::Error>> {
) -> Result<()> {
execute!(
self.terminal.backend_mut(),
style,
@@ -65,7 +66,7 @@ impl TerminalCore {
Ok(())
}
pub fn show_cursor(&mut self) -> Result<(), Box<dyn std::error::Error>> {
pub fn show_cursor(&mut self) -> Result<()> {
execute!(
self.terminal.backend_mut(),
Show
@@ -73,7 +74,7 @@ impl TerminalCore {
Ok(())
}
pub fn hide_cursor(&mut self) -> Result<(), Box<dyn std::error::Error>> {
pub fn hide_cursor(&mut self) -> Result<()> {
execute!(
self.terminal.backend_mut(),
Hide

View File

@@ -1,6 +1,7 @@
// src/tui/terminal/event_reader.rs
use crossterm::event::{self, Event};
use anyhow::{Context, Result};
pub struct EventReader;
@@ -9,7 +10,7 @@ impl EventReader {
Self
}
pub fn read_event(&self) -> Result<Event, Box<dyn std::error::Error>> {
pub fn read_event(&self) -> Result<Event> {
Ok(event::read()?)
}
}

View File

@@ -18,30 +18,28 @@ use crate::state::pages::intro::IntroState;
use crate::state::app::buffer::BufferState;
use crate::state::app::buffer::AppView;
use crate::state::app::state::AppState;
use crate::ui::handlers::context::DialogPurpose; // <-- Add DialogPurpose import
// Import SaveOutcome
use crate::ui::handlers::context::DialogPurpose;
use crate::tui::terminal::{EventReader, TerminalCore};
use crate::ui::handlers::render::render_ui;
use crate::tui::functions::common::login;
use crate::tui::functions::common::login::LoginResult;
use std::time::Instant;
use std::error::Error;
use anyhow::{Context, Result};
use crossterm::cursor::SetCursorStyle;
use crossterm::event as crossterm_event;
use tracing::{info, error};
use tokio::sync::mpsc;
pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
let config = Config::load()?;
pub async fn run_ui() -> Result<()> {
let config = Config::load().context("Failed to load configuration")?;
let theme = Theme::from_str(&config.colors.theme);
let mut terminal = TerminalCore::new()?;
let mut terminal = TerminalCore::new().context("Failed to initialize terminal")?;
let mut grpc_client = GrpcClient::new().await?;
let mut command_handler = CommandHandler::new();
// --- Channel for Login Results ---
let (login_result_sender, mut login_result_receiver) =
mpsc::channel::<LoginResult>(1);
let mut event_handler = EventHandler::new(login_result_sender.clone()).await?;
let mut event_handler = EventHandler::new(login_result_sender.clone()).await.context("Failed to create event handler")?;
let event_reader = EventReader::new();
let mut auth_state = AuthState::default();
@@ -50,16 +48,16 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
let mut intro_state = IntroState::default();
let mut admin_state = AdminState::default();
let mut buffer_state = BufferState::default();
let mut app_state = AppState::new()?;
let mut app_state = AppState::new().context("Failed to create initial app state")?;
// Initialize app state with profile tree and table structure
let column_names =
UiService::initialize_app_state(&mut grpc_client, &mut app_state)
.await?;
.await.context("Failed to initialize app state from UI service")?;
let mut form_state = FormState::new(column_names);
// Fetch the total count of Adresar entries
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state)
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await
.await?;
form_state.reset_to_empty();
@@ -121,7 +119,7 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
current_fps,
&app_state,
);
})?;
}).context("Terminal draw call failed")?;
// --- Cursor Visibility Logic ---
// (Keep existing cursor logic here - depends on state drawn above)
@@ -132,13 +130,13 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
AppMode::ReadOnly => {
if !app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; }
else { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; }
terminal.show_cursor()?;
terminal.show_cursor().context("Failed to show cursor in ReadOnly mode")?;
}
AppMode::General => {
if app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; }
else { terminal.hide_cursor()?; }
}
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; }
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; }
}
// --- End Cursor Visibility Logic ---
@@ -150,7 +148,7 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
let mut event_outcome_result = Ok(EventOutcome::Ok(String::new()));
// Poll for events *after* drawing and checking pending actions
if crossterm_event::poll(std::time::Duration::from_millis(20))? {
let event = event_reader.read_event()?;
let event = event_reader.read_event().context("Failed to read terminal event")?;
event_outcome_result = event_handler
.handle_event(
event,
@@ -308,7 +306,7 @@ pub async fn run_ui() -> Result<(), Box<dyn Error + Send + Sync>> {
&mut form_state,
current_position_to_load,
)
.await?;
.await.with_context(|| format!("Failed to load adresar by position: {}", current_position_to_load))?;
let current_input = form_state.get_current_input();
let max_cursor_pos = if !event_handler.is_edit_mode