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