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