(** Example: Validating JSON Feeds This demonstrates: - Validating feed structure - Testing various edge cases - Handling invalid feeds - Best practices for feed construction *) open Jsonfeed let test_valid_minimal_feed () = Format.printf "=== Test: Minimal Valid Feed ===\n"; let feed = Jsonfeed.create ~title:"Minimal Feed" ~items:[] () in match Jsonfeed.validate feed with | Ok () -> Format.printf "✓ Minimal feed is valid\n\n" | Error errors -> Format.printf "✗ Minimal feed validation failed:\n"; List.iter (Format.printf " - %s\n") errors; Format.printf "\n" let test_valid_complete_feed () = Format.printf "=== Test: Complete Valid Feed ===\n"; let author = Author.create ~name:"Test Author" ~url:"https://example.com/author" ~avatar:"https://example.com/avatar.png" () in let attachment = Attachment.create ~url:"https://example.com/file.mp3" ~mime_type:"audio/mpeg" ~title:"Audio File" ~size_in_bytes:1024L ~duration_in_seconds:60 () in let item = Item.create ~id:"https://example.com/items/1" ~url:"https://example.com/items/1" ~title:"Test Item" ~content:(`Both ("

HTML content

", "Text content")) ~summary:"A test item" ~image:"https://example.com/image.jpg" ~banner_image:"https://example.com/banner.jpg" ~date_published:(Jsonfeed.parse_rfc3339 "2024-11-01T10:00:00Z" |> Option.get) ~date_modified:(Jsonfeed.parse_rfc3339 "2024-11-01T15:00:00Z" |> Option.get) ~authors:[author] ~tags:["test"; "example"] ~language:"en" ~attachments:[attachment] () in let hub = Hub.create ~type_:"WebSub" ~url:"https://pubsubhubbub.appspot.com/" () in let feed = Jsonfeed.create ~title:"Complete Feed" ~home_page_url:"https://example.com" ~feed_url:"https://example.com/feed.json" ~description:"A complete test feed" ~user_comment:"This is a test feed" ~icon:"https://example.com/icon.png" ~favicon:"https://example.com/favicon.ico" ~authors:[author] ~language:"en-US" ~hubs:[hub] ~items:[item] () in match Jsonfeed.validate feed with | Ok () -> Format.printf "✓ Complete feed is valid\n\n" | Error errors -> Format.printf "✗ Complete feed validation failed:\n"; List.iter (Format.printf " - %s\n") errors; Format.printf "\n" let test_feed_with_multiple_items () = Format.printf "=== Test: Feed with Multiple Items ===\n"; let items = List.init 10 (fun i -> Item.create ~id:(Printf.sprintf "https://example.com/items/%d" i) ~content:(`Text (Printf.sprintf "Item %d content" i)) ~title:(Printf.sprintf "Item %d" i) ~date_published:(Jsonfeed.parse_rfc3339 (Printf.sprintf "2024-11-%02dT10:00:00Z" (i + 1)) |> Option.get) () ) in let feed = Jsonfeed.create ~title:"Multi-item Feed" ~items () in match Jsonfeed.validate feed with | Ok () -> Format.printf "✓ Feed with %d items is valid\n\n" (List.length items) | Error errors -> Format.printf "✗ Multi-item feed validation failed:\n"; List.iter (Format.printf " - %s\n") errors; Format.printf "\n" let test_podcast_feed () = Format.printf "=== Test: Podcast Feed ===\n"; let host = Author.create ~name:"Podcast Host" ~url:"https://podcast.example.com/host" () in let episode1 = Attachment.create ~url:"https://podcast.example.com/ep1.mp3" ~mime_type:"audio/mpeg" ~title:"Episode 1" ~size_in_bytes:20_971_520L (* 20 MB *) ~duration_in_seconds:1800 (* 30 minutes *) () in (* Alternate format of the same episode *) let episode1_aac = Attachment.create ~url:"https://podcast.example.com/ep1.aac" ~mime_type:"audio/aac" ~title:"Episode 1" ~size_in_bytes:16_777_216L ~duration_in_seconds:1800 () in let item = Item.create ~id:"https://podcast.example.com/episodes/1" ~url:"https://podcast.example.com/episodes/1" ~title:"Episode 1: Introduction" ~content:(`Html "

Welcome to the first episode!

") ~date_published:(Jsonfeed.parse_rfc3339 "2024-11-01T12:00:00Z" |> Option.get) ~authors:[host] ~attachments:[episode1; episode1_aac] ~image:"https://podcast.example.com/ep1-cover.jpg" () in let feed = Jsonfeed.create ~title:"Example Podcast" ~home_page_url:"https://podcast.example.com" ~feed_url:"https://podcast.example.com/feed.json" ~authors:[host] ~items:[item] () in match Jsonfeed.validate feed with | Ok () -> Format.printf "✓ Podcast feed is valid\n\n" | Error errors -> Format.printf "✗ Podcast feed validation failed:\n"; List.iter (Format.printf " - %s\n") errors; Format.printf "\n" let test_microblog_feed () = Format.printf "=== Test: Microblog Feed (no titles) ===\n"; let author = Author.create ~name:"Microblogger" ~url:"https://micro.example.com" () in let items = [ Item.create ~id:"https://micro.example.com/1" ~content:(`Text "Just posted a new photo!") ~date_published:(Jsonfeed.parse_rfc3339 "2024-11-01T08:00:00Z" |> Option.get) (); Item.create ~id:"https://micro.example.com/2" ~content:(`Text "Having a great day! ☀️") ~date_published:(Jsonfeed.parse_rfc3339 "2024-11-01T12:30:00Z" |> Option.get) (); Item.create ~id:"https://micro.example.com/3" ~content:(`Html "

Check out this link

") ~date_published:(Jsonfeed.parse_rfc3339 "2024-11-01T16:45:00Z" |> Option.get) () ] in let feed = Jsonfeed.create ~title:"Microblog" ~home_page_url:"https://micro.example.com" ~authors:[author] ~items () in match Jsonfeed.validate feed with | Ok () -> Format.printf "✓ Microblog feed with %d items is valid\n\n" (List.length items) | Error errors -> Format.printf "✗ Microblog feed validation failed:\n"; List.iter (Format.printf " - %s\n") errors; Format.printf "\n" let test_expired_feed () = Format.printf "=== Test: Expired Feed ===\n"; let feed = Jsonfeed.create ~title:"Archived Blog" ~home_page_url:"https://archive.example.com" ~description:"This blog is no longer updated" ~expired:true ~items:[] () in match Jsonfeed.validate feed with | Ok () -> Format.printf "✓ Expired feed is valid\n\n" | Error errors -> Format.printf "✗ Expired feed validation failed:\n"; List.iter (Format.printf " - %s\n") errors; Format.printf "\n" let test_paginated_feed () = Format.printf "=== Test: Paginated Feed ===\n"; let items = List.init 25 (fun i -> Item.create ~id:(Printf.sprintf "https://example.com/items/%d" i) ~content:(`Text (Printf.sprintf "Item %d" i)) () ) in let feed = Jsonfeed.create ~title:"Large Feed" ~home_page_url:"https://example.com" ~feed_url:"https://example.com/feed.json?page=1" ~next_url:"https://example.com/feed.json?page=2" ~items () in match Jsonfeed.validate feed with | Ok () -> Format.printf "✓ Paginated feed is valid (page 1 with next_url)\n\n" | Error errors -> Format.printf "✗ Paginated feed validation failed:\n"; List.iter (Format.printf " - %s\n") errors; Format.printf "\n" let test_invalid_feed_from_json () = Format.printf "=== Test: Parsing Invalid JSON ===\n"; (* Missing required version field *) let invalid_json1 = {|{ "title": "Test", "items": [] }|} in (match Jsonfeed.of_string invalid_json1 with | Ok _ -> Format.printf "✗ Should have failed (missing version)\n" | Error (`Msg err) -> Format.printf "✓ Correctly rejected invalid feed: %s\n" err); (* Missing required title field *) let invalid_json2 = {|{ "version": "https://jsonfeed.org/version/1.1", "items": [] }|} in (match Jsonfeed.of_string invalid_json2 with | Ok _ -> Format.printf "✗ Should have failed (missing title)\n" | Error (`Msg err) -> Format.printf "✓ Correctly rejected invalid feed: %s\n" err); (* Item without id *) let invalid_json3 = {|{ "version": "https://jsonfeed.org/version/1.1", "title": "Test", "items": [{ "content_text": "Hello" }] }|} in (match Jsonfeed.of_string invalid_json3 with | Ok _ -> Format.printf "✗ Should have failed (item without id)\n" | Error (`Msg err) -> Format.printf "✓ Correctly rejected invalid feed: %s\n" err); Format.printf "\n" let main () = Format.printf "\n=== JSON Feed Validation Tests ===\n\n"; test_valid_minimal_feed (); test_valid_complete_feed (); test_feed_with_multiple_items (); test_podcast_feed (); test_microblog_feed (); test_expired_feed (); test_paginated_feed (); test_invalid_feed_from_json (); Format.printf "=== All Tests Complete ===\n" let () = main ()