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 ()