(** JSON Feed format parser and serializer. This library implements the JSON Feed specification version 1.1, providing type-safe parsing and serialization of JSON Feed documents. JSON Feed is a syndication format similar to RSS and Atom, but using JSON instead of XML. {b Key Features:} - Type-safe construction with compile-time validation - Support for all JSON Feed 1.1 fields - RFC 3339 date parsing with Ptime integration - Streaming parsing and serialization with Jsonm - Comprehensive documentation and examples {b Quick Start:} {[ (* Create a simple feed *) let feed = Jsonfeed.create ~title:"My Blog" ~home_page_url:"https://example.com" ~feed_url:"https://example.com/feed.json" ~items:[ Item.create ~id:"https://example.com/post/1" ~content:(Item.Html "

Hello, world!

") ~title:"First Post" () ] () (* Serialize to string *) let json = Jsonfeed.to_string feed (* Parse from string *) match Jsonfeed.of_string json with | Ok feed -> Printf.printf "Feed: %s\n" (Jsonfeed.title feed) | Error (`Msg err) -> Printf.eprintf "Error: %s\n" err ]} @see JSON Feed Specification *) (** The type representing a complete JSON Feed. *) type t (** Exception raised when attempting to parse an invalid feed. *) exception Invalid_feed of string (** {1 Construction} *) (** [create ~title ?home_page_url ?feed_url ?description ?user_comment ?next_url ?icon ?favicon ?authors ?language ?expired ?hubs ~items ()] creates a JSON Feed. @param title The name of the feed (required) @param home_page_url The URL of the resource the feed describes @param feed_url The URL of the feed itself (serves as unique identifier) @param description Additional information about the feed @param user_comment A description of the feed's purpose for humans reading the raw JSON @param next_url URL of the next page of items (for pagination) @param icon The feed's icon URL (should be square, 512x512 or larger) @param favicon The feed's favicon URL (should be square, 64x64 or larger) @param authors The feed's default authors (inherited by items without authors) @param language The primary language of the feed (RFC 5646 format, e.g. ["en-US"]) @param expired Whether the feed will update again ([true] means no more updates) @param hubs Endpoints for real-time notifications @param items The list of feed items (required) {b Examples:} {[ (* Minimal feed *) let feed = Jsonfeed.create ~title:"My Blog" ~items:[] () (* Full-featured blog feed *) let feed = Jsonfeed.create ~title:"Example Blog" ~home_page_url:"https://example.com" ~feed_url:"https://example.com/feed.json" ~description:"A blog about OCaml and functional programming" ~icon:"https://example.com/icon.png" ~authors:[ Author.create ~name:"Jane Doe" ~url:"https://example.com/about" () ] ~language:"en-US" ~items:[ Item.create ~id:"https://example.com/posts/1" ~content:(Item.Html "

First post

") ~title:"Hello World" (); Item.create ~id:"https://example.com/posts/2" ~content:(Item.Html "

Second post

") ~title:"Another Post" () ] () (* Podcast feed with hubs *) let hub = Hub.create ~type_:"WebSub" ~url:"https://pubsubhubbub.appspot.com/" () in let feed = Jsonfeed.create ~title:"My Podcast" ~home_page_url:"https://podcast.example.com" ~feed_url:"https://podcast.example.com/feed.json" ~hubs:[hub] ~items:[ Item.create ~id:"https://podcast.example.com/episodes/1" ~content:(Item.Html "

Episode description

") ~title:"Episode 1" ~attachments:[ Attachment.create ~url:"https://podcast.example.com/ep1.mp3" ~mime_type:"audio/mpeg" ~duration_in_seconds:1800 () ] () ] () ]} *) val create : title:string -> ?home_page_url:string -> ?feed_url:string -> ?description:string -> ?user_comment:string -> ?next_url:string -> ?icon:string -> ?favicon:string -> ?authors:Author.t list -> ?language:string -> ?expired:bool -> ?hubs:Hub.t list -> items:Item.t list -> unit -> t (** {1 Accessors} *) (** [version t] returns the JSON Feed version URL. This is always ["https://jsonfeed.org/version/1.1"] for feeds created by this library, but may differ when parsing external feeds. *) val version : t -> string (** [title t] returns the feed's title. *) val title : t -> string (** [home_page_url t] returns the feed's home page URL, if set. *) val home_page_url : t -> string option (** [feed_url t] returns the feed's URL, if set. *) val feed_url : t -> string option (** [description t] returns the feed's description, if set. *) val description : t -> string option (** [user_comment t] returns the feed's user comment, if set. *) val user_comment : t -> string option (** [next_url t] returns the URL for the next page of items, if set. *) val next_url : t -> string option (** [icon t] returns the feed's icon URL, if set. *) val icon : t -> string option (** [favicon t] returns the feed's favicon URL, if set. *) val favicon : t -> string option (** [authors t] returns the feed's default authors, if set. *) val authors : t -> Author.t list option (** [language t] returns the feed's primary language, if set. *) val language : t -> string option (** [expired t] returns whether the feed will update again. *) val expired : t -> bool option (** [hubs t] returns the feed's hub endpoints, if set. *) val hubs : t -> Hub.t list option (** [items t] returns the feed's items. *) val items : t -> Item.t list (** {1 Parsing and Serialization} *) (** Error type for parsing operations. *) type error = [ `Msg of string ] (** [of_jsonm decoder] parses a JSON Feed from a Jsonm decoder. This is the lowest-level parsing function, suitable for integration with streaming JSON processing pipelines. @param decoder A Jsonm decoder positioned at the start of a JSON Feed document @return [Ok feed] on success, [Error (`Msg err)] on parse error {b Example:} {[ let decoder = Jsonm.decoder (`String json_string) in match Jsonfeed.of_jsonm decoder with | Ok feed -> (* process feed *) | Error (`Msg err) -> (* handle error *) ]} *) val of_jsonm : Jsonm.decoder -> (t, [> error]) result (** [to_jsonm encoder feed] serializes a JSON Feed to a Jsonm encoder. This is the lowest-level serialization function, suitable for integration with streaming JSON generation pipelines. @param encoder A Jsonm encoder @param feed The feed to serialize {b Example:} {[ let buffer = Buffer.create 1024 in let encoder = Jsonm.encoder (`Buffer buffer) in Jsonfeed.to_jsonm encoder feed; let json = Buffer.contents buffer ]} *) val to_jsonm : Jsonm.encoder -> t -> unit (** [of_string s] parses a JSON Feed from a string. @param s A JSON string containing a JSON Feed document @return [Ok feed] on success, [Error (`Msg err)] on parse error {b Example:} {[ let json = {|{ "version": "https://jsonfeed.org/version/1.1", "title": "My Feed", "items": [] }|} in match Jsonfeed.of_string json with | Ok feed -> Printf.printf "Parsed: %s\n" (Jsonfeed.title feed) | Error (`Msg err) -> Printf.eprintf "Error: %s\n" err ]} *) val of_string : string -> (t, [> error]) result (** [to_string ?minify feed] serializes a JSON Feed to a string. @param minify If [true], produces compact JSON without whitespace. If [false] (default), produces indented, human-readable JSON. @param feed The feed to serialize @return A JSON string {b Example:} {[ let json = Jsonfeed.to_string feed let compact = Jsonfeed.to_string ~minify:true feed ]} *) val to_string : ?minify:bool -> t -> string (** {1 Date Utilities} *) (** [parse_rfc3339 s] parses an RFC 3339 date/time string. This function parses timestamps in the format required by JSON Feed, such as ["2024-11-03T10:30:00Z"] or ["2024-11-03T10:30:00-08:00"]. @param s An RFC 3339 formatted date/time string @return [Some time] on success, [None] if the string is invalid {b Examples:} {[ parse_rfc3339 "2024-11-03T10:30:00Z" (* returns Some time *) parse_rfc3339 "2024-11-03T10:30:00-08:00" (* returns Some time *) parse_rfc3339 "invalid" (* returns None *) ]} *) val parse_rfc3339 : string -> Ptime.t option (** [format_rfc3339 time] formats a timestamp as an RFC 3339 string. The output uses UTC timezone (Z suffix) and includes fractional seconds if the timestamp has sub-second precision. @param time A Ptime timestamp @return An RFC 3339 formatted string {b Example:} {[ let now = Ptime_clock.now () in let s = format_rfc3339 now (* returns "2024-11-03T10:30:45.123Z" or similar *) ]} *) val format_rfc3339 : Ptime.t -> string (** {1 Validation} *) (** [validate feed] validates a JSON Feed. Checks that: - All required fields are present - All items have unique IDs - All items have valid content - All URLs are well-formed (if possible) - Authors have at least one field set @param feed The feed to validate @return [Ok ()] if valid, [Error errors] with a list of validation issues {b Example:} {[ match Jsonfeed.validate feed with | Ok () -> (* feed is valid *) | Error errors -> List.iter (Printf.eprintf "Validation error: %s\n") errors ]} *) val validate : t -> (unit, string list) result (** {1 Comparison} *) (** [equal a b] tests equality between two feeds. Feeds are compared structurally, including all fields and items. *) val equal : t -> t -> bool (** {1 Pretty Printing} *) (** [pp ppf feed] pretty prints a feed to the formatter. The output is human-readable and suitable for debugging. It shows the feed's metadata and a summary of items. {b Example output:} {v Feed: My Blog (https://example.com) Items: 2 Authors: Jane Doe Language: en-US v} *) val pp : Format.formatter -> t -> unit (** [pp_summary ppf feed] prints a brief summary of the feed. Shows only the title and item count. {b Example output:} {v My Blog (2 items) v} *) val pp_summary : Format.formatter -> t -> unit (** {1 Feed Content} *) (** Author information for feeds and items. *) module Author = Author (** Attachments for feed items (audio, video, downloads). *) module Attachment = Attachment (** Hub endpoints for real-time notifications. *) module Hub = Hub (** Feed items (posts, episodes, entries). *) module Item = Item