···1+{
2+ "version": "https://jsonfeed.org/version/1",
3+ "title": "JSON Feed",
4+ "icon": "https://cdn.micro.blog/jsonfeed/avatar.jpg",
5+ "home_page_url": "https://www.jsonfeed.org/",
6+ "feed_url": "https://www.jsonfeed.org/feed.json",
7+ "items": [
8+ {
9+ "id": "http://jsonfeed.micro.blog/2020/08/07/json-feed-version.html",
10+ "title": "JSON Feed version 1.1",
11+ "content_html": "<p>We’ve updated the spec to <a href=\"https://jsonfeed.org/version/1.1\">version 1.1</a>. It’s a minor update to JSON Feed, clarifying a few things in the spec and adding a couple new fields such as <code>authors</code> and <code>language</code>.</p>\n\n<p>For version 1.1, we’re starting to move to the more specific MIME type <code>application/feed+json</code>. Clients that parse HTML to discover feeds should prefer that MIME type, while still falling back to accepting <code>application/json</code> too.</p>\n\n<p>The <a href=\"https://jsonfeed.org/code/\">code page</a> has also been updated with several new code libraries and apps that support JSON Feed.</p>\n",
12+13+ "date_published": "2020-08-07T11:44:36-05:00",
14+ "url": "https://www.jsonfeed.org/2020/08/07/json-feed-version.html"
15+ },
16+ {
17+ "id": "http://jsonfeed.micro.blog/2017/05/17/announcing-json-feed.html",
18+ "title": "Announcing JSON Feed",
19+ "content_html": "\n\n<p>We — Manton Reece and Brent Simmons — have noticed that JSON has become the developers’ choice for APIs, and that developers will often go out of their way to avoid XML. JSON is simpler to read and write, and it’s less prone to bugs.</p>\n\n<p>So we developed JSON Feed, a format similar to <a href=\"http://cyber.harvard.edu/rss/rss.html\">RSS</a> and <a href=\"https://tools.ietf.org/html/rfc4287\">Atom</a> but in JSON. It reflects the lessons learned from our years of work reading and publishing feeds.</p>\n\n<p><a href=\"https://jsonfeed.org/version/1\">See the spec</a>. It’s at version 1, which may be the only version ever needed. If future versions are needed, version 1 feeds will still be valid feeds.</p>\n\n<h4 id=\"notes\">Notes</h4>\n\n<p>We have a <a href=\"https://github.com/manton/jsonfeed-wp\">WordPress plugin</a> and, coming soon, a JSON Feed Parser for Swift. As more code is written, by us and others, we’ll update the <a href=\"https://jsonfeed.org/code\">code</a> page.</p>\n\n<p>See <a href=\"https://jsonfeed.org/mappingrssandatom\">Mapping RSS and Atom to JSON Feed</a> for more on the similarities between the formats.</p>\n\n<p>This website — the Markdown files and supporting resources — <a href=\"https://github.com/brentsimmons/JSONFeed\">is up on GitHub</a>, and you’re welcome to comment there.</p>\n\n<p>This website is also a blog, and you can subscribe to the <a href=\"https://jsonfeed.org/xml/rss.xml\">RSS feed</a> or the <a href=\"https://jsonfeed.org/feed.json\">JSON feed</a> (if your reader supports it).</p>\n\n<p>We worked with a number of people on this over the course of several months. We list them, and thank them, at the bottom of the <a href=\"https://jsonfeed.org/version/1\">spec</a>. But — most importantly — <a href=\"http://furbo.org/\">Craig Hockenberry</a> spent a little time making it look pretty. :)</p>\n",
20+21+ "date_published": "2017-05-17T10:02:12-05:00",
22+ "url": "https://www.jsonfeed.org/2017/05/17/announcing-json-feed.html"
23+ }
24+ ]
25+}
···1+(** Attachments for JSON Feed items.
2+3+ An attachment represents an external resource related to a feed item,
4+ such as audio files for podcasts, video files, or other downloadable content.
5+ Attachments with identical titles indicate alternate formats of the same resource.
6+7+ @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
8+9+10+(** The type representing an attachment. *)
11+type t
12+13+14+(** {1 Construction} *)
15+16+(** [create ~url ~mime_type ?title ?size_in_bytes ?duration_in_seconds ()]
17+ creates an attachment object.
18+19+ @param url The location of the attachment (required)
20+ @param mime_type The MIME type of the attachment, e.g. ["audio/mpeg"] (required)
21+ @param title The name of the attachment; identical titles indicate alternate formats
22+ of the same resource
23+ @param size_in_bytes The size of the attachment file in bytes
24+ @param duration_in_seconds The duration of the attachment in seconds (for audio/video)
25+26+ {b Examples:}
27+ {[
28+ (* Simple attachment *)
29+ let att = Attachment.create
30+ ~url:"https://example.com/episode.mp3"
31+ ~mime_type:"audio/mpeg" ()
32+33+ (* Podcast episode with metadata *)
34+ let att = Attachment.create
35+ ~url:"https://example.com/episode.mp3"
36+ ~mime_type:"audio/mpeg"
37+ ~title:"Episode 42"
38+ ~size_in_bytes:15_728_640L
39+ ~duration_in_seconds:1800 ()
40+41+ (* Alternate format (same title indicates same content) *)
42+ let att2 = Attachment.create
43+ ~url:"https://example.com/episode.aac"
44+ ~mime_type:"audio/aac"
45+ ~title:"Episode 42"
46+ ~size_in_bytes:12_582_912L
47+ ~duration_in_seconds:1800 ()
48+ ]} *)
49+val create :
50+ url:string ->
51+ mime_type:string ->
52+ ?title:string ->
53+ ?size_in_bytes:int64 ->
54+ ?duration_in_seconds:int ->
55+ unit ->
56+ t
57+58+59+(** {1 Accessors} *)
60+61+(** [url t] returns the attachment's URL. *)
62+val url : t -> string
63+64+(** [mime_type t] returns the attachment's MIME type. *)
65+val mime_type : t -> string
66+67+(** [title t] returns the attachment's title, if set. *)
68+val title : t -> string option
69+70+(** [size_in_bytes t] returns the attachment's size in bytes, if set. *)
71+val size_in_bytes : t -> int64 option
72+73+(** [duration_in_seconds t] returns the attachment's duration, if set. *)
74+val duration_in_seconds : t -> int option
75+76+77+(** {1 Comparison} *)
78+79+(** [equal a b] tests equality between two attachments. *)
80+val equal : t -> t -> bool
81+82+83+(** {1 Pretty Printing} *)
84+85+(** [pp ppf t] pretty prints an attachment to the formatter.
86+87+ The output is human-readable and suitable for debugging.
88+89+ {b Example output:}
90+ {v episode.mp3 (audio/mpeg, 15.0 MB, 30m0s) v} *)
91+val pp : Format.formatter -> t -> unit
+32
lib/author.ml
···00000000000000000000000000000000
···1+(** Author information for JSON Feed items and feeds. *)
2+3+type t = {
4+ name : string option;
5+ url : string option;
6+ avatar : string option;
7+}
8+9+let create ?name ?url ?avatar () =
10+ if name = None && url = None && avatar = None then
11+ invalid_arg "Author.create: at least one field (name, url, or avatar) must be provided";
12+ { name; url; avatar }
13+14+let name t = t.name
15+let url t = t.url
16+let avatar t = t.avatar
17+18+let is_valid t =
19+ t.name <> None || t.url <> None || t.avatar <> None
20+21+let equal a b =
22+ a.name = b.name && a.url = b.url && a.avatar = b.avatar
23+24+let pp ppf t =
25+ match t.name, t.url with
26+ | Some name, Some url -> Format.fprintf ppf "%s <%s>" name url
27+ | Some name, None -> Format.fprintf ppf "%s" name
28+ | None, Some url -> Format.fprintf ppf "<%s>" url
29+ | None, None ->
30+ match t.avatar with
31+ | Some avatar -> Format.fprintf ppf "(avatar: %s)" avatar
32+ | None -> Format.fprintf ppf "(empty author)"
···1+(** Author information for JSON Feed items and feeds.
2+3+ An author object provides information about the creator of a feed or item.
4+ According to the JSON Feed 1.1 specification, at least one field must be
5+ present when an author object is included.
6+7+ @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
8+9+10+(** The type representing an author. *)
11+type t
12+13+14+(** {1 Construction} *)
15+16+(** [create ?name ?url ?avatar ()] creates an author object.
17+18+ At least one of the optional parameters must be provided, otherwise
19+ the function will raise [Invalid_argument].
20+21+ @param name The author's name
22+ @param url URL of the author's website or profile
23+ @param avatar URL of the author's avatar image (should be square, 512x512 or larger)
24+25+ {b Examples:}
26+ {[
27+ let author = Author.create ~name:"Jane Doe" ()
28+ let author = Author.create ~name:"Jane Doe" ~url:"https://janedoe.com" ()
29+ let author = Author.create
30+ ~name:"Jane Doe"
31+ ~url:"https://janedoe.com"
32+ ~avatar:"https://janedoe.com/avatar.png" ()
33+ ]} *)
34+val create : ?name:string -> ?url:string -> ?avatar:string -> unit -> t
35+36+37+(** {1 Accessors} *)
38+39+(** [name t] returns the author's name, if set. *)
40+val name : t -> string option
41+42+(** [url t] returns the author's URL, if set. *)
43+val url : t -> string option
44+45+(** [avatar t] returns the author's avatar URL, if set. *)
46+val avatar : t -> string option
47+48+49+(** {1 Predicates} *)
50+51+(** [is_valid t] checks if the author has at least one field set.
52+53+ This should always return [true] for authors created via {!create},
54+ but may be useful when parsing from external sources. *)
55+val is_valid : t -> bool
56+57+58+(** {1 Comparison} *)
59+60+(** [equal a b] tests equality between two authors. *)
61+val equal : t -> t -> bool
62+63+64+(** {1 Pretty Printing} *)
65+66+(** [pp ppf t] pretty prints an author to the formatter.
67+68+ The output is human-readable and suitable for debugging.
69+70+ {b Example output:}
71+ {v Jane Doe <https://janedoe.com> v} *)
72+val pp : Format.formatter -> t -> unit
···1+(** Hub endpoints for real-time notifications.
2+3+ Hubs describe endpoints that can be used to subscribe to real-time
4+ notifications of changes to the feed. This is an optional and rarely-used
5+ feature of JSON Feed, primarily for feeds that update frequently.
6+7+ @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
8+9+10+(** The type representing a hub endpoint. *)
11+type t
12+13+14+(** {1 Construction} *)
15+16+(** [create ~type_ ~url ()] creates a hub object.
17+18+ @param type_ The type of hub protocol (e.g., ["rssCloud"], ["WebSub"])
19+ @param url The URL endpoint for the hub
20+21+ {b Example:}
22+ {[
23+ let hub = Hub.create
24+ ~type_:"WebSub"
25+ ~url:"https://pubsubhubbub.appspot.com/" ()
26+ ]} *)
27+val create : type_:string -> url:string -> unit -> t
28+29+30+(** {1 Accessors} *)
31+32+(** [type_ t] returns the hub's protocol type. *)
33+val type_ : t -> string
34+35+(** [url t] returns the hub's endpoint URL. *)
36+val url : t -> string
37+38+39+(** {1 Comparison} *)
40+41+(** [equal a b] tests equality between two hubs. *)
42+val equal : t -> t -> bool
43+44+45+(** {1 Pretty Printing} *)
46+47+(** [pp ppf t] pretty prints a hub to the formatter.
48+49+ The output is human-readable and suitable for debugging.
50+51+ {b Example output:}
52+ {v WebSub: https://pubsubhubbub.appspot.com/ v} *)
53+val pp : Format.formatter -> t -> unit
···1+(** Feed items in a JSON Feed.
2+3+ An item represents a single entry in a feed, such as a blog post, podcast episode,
4+ or microblog entry. Each item must have a unique identifier and content.
5+6+ @see <https://www.jsonfeed.org/version/1.1/> JSON Feed Specification *)
7+8+9+(** The type representing a feed item. *)
10+type t
11+12+(** Content representation for an item.
13+14+ The JSON Feed specification requires that each item has at least one
15+ form of content. This type enforces that requirement at compile time.
16+17+ - [`Html s]: Item has HTML content only
18+ - [`Text s]: Item has plain text content only
19+ - [`Both (html, text)]: Item has both HTML and plain text versions *)
20+type content =
21+ [ `Html of string
22+ | `Text of string
23+ | `Both of string * string
24+ ]
25+26+27+(** {1 Construction} *)
28+29+(** [create ~id ~content ?url ?external_url ?title ?summary ?image ?banner_image
30+ ?date_published ?date_modified ?authors ?tags ?language ?attachments ()]
31+ creates a feed item.
32+33+ @param id Unique identifier for the item (required). Should be a full URL if possible.
34+ @param content The item's content in HTML and/or plain text (required)
35+ @param url Permalink to the item
36+ @param external_url URL of an external resource (useful for linkblogs)
37+ @param title Plain text title of the item
38+ @param summary Plain text summary/excerpt of the item
39+ @param image URL of the main featured image for the item
40+ @param banner_image URL of a banner image for the item
41+ @param date_published Publication date/time (RFC 3339 format)
42+ @param date_modified Last modification date/time (RFC 3339 format)
43+ @param authors Item-specific authors (overrides feed-level authors)
44+ @param tags Plain text tags/categories for the item
45+ @param language Primary language of the item (RFC 5646 format, e.g. ["en-US"])
46+ @param attachments Related resources like audio files or downloads
47+48+ {b Examples:}
49+ {[
50+ (* Simple blog post *)
51+ let item = Item.create
52+ ~id:"https://example.com/posts/42"
53+ ~content:(`Html "<p>Hello, world!</p>")
54+ ~title:"My First Post"
55+ ~url:"https://example.com/posts/42" ()
56+57+ (* Microblog entry with plain text *)
58+ let item = Item.create
59+ ~id:"https://example.com/micro/123"
60+ ~content:(`Text "Just posted a new photo!")
61+ ~date_published:(Ptime.of_float_s (Unix.time ()) |> Option.get) ()
62+63+ (* Article with both HTML and plain text *)
64+ let item = Item.create
65+ ~id:"https://example.com/article/99"
66+ ~content:(`Both ("<p>Rich content</p>", "Plain version"))
67+ ~title:"Article Title"
68+ ~tags:["ocaml"; "programming"] ()
69+70+ (* Podcast episode with attachment *)
71+ let attachment = Attachment.create
72+ ~url:"https://example.com/ep1.mp3"
73+ ~mime_type:"audio/mpeg"
74+ ~duration_in_seconds:1800 () in
75+ let item = Item.create
76+ ~id:"https://example.com/podcast/1"
77+ ~content:(`Html "<p>Episode description</p>")
78+ ~title:"Episode 1"
79+ ~attachments:[attachment] ()
80+ ]} *)
81+val create :
82+ id:string ->
83+ content:content ->
84+ ?url:string ->
85+ ?external_url:string ->
86+ ?title:string ->
87+ ?summary:string ->
88+ ?image:string ->
89+ ?banner_image:string ->
90+ ?date_published:Ptime.t ->
91+ ?date_modified:Ptime.t ->
92+ ?authors:Author.t list ->
93+ ?tags:string list ->
94+ ?language:string ->
95+ ?attachments:Attachment.t list ->
96+ unit ->
97+ t
98+99+100+(** {1 Accessors} *)
101+102+(** [id t] returns the item's unique identifier. *)
103+val id : t -> string
104+105+(** [content t] returns the item's content. *)
106+val content : t -> content
107+108+(** [url t] returns the item's permalink URL, if set. *)
109+val url : t -> string option
110+111+(** [external_url t] returns the external resource URL, if set. *)
112+val external_url : t -> string option
113+114+(** [title t] returns the item's title, if set. *)
115+val title : t -> string option
116+117+(** [summary t] returns the item's summary, if set. *)
118+val summary : t -> string option
119+120+(** [image t] returns the item's featured image URL, if set. *)
121+val image : t -> string option
122+123+(** [banner_image t] returns the item's banner image URL, if set. *)
124+val banner_image : t -> string option
125+126+(** [date_published t] returns the item's publication date, if set. *)
127+val date_published : t -> Ptime.t option
128+129+(** [date_modified t] returns the item's last modification date, if set. *)
130+val date_modified : t -> Ptime.t option
131+132+(** [authors t] returns the item's authors, if set. *)
133+val authors : t -> Author.t list option
134+135+(** [tags t] returns the item's tags, if set. *)
136+val tags : t -> string list option
137+138+(** [language t] returns the item's language code, if set. *)
139+val language : t -> string option
140+141+(** [attachments t] returns the item's attachments, if set. *)
142+val attachments : t -> Attachment.t list option
143+144+145+(** {1 Content Helpers} *)
146+147+(** [content_html t] extracts HTML content from the item.
148+149+ Returns [Some html] if the item has HTML content (either [Html] or [Both]),
150+ [None] otherwise. *)
151+val content_html : t -> string option
152+153+(** [content_text t] extracts plain text content from the item.
154+155+ Returns [Some text] if the item has plain text content (either [Text] or [Both]),
156+ [None] otherwise. *)
157+val content_text : t -> string option
158+159+160+(** {1 Comparison} *)
161+162+(** [equal a b] tests equality between two items.
163+164+ Items are considered equal if they have the same ID. *)
165+val equal : t -> t -> bool
166+167+(** [compare a b] compares two items by their publication dates.
168+169+ Items without publication dates are considered older than items with dates.
170+ Useful for sorting items chronologically. *)
171+val compare : t -> t -> int
172+173+174+(** {1 Pretty Printing} *)
175+176+(** [pp ppf t] pretty prints an item to the formatter.
177+178+ The output is human-readable and suitable for debugging.
179+180+ {b Example output:}
181+ {v [2024-11-03] My First Post (https://example.com/posts/42) v} *)
182+val pp : Format.formatter -> t -> unit
183+184+(** [pp_content ppf content] pretty prints content to the formatter.
185+186+ {b Example output:}
187+ {v HTML (123 chars) v}
188+ {v Text (56 chars) v}
189+ {v Both (HTML: 123 chars, Text: 56 chars) v} *)
190+val pp_content : Format.formatter -> content -> unit
···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. *)
43+type t
44+45+(** Exception raised when attempting to parse an invalid feed. *)
46+exception 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+ ]} *)
129+val 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. *)
153+val version : t -> string
154+155+(** [title t] returns the feed's title. *)
156+val title : t -> string
157+158+(** [home_page_url t] returns the feed's home page URL, if set. *)
159+val home_page_url : t -> string option
160+161+(** [feed_url t] returns the feed's URL, if set. *)
162+val feed_url : t -> string option
163+164+(** [description t] returns the feed's description, if set. *)
165+val description : t -> string option
166+167+(** [user_comment t] returns the feed's user comment, if set. *)
168+val user_comment : t -> string option
169+170+(** [next_url t] returns the URL for the next page of items, if set. *)
171+val next_url : t -> string option
172+173+(** [icon t] returns the feed's icon URL, if set. *)
174+val icon : t -> string option
175+176+(** [favicon t] returns the feed's favicon URL, if set. *)
177+val favicon : t -> string option
178+179+(** [authors t] returns the feed's default authors, if set. *)
180+val authors : t -> Author.t list option
181+182+(** [language t] returns the feed's primary language, if set. *)
183+val language : t -> string option
184+185+(** [expired t] returns whether the feed will update again. *)
186+val expired : t -> bool option
187+188+(** [hubs t] returns the feed's hub endpoints, if set. *)
189+val hubs : t -> Hub.t list option
190+191+(** [items t] returns the feed's items. *)
192+val items : t -> Item.t list
193+194+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+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+ ]} *)
215+val 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+ ]} *)
232+val 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+ ]} *)
250+val 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+ ]} *)
264+val 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+ ]} *)
288+val 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+ ]} *)
304+val 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+ ]} *)
328+val 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. *)
336+val 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} *)
353+val 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} *)
361+val pp_summary : Format.formatter -> t -> unit
362+363+364+(** {1 Feed Content} *)
365+366+(** Author information for feeds and items. *)
367+module Author = Author
368+369+(** Attachments for feed items (audio, video, downloads). *)
370+module Attachment = Attachment
371+372+(** Hub endpoints for real-time notifications. *)
373+module Hub = Hub
374+375+(** Feed items (posts, episodes, entries). *)
376+module Item = Item