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 "$"
16 else
17 let indices = ctx in
18 let rec format_path acc = function
19 | [] -> if acc = "" then "$" else "$" ^ acc
20 | ((_kinded_sort, _meta), idx) :: rest ->
21 let segment = 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 (function
45 | '"' -> Buffer.add_string buf "\\\""
46 | '\\' -> Buffer.add_string buf "\\\\"
47 | '\n' -> Buffer.add_string buf "\\n"
48 | '\r' -> Buffer.add_string buf "\\r"
49 | '\t' -> Buffer.add_string buf "\\t"
50 | c when c < ' ' -> Printf.bprintf buf "\\u%04x" (Char.code c)
51 | c -> Buffer.add_char buf c
52 ) s;
53 Buffer.contents buf
54
55(* Output success as JSON *)
56let output_success field value =
57 Printf.printf {|{"status":"ok","field":"%s","value":"%s"}|}
58 (escape_json_string field)
59 (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 {|{"status":"error","message":"%s","location":{"file":"%s","line":%d,"column":%d,"byte_start":%d,"byte_end":%d},"context":"%s"}|}
74 (escape_json_string message)
75 (escape_json_string file)
76 line_num
77 column
78 first_byte
79 last_byte
80 (escape_json_string context);
81 print_newline ()
82
83let main () =
84 (* Disable ANSI styling in error messages for consistent output *)
85 Jsont.Error.disable_ansi_styler ();
86
87 if Array.length Sys.argv < 2 then (
88 Printf.eprintf "Usage: %s <file> [field]\n" Sys.argv.(0);
89 Printf.eprintf "Fields: title, version, item_count, first_item_id\n";
90 exit 1
91 );
92
93 let file = Sys.argv.(1) in
94 let field = if Array.length Sys.argv > 2 then Sys.argv.(2) else "title" in
95
96 (* Read file *)
97 let content =
98 try
99 In_channel.with_open_text file In_channel.input_all
100 with Sys_error msg ->
101 Printf.printf {|{"status":"error","message":"File error: %s"}|}
102 (escape_json_string msg);
103 print_newline ();
104 exit 1
105 in
106
107 (* Parse with location tracking *)
108 match Jsonfeed.decode_string ~locs:true ~file content with
109 | Ok feed ->
110 let value = extract_field field feed in
111 output_success field value
112 | Error err ->
113 output_error err;
114 exit 1
115
116let () = main ()