diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 2bdb485..e4b44ce 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -12,6 +12,9 @@ wasm-bindgen = "0.2.84" console_error_panic_hook = { version = "0.1.7" } getrandom = { version = "0.2", features = ["js"] } wee_alloc = "0.4.5" +serde-wasm-bindgen = "0.6.5" +serde = { workspace = true, features = ["derive"] } +miette = { version = "7.2.0" } [dev-dependencies] wasm-bindgen-test = "0.3.34" diff --git a/bindings/wasm/sqlsonnet-playground/src/App.tsx b/bindings/wasm/sqlsonnet-playground/src/App.tsx index d353646..02f5b0c 100644 --- a/bindings/wasm/sqlsonnet-playground/src/App.tsx +++ b/bindings/wasm/sqlsonnet-playground/src/App.tsx @@ -13,16 +13,20 @@ import "codemirror/mode/sql/sql.js"; import { jsonnet } from "./jsonnet.js"; import { UnControlled as CodeMirror } from "react-codemirror2"; +type Location = [line: number, col: number]; + function Editor({ value, onChange = (_data) => {}, mode, readOnly = false, + location = null, }: { value: string; onChange?: (data: string) => void; mode: string; readOnly?: boolean; + location?: Location | null; }) { const editor = useRef(); const wrapper = useRef(); @@ -36,6 +40,23 @@ function Editor({ wrapper.current.hydrated = false; } }; + if (location) { + // Set marker + if (editor.current) { + // @ts-ignore + editor.current.markText( + { line: location[0], ch: location[1] }, + { line: location[0], ch: location[1] + 1 }, + { className: "mark" }, + ); + } + } else { + // Unset marker + if (editor.current) { + // @ts-ignore + editor.current.doc.getAllMarks().forEach((marker) => marker.clear()); + } + } return ( { setAlert(""); + setLocation(null); getWasm().then(() => { try { setValueSql(to_sql(data)); } catch (error: any) { - setAlert(error.toString()); + if (typeof error == "object") { + setAlert(error.message); + if (error.location) { + setLocation(error.location); + } + } else { + setAlert(error.toString()); + } } }); }; @@ -98,7 +128,12 @@ function App() { <>
- +

Input jsonnet

diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 400e385..cffe5fd 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -1,3 +1,4 @@ +use miette::Diagnostic; use wasm_bindgen::prelude::*; use sqlsonnet::{FsResolver, Query}; @@ -9,12 +10,41 @@ pub fn set_panic_hook() { console_error_panic_hook::set_once(); } +#[derive(serde::Serialize)] +pub struct Error { + message: String, + location: Option<[usize; 2]>, +} +impl From for Error { + fn from(source: sqlsonnet::Error) -> Self { + Self { + message: source.to_string(), + location: if let (Some(source_code), Some(labels)) = + (source.source_code(), source.labels()) + { + labels + .filter_map(|l| source_code.read_span(l.inner(), 0, 0).ok()) + // Subtract 1 for the initial line + .map(|sc| [sc.line() - 1, sc.column()]) + .next() + } else { + None + }, + } + } +} + +impl From for JsValue { + fn from(source: Error) -> Self { + serde_wasm_bindgen::to_value(&source).unwrap() + } +} + #[wasm_bindgen] -pub fn to_sql(input: &str) -> Result { +pub fn to_sql(input: &str) -> Result { let query = Query::from_jsonnet( - &format!("{}{}", sqlsonnet::import_utils(), input), + &format!("{}\n{}", sqlsonnet::import_utils(), input), FsResolver::default(), - ) - .map_err(|e| e.to_string())?; + )?; Ok(query.to_sql(false)) }