OCaml library for JSONfeed parsing and creation

switch error types

+20 -28
+1 -1
README.md
··· 67 | Some title -> Printf.printf "- %s\n" title 68 | None -> () 69 ) (Jsonfeed.items feed) 70 - | Error (`Msg err) -> 71 Printf.eprintf "Parse error: %s\n" err 72 73 (* Parse from file *)
··· 67 | Some title -> Printf.printf "- %s\n" title 68 | None -> () 69 ) (Jsonfeed.items feed) 70 + | Error err -> 71 Printf.eprintf "Parse error: %s\n" err 72 73 (* Parse from file *)
+2 -2
example/feed_parser.ml
··· 182 Format.printf "✓ Round-trip successful: feeds are equal\n" 183 else 184 Format.printf "✗ Round-trip failed: feeds differ\n" 185 - | Error (`Msg err) -> 186 Format.eprintf "✗ Round-trip failed: %s\n" err) 187 - | Error (`Msg err) -> 188 Format.eprintf "Error parsing feed: %s\n" err 189 with 190 | Sys_error msg ->
··· 182 Format.printf "✓ Round-trip successful: feeds are equal\n" 183 else 184 Format.printf "✗ Round-trip failed: feeds differ\n" 185 + | Error err -> 186 Format.eprintf "✗ Round-trip failed: %s\n" err) 187 + | Error err -> 188 Format.eprintf "Error parsing feed: %s\n" err 189 with 190 | Sys_error msg ->
+3 -3
example/feed_validator.ml
··· 256 257 (match Jsonfeed.of_string invalid_json1 with 258 | Ok _ -> Format.printf "✗ Should have failed (missing version)\n" 259 - | Error (`Msg err) -> 260 Format.printf "✓ Correctly rejected invalid feed: %s\n" err); 261 262 (* Missing required title field *) ··· 267 268 (match Jsonfeed.of_string invalid_json2 with 269 | Ok _ -> Format.printf "✗ Should have failed (missing title)\n" 270 - | Error (`Msg err) -> 271 Format.printf "✓ Correctly rejected invalid feed: %s\n" err); 272 273 (* Item without id *) ··· 281 282 (match Jsonfeed.of_string invalid_json3 with 283 | Ok _ -> Format.printf "✗ Should have failed (item without id)\n" 284 - | Error (`Msg err) -> 285 Format.printf "✓ Correctly rejected invalid feed: %s\n" err); 286 287 Format.printf "\n"
··· 256 257 (match Jsonfeed.of_string invalid_json1 with 258 | Ok _ -> Format.printf "✗ Should have failed (missing version)\n" 259 + | Error err -> 260 Format.printf "✓ Correctly rejected invalid feed: %s\n" err); 261 262 (* Missing required title field *) ··· 267 268 (match Jsonfeed.of_string invalid_json2 with 269 | Ok _ -> Format.printf "✗ Should have failed (missing title)\n" 270 + | Error err -> 271 Format.printf "✓ Correctly rejected invalid feed: %s\n" err); 272 273 (* Item without id *) ··· 281 282 (match Jsonfeed.of_string invalid_json3 with 283 | Ok _ -> Format.printf "✗ Should have failed (item without id)\n" 284 + | Error err -> 285 Format.printf "✓ Correctly rejected invalid feed: %s\n" err); 286 287 Format.printf "\n"
+2 -3
lib/jsonfeed.ml
··· 72 73 (* JSON parsing and serialization *) 74 75 - type error = [ `Msg of string ] 76 77 - let error_msgf fmt = Format.kasprintf (fun s -> Error (`Msg s)) fmt 78 79 (* JSON parsing helpers *) 80 ··· 350 (* JSON serialization *) 351 352 let to_jsonm enc feed = 353 - (* Simplified serialization using Jsonm *) 354 let enc_field name value_fn = 355 ignore (Jsonm.encode enc (`Lexeme (`Name name))); 356 value_fn ()
··· 72 73 (* JSON parsing and serialization *) 74 75 + type error = string 76 77 + let error_msgf fmt = Format.kasprintf (fun s -> Error s) fmt 78 79 (* JSON parsing helpers *) 80 ··· 350 (* JSON serialization *) 351 352 let to_jsonm enc feed = 353 let enc_field name value_fn = 354 ignore (Jsonm.encode enc (`Lexeme (`Name name))); 355 value_fn ()
+8 -15
lib/jsonfeed.mli
··· 4 type-safe parsing and serialization of JSON Feed documents. JSON Feed is a 5 syndication format similar to RSS and Atom, but using JSON instead of XML. 6 7 - {b Key Features:} 8 - - Type-safe construction with compile-time validation 9 - - Support for all JSON Feed 1.1 fields 10 - - RFC 3339 date parsing with Ptime integration 11 - - Streaming parsing and serialization with Jsonm 12 - - Comprehensive documentation and examples 13 - 14 {b Quick Start:} 15 {[ 16 (* Create a simple feed *) ··· 33 (* Parse from string *) 34 match Jsonfeed.of_string json with 35 | Ok feed -> Printf.printf "Feed: %s\n" (Jsonfeed.title feed) 36 - | Error (`Msg err) -> Printf.eprintf "Error: %s\n" err 37 ]} 38 39 @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *) ··· 195 (** {1 Parsing and Serialization} *) 196 197 (** Error type for parsing operations. *) 198 - type error = [ `Msg of string ] 199 200 (** [of_jsonm decoder] parses a JSON Feed from a Jsonm decoder. 201 ··· 203 with streaming JSON processing pipelines. 204 205 @param decoder A Jsonm decoder positioned at the start of a JSON Feed document 206 - @return [Ok feed] on success, [Error (`Msg err)] on parse error 207 208 {b Example:} 209 {[ 210 let decoder = Jsonm.decoder (`String json_string) in 211 match Jsonfeed.of_jsonm decoder with 212 | Ok feed -> (* process feed *) 213 - | Error (`Msg err) -> (* handle error *) 214 ]} *) 215 - val of_jsonm : Jsonm.decoder -> (t, [> error]) result 216 217 (** [to_jsonm encoder feed] serializes a JSON Feed to a Jsonm encoder. 218 ··· 234 (** [of_string s] parses a JSON Feed from a string. 235 236 @param s A JSON string containing a JSON Feed document 237 - @return [Ok feed] on success, [Error (`Msg err)] on parse error 238 239 {b Example:} 240 {[ ··· 245 }|} in 246 match Jsonfeed.of_string json with 247 | Ok feed -> Printf.printf "Parsed: %s\n" (Jsonfeed.title feed) 248 - | Error (`Msg err) -> Printf.eprintf "Error: %s\n" err 249 ]} *) 250 - val of_string : string -> (t, [> error]) result 251 252 (** [to_string ?minify feed] serializes a JSON Feed to a string. 253
··· 4 type-safe parsing and serialization of JSON Feed documents. JSON Feed is a 5 syndication format similar to RSS and Atom, but using JSON instead of XML. 6 7 {b Quick Start:} 8 {[ 9 (* Create a simple feed *) ··· 26 (* Parse from string *) 27 match Jsonfeed.of_string json with 28 | Ok feed -> Printf.printf "Feed: %s\n" (Jsonfeed.title feed) 29 + | Error err -> Printf.eprintf "Error: %s\n" err 30 ]} 31 32 @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *) ··· 188 (** {1 Parsing and Serialization} *) 189 190 (** Error type for parsing operations. *) 191 + type error = string 192 193 (** [of_jsonm decoder] parses a JSON Feed from a Jsonm decoder. 194 ··· 196 with streaming JSON processing pipelines. 197 198 @param decoder A Jsonm decoder positioned at the start of a JSON Feed document 199 + @return [Ok feed] on success, [Error err] on parse error 200 201 {b Example:} 202 {[ 203 let decoder = Jsonm.decoder (`String json_string) in 204 match Jsonfeed.of_jsonm decoder with 205 | Ok feed -> (* process feed *) 206 + | Error err -> (* handle error *) 207 ]} *) 208 + val of_jsonm : Jsonm.decoder -> (t, error) result 209 210 (** [to_jsonm encoder feed] serializes a JSON Feed to a Jsonm encoder. 211 ··· 227 (** [of_string s] parses a JSON Feed from a string. 228 229 @param s A JSON string containing a JSON Feed document 230 + @return [Ok feed] on success, [Error err] on parse error 231 232 {b Example:} 233 {[ ··· 238 }|} in 239 match Jsonfeed.of_string json with 240 | Ok feed -> Printf.printf "Parsed: %s\n" (Jsonfeed.title feed) 241 + | Error err -> Printf.eprintf "Error: %s\n" err 242 ]} *) 243 + val of_string : string -> (t, error) result 244 245 (** [to_string ?minify feed] serializes a JSON Feed to a string. 246
+4 -4
test/test_jsonfeed.ml
··· 235 | Ok feed -> 236 Alcotest.(check string) "title" "Test Feed" (Jsonfeed.title feed); 237 Alcotest.(check int) "items" 0 (List.length (Jsonfeed.items feed)) 238 - | Error (`Msg err) -> 239 Alcotest.fail (Printf.sprintf "Parse failed: %s" err) 240 241 let test_feed_parse_with_item () = ··· 258 Alcotest.(check string) "item id" "https://example.com/1" (Item.id item); 259 Alcotest.(check (option string)) "content_html" (Some "<p>Hello</p>") (Item.content_html item) 260 | _ -> Alcotest.fail "Expected 1 item") 261 - | Error (`Msg err) -> 262 Alcotest.fail (Printf.sprintf "Parse failed: %s" err) 263 264 let test_feed_roundtrip () = ··· 288 Alcotest.(check int) "items count" 289 (List.length (Jsonfeed.items feed1)) 290 (List.length (Jsonfeed.items feed2)) 291 - | Error (`Msg err) -> 292 Alcotest.fail (Printf.sprintf "Round-trip failed: %s" err) 293 294 let test_feed_parse_invalid_missing_content () = ··· 303 }|} in 304 match Jsonfeed.of_string json with 305 | Ok _ -> Alcotest.fail "Should reject item without content" 306 - | Error (`Msg err) -> 307 Alcotest.(check bool) "has error" true 308 (contains_substring err "content") 309
··· 235 | Ok feed -> 236 Alcotest.(check string) "title" "Test Feed" (Jsonfeed.title feed); 237 Alcotest.(check int) "items" 0 (List.length (Jsonfeed.items feed)) 238 + | Error err -> 239 Alcotest.fail (Printf.sprintf "Parse failed: %s" err) 240 241 let test_feed_parse_with_item () = ··· 258 Alcotest.(check string) "item id" "https://example.com/1" (Item.id item); 259 Alcotest.(check (option string)) "content_html" (Some "<p>Hello</p>") (Item.content_html item) 260 | _ -> Alcotest.fail "Expected 1 item") 261 + | Error err -> 262 Alcotest.fail (Printf.sprintf "Parse failed: %s" err) 263 264 let test_feed_roundtrip () = ··· 288 Alcotest.(check int) "items count" 289 (List.length (Jsonfeed.items feed1)) 290 (List.length (Jsonfeed.items feed2)) 291 + | Error err -> 292 Alcotest.fail (Printf.sprintf "Round-trip failed: %s" err) 293 294 let test_feed_parse_invalid_missing_content () = ··· 303 }|} in 304 match Jsonfeed.of_string json with 305 | Ok _ -> Alcotest.fail "Should reject item without content" 306 + | Error err -> 307 Alcotest.(check bool) "has error" true 308 (contains_substring err "content") 309