OCaml library for JSONfeed parsing and creation
1(** Test executable for verifying jsont location tracking
2
3 Usage: test_location_errors <file> [field]
4
5 Parses JSON feed files and outputs JSON with either:
6 - Success: {"status":"ok", "field":"<field>", "value":"<value>"}
7 - Error: {"status":"error", "message":"...", "location":{...}, "context":"..."}
8*)
9
10open Jsonfeed
11
12(* Helper to format path context *)
13let format_context (ctx : Jsont.Error.Context.t) =
14 if Jsont.Error.Context.is_empty ctx then "$"
15 else
16 let indices = ctx in
17 let rec format_path acc = function
18 | [] -> if acc = "" then "$" else "$" ^ acc
19 | ((_kinded_sort, _meta), idx) :: rest ->
20 let segment =
21 match idx with
22 | Jsont.Path.Mem (name, _meta) -> "." ^ name
23 | Jsont.Path.Nth (n, _meta) -> "[" ^ string_of_int n ^ "]"
24 in
25 format_path (acc ^ segment) rest
26 in
27 format_path "" indices
28
29(* Extract field from successfully parsed feed *)
30let extract_field field feed =
31 match field with
32 | "title" -> Jsonfeed.title feed
33 | "version" -> Jsonfeed.version feed
34 | "item_count" -> string_of_int (List.length (Jsonfeed.items feed))
35 | "first_item_id" -> (
36 match Jsonfeed.items feed with
37 | [] -> "(no items)"
38 | item :: _ -> Item.id item)
39 | _ -> "(unknown field)"
40
41(* Escape JSON strings *)
42let escape_json_string s =
43 let buf = Buffer.create (String.length s) in
44 String.iter
45 (function
46 | '"' -> Buffer.add_string buf "\\\""
47 | '\\' -> Buffer.add_string buf "\\\\"
48 | '\n' -> Buffer.add_string buf "\\n"
49 | '\r' -> Buffer.add_string buf "\\r"
50 | '\t' -> Buffer.add_string buf "\\t"
51 | c when c < ' ' -> Printf.bprintf buf "\\u%04x" (Char.code c)
52 | c -> Buffer.add_char buf c)
53 s;
54 Buffer.contents buf
55
56(* Output success as JSON *)
57let output_success field value =
58 Printf.printf {|{"status":"ok","field":"%s","value":"%s"}|}
59 (escape_json_string field) (escape_json_string value);
60 print_newline ()
61
62(* Output error as JSON *)
63let output_error (ctx, meta, kind) =
64 let message = Jsont.Error.kind_to_string kind in
65 let textloc = Jsont.Meta.textloc meta in
66 let file = Jsont.Textloc.file textloc in
67 let first_byte = Jsont.Textloc.first_byte textloc in
68 let last_byte = Jsont.Textloc.last_byte textloc in
69 let line_num, line_start_byte = Jsont.Textloc.first_line textloc in
70 let column = first_byte - line_start_byte + 1 in
71 let context = format_context ctx in
72
73 Printf.printf
74 {|{"status":"error","message":"%s","location":{"file":"%s","line":%d,"column":%d,"byte_start":%d,"byte_end":%d},"context":"%s"}|}
75 (escape_json_string message)
76 (escape_json_string file) line_num column first_byte last_byte
77 (escape_json_string context);
78 print_newline ()
79
80let main () =
81 (* Disable ANSI styling in error messages for consistent output *)
82 Jsont.Error.disable_ansi_styler ();
83
84 if Array.length Sys.argv < 2 then (
85 Printf.eprintf "Usage: %s <file> [field]\n" Sys.argv.(0);
86 Printf.eprintf "Fields: title, version, item_count, first_item_id\n";
87 exit 1);
88
89 let file = Sys.argv.(1) in
90 let field = if Array.length Sys.argv > 2 then Sys.argv.(2) else "title" in
91
92 (* Read file *)
93 let content =
94 try In_channel.with_open_text file In_channel.input_all
95 with Sys_error msg ->
96 Printf.printf {|{"status":"error","message":"File error: %s"}|}
97 (escape_json_string msg);
98 print_newline ();
99 exit 1
100 in
101
102 (* Parse with location tracking *)
103 match Jsonfeed.decode_string ~locs:true ~file content with
104 | Ok feed ->
105 let value = extract_field field feed in
106 output_success field value
107 | Error err ->
108 output_error err;
109 exit 1
110
111let () = main ()