OCaml library for JSONfeed parsing and creation
1(** JSON Feed format parser and serializer. 2 3 This library implements the JSON Feed specification version 1.1, providing 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 *) 17 let feed = Jsonfeed.create 18 ~title:"My Blog" 19 ~home_page_url:"https://example.com" 20 ~feed_url:"https://example.com/feed.json" 21 ~items:[ 22 Item.create 23 ~id:"https://example.com/post/1" 24 ~content:(Item.Html "<p>Hello, world!</p>") 25 ~title:"First Post" 26 () 27 ] 28 () 29 30 (* Serialize to string *) 31 let json = Jsonfeed.to_string feed 32 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 *) 40 41 42(** The type representing a complete JSON Feed. *) 43type t 44 45(** Exception raised when attempting to parse an invalid feed. *) 46exception Invalid_feed of string 47 48(** {1 Construction} *) 49 50(** [create ~title ?home_page_url ?feed_url ?description ?user_comment ?next_url 51 ?icon ?favicon ?authors ?language ?expired ?hubs ~items ()] 52 creates a JSON Feed. 53 54 @param title The name of the feed (required) 55 @param home_page_url The URL of the resource the feed describes 56 @param feed_url The URL of the feed itself (serves as unique identifier) 57 @param description Additional information about the feed 58 @param user_comment A description of the feed's purpose for humans reading the raw JSON 59 @param next_url URL of the next page of items (for pagination) 60 @param icon The feed's icon URL (should be square, 512x512 or larger) 61 @param favicon The feed's favicon URL (should be square, 64x64 or larger) 62 @param authors The feed's default authors (inherited by items without authors) 63 @param language The primary language of the feed (RFC 5646 format, e.g. ["en-US"]) 64 @param expired Whether the feed will update again ([true] means no more updates) 65 @param hubs Endpoints for real-time notifications 66 @param items The list of feed items (required) 67 68 {b Examples:} 69 {[ 70 (* Minimal feed *) 71 let feed = Jsonfeed.create 72 ~title:"My Blog" 73 ~items:[] () 74 75 (* Full-featured blog feed *) 76 let feed = Jsonfeed.create 77 ~title:"Example Blog" 78 ~home_page_url:"https://example.com" 79 ~feed_url:"https://example.com/feed.json" 80 ~description:"A blog about OCaml and functional programming" 81 ~icon:"https://example.com/icon.png" 82 ~authors:[ 83 Author.create 84 ~name:"Jane Doe" 85 ~url:"https://example.com/about" 86 () 87 ] 88 ~language:"en-US" 89 ~items:[ 90 Item.create 91 ~id:"https://example.com/posts/1" 92 ~content:(Item.Html "<p>First post</p>") 93 ~title:"Hello World" 94 (); 95 Item.create 96 ~id:"https://example.com/posts/2" 97 ~content:(Item.Html "<p>Second post</p>") 98 ~title:"Another Post" 99 () 100 ] 101 () 102 103 (* Podcast feed with hubs *) 104 let hub = Hub.create 105 ~type_:"WebSub" 106 ~url:"https://pubsubhubbub.appspot.com/" 107 () in 108 let feed = Jsonfeed.create 109 ~title:"My Podcast" 110 ~home_page_url:"https://podcast.example.com" 111 ~feed_url:"https://podcast.example.com/feed.json" 112 ~hubs:[hub] 113 ~items:[ 114 Item.create 115 ~id:"https://podcast.example.com/episodes/1" 116 ~content:(Item.Html "<p>Episode description</p>") 117 ~title:"Episode 1" 118 ~attachments:[ 119 Attachment.create 120 ~url:"https://podcast.example.com/ep1.mp3" 121 ~mime_type:"audio/mpeg" 122 ~duration_in_seconds:1800 123 () 124 ] 125 () 126 ] 127 () 128 ]} *) 129val create : 130 title:string -> 131 ?home_page_url:string -> 132 ?feed_url:string -> 133 ?description:string -> 134 ?user_comment:string -> 135 ?next_url:string -> 136 ?icon:string -> 137 ?favicon:string -> 138 ?authors:Author.t list -> 139 ?language:string -> 140 ?expired:bool -> 141 ?hubs:Hub.t list -> 142 items:Item.t list -> 143 unit -> 144 t 145 146 147(** {1 Accessors} *) 148 149(** [version t] returns the JSON Feed version URL. 150 151 This is always ["https://jsonfeed.org/version/1.1"] for feeds created 152 by this library, but may differ when parsing external feeds. *) 153val version : t -> string 154 155(** [title t] returns the feed's title. *) 156val title : t -> string 157 158(** [home_page_url t] returns the feed's home page URL, if set. *) 159val home_page_url : t -> string option 160 161(** [feed_url t] returns the feed's URL, if set. *) 162val feed_url : t -> string option 163 164(** [description t] returns the feed's description, if set. *) 165val description : t -> string option 166 167(** [user_comment t] returns the feed's user comment, if set. *) 168val user_comment : t -> string option 169 170(** [next_url t] returns the URL for the next page of items, if set. *) 171val next_url : t -> string option 172 173(** [icon t] returns the feed's icon URL, if set. *) 174val icon : t -> string option 175 176(** [favicon t] returns the feed's favicon URL, if set. *) 177val favicon : t -> string option 178 179(** [authors t] returns the feed's default authors, if set. *) 180val authors : t -> Author.t list option 181 182(** [language t] returns the feed's primary language, if set. *) 183val language : t -> string option 184 185(** [expired t] returns whether the feed will update again. *) 186val expired : t -> bool option 187 188(** [hubs t] returns the feed's hub endpoints, if set. *) 189val hubs : t -> Hub.t list option 190 191(** [items t] returns the feed's items. *) 192val items : t -> Item.t list 193 194 195(** {1 Parsing and Serialization} *) 196 197(** Error type for parsing operations. *) 198type error = [ `Msg of string ] 199 200(** [of_jsonm decoder] parses a JSON Feed from a Jsonm decoder. 201 202 This is the lowest-level parsing function, suitable for integration 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 ]} *) 215val of_jsonm : Jsonm.decoder -> (t, [> error]) result 216 217(** [to_jsonm encoder feed] serializes a JSON Feed to a Jsonm encoder. 218 219 This is the lowest-level serialization function, suitable for integration 220 with streaming JSON generation pipelines. 221 222 @param encoder A Jsonm encoder 223 @param feed The feed to serialize 224 225 {b Example:} 226 {[ 227 let buffer = Buffer.create 1024 in 228 let encoder = Jsonm.encoder (`Buffer buffer) in 229 Jsonfeed.to_jsonm encoder feed; 230 let json = Buffer.contents buffer 231 ]} *) 232val to_jsonm : Jsonm.encoder -> t -> unit 233 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 {[ 241 let json = {|{ 242 "version": "https://jsonfeed.org/version/1.1", 243 "title": "My Feed", 244 "items": [] 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 ]} *) 250val of_string : string -> (t, [> error]) result 251 252(** [to_string ?minify feed] serializes a JSON Feed to a string. 253 254 @param minify If [true], produces compact JSON without whitespace. 255 If [false] (default), produces indented, human-readable JSON. 256 @param feed The feed to serialize 257 @return A JSON string 258 259 {b Example:} 260 {[ 261 let json = Jsonfeed.to_string feed 262 let compact = Jsonfeed.to_string ~minify:true feed 263 ]} *) 264val to_string : ?minify:bool -> t -> string 265 266 267(** {1 Date Utilities} *) 268 269(** [parse_rfc3339 s] parses an RFC 3339 date/time string. 270 271 This function parses timestamps in the format required by JSON Feed, 272 such as ["2024-11-03T10:30:00Z"] or ["2024-11-03T10:30:00-08:00"]. 273 274 @param s An RFC 3339 formatted date/time string 275 @return [Some time] on success, [None] if the string is invalid 276 277 {b Examples:} 278 {[ 279 parse_rfc3339 "2024-11-03T10:30:00Z" 280 (* returns Some time *) 281 282 parse_rfc3339 "2024-11-03T10:30:00-08:00" 283 (* returns Some time *) 284 285 parse_rfc3339 "invalid" 286 (* returns None *) 287 ]} *) 288val parse_rfc3339 : string -> Ptime.t option 289 290(** [format_rfc3339 time] formats a timestamp as an RFC 3339 string. 291 292 The output uses UTC timezone (Z suffix) and includes fractional seconds 293 if the timestamp has sub-second precision. 294 295 @param time A Ptime timestamp 296 @return An RFC 3339 formatted string 297 298 {b Example:} 299 {[ 300 let now = Ptime_clock.now () in 301 let s = format_rfc3339 now 302 (* returns "2024-11-03T10:30:45.123Z" or similar *) 303 ]} *) 304val format_rfc3339 : Ptime.t -> string 305 306 307(** {1 Validation} *) 308 309(** [validate feed] validates a JSON Feed. 310 311 Checks that: 312 - All required fields are present 313 - All items have unique IDs 314 - All items have valid content 315 - All URLs are well-formed (if possible) 316 - Authors have at least one field set 317 318 @param feed The feed to validate 319 @return [Ok ()] if valid, [Error errors] with a list of validation issues 320 321 {b Example:} 322 {[ 323 match Jsonfeed.validate feed with 324 | Ok () -> (* feed is valid *) 325 | Error errors -> 326 List.iter (Printf.eprintf "Validation error: %s\n") errors 327 ]} *) 328val validate : t -> (unit, string list) result 329 330 331(** {1 Comparison} *) 332 333(** [equal a b] tests equality between two feeds. 334 335 Feeds are compared structurally, including all fields and items. *) 336val equal : t -> t -> bool 337 338 339(** {1 Pretty Printing} *) 340 341(** [pp ppf feed] pretty prints a feed to the formatter. 342 343 The output is human-readable and suitable for debugging. It shows 344 the feed's metadata and a summary of items. 345 346 {b Example output:} 347 {v 348 Feed: My Blog (https://example.com) 349 Items: 2 350 Authors: Jane Doe 351 Language: en-US 352 v} *) 353val pp : Format.formatter -> t -> unit 354 355(** [pp_summary ppf feed] prints a brief summary of the feed. 356 357 Shows only the title and item count. 358 359 {b Example output:} 360 {v My Blog (2 items) v} *) 361val pp_summary : Format.formatter -> t -> unit 362 363 364(** {1 Feed Content} *) 365 366(** Author information for feeds and items. *) 367module Author = Author 368 369(** Attachments for feed items (audio, video, downloads). *) 370module Attachment = Attachment 371 372(** Hub endpoints for real-time notifications. *) 373module Hub = Hub 374 375(** Feed items (posts, episodes, entries). *) 376module Item = Item