json_feed_model/
lib.rs

1//! # JSON Feed Model
2//!
3//! [JSON Feed][jsonfeed] Model provides types which can be used to manipulate JSON
4//! Feed data.
5//!
6//! The crate is basically a [newtype][newtype] wrapper around [Serde
7//! JSON][serde_json]'s `Map` type and provides methods to JSON Feed properties.
8//!
9//! For example, a library user can have a slice of bytes and create a `Feed` by
10//! calling `from_slice`. If the slice of bytes is a JSON object, then a `Feed`
11//! instance is returned. The only guarantee which `Feed` and other model types make
12//! is that the JSON data is a JSON object.
13//!
14//! The library user can call `is_valid(Version::Version1_1)` on the `Feed` instance
15//! to determine if the JSON object is a valid Version 1.1 JSON Feed.
16//!
17//! ## Documentation
18//!
19//! * [Latest API Docs][api_docs]
20//!
21//! ### Alloc Only
22//!
23//! If the host environment has an allocator but does not have access to the Rust `std` library:
24//!
25//! ```toml
26//! [dependencies]
27//! json-feed-model = { version = "0.2.0", default-features = false, features = ["alloc"]}
28//! ```
29//!
30//! # Accessor Methods
31//!
32//! If the library user wants to read or write data, then methods like `title()`,
33//! `set_title(...)`, and `remove_title()` exist on `Feed`.
34//!
35//! For "getter" methods, the return type is a `Result<Option<type>, ...>`.  The
36//! "getter" may fail due to expecting the wrong JSON type. For instance, if a field
37//! is expected to be a JSON string but the value is a JSON number, then an
38//! `Error::UnexpectedType` will be returned. The field value may or may not be
39//! present so the `Option` type is used to indicate if a value exists.
40//!
41//! For "setter" and "remove" methods, any existing value in the JSON object is
42//! returned.
43//!
44//! # Owned, Borrowed, and Borrowed Mutable Types
45//!
46//! There are 3 variants of every model type, the "owned" data type (e.g. `Feed`),
47//! the borrowed data type (e.g.  `FeedRef`), and the borrowed mutable data type
48//! (e.g. `FeedMut`). In most cases, the "owned" data type will be the primary kind
49//! explicitly used. The borrowed and borrowed mutable variants may be returned from
50//! "getter" methods for performance reasons.
51//!
52//! A few standard traits are implemented like `From<Map<String,Value>>` and
53//! `Serialize` as well as a few helper methods like `as_map()` and `as_map_mut()`
54//! for the model types.
55//!
56//! ## Examples
57//!
58//! The following example shows how to read properties.
59//!
60//! ```rust
61//! use json_feed_model::{Feed, ItemRef, Version};
62//!
63//! let json = serde_json::json!({
64//!     "version": "https://jsonfeed.org/version/1.1",
65//!     "title": "Lorem ipsum dolor sit amet.",
66//!     "home_page_url": "https://example.org/",
67//!     "feed_url": "https://example.org/feed.json",
68//!     "items": [
69//!         {
70//!             "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0",
71//!             "content_text": "Aenean tristique dictum mauris, et.",
72//!             "url": "https://example.org/aenean-tristique"
73//!         },
74//!         {
75//!             "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
76//!             "content_html": "Vestibulum non magna vitae tortor.",
77//!             "url": "https://example.org/vestibulum-non"
78//!         }
79//!     ]
80//! });
81//!
82//! let feed = json_feed_model::from_value(json)?;
83//!
84//! assert!(feed.is_valid(&Version::Version1_1));
85//!
86//! assert_eq!(feed.version()?, Some(json_feed_model::VERSION_1_1));
87//! assert_eq!(feed.title()?, Some("Lorem ipsum dolor sit amet."));
88//! assert_eq!(feed.home_page_url()?, Some("https://example.org/"));
89//! assert_eq!(feed.feed_url()?, Some("https://example.org/feed.json"));
90//!
91//! let items: Option<Vec<ItemRef>> = feed.items()?;
92//! assert!(items.is_some());
93//! let items: Vec<ItemRef> = items.unwrap();
94//! assert_eq!(items.len(), 2);
95//!
96//! assert_eq!(items[0].id()?, Some("cd7f0673-8e81-4e13-b273-4bd1b83967d0"));
97//! assert_eq!(
98//!     items[0].content_text()?,
99//!     Some("Aenean tristique dictum mauris, et.")
100//! );
101//! assert_eq!(
102//!     items[0].url()?,
103//!     Some("https://example.org/aenean-tristique")
104//! );
105//!
106//! assert_eq!(items[1].id()?, Some("2bcb497d-c40b-4493-b5ae-bc63c74b48fa"));
107//! assert_eq!(
108//!     items[1].content_html()?,
109//!     Some("Vestibulum non magna vitae tortor.")
110//! );
111//! assert_eq!(items[1].url()?, Some("https://example.org/vestibulum-non"));
112//! # Ok::<(), json_feed_model::Error>(())
113//! ```
114//!
115//! ### Custom Extension
116//!
117//! The following example uses a custom trait to write and then read a custom extension.
118//! It also shows a simple way to use `serde_json` to write the JSON Feed. See
119//! `serde_json` for other serialization methods.
120//!
121//! ```rust
122//! use json_feed_model::{Feed, Item, Version};
123//! use serde_json::Value;
124//!
125//! trait ExampleExtension {
126//!     fn example(&self) -> Result<Option<&str>, json_feed_model::Error>;
127//!
128//!     fn set_example<T>(&mut self, value: T) -> Option<Value>
129//!     where
130//!         T: ToString;
131//! }
132//!
133//! impl ExampleExtension for Feed {
134//!     fn example(&self) -> Result<Option<&str>, json_feed_model::Error> {
135//!         self.as_map().get("_example").map_or_else(
136//!             || Ok(None),
137//!             |value| match value {
138//!                 Value::String(s) => Ok(Some(s.as_str())),
139//!                 _ => Err(json_feed_model::Error::UnexpectedType),
140//!             },
141//!         )
142//!     }
143//!
144//!     fn set_example<T>(&mut self, value: T) -> Option<Value>
145//!     where
146//!         T: ToString,
147//!     {
148//!         self.as_map_mut()
149//!             .insert(String::from("_example"), Value::String(value.to_string()))
150//!     }
151//! }
152//!
153//! let mut feed = Feed::new();
154//! feed.set_version(Version::Version1_1);
155//! feed.set_title("Lorem ipsum dolor sit amet.");
156//!
157//! feed.set_example("123456");
158//!
159//! let mut item = Item::new();
160//! item.set_id("2bcb497d-c40b-4493-b5ae-bc63c74b48fa");
161//! item.set_content_text("Vestibulum non magna vitae tortor.");
162//! item.set_url("https://example.org/vestibulum-non");
163//!
164//! feed.set_items(vec![item]);
165//!
166//! assert!(feed.is_valid(&Version::Version1_1));
167//!
168//! let expected_json = serde_json::json!({
169//!     "version": "https://jsonfeed.org/version/1.1",
170//!     "title": "Lorem ipsum dolor sit amet.",
171//!     "_example": "123456",
172//!     "items": [
173//!         {
174//!             "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
175//!             "content_text": "Vestibulum non magna vitae tortor.",
176//!             "url": "https://example.org/vestibulum-non",
177//!         }
178//!     ]
179//! });
180//! assert_eq!(feed, json_feed_model::from_value(expected_json)?);
181//!
182//! assert_eq!(feed.example()?, Some("123456"));
183//!
184//! let output = serde_json::to_string(&feed);
185//! assert!(output.is_ok());
186//! # Ok::<(), json_feed_model::Error>(())
187//! ```
188//!
189//! ## License
190//!
191//! Licensed under either of [Apache License, Version 2.0][license_apache] or [MIT
192//! License][license_mit] at your option.
193//!
194//! ### Contributions
195//!
196//! Unless you explicitly state otherwise, any contribution intentionally submitted
197//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
198//! dual licensed as above, without any additional terms or conditions.
199//!
200//! [license_apache]: LICENSE-APACHE
201//! [license_mit]: LICENSE-MIT
202//! [jsonfeed]: https://jsonfeed.org/
203//! [newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
204//! [serde_json]: https://github.com/serde-rs/json
205//! [api_docs]: https://docs.rs/json-feed-model/
206
207#![cfg_attr(not(feature = "std"), no_std)]
208#![cfg_attr(docsrs, feature(doc_cfg))]
209#![warn(
210    missing_copy_implementations,
211    missing_debug_implementations,
212    missing_docs,
213    rust_2018_idioms,
214    unused_lifetimes,
215    unused_qualifications
216)]
217
218#[cfg(all(feature = "alloc", not(feature = "std")))]
219extern crate alloc;
220
221use core::str;
222
223#[cfg(all(feature = "alloc", not(feature = "std")))]
224use alloc::{
225    collections::BTreeSet,
226    string::{String, ToString},
227    vec::Vec,
228};
229#[cfg(feature = "std")]
230use std::{
231    collections::BTreeSet,
232    string::{String, ToString},
233    vec::Vec,
234};
235
236use serde_json::{Map, Value};
237
238/// Version 1 identifier (for 1.0 feeds)
239pub const VERSION_1: &str = "https://jsonfeed.org/version/1";
240
241/// Version 1.1 identifier
242pub const VERSION_1_1: &str = "https://jsonfeed.org/version/1.1";
243
244/// A JSON Feed spec version identifier
245#[derive(Clone, Debug, Eq, PartialEq)]
246pub enum Version<'a> {
247    /// <https://jsonfeed.org/version/1>
248    Version1,
249    /// <https://jsonfeed.org/version/1.1>
250    Version1_1,
251    /// An unknown version
252    Unknown(&'a str),
253}
254
255impl AsRef<str> for Version<'_> {
256    fn as_ref(&self) -> &str {
257        match self {
258            Version::Version1 => VERSION_1,
259            Version::Version1_1 => VERSION_1_1,
260            Version::Unknown(v) => v,
261        }
262    }
263}
264
265impl<'a> From<&'a str> for Version<'a> {
266    fn from(value: &'a str) -> Self {
267        match value {
268            VERSION_1 => Version::Version1,
269            VERSION_1_1 => Version::Version1_1,
270            _ => Version::Unknown(value),
271        }
272    }
273}
274
275impl core::fmt::Display for Version<'_> {
276    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
277        write!(f, "{}", self.as_ref())
278    }
279}
280
281/// All of the possible crate errors.
282#[derive(Debug)]
283#[non_exhaustive]
284pub enum Error {
285    /// If the JSON value is an unexpected type.
286    ///
287    /// For instance, if a JSON string is expected but the actual value is a JSON object, then
288    /// `UnexpectedType` would be returned as an error.
289    UnexpectedType,
290    /// If there is an error decoding the JSON.
291    SerdeJson(serde_json::Error),
292}
293
294impl From<serde_json::Error> for Error {
295    fn from(error: serde_json::Error) -> Self {
296        Error::SerdeJson(error)
297    }
298}
299
300macro_rules! get_set_rm_str {
301    ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr) => {
302        get_set_rm_str!($key_expr, $getter, $getter_doc, $setter, $setter_doc);
303
304        #[doc=$remover_doc]
305        pub fn $remover(&mut self) -> Option<Value> {
306            self.value.remove($key_expr)
307        }
308    };
309
310    ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr) => {
311        get_set_rm_str!($key_expr, $getter, $getter_doc);
312
313        #[doc=$setter_doc]
314        pub fn $setter<T>(&mut self, value: T) -> Option<Value>
315        where
316            T: ToString,
317        {
318            self.value
319                .insert(String::from($key_expr), Value::String(value.to_string()))
320        }
321    };
322
323    ($key_expr:expr, $getter:ident, $getter_doc:expr) => {
324        #[doc=$getter_doc]
325        pub fn $getter(&self) -> Result<Option<&str>, Error> {
326            self.value.get($key_expr).map_or_else(
327                || Ok(None),
328                |value| match value {
329                    Value::String(s) => Ok(Some(s.as_str())),
330                    _ => Err(Error::UnexpectedType),
331                },
332            )
333        }
334    };
335}
336
337macro_rules! get_set_rm_str_array {
338    ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr) => {
339        get_set_rm_str_array!($key_expr, $getter, $getter_doc, $setter, $setter_doc);
340
341        #[doc=$remover_doc]
342        pub fn $remover(&mut self) -> Option<Value> {
343            self.value.remove($key_expr)
344        }
345    };
346
347    ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr) => {
348        get_set_rm_str_array!($key_expr, $getter, $getter_doc);
349
350        #[doc=$setter_doc]
351        pub fn $setter<I>(&mut self, values: I) -> Option<Value>
352        where
353            I: IntoIterator<Item = String>,
354        {
355            let values: Value = Value::Array(values.into_iter().map(Value::String).collect());
356            self.value.insert(String::from($key_expr), values)
357        }
358    };
359
360    ($key_expr:expr, $getter:ident, $getter_doc:expr) => {
361        #[doc=$getter_doc]
362        pub fn $getter(&self) -> Result<Option<Vec<&str>>, Error> {
363            self.value.get($key_expr).map_or_else(
364                || Ok(None),
365                |value| match value {
366                    Value::Array(arr) => arr
367                        .iter()
368                        .map(|value| match value {
369                            Value::String(s) => Ok(s.as_str()),
370                            _ => Err(Error::UnexpectedType),
371                        })
372                        .collect::<Result<Vec<&str>, Error>>()
373                        .map(Some),
374                    _ => Err(Error::UnexpectedType),
375                },
376            )
377        }
378    };
379}
380
381macro_rules! get_set_rm_bool {
382    ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr) => {
383        get_set_rm_bool!($key_expr, $getter, $getter_doc, $setter, $setter_doc);
384
385        #[doc=$remover_doc]
386        pub fn $remover(&mut self) -> Option<Value> {
387            self.value.remove($key_expr)
388        }
389    };
390
391    ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr) => {
392        get_set_rm_bool!($key_expr, $getter, $getter_doc);
393
394        #[doc=$setter_doc]
395        pub fn $setter<T>(&mut self, value: bool) -> Option<Value> {
396            self.value
397                .insert(String::from($key_expr), Value::Bool(value))
398        }
399    };
400
401    ($key_expr:expr, $getter:ident, $getter_doc:expr) => {
402        #[doc=$getter_doc]
403        pub fn $getter(&self) -> Result<Option<bool>, Error> {
404            self.value.get($key_expr).map_or_else(
405                || Ok(None),
406                |value| match value {
407                    Value::Bool(b) => Ok(Some(*b)),
408                    _ => Err(Error::UnexpectedType),
409                },
410            )
411        }
412    };
413}
414
415macro_rules! get_set_rm_u64 {
416    ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr) => {
417        get_set_rm_u64!($key_expr, $getter, $getter_doc, $setter, $setter_doc);
418
419        #[doc=$remover_doc]
420        pub fn $remover<T>(&mut self) -> Option<Value>
421        where
422            T: ToString,
423        {
424            self.value.remove($key_expr)
425        }
426    };
427
428    ($key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr) => {
429        get_set_rm_u64!($key_expr, $getter, $getter_doc);
430
431        #[doc=$setter_doc]
432        pub fn $setter<T>(&mut self, value: u64) -> Option<Value> {
433            self.value.insert(
434                String::from($key_expr),
435                Value::Number(serde_json::Number::from(value)),
436            )
437        }
438    };
439
440    ($key_expr:expr, $getter:ident, $getter_doc:expr) => {
441        #[doc=$getter_doc]
442        pub fn $getter(&self) -> Result<Option<u64>, Error> {
443            self.value.get($key_expr).map_or_else(
444                || Ok(None),
445                |value| match value {
446                    Value::Number(n) => {
447                        if let Some(n) = n.as_u64() {
448                            Ok(Some(n))
449                        } else {
450                            Err(Error::UnexpectedType)
451                        }
452                    }
453                    _ => Err(Error::UnexpectedType),
454                },
455            )
456        }
457    };
458}
459
460macro_rules! get_ref_get_ref_mut_set_rm_obj {
461    ($key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr,
462        $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr,
463        $setter:ident, $setter_type:ty, $setter_doc:expr,
464        $remover:ident, $remover_doc:expr
465    ) => {
466        get_ref_get_ref_mut_set_rm_obj!(
467            $key_expr,
468            $getter_ref,
469            $getter_ref_type,
470            $getter_ref_new,
471            $getter_ref_doc
472        );
473
474        #[doc=$getter_ref_mut_doc]
475        pub fn $getter_ref_mut(&mut self) -> Result<Option<$getter_ref_mut_type>, Error> {
476            self.value.get_mut($key_expr).map_or_else(
477                || Ok(None),
478                |value| match value {
479                    Value::Object(obj) => Ok(Some($getter_ref_mut_new(obj))),
480                    _ => Err(Error::UnexpectedType),
481                },
482            )
483        }
484
485        #[doc=$setter_doc]
486        pub fn $setter(&mut self, value: $setter_type) -> Option<Value> {
487            self.value
488                .insert(String::from($key_expr), Value::Object(value.value))
489        }
490
491        #[doc=$remover_doc]
492        pub fn $remover(&mut self) -> Option<Value> {
493            self.value.remove($key_expr)
494        }
495    };
496    ($key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr) => {
497        #[doc=$getter_ref_doc]
498        pub fn $getter_ref(&self) -> Result<Option<$getter_ref_type>, Error> {
499            self.value.get($key_expr).map_or_else(
500                || Ok(None),
501                |value| match value {
502                    Value::Object(obj) => Ok(Some($getter_ref_new(obj))),
503                    _ => Err(Error::UnexpectedType),
504                },
505            )
506        }
507    };
508}
509
510macro_rules! get_ref_get_ref_mut_set_rm_obj_array {
511    ($key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr,
512        $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr,
513        $setter:ident, $setter_type:ty, $setter_doc:expr,
514        $remover:ident, $remover_doc:expr
515    ) => {
516        get_ref_get_ref_mut_set_rm_obj_array!(
517            $key_expr,
518            $getter_ref,
519            $getter_ref_type,
520            $getter_ref_new,
521            $getter_ref_doc
522        );
523
524        #[doc=$getter_ref_mut_doc]
525        pub fn $getter_ref_mut(&mut self) -> Result<Option<Vec<$getter_ref_mut_type>>, Error> {
526            self.value.get_mut($key_expr).map_or_else(
527                || Ok(None),
528                |value| match value {
529                    Value::Array(arr) => arr
530                        .iter_mut()
531                        .map(|value| match value {
532                            Value::Object(obj) => Ok($getter_ref_mut_new(obj)),
533                            _ => Err(Error::UnexpectedType),
534                        })
535                        .collect::<Result<Vec<$getter_ref_mut_type>, Error>>()
536                        .map(Some),
537                    _ => Err(Error::UnexpectedType),
538                },
539            )
540        }
541
542        #[doc=$setter_doc]
543        pub fn $setter<I>(&mut self, items: I) -> Option<Value>
544        where
545            I: IntoIterator<Item = $setter_type>,
546        {
547            let items: Value =
548                Value::Array(items.into_iter().map(|a| Value::Object(a.value)).collect());
549            self.value.insert(String::from($key_expr), items)
550        }
551
552        #[doc=$remover_doc]
553        pub fn $remover(&mut self) -> Option<Value> {
554            self.value.remove($key_expr)
555        }
556    };
557    ($key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr) => {
558        #[doc=$getter_ref_doc]
559        pub fn $getter_ref(&self) -> Result<Option<Vec<$getter_ref_type>>, Error> {
560            self.value.get($key_expr).map_or_else(
561                || Ok(None),
562                |value| match value {
563                    Value::Array(arr) => arr
564                        .iter()
565                        .map(|value| match value {
566                            Value::Object(obj) => Ok($getter_ref_new(obj)),
567                            _ => Err(Error::UnexpectedType),
568                        })
569                        .collect::<Result<Vec<$getter_ref_type>, Error>>()
570                        .map(Some),
571                    _ => Err(Error::UnexpectedType),
572                },
573            )
574        }
575    };
576}
577
578macro_rules! json_feed_prop_decl {
579    () => {};
580    ([str_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
581        get_set_rm_str!($key_expr, $getter, $getter_doc, $setter, $setter_doc, $remover, $remover_doc);
582        json_feed_prop_decl!($($rest),*);
583    };
584    ([str_array_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
585        get_set_rm_str_array!($key_expr, $getter, $getter_doc, $setter, $setter_doc, $remover, $remover_doc);
586        json_feed_prop_decl!($($rest),*);
587    };
588    ([u64_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
589        get_set_rm_u64!($key_expr, $getter, $getter_doc, $setter, $setter_doc, $remover, $remover_doc);
590        json_feed_prop_decl!($($rest),*);
591    };
592    ([bool_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
593        get_set_rm_bool!($key_expr, $getter, $getter_doc, $setter, $setter_doc, $remover, $remover_doc);
594        json_feed_prop_decl!($($rest),*);
595    };
596    ([obj_prop, $key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr, $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr, $setter:ident, $setter_type:ty, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
597        get_ref_get_ref_mut_set_rm_obj!($key_expr, $getter_ref, $getter_ref_type, $getter_ref_new, $getter_ref_doc, $getter_ref_mut, $getter_ref_mut_type, $getter_ref_mut_new, $getter_ref_mut_doc, $setter, $setter_type, $setter_doc, $remover, $remover_doc);
598        json_feed_prop_decl!($($rest),*);
599    };
600    ([obj_array_prop, $key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr, $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr, $setter:ident, $setter_type:ty, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
601        get_ref_get_ref_mut_set_rm_obj_array!($key_expr, $getter_ref, $getter_ref_type, $getter_ref_new, $getter_ref_doc, $getter_ref_mut, $getter_ref_mut_type, $getter_ref_mut_new, $getter_ref_mut_doc, $setter, $setter_type, $setter_doc, $remover, $remover_doc);
602        json_feed_prop_decl!($($rest),*);
603    };
604}
605
606macro_rules! json_feed_prop_read_only_decl {
607    () => {};
608    ([str_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
609        get_set_rm_str!($key_expr, $getter, $getter_doc);
610        json_feed_prop_read_only_decl!($($rest),*);
611    };
612    ([str_array_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
613        get_set_rm_str_array!($key_expr, $getter, $getter_doc);
614        json_feed_prop_read_only_decl!($($rest),*);
615    };
616    ([u64_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
617        get_set_rm_u64!($key_expr, $getter, $getter_doc);
618        json_feed_prop_read_only_decl!($($rest),*);
619    };
620    ([bool_prop, $key_expr:expr, $getter:ident, $getter_doc:expr, $setter:ident, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
621        get_set_rm_bool!($key_expr, $getter, $getter_doc);
622        json_feed_prop_read_only_decl!($($rest),*);
623    };
624    ([obj_prop, $key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr, $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_doc:expr, $setter:ident, $setter_type:ty, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
625        get_ref_get_ref_mut_set_rm_obj!($key_expr, $getter_ref, $getter_ref_type, $getter_ref_new, $getter_ref_doc);
626        json_feed_prop_read_only_decl!($($rest),*);
627    };
628    ([obj_array_prop, $key_expr:expr, $getter_ref:ident, $getter_ref_type:ty, $getter_ref_new:expr, $getter_ref_doc:expr, $getter_ref_mut:ident, $getter_ref_mut_type:ty, $getter_ref_mut_new:expr, $getter_ref_mut_do:expr, $setter:ident, $setter_type:ty, $setter_doc:expr, $remover:ident, $remover_doc:expr] $(,$rest:tt)*) => {
629        get_ref_get_ref_mut_set_rm_obj_array!($key_expr, $getter_ref, $getter_ref_type, $getter_ref_new, $getter_ref_doc);
630        json_feed_prop_read_only_decl!($($rest),*);
631    };
632}
633
634macro_rules! trait_for_borrowed_type {
635    ($name:ident) => {
636        impl<'a> $name<'a> {
637            /// Returns the inner `Map` as a reference.
638            #[must_use]
639            pub fn as_map(&self) -> &Map<String, Value> {
640                self.value
641            }
642        }
643
644        impl<'a> AsRef<Map<String, Value>> for $name<'a> {
645            fn as_ref(&self) -> &Map<String, Value> {
646                self.value
647            }
648        }
649
650        impl<'a> core::fmt::Debug for $name<'a> {
651            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
652                f.debug_struct(stringify!($name))
653                    .field("value", &self.value)
654                    .finish()
655            }
656        }
657
658        impl<'a> Eq for $name<'a> {}
659
660        impl<'a> From<&'a mut Map<String, Value>> for $name<'a> {
661            fn from(value: &'a mut Map<String, Value>) -> Self {
662                Self { value }
663            }
664        }
665
666        impl<'a> PartialEq<Map<String, Value>> for $name<'a> {
667            fn eq(&self, other: &Map<String, Value>) -> bool {
668                self.value.eq(&other)
669            }
670        }
671
672        impl<'a> PartialEq<$name<'a>> for $name<'a> {
673            fn eq(&self, other: &$name<'_>) -> bool {
674                self.value.eq(&other.value)
675            }
676        }
677    };
678}
679
680macro_rules! json_feed_map_type {
681    ($owned:ident, $owned_doc:expr, $borrowed:ident, $borrowed_doc:expr, $borrowed_mut:ident, $borrowed_mut_doc:expr, $to_owned:ident,
682        $($rest:tt),*
683    ) => {
684        #[doc=$owned_doc]
685        pub struct $owned {
686            value: Map<String, Value>,
687        }
688
689        impl $owned {
690            /// Instantiates with an empty JSON object.
691            #[must_use]
692            pub fn new() -> Self {
693                Self { value: Map::new() }
694            }
695
696            /// Returns the inner `Map` as a reference.
697            #[must_use]
698            pub fn as_map(&self) -> &Map<String, Value> {
699                &self.value
700            }
701
702            /// Returns the inner `Map` as a mutable reference.
703            pub fn as_map_mut(&mut self) -> &mut Map<String, Value> {
704                &mut self.value
705            }
706
707            /// Converts the type into the inner `Map`.
708            #[must_use]
709            pub fn into_inner(self) -> Map<String, Value> {
710                self.value
711            }
712
713            json_feed_prop_decl!($($rest),*);
714        }
715
716        impl AsRef<Map<String,Value>> for $owned {
717            fn as_ref(&self) -> &Map<String, Value> {
718                &self.value
719            }
720        }
721
722        impl AsMut<Map<String,Value>> for $owned {
723            fn as_mut(&mut self) -> &mut Map<String, Value> {
724                &mut self.value
725            }
726        }
727
728        impl Clone for $owned {
729            fn clone(&self) -> $owned {
730                $owned {
731                    value: self.value.clone(),
732                }
733            }
734        }
735
736        impl core::fmt::Debug for $owned {
737            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
738                f.debug_struct(stringify!($owned))
739                    .field("value", &self.value)
740                    .finish()
741            }
742        }
743
744        impl Default for $owned {
745            fn default() -> Self {
746                Self::new()
747            }
748        }
749
750        impl Eq for $owned {}
751
752        impl From<Map<String, Value>> for $owned {
753            fn from(value: Map<String, Value>) -> Self {
754                Self {
755                    value
756                }
757            }
758        }
759
760        impl PartialEq<Map<String, Value>> for $owned {
761            fn eq(&self, other: &Map<String, Value>) -> bool {
762                self.value.eq(&other)
763            }
764        }
765
766        impl PartialEq<$owned> for $owned {
767            fn eq(&self, other: &$owned) -> bool {
768                self.value.eq(&other.value)
769            }
770        }
771
772        impl serde::Serialize for $owned
773        {
774            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
775            where
776                S: serde::Serializer,
777            {
778                self.value.serialize(serializer)
779            }
780        }
781
782        impl<'de> serde::de::Deserialize<'de> for $owned {
783            #[inline]
784            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
785            where
786                D: serde::de::Deserializer<'de>,
787            {
788                let map: Map<String, Value> = Map::deserialize(deserializer)?;
789                Ok(Self { value: map })
790            }
791        }
792
793        #[doc=$borrowed_doc]
794        pub struct $borrowed<'a> {
795            value: &'a Map<String, Value>,
796        }
797
798        trait_for_borrowed_type!($borrowed);
799
800        impl<'a> $borrowed<'a> {
801            /// Clones the inner `Map` reference and returns an owned type.
802            #[must_use]
803            pub fn $to_owned(&self) -> $owned {
804                $owned::from(self.value.clone())
805            }
806
807            json_feed_prop_read_only_decl!($($rest),*);
808        }
809
810        impl<'a> From<&'a Map<String, Value>> for $borrowed<'a> {
811            fn from(value: &'a Map<String, Value>) -> Self {
812                Self { value }
813            }
814        }
815
816        impl<'a> serde::Serialize for $borrowed<'a>
817        {
818            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
819            where
820                S: serde::Serializer,
821            {
822                self.value.serialize(serializer)
823            }
824        }
825
826        #[doc=$borrowed_mut_doc]
827        pub struct $borrowed_mut<'a> {
828            value: &'a mut Map<String, Value>,
829        }
830
831        trait_for_borrowed_type!($borrowed_mut);
832
833        impl<'a> $borrowed_mut<'a> {
834            /// Returns the inner `Map` as a mutable reference.
835            pub fn as_map_mut(&mut self) -> &mut Map<String, Value> {
836                self.value
837            }
838
839            /// Clones the inner `Map` reference and returns an owned type.
840            #[must_use]
841            pub fn $to_owned(&self) -> $owned {
842                $owned::from(self.value.clone())
843            }
844
845            json_feed_prop_decl!($($rest),*);
846        }
847
848        impl<'a> AsMut<Map<String, Value>> for $borrowed_mut<'a> {
849            fn as_mut(&mut self) -> &mut Map<String, Value> {
850                self.value
851            }
852        }
853
854        impl<'a> serde::Serialize for $borrowed_mut<'a>
855        {
856            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
857            where
858                S: serde::Serializer,
859            {
860                self.value.serialize(serializer)
861            }
862        }
863    };
864}
865
866json_feed_map_type!(
867    Author,
868    "An author of a feed or an item in the feed.
869
870# Valid Author
871
872An `Author` must have at least one of the `name`, `url`, or `avatar` properties set.
873",
874    AuthorRef,
875    "An `Author` implemented with a borrowed reference to a JSON object.",
876    AuthorMut,
877    "An `Author` implemented with a borrowed mutable reference to a JSON object.",
878    to_author,
879    [
880        str_prop,
881        "name",
882        name,
883        "The optional author's name.",
884        set_name,
885        "Sets the name.",
886        remove_name,
887        "Remove the name."
888    ],
889    [
890        str_prop,
891        "url",
892        url,
893        "An optional URL for a site which represents the author.",
894        set_url,
895        "Sets the URL.",
896        remove_url,
897        "Removes the URL."
898    ],
899    [
900        str_prop,
901        "avatar",
902        avatar,
903        "An optional URL for an image which represents the author.",
904        set_avatar,
905        "Sets the avatar.",
906        remove_avatar,
907        "Removes the avatar."
908    ]
909);
910
911json_feed_map_type!(
912    Hub,
913    "A subscription endpoint which can be used to receive feed update notifications.
914
915# Valid Hub
916
917A `Hub` must have both the `type` and `url` properties set.
918",
919    HubRef,
920    "A `Hub` implemented with a borrowed reference to a JSON object.",
921    HubMut,
922    "A `Hub` implemented with a borrowed mutable reference to a JSON object.",
923    to_hub,
924    [
925        str_prop,
926        "type",
927        hub_type,
928        "The required protocol which is used to subscribe with.",
929        set_hub_type,
930        "Sets the type.",
931        remove_hub_type,
932        "Removes the type."
933    ],
934    [
935        str_prop,
936        "url",
937        url,
938        "A required hub type specific URL which is used to subscribe with.",
939        set_url,
940        "Sets the URL.",
941        remove_url,
942        "Removes the URL."
943    ]
944);
945
946json_feed_map_type!(
947    Item,
948    "An item is a single object (blog post, story, etc.) in the feed list.
949
950# Valid Item
951
952An `Item` must have an `id` property set and either a `content_html` or `content_text` property set.
953",
954    ItemRef,
955    "An `Item` implemented with a borrowed reference to a JSON object.",
956    ItemMut,
957    "An `Item` implemented with a borrowed mutable reference to a JSON object.",
958    to_item,
959    [str_prop, "id", id, "A required unique identifier for an item.
960
961# Important
962
963The ID should be unique across all items which have ever appeared in the feed.
964An item with the same exact ID as another item (even if it is no longer in the
965current JSON feed `items` array) are considered the same item.
966
967# Version 1.0 Incompatibility
968
969While JSON Feed 1.0 permitted values which could be coerced into JSON strings (e.g. JSON numbers), this model supports only
970JSON strings. JSON Feed 1.1 strongly suggests to only use strings. In practice, the vast majority of feeds use strings.
971
972If you wish to support non-String IDs, you can directly access the underlying `Map` with `as_map_mut` or an equivalent method and
973read the JSON value.
974", set_id, "Sets the ID.", remove_id, "Removes the ID."],
975    [str_prop, "url", url, "The optional URL which the item represents.", set_url, "Sets the URL.", remove_url, "Removes the URL."],
976    [
977        str_prop,
978        "external_url",
979        external_url,
980        "An optional related external URL to the item.",
981        set_external_url,
982        "Sets the external URL.",
983        remove_external_url,
984        "Removes the external URL."
985    ],
986    [
987        str_prop,
988        "title",
989        title,
990        "An optional title for the item.",
991        set_title,
992        "Sets the title.",
993        remove_title,
994        "Removes the title."
995    ],
996    [
997        str_prop,
998        "content_html",
999        content_html,
1000        "An optional HTML string representing the content.",
1001        set_content_html,
1002        "Sets the HTML content.",
1003        remove_content_html,
1004        "Removes the HTML content."
1005    ],
1006    [
1007        str_prop,
1008        "content_text",
1009        content_text,
1010        "An optional plain text string representing the content.",
1011        set_content_text,
1012        "Sets the plain text content.",
1013        remove_content_text,
1014        "Removes the plain text content."
1015    ],
1016    [
1017        str_prop,
1018        "summary",
1019        summary,
1020        "An optional summary of the item.",
1021        set_summary,
1022        "Sets the summary.",
1023        remove_summary,
1024        "Removes the summary."
1025    ],
1026    [
1027        str_prop,
1028        "image",
1029        image,
1030        "An optional URL of an image representing the item.",
1031        set_image,
1032        "Sets the image.",
1033        remove_image,
1034        "Removes the image."
1035    ],
1036    [
1037        str_prop,
1038        "banner_image",
1039        banner_image,
1040        "An optional URL of a banner image representing the item.",
1041        set_banner_image,
1042        "Sets the banner image.",
1043        remove_banner_image,
1044        "Removes the banner image."
1045    ],
1046    [
1047        str_prop,
1048        "date_published",
1049        date_published,
1050        "The date which the item was published in [RFC 3339][rfc_3339] format.
1051
1052[rfc_3339]: https://tools.ietf.org/html/rfc3339
1053",
1054        set_date_published,
1055        "Sets the date published.",
1056        remove_date_published,
1057        "Removes the date published."
1058    ],
1059    [
1060        str_prop,
1061        "date_modified",
1062        date_modified,
1063        "The date which the item was modified in [RFC 3339][rfc_3339] format.
1064
1065[rfc_3339]: https://tools.ietf.org/html/rfc3339
1066",
1067        set_date_modified,
1068        "Sets the date modified.",
1069        remove_date_modified,
1070        "Removes the date modified."
1071    ],
1072    [
1073        obj_prop,
1074        "author",
1075        author,
1076        AuthorRef<'_>,
1077        AuthorRef::from,
1078        "An optional author.
1079
1080# Deprecation
1081
1082The `author` field is deprecated in favor of the `authors` field as of JSON Feed 1.1.
1083",
1084        author_mut,
1085        AuthorMut<'_>,
1086        AuthorMut::from,
1087        "An optional author.
1088
1089# Deprecation
1090
1091The `author` field is deprecated in favor of the `authors` field as of JSON Feed 1.1.
1092",
1093        set_author,
1094        Author,
1095        "Sets the author.",
1096        remove_author,
1097        "Removes the author."
1098    ],
1099    [
1100        obj_array_prop,
1101        "authors",
1102        authors,
1103        AuthorRef<'_>,
1104        AuthorRef::from,
1105        "An optional array of authors.",
1106        authors_mut,
1107        AuthorMut<'_>,
1108        AuthorMut::from,
1109        "An optional array of authors.",
1110        set_authors,
1111        Author,
1112        "Sets the authors.",
1113        remove_authors,
1114        "Removes the authors."
1115    ],
1116    [
1117        str_array_prop,
1118        "tags",
1119        tags,
1120        "An optional array of plain text tags.",
1121        set_tags,
1122        "Sets the tags.",
1123        remove_tags,
1124        "Removes the tags."
1125    ],
1126    [
1127        str_prop,
1128        "language",
1129        language,
1130        "The optional language which the feed data is written in.
1131
1132Valid values are from [RFC 5646][rfc_5646].
1133
1134[rfc_5646]: https://tools.ietf.org/html/rfc5646
1135",
1136        set_language,
1137        "Sets the language.",
1138        remove_language,
1139        "Removes the language."
1140    ],
1141    [
1142        obj_array_prop,
1143        "attachments",
1144        attachments,
1145        AttachmentRef<'_>,
1146        AttachmentRef::from,
1147        "An optional array of relevant resources for the item.",
1148        attachments_mut,
1149        AttachmentMut<'_>,
1150        AttachmentMut::from,
1151        "An optional array of relevant resources for the item.",
1152        set_attachments,
1153        Attachment,
1154        "Sets the attachments.",
1155        remove_attachments,
1156        "Removes the attachments."
1157    ]
1158);
1159
1160json_feed_map_type!(
1161    Attachment,
1162    "A relevant resource for an `Item`.
1163
1164# Valid Attachment
1165
1166An `Attachment` must have both the `url` and `mime_type` properties set.
1167",
1168    AttachmentRef,
1169    "An `Attachment` implemented with a borrowed reference to a JSON object.",
1170    AttachmentMut,
1171    "An `Attachment` implemented with a borrowed mutable reference to a JSON object.",
1172    to_attachment,
1173    [
1174        str_prop,
1175        "url",
1176        url,
1177        "The required URL for the attachment.",
1178        set_url,
1179        "Sets the URL.",
1180        remove_url,
1181        "Removes the URL."
1182    ],
1183    [
1184        str_prop,
1185        "mime_type",
1186        mime_type,
1187        "The required [MIME][mime] type (e.g. image/png).
1188
1189[mime]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
1190",
1191        set_mime_type,
1192        "Sets the MIME type.",
1193        remove_mime_type,
1194        "Removes the MIME type."
1195    ],
1196    [
1197        str_prop,
1198        "title",
1199        title,
1200        "An optional title for the attachment.
1201
1202# Important
1203
1204Attachments with the same title are considered to be alternative representations of an attachment.
1205 ",
1206        set_title,
1207        "Sets the title.",
1208        remove_title,
1209        "Removes the title."
1210    ],
1211    [
1212        u64_prop,
1213        "size_in_bytes",
1214        size_in_bytes,
1215        "The optional size of the attachment in bytes.",
1216        set_size_in_bytes,
1217        "Sets the size in bytes.",
1218        remove_size_in_bytes,
1219        "Removes the size in bytes."
1220    ],
1221    [
1222        u64_prop,
1223        "duration_in_seconds",
1224        duration_in_seconds,
1225        "The optional duration of the content in seconds.",
1226        set_duration_in_seconds,
1227        "Sets the duration of in seconds.",
1228        remove_duration_in_seconds,
1229        "Removes the duration in seconds."
1230    ]
1231);
1232
1233json_feed_map_type!(
1234    Feed,
1235    r#"A list of items with associated metadata.
1236
1237The type provides a view into a JSON object value with accessor methods for the standard properties.
1238`Feed` owns the underlying JSON object data and provides methods to access the backing object itself
1239with `as_map`, `as_map_mut`, and `into_inner`.
1240
1241The underlying data is not guaranteed to be a valid JSON Feed.
1242
1243# Valid Feed
1244
1245A `Feed` must have the `version` set to a valid JSON Feed version value, the `title` property set, and the `items`
1246property set.
1247
1248# Example
1249
1250```
1251use json_feed_model::{Feed};
1252# fn main() -> Result<(), json_feed_model::Error> {
1253let json = serde_json::json!({
1254    "version": "https://jsonfeed.org/version/1.1",
1255    "title": "Lorem ipsum dolor sit amet.",
1256    "home_page_url": "https://example.org/",
1257    "feed_url": "https://example.org/feed.json",
1258    "items": [
1259        {
1260            "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0",
1261            "content_text": "Aenean tristique dictum mauris, et.",
1262            "url": "https://example.org/aenean-tristique"
1263        },
1264        {
1265            "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1266            "content_html": "Vestibulum non magna vitae tortor.",
1267            "url": "https://example.org/vestibulum-non"
1268        }
1269    ]
1270});
1271let feed = json_feed_model::from_value(json).unwrap();
1272assert_eq!(feed.version()?, Some(json_feed_model::VERSION_1_1));
1273assert_eq!(feed.title()?, Some("Lorem ipsum dolor sit amet."));
1274assert_eq!(feed.home_page_url()?, Some("https://example.org/"));
1275assert_eq!(feed.feed_url()?, Some("https://example.org/feed.json"));
1276
1277let items = feed.items()?;
1278let items = items.unwrap();
1279assert_eq!(items.len(), 2);
1280
1281assert_eq!(items[0].id()?, Some("cd7f0673-8e81-4e13-b273-4bd1b83967d0"));
1282assert_eq!(
1283    items[0].content_text()?,
1284    Some("Aenean tristique dictum mauris, et.")
1285);
1286assert_eq!(
1287    items[0].url()?,
1288    Some("https://example.org/aenean-tristique")
1289);
1290
1291assert_eq!(items[1].id()?, Some("2bcb497d-c40b-4493-b5ae-bc63c74b48fa"));
1292assert_eq!(
1293    items[1].content_html()?,
1294    Some("Vestibulum non magna vitae tortor.")
1295);
1296assert_eq!(items[1].url()?, Some("https://example.org/vestibulum-non"));
1297# Ok(())
1298# }
1299```
1300    "#,
1301    FeedRef,
1302    "A `Feed` implemented with a borrowed reference to a JSON object.",
1303    FeedMut,
1304    "A `Feed` implemented with a borrowed mutable reference to a JSON object.",
1305    to_feed,
1306    [
1307        str_prop,
1308        "version",
1309        version,
1310        "The required URL formatted version identifier.
1311
1312Identifies what version of the spec the feed is suppose to be compliant with.",
1313        set_version,
1314        "Sets the version identifier.",
1315        remove_version,
1316        "Removes the version identifier."
1317    ],
1318    [
1319        str_prop,
1320        "title",
1321        title,
1322        "The optional name of the feed.",
1323        set_title,
1324        "Sets the name of the feed.",
1325        remove_title,
1326        "Removes the name of the feed."
1327    ],
1328    [
1329        str_prop,
1330        "home_page_url",
1331        home_page_url,
1332        "The optional URL which the feed is suppose to represent.",
1333        set_home_page_url,
1334        "Sets the home page URL.",
1335        remove_home_page_url,
1336        "Removes the home page URL."
1337    ],
1338    [
1339        str_prop,
1340        "feed_url",
1341        feed_url,
1342        "The optional URL which this feed can be retrieived from.",
1343        set_feed_url,
1344        "Sets the feed URL.",
1345        remove_feed_url,
1346        "Removes the feed URL."
1347    ],
1348    [
1349        str_prop,
1350        "description",
1351        description,
1352        "An optional description of the feed.",
1353        set_description,
1354        "Sets the description of the feed.",
1355        remove_description,
1356        "Removes the description of the feed."
1357    ],
1358    [
1359        str_prop,
1360        "user_comment",
1361        user_comment,
1362        "An optional meta description about the feed only intended to be viewed in the raw JSON form.",
1363        set_user_comment,
1364        "Sets the user comment.",
1365        remove_user_comment,
1366        "Removes the user comment."
1367    ],
1368    [
1369        str_prop,
1370        "next_url",
1371        next_url,
1372        "An optional pagination URL.",
1373        set_next_url,
1374        "Sets the next URL.",
1375        remove_next_url,
1376        "Removes the next URL."
1377    ],
1378    [str_prop, "icon", icon, "An optional URL to an icon for use in a list of items.", set_icon, "Sets the icon.", remove_icon, "Removes the icon."],
1379    [
1380        str_prop,
1381        "favicon",
1382        favicon,
1383        "An optional URL to a favicon suitable for use in a list of feeds.",
1384        set_favicon,
1385        "Sets the favicon URL.",
1386        remove_favicon,
1387        "Removes the favicon URL."
1388    ],
1389    [
1390        obj_prop,
1391        "author",
1392        author,
1393        AuthorRef<'_>,
1394        AuthorRef::from,
1395        "An optional author.
1396
1397# Deprecation
1398
1399The `author` field is deprecated in favor of the `authors` field as of JSON Feed 1.1.
1400",
1401        author_mut,
1402        AuthorMut<'_>,
1403        AuthorMut::from,
1404        "An optional author.
1405
1406# Deprecation
1407
1408The `author` field is deprecated in favor of the `authors` field as of JSON Feed 1.1.
1409",
1410        set_author,
1411        Author,
1412        "Sets the author.",
1413        remove_author,
1414        "Removes the author."
1415    ],
1416    [
1417        obj_array_prop,
1418        "authors",
1419        authors,
1420        AuthorRef<'_>,
1421        AuthorRef::from,
1422        "An optional array of authors.",
1423        authors_mut,
1424        AuthorMut<'_>,
1425        AuthorMut::from,
1426        "An optional array of authors.",
1427        set_authors,
1428        Author,
1429        "Sets the authors.",
1430        remove_authors,
1431        "Removes the authors."
1432    ],
1433    [
1434        str_prop,
1435        "language",
1436        language,
1437        "The optional language which the feed data is written in.
1438
1439Valid values are from [RFC 5646][rfc_5646].
1440
1441[rfc_5646]: https://tools.ietf.org/html/rfc5646
1442",
1443        set_language,
1444        "Sets the language.",
1445        remove_language,
1446        "Removes the language."
1447    ],
1448    [
1449        bool_prop,
1450        "expired",
1451        expired,
1452        "Optionally determines if the feed will be updated in the future.
1453        
1454If true, the feed will not be updated in the future. If false or `None`, then the feed may be updated in the future.",
1455        set_expired,
1456        "Sets the expired flag.",
1457        remove_expired,
1458        "Removes the expired flag."
1459    ],
1460    [
1461        obj_array_prop,
1462        "hubs",
1463        hubs,
1464        HubRef<'_>,
1465        HubRef::from,
1466        "Optional subscription endpoints which can be used to received feed update notifications.",
1467        hubs_mut,
1468        HubMut<'_>,
1469        HubMut::from,
1470        "Subscription endpoints which can be used to received feed update notifications.",
1471        set_hubs,
1472        Hub,
1473        "Sets the hubs.",
1474        remove_hubs,
1475        "Removes the hubs."
1476    ],
1477    [
1478        obj_array_prop,
1479        "items",
1480        items,
1481        ItemRef<'_>,
1482        ItemRef::from,
1483        "A required array of `Items`.",
1484        items_mut,
1485        ItemMut<'_>,
1486        ItemMut::from,
1487        "A required array of `Items`.",
1488        set_items,
1489        Item,
1490        "Sets the items.",
1491        remove_items,
1492        "Removes the items."
1493    ]
1494);
1495
1496fn is_extension_key(key: &str) -> bool {
1497    key.as_bytes().iter().next() == Some(&b'_')
1498}
1499
1500fn are_keys_valid<'a, I>(keys: I, valid_keys: &BTreeSet<&str>) -> bool
1501where
1502    I: IntoIterator<Item = &'a String>,
1503{
1504    keys.into_iter()
1505        .all(|k| valid_keys.contains(k.as_str()) || is_extension_key(k))
1506}
1507
1508fn is_valid_attachment(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1509    match version {
1510        Version::Unknown(_) => return false,
1511        Version::Version1 | Version::Version1_1 => {}
1512    }
1513    let attachment_ref = AttachmentRef::from(map);
1514    let mut valid_keys = BTreeSet::new();
1515    valid_keys.insert("url");
1516    valid_keys.insert("mime_type");
1517    valid_keys.insert("title");
1518    valid_keys.insert("size_in_bytes");
1519    valid_keys.insert("duration_in_seconds");
1520
1521    attachment_ref.url().map_or(false, |url| url.is_some())
1522        && attachment_ref
1523            .mime_type()
1524            .map_or(false, |mime_type| mime_type.is_some())
1525        && attachment_ref.title().is_ok()
1526        && attachment_ref.size_in_bytes().is_ok()
1527        && attachment_ref.duration_in_seconds().is_ok()
1528        && are_keys_valid(map.keys(), &valid_keys)
1529}
1530
1531impl Attachment {
1532    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1533    #[must_use]
1534    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1535        is_valid_attachment(&self.value, version)
1536    }
1537}
1538
1539impl AttachmentMut<'_> {
1540    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1541    #[must_use]
1542    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1543        is_valid_attachment(self.value, version)
1544    }
1545}
1546
1547impl AttachmentRef<'_> {
1548    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1549    #[must_use]
1550    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1551        is_valid_attachment(self.value, version)
1552    }
1553}
1554
1555fn is_valid_author(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1556    match version {
1557        Version::Unknown(_) => return false,
1558        Version::Version1 | Version::Version1_1 => {}
1559    }
1560    let author_ref = AuthorRef::from(map);
1561    let mut valid_keys = BTreeSet::new();
1562    valid_keys.insert("name");
1563    valid_keys.insert("avatar");
1564    valid_keys.insert("url");
1565
1566    let name_result = author_ref.name();
1567    let avatar_result = author_ref.avatar();
1568    let url_result = author_ref.url();
1569
1570    name_result.is_ok()
1571        && avatar_result.is_ok()
1572        && url_result.is_ok()
1573        && (name_result.map_or(false, |name| name.is_some())
1574            || avatar_result.map_or(false, |avatar| avatar.is_some())
1575            || url_result.map_or(false, |url| url.is_some()))
1576        && are_keys_valid(map.keys(), &valid_keys)
1577}
1578
1579impl Author {
1580    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1581    #[must_use]
1582    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1583        is_valid_author(&self.value, version)
1584    }
1585}
1586
1587impl AuthorMut<'_> {
1588    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1589    #[must_use]
1590    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1591        is_valid_author(self.value, version)
1592    }
1593}
1594
1595impl AuthorRef<'_> {
1596    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1597    #[must_use]
1598    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1599        is_valid_author(self.value, version)
1600    }
1601}
1602
1603fn is_valid_feed(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1604    match version {
1605        Version::Unknown(_) => return false,
1606        Version::Version1 | Version::Version1_1 => {}
1607    }
1608    let feed_ref = FeedRef::from(map);
1609    let mut valid_keys = BTreeSet::new();
1610    valid_keys.insert("version");
1611    valid_keys.insert("title");
1612    valid_keys.insert("home_page_url");
1613    valid_keys.insert("feed_url");
1614    valid_keys.insert("description");
1615    valid_keys.insert("user_comment");
1616    valid_keys.insert("next_url");
1617    valid_keys.insert("favicon");
1618    valid_keys.insert("author");
1619    match version {
1620        Version::Version1_1 => {
1621            valid_keys.insert("authors");
1622            valid_keys.insert("language");
1623        }
1624        Version::Version1 | Version::Unknown(_) => {}
1625    }
1626    valid_keys.insert("expired");
1627    valid_keys.insert("hubs");
1628    valid_keys.insert("items");
1629
1630    feed_ref.version().map_or(false, |v| {
1631        v.map_or(false, |v| match Version::from(v) {
1632            Version::Unknown(_) => false,
1633            Version::Version1 => match version {
1634                Version::Version1 | Version::Version1_1 => true,
1635                Version::Unknown(_) => false,
1636            },
1637            Version::Version1_1 => match version {
1638                Version::Version1 | Version::Unknown(_) => false,
1639                Version::Version1_1 => true,
1640            },
1641        })
1642    }) && feed_ref
1643        .title()
1644        .map_or_else(|_| false, |title| title.is_some())
1645        && feed_ref.items().map_or(false, |items| {
1646            items.map_or(false, |items| {
1647                items.iter().all(|item| item.is_valid(version))
1648            })
1649        })
1650        && feed_ref.hubs().map_or(false, |hubs| {
1651            hubs.map_or(true, |hubs| hubs.iter().all(|hub| hub.is_valid(version)))
1652        })
1653        && feed_ref.home_page_url().is_ok()
1654        && feed_ref.feed_url().is_ok()
1655        && feed_ref.description().is_ok()
1656        && feed_ref.user_comment().is_ok()
1657        && feed_ref.next_url().is_ok()
1658        && feed_ref.icon().is_ok()
1659        && feed_ref.favicon().is_ok()
1660        && feed_ref.author().is_ok()
1661        && feed_ref.authors().is_ok()
1662        && feed_ref.language().is_ok()
1663        && feed_ref.expired().is_ok()
1664        && are_keys_valid(map.keys(), &valid_keys)
1665}
1666
1667impl Feed {
1668    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1669    #[must_use]
1670    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1671        is_valid_feed(&self.value, version)
1672    }
1673}
1674
1675impl FeedMut<'_> {
1676    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1677    #[must_use]
1678    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1679        is_valid_feed(self.value, version)
1680    }
1681}
1682
1683impl FeedRef<'_> {
1684    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1685    #[must_use]
1686    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1687        is_valid_feed(self.value, version)
1688    }
1689}
1690
1691fn is_valid_hub(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1692    match version {
1693        Version::Unknown(_) => return false,
1694        Version::Version1 | Version::Version1_1 => {}
1695    }
1696    let hub_ref = HubRef::from(map);
1697    let mut valid_keys = BTreeSet::new();
1698    valid_keys.insert("type");
1699    valid_keys.insert("url");
1700
1701    hub_ref.url().map_or(false, |url| url.is_some())
1702        && hub_ref
1703            .hub_type()
1704            .map_or(false, |hub_type| hub_type.is_some())
1705        && are_keys_valid(map.keys(), &valid_keys)
1706}
1707
1708impl Hub {
1709    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1710    #[must_use]
1711    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1712        is_valid_hub(&self.value, version)
1713    }
1714}
1715
1716impl HubMut<'_> {
1717    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1718    #[must_use]
1719    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1720        is_valid_hub(self.value, version)
1721    }
1722}
1723
1724impl HubRef<'_> {
1725    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1726    #[must_use]
1727    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1728        is_valid_hub(self.value, version)
1729    }
1730}
1731
1732fn is_valid_item(map: &Map<String, Value>, version: &Version<'_>) -> bool {
1733    match version {
1734        Version::Unknown(_) => return false,
1735        Version::Version1 | Version::Version1_1 => {}
1736    }
1737    let item_ref = ItemRef::from(map);
1738    let mut valid_keys = BTreeSet::new();
1739    valid_keys.insert("id");
1740    valid_keys.insert("url");
1741    valid_keys.insert("external_url");
1742    valid_keys.insert("title");
1743    valid_keys.insert("content_html");
1744    valid_keys.insert("content_text");
1745    valid_keys.insert("summary");
1746    valid_keys.insert("image");
1747    valid_keys.insert("banner_image");
1748    valid_keys.insert("date_published");
1749    valid_keys.insert("date_modified");
1750    valid_keys.insert("author");
1751    match version {
1752        Version::Version1_1 => {
1753            valid_keys.insert("authors");
1754            valid_keys.insert("language");
1755        }
1756        Version::Version1 | Version::Unknown(_) => {}
1757    }
1758    valid_keys.insert("tags");
1759    valid_keys.insert("attachments");
1760
1761    let content_html_result = item_ref.content_html();
1762    let content_text_result = item_ref.content_text();
1763
1764    item_ref.id().map_or(false, |id| id.is_some())
1765        && item_ref.authors().map_or(false, |authors| {
1766            authors.map_or(true, |authors| {
1767                authors.iter().all(|author| author.is_valid(version))
1768            })
1769        })
1770        && item_ref.attachments().map_or(false, |attachments| {
1771            attachments.map_or(true, |attachments| {
1772                attachments
1773                    .iter()
1774                    .all(|attachment| attachment.is_valid(version))
1775            })
1776        })
1777        && item_ref.id().is_ok()
1778        && item_ref.url().is_ok()
1779        && item_ref.external_url().is_ok()
1780        && item_ref.title().is_ok()
1781        && content_html_result.is_ok()
1782        && content_text_result.is_ok()
1783        && (content_text_result.map_or(false, |content| content.is_some())
1784            || content_html_result.map_or(false, |content| content.is_some()))
1785        && item_ref.summary().is_ok()
1786        && item_ref.image().is_ok()
1787        && item_ref.banner_image().is_ok()
1788        && item_ref.date_published().is_ok()
1789        && item_ref.date_modified().is_ok()
1790        && item_ref.author().is_ok()
1791        && item_ref.tags().is_ok()
1792        && item_ref.language().is_ok()
1793        && are_keys_valid(map.keys(), &valid_keys)
1794}
1795
1796impl Item {
1797    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1798    #[must_use]
1799    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1800        is_valid_item(&self.value, version)
1801    }
1802}
1803
1804impl ItemMut<'_> {
1805    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1806    #[must_use]
1807    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1808        is_valid_item(self.value, version)
1809    }
1810}
1811
1812impl ItemRef<'_> {
1813    /// Verifies if the JSON data complies with a specific `Version` of the JSON Feed spec.
1814    #[must_use]
1815    pub fn is_valid(&self, version: &Version<'_>) -> bool {
1816        is_valid_item(self.value, version)
1817    }
1818}
1819
1820/// Attempts to JSON decode a `std::io::Read` and return a `Feed`.
1821///
1822/// # Errors
1823///
1824/// If the data cannot be JSON decoded, then `Error::SerdeJson(serde_json::Error)` is returned.
1825///
1826/// If the decoded JSON value is not an Object, then `Error::UnexpectedType` is returned.
1827#[cfg(feature = "std")]
1828pub fn from_reader<R>(reader: R) -> Result<Feed, Error>
1829where
1830    R: std::io::Read,
1831{
1832    let value = serde_json::from_reader(reader)?;
1833    from_value(value)
1834}
1835
1836/// Attempts to JSON decode a `str` and return a `Feed`.
1837///
1838/// # Errors
1839///
1840/// If the string cannot be JSON decoded, then `Error::SerdeJson(serde_json::Error)` is returned.
1841///
1842/// If the decoded JSON value is not an Object, then `Error::UnexpectedType` is returned.
1843pub fn from_str(s: &str) -> Result<Feed, Error> {
1844    from_slice(s.as_bytes())
1845}
1846
1847/// Attempts to JSON decode a byte slice and return a `Feed`.
1848///
1849/// # Errors
1850///
1851/// If the byte slice cannot be JSON decoded, then `Error::SerdeJson(serde_json::Error)` is returned.
1852///
1853/// If the decoded JSON value is not an Object, then `Error::UnexpectedType` is returned.
1854pub fn from_slice(v: &[u8]) -> Result<Feed, Error> {
1855    let value = serde_json::from_slice(v)?;
1856    from_value(value)
1857}
1858
1859/// Attempts to return a `Feed` from a JSON `Value`.
1860///
1861/// # Errors
1862///
1863/// If the JSON value is not an Object, then `Error::UnexpectedType` is returned.
1864///
1865/// # Example
1866///
1867/// If the library user wishes to save invalid JSON values, a simple check should be done
1868/// before calling the function.
1869///
1870/// ```
1871/// let value = serde_json::json!("a JSON String, not an Object");
1872/// match &value {
1873///     serde_json::Value::Object(_) => {
1874///         let feed_result = json_feed_model::from_value(value);
1875///         assert!(false, "should not have execute this code")
1876///     }
1877///     _ => {
1878///         // handle the invalid JSON value
1879///     },
1880/// }
1881pub fn from_value(value: Value) -> Result<Feed, Error> {
1882    match value {
1883        Value::Object(obj) => Ok(Feed { value: obj }),
1884        _ => Err(Error::UnexpectedType),
1885    }
1886}
1887
1888#[cfg(test)]
1889mod tests {
1890    use super::*;
1891    #[cfg(all(feature = "alloc", not(feature = "std")))]
1892    use alloc::vec;
1893
1894    #[test]
1895    fn simple_example() -> Result<(), Error> {
1896        let json = serde_json::json!({
1897            "version": "https://jsonfeed.org/version/1.1",
1898            "title": "Lorem ipsum dolor sit amet.",
1899            "home_page_url": "https://example.org/",
1900            "feed_url": "https://example.org/feed.json",
1901            "items": [
1902                {
1903                    "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0",
1904                    "content_text": "Aenean tristique dictum mauris, et.",
1905                    "url": "https://example.org/aenean-tristique"
1906                },
1907                {
1908                    "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1909                    "content_html": "Vestibulum non magna vitae tortor.",
1910                    "url": "https://example.org/vestibulum-non"
1911                }
1912            ]
1913        });
1914
1915        let feed = from_value(json)?;
1916
1917        assert!(feed.is_valid(&Version::Version1_1));
1918
1919        assert_eq!(feed.version()?, Some(VERSION_1_1));
1920        assert_eq!(feed.title()?, Some("Lorem ipsum dolor sit amet."));
1921        assert_eq!(feed.home_page_url()?, Some("https://example.org/"));
1922        assert_eq!(feed.feed_url()?, Some("https://example.org/feed.json"));
1923
1924        let items: Option<Vec<ItemRef<'_>>> = feed.items()?;
1925        assert!(items.is_some());
1926        let items: Vec<ItemRef<'_>> = items.unwrap();
1927        assert_eq!(items.len(), 2);
1928
1929        assert_eq!(items[0].id()?, Some("cd7f0673-8e81-4e13-b273-4bd1b83967d0"));
1930        assert_eq!(
1931            items[0].content_text()?,
1932            Some("Aenean tristique dictum mauris, et.")
1933        );
1934        assert_eq!(
1935            items[0].url()?,
1936            Some("https://example.org/aenean-tristique")
1937        );
1938
1939        assert_eq!(items[1].id()?, Some("2bcb497d-c40b-4493-b5ae-bc63c74b48fa"));
1940        assert_eq!(
1941            items[1].content_html()?,
1942            Some("Vestibulum non magna vitae tortor.")
1943        );
1944        assert_eq!(items[1].url()?, Some("https://example.org/vestibulum-non"));
1945
1946        Ok(())
1947    }
1948
1949    #[test]
1950    fn read_extensions() -> Result<(), Error> {
1951        let json = serde_json::json!({
1952            "version": "https://jsonfeed.org/version/1.1",
1953            "title": "Lorem ipsum dolor sit amet.",
1954            "_example": {
1955                "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0"
1956            },
1957            "items": [
1958                {
1959                    "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
1960                    "content_html": "Vestibulum non magna vitae tortor.",
1961                    "url": "https://example.org/vestibulum-non",
1962                    "_extension": 1
1963                }
1964            ]
1965        });
1966        let feed = from_value(json).unwrap();
1967
1968        assert!(feed.is_valid(&Version::Version1_1));
1969
1970        assert_eq!(feed.version()?, Some(VERSION_1_1));
1971        assert_eq!(feed.title()?, Some("Lorem ipsum dolor sit amet."));
1972
1973        let example_value = feed.as_map().get("_example");
1974        assert_eq!(
1975            example_value,
1976            Some(&serde_json::json!({ "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0" }))
1977        );
1978
1979        let items = feed.items()?;
1980        let items = items.unwrap();
1981        assert_eq!(items.len(), 1);
1982
1983        assert_eq!(items[0].id()?, Some("2bcb497d-c40b-4493-b5ae-bc63c74b48fa"));
1984        assert_eq!(
1985            items[0].content_html()?,
1986            Some("Vestibulum non magna vitae tortor.")
1987        );
1988        assert_eq!(items[0].url()?, Some("https://example.org/vestibulum-non"));
1989
1990        let extension_value = items[0].as_map().get("_extension");
1991        assert_eq!(extension_value, Some(&serde_json::json!(1)));
1992
1993        Ok(())
1994    }
1995
1996    #[test]
1997    fn write_extensions() -> Result<(), Error> {
1998        let mut feed = Feed::new();
1999        feed.set_version(Version::Version1_1);
2000        feed.set_title("Lorem ipsum dolor sit amet.");
2001        feed.as_map_mut().insert(
2002            String::from("_example"),
2003            serde_json::json!({ "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0" }),
2004        );
2005
2006        let mut item = Item::new();
2007        item.set_id("invalid-id");
2008        item.set_content_html("Vestibulum non magna vitae tortor.");
2009        item.set_url("https://example.org/vestibulum-non");
2010        item.as_map_mut()
2011            .insert(String::from("_extension"), serde_json::json!(1));
2012
2013        let items = vec![item];
2014        feed.set_items(items);
2015
2016        let item = &mut feed.items_mut()?.unwrap()[0];
2017        item.set_id("2bcb497d-c40b-4493-b5ae-bc63c74b48fa");
2018
2019        assert!(feed.is_valid(&Version::Version1_1));
2020
2021        let expected_json = serde_json::json!({
2022            "version": "https://jsonfeed.org/version/1.1",
2023            "title": "Lorem ipsum dolor sit amet.",
2024            "_example": {
2025                "id": "cd7f0673-8e81-4e13-b273-4bd1b83967d0"
2026            },
2027            "items": [
2028                {
2029                    "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
2030                    "content_html": "Vestibulum non magna vitae tortor.",
2031                    "url": "https://example.org/vestibulum-non",
2032                    "_extension": 1
2033                }
2034            ]
2035        });
2036        assert_eq!(feed, from_value(expected_json.clone())?);
2037        assert_eq!(serde_json::to_value(feed.clone())?, expected_json);
2038
2039        let output = serde_json::to_string(&feed);
2040        assert!(output.is_ok());
2041
2042        Ok(())
2043    }
2044
2045    #[test]
2046    fn is_valid_version_forward_compatible() {
2047        let json = serde_json::json!({
2048            "version": "https://jsonfeed.org/version/1",
2049            "title": "Lorem ipsum dolor sit amet.",
2050            "items": [
2051                {
2052                    "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
2053                    "content_html": "Vestibulum non magna vitae tortor.",
2054                    "url": "https://example.org/vestibulum-non",
2055                }
2056            ]
2057        });
2058        let feed = from_value(json).unwrap();
2059
2060        assert!(feed.is_valid(&Version::Version1_1));
2061        assert!(feed.is_valid(&Version::Version1));
2062    }
2063
2064    #[test]
2065    fn is_valid_version_backward_compatible() {
2066        let json = serde_json::json!({
2067            "version": "https://jsonfeed.org/version/1.1",
2068            "title": "Lorem ipsum dolor sit amet.",
2069            "items": [
2070                {
2071                    "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
2072                    "content_html": "Vestibulum non magna vitae tortor.",
2073                    "url": "https://example.org/vestibulum-non",
2074                }
2075            ]
2076        });
2077        let feed = from_value(json).unwrap();
2078
2079        assert!(feed.is_valid(&Version::Version1_1));
2080        assert!(!feed.is_valid(&Version::Version1));
2081    }
2082
2083    #[test]
2084    fn custom_extension_trait() -> Result<(), Error> {
2085        trait ExampleExtension {
2086            fn example(&self) -> Result<Option<&str>, Error>;
2087
2088            fn set_example<T>(&mut self, value: T) -> Option<Value>
2089            where
2090                T: ToString;
2091        }
2092
2093        impl ExampleExtension for Feed {
2094            fn example(&self) -> Result<Option<&str>, Error> {
2095                self.as_map().get("_example").map_or_else(
2096                    || Ok(None),
2097                    |value| match value {
2098                        Value::String(s) => Ok(Some(s.as_str())),
2099                        _ => Err(Error::UnexpectedType),
2100                    },
2101                )
2102            }
2103
2104            fn set_example<T>(&mut self, value: T) -> Option<Value>
2105            where
2106                T: ToString,
2107            {
2108                self.as_map_mut()
2109                    .insert(String::from("_example"), Value::String(value.to_string()))
2110            }
2111        }
2112
2113        let mut feed = Feed::new();
2114        feed.set_version(Version::Version1_1);
2115        feed.set_title("Lorem ipsum dolor sit amet.");
2116
2117        feed.set_example("123456");
2118
2119        let mut item = Item::new();
2120        item.set_id("2bcb497d-c40b-4493-b5ae-bc63c74b48fa");
2121        item.set_content_text("Vestibulum non magna vitae tortor.");
2122        item.set_url("https://example.org/vestibulum-non");
2123
2124        feed.set_items(vec![item]);
2125
2126        assert!(feed.is_valid(&Version::Version1_1));
2127
2128        let expected_json = serde_json::json!({
2129            "version": "https://jsonfeed.org/version/1.1",
2130            "title": "Lorem ipsum dolor sit amet.",
2131            "_example": "123456",
2132            "items": [
2133                {
2134                    "id": "2bcb497d-c40b-4493-b5ae-bc63c74b48fa",
2135                    "content_text": "Vestibulum non magna vitae tortor.",
2136                    "url": "https://example.org/vestibulum-non",
2137                }
2138            ]
2139        });
2140        assert_eq!(feed, from_value(expected_json)?);
2141
2142        assert_eq!(feed.example()?, Some("123456"));
2143
2144        let output = serde_json::to_string(&feed);
2145        assert!(output.is_ok());
2146
2147        Ok(())
2148    }
2149}