diff --git a/jsoo/jsoo.ml b/jsoo/jsoo.ml index 41cb151..c9d19fc 100644 --- a/jsoo/jsoo.ml +++ b/jsoo/jsoo.ml @@ -60,6 +60,11 @@ type plotly = data Js.t Js.js_array Js.t -> layout Js.t -> config Js.t -> unit Js.meth; + + validate : + data Js.t Js.js_array Js.t -> + layout Js.t -> + Js.js_string Js.t Js.js_array Js.t Js.meth; > let plotly : plotly Js.t = Js.Unsafe.pure_js_expr "Plotly" @@ -72,3 +77,10 @@ let create div fig = @@ List.map obj_of_graph_object fig.Figure.graphs) (obj_of_layout fig.layout) (Js.Unsafe.obj [||]) +let validate fig = + let data_array = Js.array (Array.of_list (List.map obj_of_graph_object fig.Figure.graphs)) in + let layout = obj_of_layout fig.layout in + plotly##validate data_array layout + |> Js.to_array + |> Array.to_list + |> List.map Js.to_string \ No newline at end of file diff --git a/jsoo/jsoo.mli b/jsoo/jsoo.mli index 047bc3c..e51920b 100644 --- a/jsoo/jsoo.mli +++ b/jsoo/jsoo.mli @@ -17,8 +17,17 @@ type plotly = data Js.t Js.js_array Js.t -> layout Js.t -> config Js.t -> unit Js.meth; + + validate : + data Js.t Js.js_array Js.t -> + layout Js.t -> + Js.js_string Js.t Js.js_array Js.t Js.meth; > val plotly : plotly Js.t val create : Dom_html.divElement Js.t -> Plotly.Figure.t -> unit + +(** Validate a figure using Plotly.js validate function. + Returns a list of error messages (empty list means valid). *) +val validate : Plotly.Figure.t -> string list diff --git a/python/python.ml b/python/python.ml index b7145f9..e02ef96 100644 --- a/python/python.ml +++ b/python/python.ml @@ -92,3 +92,18 @@ let write_image figure path = | Some f -> let f = Py.Callable.to_function_with_keywords f in ignore @@ f [| Py.String.of_string path |] [] + +let validate (figure : figure t) : (string list, string) result = + try + let plotly_io = Py.Import.import_module "plotly.io" in + match Py.Object.get_attr_string plotly_io "to_json" with + | None -> Error "plotly.io.to_json not found" + | Some to_json_fn -> + let to_json_fn = Py.Callable.to_function_with_keywords to_json_fn in + (try + let _ = to_json_fn [| figure |] ["validate", Py.Bool.of_bool true] in + Ok [] + with Py.E (_, _) -> + Error "Figure validation failed") + with _ -> + Ok [] diff --git a/python/python.mli b/python/python.mli index 3481b94..8f04120 100644 --- a/python/python.mli +++ b/python/python.mli @@ -8,3 +8,6 @@ val python_figure_to_json : figure t -> Ezjsonm.value val show : ?renderer:string -> figure t -> unit val write_image : figure t -> string -> unit +val validate : figure t -> (string list, string) result +(** Validate a figure using Plotly's Python validate function. + Returns Ok [] if valid, or Error with description if invalid. *) \ No newline at end of file diff --git a/test/test_all.ml b/test/test_all.ml index 250fc41..56ed044 100644 --- a/test/test_all.ml +++ b/test/test_all.ml @@ -33,31 +33,37 @@ let test_json_figure figure = let current_json = Figure.to_json figure in let test_name = filename in - if Sys.file_exists ref_path then begin - let ref_json = Ezjsonm.value_from_channel (open_in ref_path) in - - if not (json_equal current_json ref_json) then begin - Printf.printf " ✗ %s - JSON doesn't match reference\n" test_name; + (* Validate that the generated JSON produces a valid Plotly figure by deserializing it *) + match Figure.of_json current_json with + | None -> + Printf.printf " ✗ %s - Invalid Plotly figure (failed to deserialize)\n" test_name; false - end else begin - match Figure.of_json current_json with - | Some fig' -> - let roundtrip_json = Figure.to_json fig' in - if not (json_equal current_json roundtrip_json) then begin - Printf.printf " ✗ %s - Round-trip failed\n" test_name; - false - end else begin - Printf.printf " ✓ %s (JSON)\n" test_name; - true - end - | None -> - Printf.printf " ✗ %s - Failed to parse generated JSON\n" test_name; + | Some _ -> + if Sys.file_exists ref_path then begin + let ref_json = Ezjsonm.value_from_channel (open_in ref_path) in + + if not (json_equal current_json ref_json) then begin + Printf.printf " ✗ %s - JSON doesn't match reference\n" test_name; false - end - end else begin - Printf.printf " ? %s - No JSON reference\n" test_name; - false - end + end else begin + match Figure.of_json current_json with + | Some fig' -> + let roundtrip_json = Figure.to_json fig' in + if not (json_equal current_json roundtrip_json) then begin + Printf.printf " ✗ %s - Round-trip failed\n" test_name; + false + end else begin + Printf.printf " ✓ %s (JSON)\n" test_name; + true + end + | None -> + Printf.printf " ✗ %s - Failed to parse generated JSON\n" test_name; + false + end + end else begin + Printf.printf " ? %s - No JSON reference\n" test_name; + false + end let test_python_backend figure = let module Python = Plotly_python.Python in @@ -74,24 +80,28 @@ let test_python_backend figure = (* Convert figure to Python object *) let py_fig = Python.of_figure figure in - (* Extract JSON from the actual Python figure object *) - let py_json = Python.python_figure_to_json py_fig in - - (* Verify it matches the Python-specific reference *) - if Sys.file_exists ref_path then begin - let ref_json = Ezjsonm.value_from_channel (open_in ref_path) in - - if not (json_equal py_json ref_json) then begin - Printf.printf " ✗ %s - Python figure JSON doesn't match reference\n" filename; + (* Validate the figure using Plotly's Python validate function *) + match Python.validate py_fig with + | Error err -> + Printf.printf " ✗ %s - Invalid Plotly figure (validation error: %s)\n" filename err; false - end else begin - Printf.printf " ✓ %s (Python backend)\n" filename; - true - end - end else begin - Printf.printf " ? %s - No Python reference (run: dune exec test/generate_python_references.exe)\n" filename; - false - end + | Ok _errors -> + let py_json = Python.python_figure_to_json py_fig in + + if Sys.file_exists ref_path then begin + let ref_json = Ezjsonm.value_from_channel (open_in ref_path) in + + if not (json_equal py_json ref_json) then begin + Printf.printf " ✗ %s - Python figure JSON doesn't match reference\n" filename; + false + end else begin + Printf.printf " ✓ %s (Python backend)\n" filename; + true + end + end else begin + Printf.printf " ? %s - No Python reference (run: dune exec test/generate_python_references.exe)\n" filename; + false + end with e -> Printf.printf " ✗ %s - Python backend error: %s\n" filename (Printexc.to_string e); false