json_feed_model/
lib.rs

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