tana_validation/
lib.rs

1//! Tana Validation Library
2//!
3//! Shared validation and error formatting logic for Tana smart contracts.
4//! Supports both native Rust and WebAssembly compilation.
5
6use wasm_bindgen::prelude::*;
7
8/// Format a validation error with beautiful Rust/Gleam-style output
9///
10/// This function creates consistent error messages across all Tana systems:
11/// - tana-runtime (native Rust)
12/// - tana-edge (native Rust)
13/// - playground (WASM in browser)
14/// - CLI tools (WASM in Bun/Node)
15///
16/// # Arguments
17///
18/// * `code` - The source code containing the error
19/// * `file_path` - Path to the file (e.g., "contract.ts")
20/// * `error_kind` - Category of error (e.g., "Invalid Import", "Type Error")
21/// * `line_num` - Line number (1-indexed)
22/// * `col_num` - Column number (1-indexed)
23/// * `message` - Error message
24/// * `help` - Help text explaining how to fix
25/// * `underline_length` - Number of characters to underline (for ^^^)
26///
27/// # Example
28///
29/// ```rust
30/// use tana_validation::format_validation_error;
31///
32/// let error = format_validation_error(
33///     "import { console } from 'tana/invalid';",
34///     "contract.ts",
35///     "Invalid Import",
36///     1,
37///     26,
38///     "Module 'tana/invalid' not found",
39///     "Available modules: tana/core, tana/kv, tana/block",
40///     12
41/// );
42///
43/// // Produces:
44/// // Validation Error
45/// // ❌ Invalid Import
46/// //
47/// // ┌─ contract.ts:1:26
48/// // │
49/// //   1 │ import { console } from 'tana/invalid';
50/// //     │                          ^^^^^^^^^^^^ Module 'tana/invalid' not found
51/// // │
52/// // = help: Available modules: tana/core, tana/kv, tana/block
53/// // │
54/// // └─
55/// ```
56#[wasm_bindgen]
57pub fn format_validation_error(
58    code: &str,
59    file_path: &str,
60    error_kind: &str,
61    line_num: usize,
62    col_num: usize,
63    message: &str,
64    help: &str,
65    underline_length: usize,
66) -> String {
67    format_error_impl(code, file_path, error_kind, line_num, col_num, message, help, underline_length)
68}
69
70/// Internal implementation of error formatting
71/// Used by both WASM binding and native Rust code
72fn format_error_impl(
73    code: &str,
74    file_path: &str,
75    error_kind: &str,
76    line_num: usize,
77    col_num: usize,
78    message: &str,
79    help: &str,
80    underline_length: usize,
81) -> String {
82    // Get the problematic line
83    let lines: Vec<&str> = code.lines().collect();
84    let error_line = if line_num > 0 && line_num <= lines.len() {
85        lines[line_num - 1]
86    } else {
87        ""
88    };
89
90    // Ensure underline length is at least 1
91    let underline_length = underline_length.max(1);
92
93    // Build the error message with consistent formatting
94    format!(
95        "\nValidation Error\n\
96        ❌ {}\n\
97        \n\
98        ┌─ {}:{}:{}\n\
99        │\n\
100        {:>3} │ {}\n\
101            │ {}{} {}\n\
102        │\n\
103        = help: {}\n\
104        │\n\
105        └─\n",
106        error_kind,
107        file_path,
108        line_num,
109        col_num,
110        line_num,
111        error_line,
112        " ".repeat(col_num.saturating_sub(1)),
113        "^".repeat(underline_length),
114        message,
115        help
116    )
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_basic_error_formatting() {
125        let code = "import { console } from 'tana/invalid';";
126        let error = format_validation_error(
127            code,
128            "test.ts",
129            "Invalid Import",
130            1,
131            26,
132            "Module 'tana/invalid' not found",
133            "Available modules: tana/core, tana/kv",
134            12,
135        );
136
137        assert!(error.contains("❌ Invalid Import"));
138        assert!(error.contains("test.ts:1:26"));
139        assert!(error.contains("tana/invalid"));
140        assert!(error.contains("^^^^^^^^^^^^")); // 12 carets
141        assert!(error.contains("= help: Available modules"));
142    }
143
144    #[test]
145    fn test_multiline_code() {
146        let code = "line 1\nline 2 with error\nline 3";
147        let error = format_validation_error(
148            code,
149            "multi.ts",
150            "Type Error",
151            2,
152            7,
153            "Something wrong here",
154            "Fix it like this",
155            4,
156        );
157
158        assert!(error.contains("❌ Type Error"));
159        assert!(error.contains("multi.ts:2:7"));
160        assert!(error.contains("line 2 with error"));
161        assert!(error.contains("^^^^")); // 4 carets
162    }
163
164    #[test]
165    fn test_underline_length_minimum() {
166        let error = format_validation_error(
167            "test",
168            "test.ts",
169            "Error",
170            1,
171            1,
172            "msg",
173            "help",
174            0, // Should become 1
175        );
176
177        assert!(error.contains("^")); // At least one caret
178    }
179
180    #[test]
181    fn test_out_of_bounds_line() {
182        let error = format_validation_error(
183            "only one line",
184            "test.ts",
185            "Error",
186            999,
187            1,
188            "msg",
189            "help",
190            5,
191        );
192
193        // Should handle gracefully without panicking
194        assert!(error.contains("❌ Error"));
195        assert!(error.contains("999 │")); // Shows requested line number
196    }
197}