refactor: add modal/validation utils and partial App encapsulation
Quick wins refactoring (Variant 1): - Created src/utils/modal_handler.rs (120+ lines) - 4 functions for modal handling (close, confirm, yes/no) - ModalAction enum for type-safe processing - English and Russian keyboard layout support - 4 unit tests - Created src/utils/validation.rs (180+ lines) - 7 validation functions (empty, length, IDs, etc) - Covers all common validation patterns - 7 unit tests - Partial App encapsulation: - Made config field private (readonly via app.config()) - Added 30+ getter/setter methods - Updated ui/messages.rs to use config() - Updated documentation: - REFACTORING_OPPORTUNITIES.md: #1 Complete, #5 Partial - CONTEXT.md: Added quick wins section Tests: 563 passed, 0 failed Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
196
src/app/mod.rs
196
src/app/mod.rs
@@ -44,18 +44,19 @@ use ratatui::widgets::ListState;
|
||||
/// app.select_current_chat();
|
||||
/// ```
|
||||
pub struct App {
|
||||
pub config: crate::config::Config,
|
||||
// Core (config - readonly через getter)
|
||||
config: crate::config::Config,
|
||||
pub screen: AppScreen,
|
||||
pub td_client: TdClient,
|
||||
/// Состояние чата - type-safe state machine (новое!)
|
||||
pub chat_state: ChatState,
|
||||
// Auth state
|
||||
// Auth state (используются часто в UI)
|
||||
pub phone_input: String,
|
||||
pub code_input: String,
|
||||
pub password_input: String,
|
||||
pub error_message: Option<String>,
|
||||
pub status_message: Option<String>,
|
||||
// Main app state
|
||||
// Main app state (используются часто)
|
||||
pub chats: Vec<ChatInfo>,
|
||||
pub chat_list_state: ListState,
|
||||
pub selected_chat_id: Option<ChatId>,
|
||||
@@ -800,4 +801,193 @@ impl App {
|
||||
pub fn get_selected_message_for_reaction(&self) -> Option<i64> {
|
||||
self.chat_state.selected_message_id().map(|id| id.as_i64())
|
||||
}
|
||||
|
||||
// ========== Getter/Setter методы для инкапсуляции ==========
|
||||
|
||||
// Config
|
||||
pub fn config(&self) -> &crate::config::Config {
|
||||
&self.config
|
||||
}
|
||||
|
||||
// Screen
|
||||
pub fn screen(&self) -> &AppScreen {
|
||||
&self.screen
|
||||
}
|
||||
|
||||
pub fn set_screen(&mut self, screen: AppScreen) {
|
||||
self.screen = screen;
|
||||
}
|
||||
|
||||
// Auth state
|
||||
pub fn phone_input(&self) -> &str {
|
||||
&self.phone_input
|
||||
}
|
||||
|
||||
pub fn phone_input_mut(&mut self) -> &mut String {
|
||||
&mut self.phone_input
|
||||
}
|
||||
|
||||
pub fn set_phone_input(&mut self, input: String) {
|
||||
self.phone_input = input;
|
||||
}
|
||||
|
||||
pub fn code_input(&self) -> &str {
|
||||
&self.code_input
|
||||
}
|
||||
|
||||
pub fn code_input_mut(&mut self) -> &mut String {
|
||||
&mut self.code_input
|
||||
}
|
||||
|
||||
pub fn set_code_input(&mut self, input: String) {
|
||||
self.code_input = input;
|
||||
}
|
||||
|
||||
pub fn password_input(&self) -> &str {
|
||||
&self.password_input
|
||||
}
|
||||
|
||||
pub fn password_input_mut(&mut self) -> &mut String {
|
||||
&mut self.password_input
|
||||
}
|
||||
|
||||
pub fn set_password_input(&mut self, input: String) {
|
||||
self.password_input = input;
|
||||
}
|
||||
|
||||
pub fn error_message(&self) -> Option<&str> {
|
||||
self.error_message.as_deref()
|
||||
}
|
||||
|
||||
pub fn set_error_message(&mut self, message: Option<String>) {
|
||||
self.error_message = message;
|
||||
}
|
||||
|
||||
pub fn status_message(&self) -> Option<&str> {
|
||||
self.status_message.as_deref()
|
||||
}
|
||||
|
||||
pub fn set_status_message(&mut self, message: Option<String>) {
|
||||
self.status_message = message;
|
||||
}
|
||||
|
||||
// Main app state
|
||||
pub fn chats(&self) -> &[ChatInfo] {
|
||||
&self.chats
|
||||
}
|
||||
|
||||
pub fn chats_mut(&mut self) -> &mut Vec<ChatInfo> {
|
||||
&mut self.chats
|
||||
}
|
||||
|
||||
pub fn set_chats(&mut self, chats: Vec<ChatInfo>) {
|
||||
self.chats = chats;
|
||||
}
|
||||
|
||||
pub fn chat_list_state(&self) -> &ListState {
|
||||
&self.chat_list_state
|
||||
}
|
||||
|
||||
pub fn chat_list_state_mut(&mut self) -> &mut ListState {
|
||||
&mut self.chat_list_state
|
||||
}
|
||||
|
||||
pub fn selected_chat_id(&self) -> Option<ChatId> {
|
||||
self.selected_chat_id
|
||||
}
|
||||
|
||||
pub fn set_selected_chat_id(&mut self, id: Option<ChatId>) {
|
||||
self.selected_chat_id = id;
|
||||
}
|
||||
|
||||
pub fn message_input(&self) -> &str {
|
||||
&self.message_input
|
||||
}
|
||||
|
||||
pub fn message_input_mut(&mut self) -> &mut String {
|
||||
&mut self.message_input
|
||||
}
|
||||
|
||||
pub fn set_message_input(&mut self, input: String) {
|
||||
self.message_input = input;
|
||||
}
|
||||
|
||||
pub fn cursor_position(&self) -> usize {
|
||||
self.cursor_position
|
||||
}
|
||||
|
||||
pub fn set_cursor_position(&mut self, pos: usize) {
|
||||
self.cursor_position = pos;
|
||||
}
|
||||
|
||||
pub fn message_scroll_offset(&self) -> usize {
|
||||
self.message_scroll_offset
|
||||
}
|
||||
|
||||
pub fn set_message_scroll_offset(&mut self, offset: usize) {
|
||||
self.message_scroll_offset = offset;
|
||||
}
|
||||
|
||||
pub fn selected_folder_id(&self) -> Option<i32> {
|
||||
self.selected_folder_id
|
||||
}
|
||||
|
||||
pub fn set_selected_folder_id(&mut self, id: Option<i32>) {
|
||||
self.selected_folder_id = id;
|
||||
}
|
||||
|
||||
pub fn is_loading(&self) -> bool {
|
||||
self.is_loading
|
||||
}
|
||||
|
||||
pub fn set_loading(&mut self, loading: bool) {
|
||||
self.is_loading = loading;
|
||||
}
|
||||
|
||||
// Search state
|
||||
pub fn is_searching(&self) -> bool {
|
||||
self.is_searching
|
||||
}
|
||||
|
||||
pub fn set_searching(&mut self, searching: bool) {
|
||||
self.is_searching = searching;
|
||||
}
|
||||
|
||||
pub fn search_query(&self) -> &str {
|
||||
&self.search_query
|
||||
}
|
||||
|
||||
pub fn search_query_mut(&mut self) -> &mut String {
|
||||
&mut self.search_query
|
||||
}
|
||||
|
||||
pub fn set_search_query(&mut self, query: String) {
|
||||
self.search_query = query;
|
||||
}
|
||||
|
||||
// Redraw flag
|
||||
pub fn needs_redraw(&self) -> bool {
|
||||
self.needs_redraw
|
||||
}
|
||||
|
||||
pub fn set_needs_redraw(&mut self, redraw: bool) {
|
||||
self.needs_redraw = redraw;
|
||||
}
|
||||
|
||||
pub fn mark_for_redraw(&mut self) {
|
||||
self.needs_redraw = true;
|
||||
}
|
||||
|
||||
// Typing indicator
|
||||
pub fn last_typing_sent(&self) -> Option<std::time::Instant> {
|
||||
self.last_typing_sent
|
||||
}
|
||||
|
||||
pub fn set_last_typing_sent(&mut self, time: Option<std::time::Instant>) {
|
||||
self.last_typing_sent = time;
|
||||
}
|
||||
|
||||
pub fn update_last_typing_sent(&mut self) {
|
||||
self.last_typing_sent = Some(std::time::Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,15 +326,15 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
}
|
||||
|
||||
// Форматируем время (HH:MM) с учётом timezone из config
|
||||
let time = format_timestamp_with_tz(msg.date(), &app.config.general.timezone);
|
||||
let time = format_timestamp_with_tz(msg.date(), &app.config().general.timezone);
|
||||
|
||||
// Цвет сообщения (из config или жёлтый если выбрано)
|
||||
let msg_color = if is_selected {
|
||||
app.config.parse_color(&app.config.colors.selected_message)
|
||||
app.config().parse_color(&app.config().colors.selected_message)
|
||||
} else if msg.is_outgoing() {
|
||||
app.config.parse_color(&app.config.colors.outgoing_message)
|
||||
app.config().parse_color(&app.config().colors.outgoing_message)
|
||||
} else {
|
||||
app.config.parse_color(&app.config.colors.incoming_message)
|
||||
app.config().parse_color(&app.config().colors.incoming_message)
|
||||
};
|
||||
|
||||
// Маркер выбора
|
||||
@@ -531,10 +531,10 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
||||
|
||||
let style = if reaction.is_chosen {
|
||||
Style::default()
|
||||
.fg(app.config.parse_color(&app.config.colors.reaction_chosen))
|
||||
.fg(app.config().parse_color(&app.config().colors.reaction_chosen))
|
||||
} else {
|
||||
Style::default()
|
||||
.fg(app.config.parse_color(&app.config.colors.reaction_other))
|
||||
.fg(app.config().parse_color(&app.config().colors.reaction_other))
|
||||
};
|
||||
|
||||
reaction_spans.push(Span::styled(reaction_text, style));
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
pub mod formatting;
|
||||
pub mod modal_handler;
|
||||
pub mod retry;
|
||||
pub mod tdlib;
|
||||
pub mod validation;
|
||||
|
||||
pub use formatting::*;
|
||||
pub use modal_handler::*;
|
||||
pub use retry::{with_timeout, with_timeout_msg};
|
||||
pub use tdlib::*;
|
||||
pub use validation::*;
|
||||
|
||||
184
src/utils/modal_handler.rs
Normal file
184
src/utils/modal_handler.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
//! Modal dialog utilities
|
||||
//!
|
||||
//! Переиспользуемая логика для обработки модальных окон (диалогов).
|
||||
|
||||
use crossterm::event::KeyCode;
|
||||
|
||||
/// Результат обработки клавиши в модальном окне.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ModalAction {
|
||||
/// Закрыть модалку (Escape была нажата)
|
||||
Close,
|
||||
/// Подтвердить действие (Enter была нажата)
|
||||
Confirm,
|
||||
/// Продолжить обработку ввода (другая клавиша)
|
||||
Continue,
|
||||
}
|
||||
|
||||
/// Обрабатывает стандартные клавиши для модальных окон.
|
||||
///
|
||||
/// Проверяет клавиши Escape (закрыть) и Enter (подтвердить).
|
||||
/// Если нажата другая клавиша, возвращает `Continue`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key_code` - код нажатой клавиши
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `ModalAction::Close` - если нажата Escape
|
||||
/// * `ModalAction::Confirm` - если нажата Enter
|
||||
/// * `ModalAction::Continue` - для других клавиш
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use crossterm::event::KeyCode;
|
||||
/// use tele_tui::utils::modal_handler::{handle_modal_key, ModalAction};
|
||||
///
|
||||
/// assert_eq!(handle_modal_key(KeyCode::Esc), ModalAction::Close);
|
||||
/// assert_eq!(handle_modal_key(KeyCode::Enter), ModalAction::Confirm);
|
||||
/// assert_eq!(handle_modal_key(KeyCode::Char('a')), ModalAction::Continue);
|
||||
/// ```
|
||||
pub fn handle_modal_key(key_code: KeyCode) -> ModalAction {
|
||||
match key_code {
|
||||
KeyCode::Esc => ModalAction::Close,
|
||||
KeyCode::Enter => ModalAction::Confirm,
|
||||
_ => ModalAction::Continue,
|
||||
}
|
||||
}
|
||||
|
||||
/// Проверяет, нужно ли закрыть модалку (нажата Escape).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use crossterm::event::KeyCode;
|
||||
/// use tele_tui::utils::modal_handler::should_close_modal;
|
||||
///
|
||||
/// assert!(should_close_modal(KeyCode::Esc));
|
||||
/// assert!(!should_close_modal(KeyCode::Enter));
|
||||
/// assert!(!should_close_modal(KeyCode::Char('q')));
|
||||
/// ```
|
||||
pub fn should_close_modal(key_code: KeyCode) -> bool {
|
||||
matches!(key_code, KeyCode::Esc)
|
||||
}
|
||||
|
||||
/// Проверяет, нужно ли подтвердить действие в модалке (нажата Enter).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use crossterm::event::KeyCode;
|
||||
/// use tele_tui::utils::modal_handler::should_confirm_modal;
|
||||
///
|
||||
/// assert!(should_confirm_modal(KeyCode::Enter));
|
||||
/// assert!(!should_confirm_modal(KeyCode::Esc));
|
||||
/// assert!(!should_confirm_modal(KeyCode::Char('y')));
|
||||
/// ```
|
||||
pub fn should_confirm_modal(key_code: KeyCode) -> bool {
|
||||
matches!(key_code, KeyCode::Enter)
|
||||
}
|
||||
|
||||
/// Обрабатывает клавиши для подтверждения Yes/No.
|
||||
///
|
||||
/// Поддерживает:
|
||||
/// - `y` / `Y` / `д` / `Д` - да (confirm)
|
||||
/// - `n` / `N` / `т` / `Т` - нет (close)
|
||||
/// - `Enter` - подтвердить (confirm)
|
||||
/// - `Esc` - отменить (close)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key_code` - код нажатой клавиши
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Some(true)` - подтверждение (yes/Enter)
|
||||
/// * `Some(false)` - отмена (no/Escape)
|
||||
/// * `None` - другая клавиша (продолжить ввод)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use crossterm::event::KeyCode;
|
||||
/// use tele_tui::utils::modal_handler::handle_yes_no;
|
||||
///
|
||||
/// assert_eq!(handle_yes_no(KeyCode::Char('y')), Some(true));
|
||||
/// assert_eq!(handle_yes_no(KeyCode::Char('Y')), Some(true));
|
||||
/// assert_eq!(handle_yes_no(KeyCode::Char('д')), Some(true)); // русская 'y'
|
||||
/// assert_eq!(handle_yes_no(KeyCode::Enter), Some(true));
|
||||
///
|
||||
/// assert_eq!(handle_yes_no(KeyCode::Char('n')), Some(false));
|
||||
/// assert_eq!(handle_yes_no(KeyCode::Char('т')), Some(false)); // русская 'n'
|
||||
/// assert_eq!(handle_yes_no(KeyCode::Esc), Some(false));
|
||||
///
|
||||
/// assert_eq!(handle_yes_no(KeyCode::Char('a')), None);
|
||||
/// ```
|
||||
pub fn handle_yes_no(key_code: KeyCode) -> Option<bool> {
|
||||
match key_code {
|
||||
// Yes - подтверждение (английская и русская раскладка)
|
||||
KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Char('д') | KeyCode::Char('Д') => {
|
||||
Some(true)
|
||||
}
|
||||
KeyCode::Enter => Some(true),
|
||||
|
||||
// No - отмена (английская и русская раскладка)
|
||||
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Char('т') | KeyCode::Char('Т') => {
|
||||
Some(false)
|
||||
}
|
||||
KeyCode::Esc => Some(false),
|
||||
|
||||
// Другие клавиши - продолжить
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_handle_modal_key() {
|
||||
assert_eq!(handle_modal_key(KeyCode::Esc), ModalAction::Close);
|
||||
assert_eq!(handle_modal_key(KeyCode::Enter), ModalAction::Confirm);
|
||||
assert_eq!(handle_modal_key(KeyCode::Char('a')), ModalAction::Continue);
|
||||
assert_eq!(handle_modal_key(KeyCode::Up), ModalAction::Continue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_close_modal() {
|
||||
assert!(should_close_modal(KeyCode::Esc));
|
||||
assert!(!should_close_modal(KeyCode::Enter));
|
||||
assert!(!should_close_modal(KeyCode::Char('q')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_confirm_modal() {
|
||||
assert!(should_confirm_modal(KeyCode::Enter));
|
||||
assert!(!should_confirm_modal(KeyCode::Esc));
|
||||
assert!(!should_confirm_modal(KeyCode::Char('y')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_yes_no() {
|
||||
// Yes variants
|
||||
assert_eq!(handle_yes_no(KeyCode::Char('y')), Some(true));
|
||||
assert_eq!(handle_yes_no(KeyCode::Char('Y')), Some(true));
|
||||
assert_eq!(handle_yes_no(KeyCode::Char('д')), Some(true)); // Russian
|
||||
assert_eq!(handle_yes_no(KeyCode::Char('Д')), Some(true)); // Russian
|
||||
assert_eq!(handle_yes_no(KeyCode::Enter), Some(true));
|
||||
|
||||
// No variants
|
||||
assert_eq!(handle_yes_no(KeyCode::Char('n')), Some(false));
|
||||
assert_eq!(handle_yes_no(KeyCode::Char('N')), Some(false));
|
||||
assert_eq!(handle_yes_no(KeyCode::Char('т')), Some(false)); // Russian
|
||||
assert_eq!(handle_yes_no(KeyCode::Char('Т')), Some(false)); // Russian
|
||||
assert_eq!(handle_yes_no(KeyCode::Esc), Some(false));
|
||||
|
||||
// Other keys
|
||||
assert_eq!(handle_yes_no(KeyCode::Char('a')), None);
|
||||
assert_eq!(handle_yes_no(KeyCode::Up), None);
|
||||
assert_eq!(handle_yes_no(KeyCode::Char(' ')), None);
|
||||
}
|
||||
}
|
||||
191
src/utils/validation.rs
Normal file
191
src/utils/validation.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
//! Input validation utilities
|
||||
//!
|
||||
//! Переиспользуемые валидаторы для проверки пользовательского ввода.
|
||||
|
||||
use crate::types::{ChatId, MessageId, UserId};
|
||||
|
||||
/// Проверяет, что строка не пустая (после trim).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tele_tui::utils::validation::is_non_empty;
|
||||
///
|
||||
/// assert!(is_non_empty("hello"));
|
||||
/// assert!(is_non_empty(" text "));
|
||||
/// assert!(!is_non_empty(""));
|
||||
/// assert!(!is_non_empty(" "));
|
||||
/// ```
|
||||
pub fn is_non_empty(text: &str) -> bool {
|
||||
!text.trim().is_empty()
|
||||
}
|
||||
|
||||
/// Проверяет, что текст не превышает максимальную длину.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - текст для проверки
|
||||
/// * `max_length` - максимальная длина в символах
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tele_tui::utils::validation::is_within_length;
|
||||
///
|
||||
/// assert!(is_within_length("hello", 10));
|
||||
/// assert!(!is_within_length("very long text here", 5));
|
||||
/// ```
|
||||
pub fn is_within_length(text: &str, max_length: usize) -> bool {
|
||||
text.chars().count() <= max_length
|
||||
}
|
||||
|
||||
/// Проверяет валидность ID чата (не нулевой).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tele_tui::types::ChatId;
|
||||
/// use tele_tui::utils::validation::is_valid_chat_id;
|
||||
///
|
||||
/// assert!(is_valid_chat_id(ChatId::new(123)));
|
||||
/// assert!(!is_valid_chat_id(ChatId::new(0)));
|
||||
/// assert!(!is_valid_chat_id(ChatId::new(-1)));
|
||||
/// ```
|
||||
pub fn is_valid_chat_id(chat_id: ChatId) -> bool {
|
||||
chat_id.as_i64() > 0
|
||||
}
|
||||
|
||||
/// Проверяет валидность ID сообщения (не нулевой).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tele_tui::types::MessageId;
|
||||
/// use tele_tui::utils::validation::is_valid_message_id;
|
||||
///
|
||||
/// assert!(is_valid_message_id(MessageId::new(456)));
|
||||
/// assert!(!is_valid_message_id(MessageId::new(0)));
|
||||
/// ```
|
||||
pub fn is_valid_message_id(message_id: MessageId) -> bool {
|
||||
message_id.as_i64() > 0
|
||||
}
|
||||
|
||||
/// Проверяет валидность ID пользователя (не нулевой).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tele_tui::types::UserId;
|
||||
/// use tele_tui::utils::validation::is_valid_user_id;
|
||||
///
|
||||
/// assert!(is_valid_user_id(UserId::new(789)));
|
||||
/// assert!(!is_valid_user_id(UserId::new(0)));
|
||||
/// ```
|
||||
pub fn is_valid_user_id(user_id: UserId) -> bool {
|
||||
user_id.as_i64() > 0
|
||||
}
|
||||
|
||||
/// Проверяет, что вектор не пустой.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tele_tui::utils::validation::has_items;
|
||||
///
|
||||
/// assert!(has_items(&vec![1, 2, 3]));
|
||||
/// assert!(!has_items::<i32>(&vec![]));
|
||||
/// ```
|
||||
pub fn has_items<T>(items: &[T]) -> bool {
|
||||
!items.is_empty()
|
||||
}
|
||||
|
||||
/// Комбинированная валидация текстового ввода:
|
||||
/// - Не пустой (после trim)
|
||||
/// - В пределах максимальной длины
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tele_tui::utils::validation::validate_text_input;
|
||||
///
|
||||
/// assert!(validate_text_input("hello", 100).is_ok());
|
||||
/// assert!(validate_text_input("", 100).is_err());
|
||||
/// assert!(validate_text_input(" ", 100).is_err());
|
||||
/// assert!(validate_text_input("very long text", 5).is_err());
|
||||
/// ```
|
||||
pub fn validate_text_input(text: &str, max_length: usize) -> Result<(), String> {
|
||||
if !is_non_empty(text) {
|
||||
return Err("Text cannot be empty".to_string());
|
||||
}
|
||||
if !is_within_length(text, max_length) {
|
||||
return Err(format!(
|
||||
"Text exceeds maximum length of {} characters",
|
||||
max_length
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_non_empty() {
|
||||
assert!(is_non_empty("hello"));
|
||||
assert!(is_non_empty(" text "));
|
||||
assert!(!is_non_empty(""));
|
||||
assert!(!is_non_empty(" "));
|
||||
assert!(!is_non_empty("\t\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_within_length() {
|
||||
assert!(is_within_length("hello", 10));
|
||||
assert!(is_within_length("hello", 5));
|
||||
assert!(!is_within_length("hello", 4));
|
||||
assert!(is_within_length("", 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_valid_chat_id() {
|
||||
assert!(is_valid_chat_id(ChatId::new(123)));
|
||||
assert!(is_valid_chat_id(ChatId::new(999999)));
|
||||
assert!(!is_valid_chat_id(ChatId::new(0)));
|
||||
assert!(!is_valid_chat_id(ChatId::new(-1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_valid_message_id() {
|
||||
assert!(is_valid_message_id(MessageId::new(456)));
|
||||
assert!(!is_valid_message_id(MessageId::new(0)));
|
||||
assert!(!is_valid_message_id(MessageId::new(-1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_valid_user_id() {
|
||||
assert!(is_valid_user_id(UserId::new(789)));
|
||||
assert!(!is_valid_user_id(UserId::new(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_items() {
|
||||
assert!(has_items(&vec![1, 2, 3]));
|
||||
assert!(has_items(&vec!["a"]));
|
||||
assert!(!has_items::<i32>(&vec![]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_text_input() {
|
||||
// Valid
|
||||
assert!(validate_text_input("hello", 100).is_ok());
|
||||
assert!(validate_text_input("test message", 20).is_ok());
|
||||
|
||||
// Empty
|
||||
assert!(validate_text_input("", 100).is_err());
|
||||
assert!(validate_text_input(" ", 100).is_err());
|
||||
|
||||
// Too long
|
||||
assert!(validate_text_input("very long text", 5).is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user