diff --git a/examples/parts/arrays.rs b/examples/parts/arrays.rs index 7f68638..260a301 100644 --- a/examples/parts/arrays.rs +++ b/examples/parts/arrays.rs @@ -1,7 +1,4 @@ -use serde::{ - Deserialize, - Serialize, -}; +use serde::{Deserialize, Serialize}; use serde_json::json; use toon_format::encode_default; diff --git a/examples/parts/arrays_of_arrays.rs b/examples/parts/arrays_of_arrays.rs index 5eb6ae7..bd6c319 100644 --- a/examples/parts/arrays_of_arrays.rs +++ b/examples/parts/arrays_of_arrays.rs @@ -1,7 +1,4 @@ -use serde::{ - Deserialize, - Serialize, -}; +use serde::{Deserialize, Serialize}; use serde_json::json; use toon_format::encode_default; diff --git a/examples/parts/decode_strict.rs b/examples/parts/decode_strict.rs index 14303bd..5af6bf9 100644 --- a/examples/parts/decode_strict.rs +++ b/examples/parts/decode_strict.rs @@ -1,8 +1,5 @@ use serde_json::Value; -use toon_format::{ - decode, - DecodeOptions, -}; +use toon_format::{decode, DecodeOptions}; pub fn decode_strict() { // Malformed: header says 2 rows, but only 1 provided diff --git a/examples/parts/delimiters.rs b/examples/parts/delimiters.rs index b44eeaa..338e7ee 100644 --- a/examples/parts/delimiters.rs +++ b/examples/parts/delimiters.rs @@ -1,9 +1,5 @@ use serde_json::json; -use toon_format::{ - encode, - Delimiter, - EncodeOptions, -}; +use toon_format::{encode, Delimiter, EncodeOptions}; pub fn delimiters() { let data = json!({ diff --git a/examples/parts/mixed_arrays.rs b/examples/parts/mixed_arrays.rs index c3e120b..3cf4afd 100644 --- a/examples/parts/mixed_arrays.rs +++ b/examples/parts/mixed_arrays.rs @@ -1,7 +1,4 @@ -use serde::{ - Deserialize, - Serialize, -}; +use serde::{Deserialize, Serialize}; use serde_json::json; use toon_format::encode_default; diff --git a/examples/parts/objects.rs b/examples/parts/objects.rs index d7b1bca..da6769f 100644 --- a/examples/parts/objects.rs +++ b/examples/parts/objects.rs @@ -1,7 +1,4 @@ -use serde::{ - Deserialize, - Serialize, -}; +use serde::{Deserialize, Serialize}; use serde_json::json; use toon_format::encode_default; diff --git a/examples/parts/round_trip.rs b/examples/parts/round_trip.rs index 5c5c3bd..b5873a8 100644 --- a/examples/parts/round_trip.rs +++ b/examples/parts/round_trip.rs @@ -1,15 +1,6 @@ -use serde::{ - Deserialize, - Serialize, -}; -use serde_json::{ - json, - Value, -}; -use toon_format::{ - decode_default, - encode_default, -}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use toon_format::{decode_default, encode_default}; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Product { diff --git a/examples/parts/structs.rs b/examples/parts/structs.rs index 7b49e48..14ec066 100644 --- a/examples/parts/structs.rs +++ b/examples/parts/structs.rs @@ -1,11 +1,5 @@ -use serde::{ - Deserialize, - Serialize, -}; -use toon_format::{ - decode_default, - encode_default, -}; +use serde::{Deserialize, Serialize}; +use toon_format::{decode_default, encode_default}; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct User { diff --git a/examples/parts/tabular.rs b/examples/parts/tabular.rs index 1e210ca..c93ba08 100644 --- a/examples/parts/tabular.rs +++ b/examples/parts/tabular.rs @@ -1,7 +1,4 @@ -use serde::{ - Deserialize, - Serialize, -}; +use serde::{Deserialize, Serialize}; use serde_json::json; use toon_format::encode_default; diff --git a/src/cli/main.rs b/src/cli/main.rs index ecc8e9f..311fb00 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -1,36 +1,17 @@ use std::{ fs, - io::{ - self, - Read, - Write, - }, - path::{ - Path, - PathBuf, - }, + io::{self, Read, Write}, + path::{Path, PathBuf}, }; -use anyhow::{ - bail, - Context, - Result, -}; +use anyhow::{bail, Context, Result}; use clap::Parser; use comfy_table::Table; use serde::Serialize; use tiktoken_rs::cl100k_base; use toon_format::{ - decode, - encode, - types::{ - DecodeOptions, - Delimiter, - EncodeOptions, - Indent, - KeyFoldingMode, - PathExpansionMode, - }, + decode, encode, + types::{DecodeOptions, Delimiter, EncodeOptions, Indent, KeyFoldingMode, PathExpansionMode}, }; #[derive(Parser, Debug)] diff --git a/src/decode/expansion.rs b/src/decode/expansion.rs index cd645bc..3a9d080 100644 --- a/src/decode/expansion.rs +++ b/src/decode/expansion.rs @@ -2,13 +2,7 @@ use indexmap::IndexMap; use crate::{ constants::QUOTED_KEY_MARKER, - types::{ - is_identifier_segment, - JsonValue as Value, - PathExpansionMode, - ToonError, - ToonResult, - }, + types::{is_identifier_segment, JsonValue as Value, PathExpansionMode, ToonError, ToonResult}, }; pub fn should_expand_key(key: &str, mode: PathExpansionMode) -> Option> { @@ -90,7 +84,7 @@ pub fn deep_merge_value( } } else { target.insert(first_key.clone(), Value::Object(IndexMap::new())); - match target.get_mut(first_key).unwrap() { + match target.get_mut(first_key).expect("key was just inserted") { Value::Object(obj) => obj, _ => unreachable!(), } diff --git a/src/decode/mod.rs b/src/decode/mod.rs index 574b3a7..420055c 100644 --- a/src/decode/mod.rs +++ b/src/decode/mod.rs @@ -6,10 +6,7 @@ pub mod validation; use serde_json::Value; -use crate::types::{ - DecodeOptions, - ToonResult, -}; +use crate::types::{DecodeOptions, ToonResult}; /// Decode a TOON string into any deserializable type. /// diff --git a/src/decode/parser.rs b/src/decode/parser.rs index 0f40084..77f8fa6 100644 --- a/src/decode/parser.rs +++ b/src/decode/parser.rs @@ -1,29 +1,12 @@ -use serde_json::{ - Map, - Number, - Value, -}; +use serde_json::{Map, Number, Value}; use crate::{ - constants::{ - KEYWORDS, - MAX_DEPTH, - QUOTED_KEY_MARKER, - }, + constants::{KEYWORDS, MAX_DEPTH, QUOTED_KEY_MARKER}, decode::{ - scanner::{ - Scanner, - Token, - }, + scanner::{Scanner, Token}, validation, }, - types::{ - DecodeOptions, - Delimiter, - ErrorContext, - ToonError, - ToonResult, - }, + types::{DecodeOptions, Delimiter, ErrorContext, ToonError, ToonResult}, utils::validation::validate_depth, }; @@ -1441,10 +1424,7 @@ mod tests { #[test] fn test_round_trip_parentheses() { - use crate::{ - decode::decode_default, - encode::encode_default, - }; + use crate::{decode::decode_default, encode::encode_default}; let original = json!({ "message": "Mostly Functions (3 of 3)", diff --git a/src/decode/scanner.rs b/src/decode/scanner.rs index 2301330..9ca7615 100644 --- a/src/decode/scanner.rs +++ b/src/decode/scanner.rs @@ -1,8 +1,4 @@ -use crate::types::{ - Delimiter, - ToonError, - ToonResult, -}; +use crate::types::{Delimiter, ToonError, ToonResult}; /// Tokens produced by the scanner during lexical analysis. #[derive(Debug, Clone, PartialEq)] @@ -352,9 +348,10 @@ impl Scanner { // Leading zeros like "05" are strings, but "0", "0.5", "-0" are numbers if s.starts_with('0') && s.len() > 1 { - let second_char = s.chars().nth(1).unwrap(); - if second_char.is_ascii_digit() { - return Ok(Token::String(s.to_string(), false)); + if let Some(second_char) = s.chars().nth(1) { + if second_char.is_ascii_digit() { + return Ok(Token::String(s.to_string(), false)); + } } } @@ -458,12 +455,18 @@ impl Scanner { _ => {} } - if trimmed.starts_with('-') || trimmed.chars().next().unwrap().is_ascii_digit() { + if trimmed.starts_with('-') + || trimmed + .chars() + .next() + .is_some_and(|c| c.is_ascii_digit()) + { // Leading zeros like "05" are strings if trimmed.starts_with('0') && trimmed.len() > 1 { - let second_char = trimmed.chars().nth(1).unwrap(); - if second_char.is_ascii_digit() { - return Ok(Token::String(trimmed.to_string(), false)); + if let Some(second_char) = trimmed.chars().nth(1) { + if second_char.is_ascii_digit() { + return Ok(Token::String(trimmed.to_string(), false)); + } } } diff --git a/src/decode/validation.rs b/src/decode/validation.rs index 40608ef..9ff36b9 100644 --- a/src/decode/validation.rs +++ b/src/decode/validation.rs @@ -1,7 +1,4 @@ -use crate::types::{ - ToonError, - ToonResult, -}; +use crate::types::{ToonError, ToonResult}; /// Validate that array length matches expected value. pub fn validate_array_length(expected: usize, actual: usize) -> ToonResult<()> { diff --git a/src/encode/folding.rs b/src/encode/folding.rs index beec7ae..0034970 100644 --- a/src/encode/folding.rs +++ b/src/encode/folding.rs @@ -1,8 +1,4 @@ -use crate::types::{ - is_identifier_segment, - JsonValue as Value, - KeyFoldingMode, -}; +use crate::types::{is_identifier_segment, JsonValue as Value, KeyFoldingMode}; /// Result of chain analysis for folding. pub struct FoldableChain { diff --git a/src/encode/mod.rs b/src/encode/mod.rs index 0a11987..69b2d1b 100644 --- a/src/encode/mod.rs +++ b/src/encode/mod.rs @@ -7,19 +7,9 @@ use indexmap::IndexMap; use crate::{ constants::MAX_DEPTH, types::{ - EncodeOptions, - IntoJsonValue, - JsonValue as Value, - KeyFoldingMode, - ToonError, - ToonResult, - }, - utils::{ - format_canonical_number, - normalize, - validation::validate_depth, - QuotingContext, + EncodeOptions, IntoJsonValue, JsonValue as Value, KeyFoldingMode, ToonError, ToonResult, }, + utils::{format_canonical_number, normalize, validation::validate_depth, QuotingContext}, }; /// Encode any serializable value to TOON format. @@ -225,7 +215,7 @@ fn write_object_impl( writer.write_newline()?; } - let value = &obj[*key]; + let value = obj.get(*key).expect("key exists in field list"); // Check if this key-value pair can be folded (v1.5 feature) // Don't fold if any sibling key is a dotted path starting with this key diff --git a/src/encode/writer.rs b/src/encode/writer.rs index ba8ad96..7edf35e 100644 --- a/src/encode/writer.rs +++ b/src/encode/writer.rs @@ -1,15 +1,7 @@ use crate::{ - types::{ - Delimiter, - EncodeOptions, - ToonResult, - }, + types::{Delimiter, EncodeOptions, ToonResult}, utils::{ - string::{ - is_valid_unquoted_key, - needs_quoting, - quote_string, - }, + string::{is_valid_unquoted_key, needs_quoting, quote_string}, QuotingContext, }, }; diff --git a/src/lib.rs b/src/lib.rs index 67c0133..ce59d22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,66 +34,27 @@ pub mod types; pub mod utils; pub use decode::{ - decode, - decode_default, - decode_no_coerce, - decode_no_coerce_with_options, - decode_strict, + decode, decode_default, decode_no_coerce, decode_no_coerce_with_options, decode_strict, decode_strict_with_options, }; -pub use encode::{ - encode, - encode_array, - encode_default, - encode_object, -}; -pub use types::{ - DecodeOptions, - Delimiter, - EncodeOptions, - Indent, - ToonError, -}; +pub use encode::{encode, encode_array, encode_default, encode_object}; +pub use types::{DecodeOptions, Delimiter, EncodeOptions, Indent, ToonError}; pub use utils::{ - literal::{ - is_keyword, - is_literal_like, - }, + literal::{is_keyword, is_literal_like}, normalize, - string::{ - escape_string, - is_valid_unquoted_key, - needs_quoting, - }, + string::{escape_string, is_valid_unquoted_key, needs_quoting}, }; #[cfg(test)] mod tests { - use serde_json::{ - json, - Value, - }; + use serde_json::{json, Value}; use crate::{ constants::is_keyword, - decode::{ - decode_default, - decode_strict, - }, - encode::{ - encode, - encode_default, - }, - types::{ - Delimiter, - EncodeOptions, - }, - utils::{ - escape_string, - is_literal_like, - needs_quoting, - normalize, - }, + decode::{decode_default, decode_strict}, + encode::{encode, encode_default}, + types::{Delimiter, EncodeOptions}, + utils::{escape_string, is_literal_like, needs_quoting, normalize}, }; #[test] @@ -160,10 +121,7 @@ mod tests { assert!(needs_quoting("true", Delimiter::Comma.as_char())); } - use serde::{ - Deserialize, - Serialize, - }; + use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct TestUser { @@ -174,10 +132,7 @@ mod tests { #[test] fn test_encode_decode_simple_struct() { - use crate::{ - decode_default, - encode_default, - }; + use crate::{decode_default, encode_default}; let user = TestUser { name: "Alice".to_string(), @@ -203,10 +158,7 @@ mod tests { #[test] fn test_encode_decode_with_array() { - use crate::{ - decode_default, - encode_default, - }; + use crate::{decode_default, encode_default}; let product = TestProduct { id: 42, @@ -221,10 +173,7 @@ mod tests { #[test] fn test_encode_decode_vec_of_structs() { - use crate::{ - decode_default, - encode_default, - }; + use crate::{decode_default, encode_default}; let users = vec![ TestUser { @@ -262,10 +211,7 @@ mod tests { #[test] fn test_encode_decode_nested_structs() { - use crate::{ - decode_default, - encode_default, - }; + use crate::{decode_default, encode_default}; let nested = Nested { outer: OuterStruct { @@ -283,10 +229,7 @@ mod tests { #[test] fn test_round_trip_list_item_tabular_v3() { - use crate::{ - decode_default, - encode_default, - }; + use crate::{decode_default, encode_default}; let original = json!({ "items": [ @@ -309,10 +252,7 @@ mod tests { #[test] fn test_round_trip_complex_list_item_tabular_v3() { - use crate::{ - decode_default, - encode_default, - }; + use crate::{decode_default, encode_default}; let original = json!({ "data": [ @@ -342,10 +282,7 @@ mod tests { #[test] fn test_round_trip_mixed_list_items_v3() { - use crate::{ - decode_default, - encode_default, - }; + use crate::{decode_default, encode_default}; let original = json!({ "entries": [ diff --git a/src/tui/app.rs b/src/tui/app.rs index b5bbb6b..7b23c31 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,39 +1,18 @@ -use std::{ - fs, - path::PathBuf, - time::Duration, -}; +use std::{fs, path::PathBuf, time::Duration}; -use anyhow::{ - Context, - Result, -}; +use anyhow::{Context, Result}; use chrono::Local; -use crossterm::event::{ - KeyCode, - KeyEvent, -}; +use crossterm::event::{KeyCode, KeyEvent}; use tiktoken_rs::cl100k_base; use crate::{ - decode, - encode, + decode, encode, tui::{ components::FileBrowser, - events::{ - Event, - EventHandler, - }, - keybindings::{ - Action, - KeyBindings, - }, + events::{Event, EventHandler}, + keybindings::{Action, KeyBindings}, repl_command::ReplCommand, - state::{ - app_state::ConversionStats, - AppState, - ConversionHistory, - }, + state::{app_state::ConversionStats, AppState, ConversionHistory}, ui, }, }; diff --git a/src/tui/components/diff_viewer.rs b/src/tui/components/diff_viewer.rs index 516e73d..9494fb8 100644 --- a/src/tui/components/diff_viewer.rs +++ b/src/tui/components/diff_viewer.rs @@ -1,30 +1,13 @@ //! Side-by-side diff viewer for input/output comparison. use ratatui::{ - layout::{ - Alignment, - Constraint, - Direction, - Layout, - Rect, - }, - text::{ - Line, - Span, - }, - widgets::{ - Block, - Borders, - Paragraph, - Wrap, - }, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + text::{Line, Span}, + widgets::{Block, Borders, Paragraph, Wrap}, Frame, }; -use crate::tui::{ - state::AppState, - theme::Theme, -}; +use crate::tui::{state::AppState, theme::Theme}; pub struct DiffViewer; diff --git a/src/tui/components/editor.rs b/src/tui/components/editor.rs index 6171f21..00fb043 100644 --- a/src/tui/components/editor.rs +++ b/src/tui/components/editor.rs @@ -2,17 +2,11 @@ use ratatui::{ layout::Rect, - widgets::{ - Block, - Borders, - }, + widgets::{Block, Borders}, Frame, }; -use crate::tui::{ - state::AppState, - theme::Theme, -}; +use crate::tui::{state::AppState, theme::Theme}; pub struct EditorComponent; diff --git a/src/tui/components/file_browser.rs b/src/tui/components/file_browser.rs index 52baf55..4baf080 100644 --- a/src/tui/components/file_browser.rs +++ b/src/tui/components/file_browser.rs @@ -3,31 +3,13 @@ use std::fs; use ratatui::{ - layout::{ - Alignment, - Constraint, - Direction, - Layout, - Rect, - }, - text::{ - Line, - Span, - }, - widgets::{ - Block, - Borders, - List, - ListItem, - Paragraph, - }, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListItem, Paragraph}, Frame, }; -use crate::tui::{ - state::AppState, - theme::Theme, -}; +use crate::tui::{state::AppState, theme::Theme}; /// File browser state and rendering. pub struct FileBrowser { diff --git a/src/tui/components/help_screen.rs b/src/tui/components/help_screen.rs index 2f2be40..8cfe995 100644 --- a/src/tui/components/help_screen.rs +++ b/src/tui/components/help_screen.rs @@ -1,31 +1,13 @@ //! Help screen showing keyboard shortcuts. use ratatui::{ - layout::{ - Alignment, - Constraint, - Direction, - Layout, - Rect, - }, - text::{ - Line, - Span, - }, - widgets::{ - Block, - Borders, - List, - ListItem, - Paragraph, - }, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListItem, Paragraph}, Frame, }; -use crate::tui::{ - keybindings::KeyBindings, - theme::Theme, -}; +use crate::tui::{keybindings::KeyBindings, theme::Theme}; pub struct HelpScreen; diff --git a/src/tui/components/history_panel.rs b/src/tui/components/history_panel.rs index 9bdc945..764a09d 100644 --- a/src/tui/components/history_panel.rs +++ b/src/tui/components/history_panel.rs @@ -1,31 +1,13 @@ //! Conversion history panel. use ratatui::{ - layout::{ - Alignment, - Constraint, - Direction, - Layout, - Rect, - }, - text::{ - Line, - Span, - }, - widgets::{ - Block, - Borders, - List, - ListItem, - Paragraph, - }, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListItem, Paragraph}, Frame, }; -use crate::tui::{ - state::AppState, - theme::Theme, -}; +use crate::tui::{state::AppState, theme::Theme}; pub struct HistoryPanel; diff --git a/src/tui/components/repl_panel.rs b/src/tui/components/repl_panel.rs index 681a566..5acb36c 100644 --- a/src/tui/components/repl_panel.rs +++ b/src/tui/components/repl_panel.rs @@ -1,36 +1,12 @@ use ratatui::{ - layout::{ - Constraint, - Direction, - Layout, - Margin, - Rect, - }, - style::{ - Color, - Modifier, - Style, - }, - text::{ - Line, - Span, - }, - widgets::{ - Block, - Borders, - Paragraph, - Scrollbar, - ScrollbarOrientation, - ScrollbarState, - Wrap, - }, + layout::{Constraint, Direction, Layout, Margin, Rect}, + style::{Color, Modifier, Style}, + text::{Line, Span}, + widgets::{Block, Borders, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Wrap}, Frame, }; -use crate::tui::state::{ - AppState, - ReplLineKind, -}; +use crate::tui::state::{AppState, ReplLineKind}; pub struct ReplPanel; diff --git a/src/tui/components/settings_panel.rs b/src/tui/components/settings_panel.rs index c8d84c1..a7debad 100644 --- a/src/tui/components/settings_panel.rs +++ b/src/tui/components/settings_panel.rs @@ -1,38 +1,15 @@ //! Settings panel for configuring encode/decode options. use ratatui::{ - layout::{ - Alignment, - Constraint, - Direction, - Layout, - Rect, - }, - text::{ - Line, - Span, - }, - widgets::{ - Block, - Borders, - List, - ListItem, - Paragraph, - }, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListItem, Paragraph}, Frame, }; use crate::{ - tui::{ - state::AppState, - theme::Theme, - }, - types::{ - Delimiter, - Indent, - KeyFoldingMode, - PathExpansionMode, - }, + tui::{state::AppState, theme::Theme}, + types::{Delimiter, Indent, KeyFoldingMode, PathExpansionMode}, }; pub struct SettingsPanel; diff --git a/src/tui/components/stats_bar.rs b/src/tui/components/stats_bar.rs index 3691d9b..4afb0e8 100644 --- a/src/tui/components/stats_bar.rs +++ b/src/tui/components/stats_bar.rs @@ -2,22 +2,12 @@ use ratatui::{ layout::Rect, - text::{ - Line, - Span, - }, - widgets::{ - Block, - Borders, - Paragraph, - }, + text::{Line, Span}, + widgets::{Block, Borders, Paragraph}, Frame, }; -use crate::tui::{ - state::AppState, - theme::Theme, -}; +use crate::tui::{state::AppState, theme::Theme}; pub struct StatsBar; diff --git a/src/tui/components/status_bar.rs b/src/tui/components/status_bar.rs index 8d1fb00..999f650 100644 --- a/src/tui/components/status_bar.rs +++ b/src/tui/components/status_bar.rs @@ -1,29 +1,13 @@ //! Status bar showing mode, file, and key commands. use ratatui::{ - layout::{ - Alignment, - Constraint, - Direction, - Layout, - Rect, - }, - text::{ - Line, - Span, - }, - widgets::{ - Block, - Borders, - Paragraph, - }, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + text::{Line, Span}, + widgets::{Block, Borders, Paragraph}, Frame, }; -use crate::tui::{ - state::AppState, - theme::Theme, -}; +use crate::tui::{state::AppState, theme::Theme}; pub struct StatusBar; diff --git a/src/tui/events.rs b/src/tui/events.rs index 64a6e22..0d22c91 100644 --- a/src/tui/events.rs +++ b/src/tui/events.rs @@ -2,11 +2,7 @@ use std::time::Duration; -use crossterm::event::{ - self, - Event as CrosstermEvent, - KeyEvent, -}; +use crossterm::event::{self, Event as CrosstermEvent, KeyEvent}; /// TUI events. pub enum Event { diff --git a/src/tui/keybindings.rs b/src/tui/keybindings.rs index e82ccb1..f755e02 100644 --- a/src/tui/keybindings.rs +++ b/src/tui/keybindings.rs @@ -1,10 +1,6 @@ //! Keyboard shortcuts and action mapping. -use crossterm::event::{ - KeyCode, - KeyEvent, - KeyModifiers, -}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; /// Actions that can be triggered by keyboard shortcuts. #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/tui/mod.rs b/src/tui/mod.rs index faa352b..a7037ed 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -18,17 +18,9 @@ use anyhow::Result; pub use app::TuiApp; use crossterm::{ execute, - terminal::{ - disable_raw_mode, - enable_raw_mode, - EnterAlternateScreen, - LeaveAlternateScreen, - }, -}; -use ratatui::{ - backend::CrosstermBackend, - Terminal, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; +use ratatui::{backend::CrosstermBackend, Terminal}; /// Initialize and run the TUI application. /// diff --git a/src/tui/repl_command.rs b/src/tui/repl_command.rs index 5191f2f..33e15ee 100644 --- a/src/tui/repl_command.rs +++ b/src/tui/repl_command.rs @@ -1,9 +1,6 @@ //! REPL command parser with inline data support -use anyhow::{ - bail, - Result, -}; +use anyhow::{bail, Result}; /// Parsed REPL command with inline data #[derive(Debug, Clone)] diff --git a/src/tui/state/app_state.rs b/src/tui/state/app_state.rs index 7c70b32..d4925b8 100644 --- a/src/tui/state/app_state.rs +++ b/src/tui/state/app_state.rs @@ -1,20 +1,9 @@ //! Main application state. -use super::{ - EditorState, - FileState, - ReplState, -}; +use super::{EditorState, FileState, ReplState}; use crate::{ tui::theme::Theme, - types::{ - DecodeOptions, - Delimiter, - EncodeOptions, - Indent, - KeyFoldingMode, - PathExpansionMode, - }, + types::{DecodeOptions, Delimiter, EncodeOptions, Indent, KeyFoldingMode, PathExpansionMode}, }; /// Conversion mode (encode/decode). diff --git a/src/tui/state/file_state.rs b/src/tui/state/file_state.rs index 436e971..6467381 100644 --- a/src/tui/state/file_state.rs +++ b/src/tui/state/file_state.rs @@ -2,10 +2,7 @@ use std::path::PathBuf; -use chrono::{ - DateTime, - Local, -}; +use chrono::{DateTime, Local}; /// A file or directory entry. #[derive(Debug, Clone)] diff --git a/src/tui/state/mod.rs b/src/tui/state/mod.rs index 9943270..def4e5f 100644 --- a/src/tui/state/mod.rs +++ b/src/tui/state/mod.rs @@ -5,21 +5,7 @@ pub mod editor_state; pub mod file_state; pub mod repl_state; -pub use app_state::{ - AppState, - ConversionStats, - Mode, -}; -pub use editor_state::{ - EditorMode, - EditorState, -}; -pub use file_state::{ - ConversionHistory, - FileState, -}; -pub use repl_state::{ - ReplLine, - ReplLineKind, - ReplState, -}; +pub use app_state::{AppState, ConversionStats, Mode}; +pub use editor_state::{EditorMode, EditorState}; +pub use file_state::{ConversionHistory, FileState}; +pub use repl_state::{ReplLine, ReplLineKind, ReplState}; diff --git a/src/tui/theme.rs b/src/tui/theme.rs index 8d436d0..3cf5e56 100644 --- a/src/tui/theme.rs +++ b/src/tui/theme.rs @@ -1,10 +1,6 @@ //! Color themes for the TUI. -use ratatui::style::{ - Color, - Modifier, - Style, -}; +use ratatui::style::{Color, Modifier, Style}; /// Available color themes. #[derive(Debug, Clone, Copy, PartialEq, Default)] diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 2910c37..22e3ae2 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -1,42 +1,19 @@ use ratatui::{ - layout::{ - Alignment, - Constraint, - Direction, - Layout, - Rect, - }, - text::{ - Line, - Span, - }, - widgets::{ - Block, - Borders, - Paragraph, - }, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + text::{Line, Span}, + widgets::{Block, Borders, Paragraph}, Frame, }; use super::{ components::{ - DiffViewer, - EditorComponent, - FileBrowser, - HelpScreen, - HistoryPanel, - ReplPanel, - SettingsPanel, - StatsBar, - StatusBar, + DiffViewer, EditorComponent, FileBrowser, HelpScreen, HistoryPanel, ReplPanel, + SettingsPanel, StatsBar, StatusBar, }, state::AppState, theme::Theme, }; -use crate::types::{ - KeyFoldingMode, - PathExpansionMode, -}; +use crate::types::{KeyFoldingMode, PathExpansionMode}; /// Main render function - orchestrates all UI components. pub fn render(f: &mut Frame, app: &mut AppState, file_browser: &mut FileBrowser) { diff --git a/src/types/delimiter.rs b/src/types/delimiter.rs index e554e6a..cb387a5 100644 --- a/src/types/delimiter.rs +++ b/src/types/delimiter.rs @@ -1,9 +1,6 @@ use std::fmt; -use serde::{ - Deserialize, - Serialize, -}; +use serde::{Deserialize, Serialize}; /// Delimiter character used to separate array elements. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] diff --git a/src/types/mod.rs b/src/types/mod.rs index 42b524f..ebfaf68 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -5,23 +5,7 @@ mod options; mod value; pub use delimiter::Delimiter; -pub use errors::{ - ErrorContext, - ToonError, - ToonResult, -}; -pub use folding::{ - is_identifier_segment, - KeyFoldingMode, - PathExpansionMode, -}; -pub use options::{ - DecodeOptions, - EncodeOptions, - Indent, -}; -pub use value::{ - IntoJsonValue, - JsonValue, - Number, -}; +pub use errors::{ErrorContext, ToonError, ToonResult}; +pub use folding::{is_identifier_segment, KeyFoldingMode, PathExpansionMode}; +pub use options::{DecodeOptions, EncodeOptions, Indent}; +pub use value::{IntoJsonValue, JsonValue, Number}; diff --git a/src/types/options.rs b/src/types/options.rs index 5e1ecb3..ec4562c 100644 --- a/src/types/options.rs +++ b/src/types/options.rs @@ -1,10 +1,6 @@ use crate::{ constants::DEFAULT_INDENT, - types::{ - Delimiter, - KeyFoldingMode, - PathExpansionMode, - }, + types::{Delimiter, KeyFoldingMode, PathExpansionMode}, }; #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/types/value.rs b/src/types/value.rs index 15885cb..5a86e21 100644 --- a/src/types/value.rs +++ b/src/types/value.rs @@ -1,9 +1,6 @@ use std::{ fmt, - ops::{ - Index, - IndexMut, - }, + ops::{Index, IndexMut}, }; use indexmap::IndexMap; @@ -327,6 +324,20 @@ impl JsonValue { } } + pub fn get(&self, key: &str) -> Option<&JsonValue> { + match self { + JsonValue::Object(obj) => obj.get(key), + _ => None, + } + } + + pub fn get_index(&self, index: usize) -> Option<&JsonValue> { + match self { + JsonValue::Array(arr) => arr.get(index), + _ => None, + } + } + /// Takes the value, leaving Null in its place. pub fn take(&mut self) -> JsonValue { std::mem::replace(self, JsonValue::Null) @@ -380,8 +391,16 @@ impl Index for JsonValue { fn index(&self, index: usize) -> &Self::Output { match self { - JsonValue::Array(arr) => &arr[index], - _ => panic!("cannot index into non-array value with usize"), + JsonValue::Array(arr) => arr.get(index).unwrap_or_else(|| { + panic!( + "index {index} out of bounds for array of length {}", + arr.len() + ) + }), + _ => panic!( + "cannot index into non-array value of type {}", + self.type_name() + ), } } } @@ -389,8 +408,16 @@ impl Index for JsonValue { impl IndexMut for JsonValue { fn index_mut(&mut self, index: usize) -> &mut Self::Output { match self { - JsonValue::Array(arr) => &mut arr[index], - _ => panic!("cannot index into non-array value with usize"), + JsonValue::Array(arr) => { + let len = arr.len(); + arr.get_mut(index).unwrap_or_else(|| { + panic!("index {index} out of bounds for array of length {len}") + }) + } + _ => panic!( + "cannot index into non-array value of type {}", + self.type_name() + ), } } } @@ -400,10 +427,13 @@ impl Index<&str> for JsonValue { fn index(&self, key: &str) -> &Self::Output { match self { - JsonValue::Object(obj) => obj - .get(key) - .unwrap_or_else(|| panic!("key '{key}' not found in object")), - _ => panic!("cannot index into non-object value with &str"), + JsonValue::Object(obj) => obj.get(key).unwrap_or_else(|| { + panic!("key '{key}' not found in object with {} entries", obj.len()) + }), + _ => panic!( + "cannot index into non-object value of type {}", + self.type_name() + ), } } } @@ -411,10 +441,16 @@ impl Index<&str> for JsonValue { impl IndexMut<&str> for JsonValue { fn index_mut(&mut self, key: &str) -> &mut Self::Output { match self { - JsonValue::Object(obj) => obj - .get_mut(key) - .unwrap_or_else(|| panic!("key '{key}' not found in object")), - _ => panic!("cannot index into non-object value with &str"), + JsonValue::Object(obj) => { + let len = obj.len(); + obj.get_mut(key).unwrap_or_else(|| { + panic!("key '{key}' not found in object with {len} entries") + }) + } + _ => panic!( + "cannot index into non-object value of type {}", + self.type_name() + ), } } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index cf355c6..1b300cb 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,25 +4,13 @@ pub mod string; pub mod validation; use indexmap::IndexMap; -pub use literal::{ - is_keyword, - is_literal_like, - is_numeric_like, - is_structural_char, -}; +pub use literal::{is_keyword, is_literal_like, is_numeric_like, is_structural_char}; pub use number::format_canonical_number; pub use string::{ - escape_string, - is_valid_unquoted_key, - needs_quoting, - quote_string, - unescape_string, + escape_string, is_valid_unquoted_key, needs_quoting, quote_string, unescape_string, }; -use crate::types::{ - JsonValue as Value, - Number, -}; +use crate::types::{JsonValue as Value, Number}; /// Context for determining when quoting is needed. #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/utils/string.rs b/src/utils/string.rs index 7423d5a..3622990 100644 --- a/src/utils/string.rs +++ b/src/utils/string.rs @@ -1,7 +1,4 @@ -use crate::{ - types::Delimiter, - utils::literal, -}; +use crate::{types::Delimiter, utils::literal}; /// Escape special characters in a string for quoted output. pub fn escape_string(s: &str) -> String { diff --git a/src/utils/validation.rs b/src/utils/validation.rs index d14167b..72cdf84 100644 --- a/src/utils/validation.rs +++ b/src/utils/validation.rs @@ -1,9 +1,6 @@ use serde_json::Value; -use crate::types::{ - ToonError, - ToonResult, -}; +use crate::types::{ToonError, ToonResult}; /// Validate that nesting depth doesn't exceed the maximum. pub fn validate_depth(depth: usize, max_depth: usize) -> ToonResult<()> { diff --git a/tests/arrays.rs b/tests/arrays.rs index 437fafa..b879f52 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,13 +1,7 @@ use std::f64; -use serde_json::{ - json, - Value, -}; -use toon_format::{ - decode_default, - encode_default, -}; +use serde_json::{json, Value}; +use toon_format::{decode_default, encode_default}; #[test] fn test_tabular_arrays() { diff --git a/tests/delimiters.rs b/tests/delimiters.rs index 7d1bb28..7e52a67 100644 --- a/tests/delimiters.rs +++ b/tests/delimiters.rs @@ -1,14 +1,5 @@ -use serde_json::{ - json, - Value, -}; -use toon_format::{ - decode_default, - encode, - encode_default, - Delimiter, - EncodeOptions, -}; +use serde_json::{json, Value}; +use toon_format::{decode_default, encode, encode_default, Delimiter, EncodeOptions}; #[test] fn test_delimiter_variants() { diff --git a/tests/errors.rs b/tests/errors.rs index 935b567..a100332 100644 --- a/tests/errors.rs +++ b/tests/errors.rs @@ -1,14 +1,5 @@ -use serde_json::{ - json, - Value, -}; -use toon_format::{ - decode, - decode_default, - decode_strict, - DecodeOptions, - ToonError, -}; +use serde_json::{json, Value}; +use toon_format::{decode, decode_default, decode_strict, DecodeOptions, ToonError}; #[test] fn test_invalid_syntax_errors() { diff --git a/tests/numeric.rs b/tests/numeric.rs index 18f4679..432c43c 100644 --- a/tests/numeric.rs +++ b/tests/numeric.rs @@ -1,13 +1,7 @@ use core::f64; -use serde_json::{ - json, - Value, -}; -use toon_format::{ - decode_default, - encode_default, -}; +use serde_json::{json, Value}; +use toon_format::{decode_default, encode_default}; #[test] fn test_numeric_edge_cases() { diff --git a/tests/objects.rs b/tests/objects.rs index f73446b..7709b74 100644 --- a/tests/objects.rs +++ b/tests/objects.rs @@ -1,11 +1,5 @@ -use serde_json::{ - json, - Value, -}; -use toon_format::{ - decode_default, - encode_default, -}; +use serde_json::{json, Value}; +use toon_format::{decode_default, encode_default}; #[test] fn test_special_characters_and_quoting() { diff --git a/tests/panic_safety.rs b/tests/panic_safety.rs new file mode 100644 index 0000000..8630670 --- /dev/null +++ b/tests/panic_safety.rs @@ -0,0 +1,27 @@ +//! Tests to verify panic safety of JsonValue operations + +use toon_format::types::JsonValue; + +#[test] +fn test_get_missing_key_returns_none() { + let obj = JsonValue::Object(Default::default()); + assert!(obj.get("nonexistent").is_none()); +} + +#[test] +fn test_get_on_non_object_returns_none() { + let arr = JsonValue::Array(vec![]); + assert!(arr.get("key").is_none()); +} + +#[test] +fn test_get_index_out_of_bounds_returns_none() { + let arr = JsonValue::Array(vec![]); + assert!(arr.get_index(0).is_none()); +} + +#[test] +fn test_get_index_on_non_array_returns_none() { + let obj = JsonValue::Object(Default::default()); + assert!(obj.get_index(0).is_none()); +} diff --git a/tests/real_world.rs b/tests/real_world.rs index d47471f..708df22 100644 --- a/tests/real_world.rs +++ b/tests/real_world.rs @@ -1,11 +1,5 @@ -use serde_json::{ - json, - Value, -}; -use toon_format::{ - decode_default, - encode_default, -}; +use serde_json::{json, Value}; +use toon_format::{decode_default, encode_default}; #[test] fn test_real_world_github_data() { diff --git a/tests/round_trip.rs b/tests/round_trip.rs index 3125276..c40dea0 100644 --- a/tests/round_trip.rs +++ b/tests/round_trip.rs @@ -1,13 +1,7 @@ use std::f64; -use serde_json::{ - json, - Value, -}; -use toon_format::{ - decode_default, - encode_default, -}; +use serde_json::{json, Value}; +use toon_format::{decode_default, encode_default}; #[test] fn test_comprehensive_round_trips() { diff --git a/tests/spec_fixtures.rs b/tests/spec_fixtures.rs index 17c18f7..7d4a308 100644 --- a/tests/spec_fixtures.rs +++ b/tests/spec_fixtures.rs @@ -2,16 +2,8 @@ use datatest_stable::Utf8Path; use serde::Deserialize; use serde_json::Value; use toon_format::{ - decode, - encode, - types::{ - DecodeOptions, - Delimiter, - EncodeOptions, - Indent, - KeyFoldingMode, - PathExpansionMode, - }, + decode, encode, + types::{DecodeOptions, Delimiter, EncodeOptions, Indent, KeyFoldingMode, PathExpansionMode}, }; #[derive(Deserialize, Debug)] diff --git a/tests/unicode.rs b/tests/unicode.rs index 9c7f35d..447e709 100644 --- a/tests/unicode.rs +++ b/tests/unicode.rs @@ -1,11 +1,5 @@ -use serde_json::{ - json, - Value, -}; -use toon_format::{ - decode_default, - encode_default, -}; +use serde_json::{json, Value}; +use toon_format::{decode_default, encode_default}; #[test] fn test_unicode_strings() {