1use wasm_bindgen::prelude::*;
7
8use rustledger_core::Directive;
9use rustledger_parser::ParseResult as ParserResult;
10use rustledger_query::{Executor, parse as parse_query};
11
12use crate::convert::{directive_to_json, value_to_cell};
13use crate::editor;
14use crate::helpers::{load_and_interpolate, run_validation, to_js};
15#[cfg(feature = "plugins")]
16use crate::types::PluginResult;
17use crate::types::{Error, FormatResult, LedgerOptions, PadResult, QueryResult};
18
19#[wasm_bindgen]
34pub struct ParsedLedger {
35 source: String,
37 parse_result: ParserResult,
39 directives: Vec<Directive>,
41 options: LedgerOptions,
43 parse_errors: Vec<Error>,
45 validation_errors: Vec<Error>,
47 editor_cache: editor::EditorCache,
49}
50
51#[wasm_bindgen]
52impl ParsedLedger {
53 #[wasm_bindgen(constructor)]
57 pub fn new(source: &str) -> Self {
58 let load = load_and_interpolate(source);
59 let validation_errors = run_validation(&load);
60
61 let editor_cache = editor::EditorCache::new(source, &load.parse_result);
63
64 Self {
65 source: source.to_string(),
66 parse_result: load.parse_result,
67 directives: load.directives,
68 options: load.options,
69 parse_errors: load.errors,
70 validation_errors,
71 editor_cache,
72 }
73 }
74
75 #[wasm_bindgen(js_name = "isValid")]
77 pub fn is_valid(&self) -> bool {
78 self.parse_errors.is_empty() && self.validation_errors.is_empty()
79 }
80
81 #[wasm_bindgen(js_name = "getErrors")]
83 pub fn get_errors(&self) -> Result<JsValue, JsError> {
84 let mut all_errors = self.parse_errors.clone();
85 all_errors.extend(self.validation_errors.clone());
86 to_js(&all_errors)
87 }
88
89 #[wasm_bindgen(js_name = "getParseErrors")]
91 pub fn get_parse_errors(&self) -> Result<JsValue, JsError> {
92 to_js(&self.parse_errors)
93 }
94
95 #[wasm_bindgen(js_name = "getValidationErrors")]
97 pub fn get_validation_errors(&self) -> Result<JsValue, JsError> {
98 to_js(&self.validation_errors)
99 }
100
101 #[wasm_bindgen(js_name = "getDirectives")]
103 pub fn get_directives(&self) -> Result<JsValue, JsError> {
104 let directives: Vec<_> = self.directives.iter().map(directive_to_json).collect();
105 to_js(&directives)
106 }
107
108 #[wasm_bindgen(js_name = "getOptions")]
110 pub fn get_options(&self) -> Result<JsValue, JsError> {
111 to_js(&self.options)
112 }
113
114 #[wasm_bindgen(js_name = "directiveCount")]
116 pub fn directive_count(&self) -> usize {
117 self.directives.len()
118 }
119
120 #[wasm_bindgen]
122 pub fn query(&self, query_str: &str) -> Result<JsValue, JsError> {
123 if !self.parse_errors.is_empty() {
124 let result = QueryResult {
125 columns: Vec::new(),
126 rows: Vec::new(),
127 errors: self.parse_errors.clone(),
128 };
129 return to_js(&result);
130 }
131
132 let query = match parse_query(query_str) {
133 Ok(q) => q,
134 Err(e) => {
135 let result = QueryResult {
136 columns: Vec::new(),
137 rows: Vec::new(),
138 errors: vec![Error::new(e.to_string())],
139 };
140 return to_js(&result);
141 }
142 };
143
144 let mut executor = Executor::new(&self.directives);
145 match executor.execute(&query) {
146 Ok(result) => {
147 let rows: Vec<Vec<_>> = result
148 .rows
149 .iter()
150 .map(|row| row.iter().map(value_to_cell).collect())
151 .collect();
152
153 let query_result = QueryResult {
154 columns: result.columns,
155 rows,
156 errors: Vec::new(),
157 };
158 to_js(&query_result)
159 }
160 Err(e) => {
161 let result = QueryResult {
162 columns: Vec::new(),
163 rows: Vec::new(),
164 errors: vec![Error::new(format!("Query execution error: {e}"))],
165 };
166 to_js(&result)
167 }
168 }
169 }
170
171 #[wasm_bindgen]
173 pub fn balances(&self) -> Result<JsValue, JsError> {
174 self.query("BALANCES")
175 }
176
177 #[wasm_bindgen]
179 pub fn format(&self) -> Result<JsValue, JsError> {
180 use rustledger_core::{FormatConfig, format_directive};
181
182 if !self.parse_errors.is_empty() {
183 let result = FormatResult {
184 formatted: None,
185 errors: self.parse_errors.clone(),
186 };
187 return to_js(&result);
188 }
189
190 let config = FormatConfig::default();
191 let mut formatted = String::new();
192
193 for directive in &self.directives {
194 formatted.push_str(&format_directive(directive, &config));
195 formatted.push('\n');
196 }
197
198 let result = FormatResult {
199 formatted: Some(formatted),
200 errors: Vec::new(),
201 };
202 to_js(&result)
203 }
204
205 #[wasm_bindgen(js_name = "expandPads")]
207 pub fn expand_pads(&self) -> Result<JsValue, JsError> {
208 use rustledger_booking::process_pads;
209
210 if !self.parse_errors.is_empty() {
211 let result = PadResult {
212 directives: Vec::new(),
213 padding_transactions: Vec::new(),
214 errors: self.parse_errors.clone(),
215 };
216 return to_js(&result);
217 }
218
219 let pad_result = process_pads(&self.directives);
220
221 let result = PadResult {
222 directives: pad_result
223 .directives
224 .iter()
225 .map(directive_to_json)
226 .collect(),
227 padding_transactions: pad_result
228 .padding_transactions
229 .iter()
230 .map(|txn| directive_to_json(&Directive::Transaction(txn.clone())))
231 .collect(),
232 errors: pad_result
233 .errors
234 .iter()
235 .map(|e| Error::new(e.message.clone()))
236 .collect(),
237 };
238 to_js(&result)
239 }
240
241 #[cfg(feature = "plugins")]
243 #[wasm_bindgen(js_name = "runPlugin")]
244 pub fn run_plugin(&self, plugin_name: &str) -> Result<JsValue, JsError> {
245 use rustledger_plugin::{
246 NativePluginRegistry, PluginInput, PluginOptions, directives_to_wrappers,
247 wrappers_to_directives,
248 };
249
250 if !self.parse_errors.is_empty() {
251 let result = PluginResult {
252 directives: Vec::new(),
253 errors: self.parse_errors.clone(),
254 };
255 return to_js(&result);
256 }
257
258 let registry = NativePluginRegistry::new();
259 let Some(plugin) = registry.find(plugin_name) else {
260 let result = PluginResult {
261 directives: Vec::new(),
262 errors: vec![Error::new(format!("Unknown plugin: {plugin_name}"))],
263 };
264 return to_js(&result);
265 };
266
267 let wrappers = directives_to_wrappers(&self.directives);
268 let input = PluginInput {
269 directives: wrappers,
270 options: PluginOptions::default(),
271 config: None,
272 };
273
274 let output = plugin.process(input);
275
276 let output_directives = match wrappers_to_directives(&output.directives) {
277 Ok(dirs) => dirs,
278 Err(e) => {
279 let result = PluginResult {
280 directives: Vec::new(),
281 errors: vec![Error::new(format!("Conversion error: {e}"))],
282 };
283 return to_js(&result);
284 }
285 };
286
287 let result = PluginResult {
288 directives: output_directives.iter().map(directive_to_json).collect(),
289 errors: output
290 .errors
291 .iter()
292 .map(|e| match e.severity {
293 rustledger_plugin::PluginErrorSeverity::Warning => {
294 Error::warning(e.message.clone())
295 }
296 rustledger_plugin::PluginErrorSeverity::Error => Error::new(e.message.clone()),
297 })
298 .collect(),
299 };
300 to_js(&result)
301 }
302
303 #[wasm_bindgen(js_name = "getCompletions")]
312 pub fn get_completions(&self, line: u32, character: u32) -> Result<JsValue, JsError> {
313 let result =
314 editor::get_completions_cached(&self.source, line, character, &self.editor_cache);
315 to_js(&result)
316 }
317
318 #[wasm_bindgen(js_name = "getHoverInfo")]
322 pub fn get_hover_info(&self, line: u32, character: u32) -> Result<JsValue, JsError> {
323 let result = editor::get_hover_info_cached(
324 &self.source,
325 line,
326 character,
327 &self.parse_result,
328 &self.editor_cache,
329 );
330 to_js(&result)
331 }
332
333 #[wasm_bindgen(js_name = "getDefinition")]
338 pub fn get_definition(&self, line: u32, character: u32) -> Result<JsValue, JsError> {
339 let result = editor::get_definition_cached(
340 &self.source,
341 line,
342 character,
343 &self.parse_result,
344 &self.editor_cache,
345 );
346 to_js(&result)
347 }
348
349 #[wasm_bindgen(js_name = "getDocumentSymbols")]
354 pub fn get_document_symbols(&self) -> Result<JsValue, JsError> {
355 let result = editor::get_document_symbols_cached(&self.parse_result, &self.editor_cache);
356 to_js(&result)
357 }
358
359 #[wasm_bindgen(js_name = "getReferences")]
364 pub fn get_references(&self, line: u32, character: u32) -> Result<JsValue, JsError> {
365 let result = editor::get_references_cached(
366 &self.source,
367 line,
368 character,
369 &self.parse_result,
370 &self.editor_cache,
371 );
372 to_js(&result)
373 }
374}