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}