json_api/
resource.rs

1use std::mem;
2
3use doc::{Data, Document, Identifier, Object};
4use error::Error;
5use query::Query;
6use value::Set;
7use value::fields::Key;
8use view::{Context, Render};
9
10/// A trait indicating that the given type can be represented as a resource.
11///
12/// Implementing this trait manually is not recommended. The [`resource!`] macro provides
13/// a friendly DSL that implements trait with some additional functionality.
14///
15/// # Example
16///
17/// ```
18/// #[macro_use]
19/// extern crate json_api;
20///
21/// struct Post(u64);
22///
23/// resource!(Post, |&self| {
24///     kind "posts";
25///     id self.0;
26/// });
27/// #
28/// # fn main() {}
29/// ```
30///
31/// [`resource!`]: ./macro.resource.html
32pub trait Resource {
33    /// Returns a key containing the type of resource.
34    ///
35    /// # Example
36    ///
37    /// ```
38    /// # #[macro_use]
39    /// # extern crate json_api;
40    /// #
41    /// # struct Post(u64);
42    /// #
43    /// # resource!(Post, |&self| {
44    /// #     kind "posts";
45    /// #     id self.0;
46    /// # });
47    /// #
48    /// # fn main() {
49    /// use json_api::Resource;
50    ///
51    /// let kind = Post::kind();
52    /// assert_eq!(kind, "posts");
53    /// # }
54    /// ```
55    fn kind() -> Key;
56
57    /// Returns a given resource's id as a string.
58    ///
59    /// # Example
60    ///
61    /// ```
62    /// # #[macro_use]
63    /// # extern crate json_api;
64    /// #
65    /// # struct Post(u64);
66    /// #
67    /// # resource!(Post, |&self| {
68    /// #     kind "posts";
69    /// #     id self.0;
70    /// # });
71    /// #
72    /// # fn main() {
73    /// use json_api::Resource;
74    ///
75    /// let post = Post(25);
76    /// assert_eq!(post.id(), "25");
77    /// # }
78    /// ```
79    fn id(&self) -> String;
80
81    /// Renders a given resource as an identifier object.
82    ///
83    ///
84    /// Calling this function directly is not recommended. It is much more ergonomic to
85    /// use the [`json_api::to_doc`] function.
86    ///
87    /// [`json_api::to_doc`]: ./fn.to_doc.html
88    fn to_ident(&self, ctx: &mut Context) -> Result<Identifier, Error>;
89
90    /// Renders a given resource as a resource object.
91    ///
92    /// Calling this function directly is not recommended. It is much more ergonomic to
93    /// use the [`json_api::to_doc`] function.
94    ///
95    /// [`json_api::to_doc`]: ./fn.to_doc.html
96    fn to_object(&self, ctx: &mut Context) -> Result<Object, Error>;
97}
98
99impl<'a, T: Resource> Render<Identifier> for &'a T {
100    fn render(self, query: Option<&Query>) -> Result<Document<Identifier>, Error> {
101        let mut incl = Set::new();
102        let mut ctx = Context::new(T::kind(), query, &mut incl);
103
104        self.to_ident(&mut ctx)?.render(query)
105    }
106}
107
108impl<'a, T: Resource> Render<Identifier> for &'a [T] {
109    fn render(self, query: Option<&Query>) -> Result<Document<Identifier>, Error> {
110        let mut incl = Set::new();
111        let mut ctx = Context::new(T::kind(), query, &mut incl);
112
113        self.into_iter()
114            .map(|item| item.to_ident(&mut ctx))
115            .collect::<Result<Vec<_>, _>>()?
116            .render(query)
117    }
118}
119
120impl<'a, T: Resource> Render<Object> for &'a T {
121    fn render(self, query: Option<&Query>) -> Result<Document<Object>, Error> {
122        let mut incl = Set::new();
123        let (data, links, meta) = {
124            let mut ctx = Context::new(T::kind(), query, &mut incl);
125            let mut obj = self.to_object(&mut ctx)?;
126            let links = mem::replace(&mut obj.links, Default::default());
127            let meta = mem::replace(&mut obj.meta, Default::default());
128
129            (obj.into(), links, meta)
130        };
131
132        Ok(Document::Ok {
133            data,
134            links,
135            meta,
136            included: incl,
137            jsonapi: Default::default(),
138        })
139    }
140}
141
142impl<'a, T: Resource> Render<Object> for &'a [T] {
143    fn render(self, query: Option<&Query>) -> Result<Document<Object>, Error> {
144        let mut incl = Set::new();
145        let mut data = Vec::with_capacity(self.len());
146
147        {
148            let mut ctx = Context::new(T::kind(), query, &mut incl);
149
150            for item in self {
151                data.push(item.to_object(&mut ctx)?);
152            }
153        }
154
155        Ok(Document::Ok {
156            data: Data::Collection(data),
157            links: Default::default(),
158            meta: Default::default(),
159            included: incl,
160            jsonapi: Default::default(),
161        })
162    }
163}
164
165/// A DSL for implementing the `Resource` trait.
166///
167/// # Examples
168///
169/// The `resource!` macro is both concise and flexible. Many of the keywords are
170/// overloaded to provide a higher level of customization when necessary.
171///
172/// Here is a simple example that simply defines the resources id, kind, attributes, and
173/// relationships.
174///
175/// ```
176/// #[macro_use]
177/// extern crate json_api;
178///
179/// struct Post {
180///     id: u64,
181///     body: String,
182///     title: String,
183///     author: Option<User>,
184///     comments: Vec<Comment>,
185/// }
186///
187/// resource!(Post, |&self| {
188///     // Define the id.
189///     id self.id;
190///
191///     // Define the resource "type"
192///     kind "posts";
193///
194///     // Define attributes with a comma seperated list of field names.
195///     attrs body, title;
196///
197///     // Define relationships with a comma seperated list of field names.
198///     has_one author;
199///     has_many comments;
200/// });
201/// #
202/// # struct User;
203/// #
204/// # resource!(User, |&self| {
205/// #     kind "users";
206/// #     id String::new();
207/// # });
208/// #
209/// # struct Comment;
210/// #
211/// # resource!(Comment, |&self| {
212/// #     kind "comments";
213/// #     id String::new();
214/// # });
215/// #
216/// # fn main() {}
217/// ```
218///
219/// Now let's take a look at how we can use the same DSL to get a higher level
220/// customization.
221///
222/// ```
223/// #[macro_use]
224/// extern crate json_api;
225///
226/// struct Post {
227///     id: u64,
228///     body: String,
229///     title: String,
230///     author: Option<User>,
231///     comments: Vec<Comment>,
232/// }
233///
234/// resource!(Post, |&self| {
235///     kind "articles";
236///     id self.id;
237///
238///     attrs body, title;
239///
240///     // Define a virtual attribute with an expression
241///     attr "preview", {
242///         self.body
243///             .chars()
244///             .take(140)
245///             .collect::<String>()
246///     }
247///
248///     // Define a relationship with granular detail
249///     has_one "author", {
250///         // Data for has one should be Option<&T> where T: Resource
251///         data self.author.as_ref();
252///
253///         // Define relationship links
254///         link "self", format!("/articles/{}/relationships/author", self.id);
255///         link "related", format!("/articles/{}/author", self.id);
256///
257///         // Define arbitrary meta members with a block expression
258///         meta "read-only", true
259///     }
260///
261///     // Define a relationship with granular detail
262///     has_many "comments", {
263///         // Data for has one should be an Iterator<Item = &T> where T: Resource
264///         data self.comments.iter();
265///
266///         // Define relationship links
267///         link "self", format!("/articles/{}/relationships/comments", self.id);
268///         link "related", format!("/articles/{}/comments", self.id);
269///
270///         // Define arbitrary meta members with a block expression
271///         meta "total", {
272///             self.comments.len()
273///         }
274///     }
275///
276///     // You can also define links with granular details as well
277///     link "self", {
278///         href format!("/articles/{}", self.id);
279///     }
280///
281///     // Define arbitrary meta members an expression
282///     meta "copyright", self.author.as_ref().map(|user| {
283///         format!("© 2017 {}", user.full_name())
284///     });
285/// });
286/// #
287/// # struct User;
288/// #
289/// # impl User {
290/// #     fn full_name(&self) -> String {
291/// #         String::new()
292/// #     }
293/// # }
294/// #
295/// # resource!(User, |&self| {
296/// #     kind "users";
297/// #     id String::new();
298/// # });
299/// #
300/// # struct Comment;
301/// #
302/// # resource!(Comment, |&self| {
303/// #     kind "comments";
304/// #     id String::new();
305/// # });
306/// #
307/// # fn main() {}
308/// ```
309#[macro_export]
310macro_rules! resource {
311    ($target:ident, |&$this:ident| { $($rest:tt)* }) => {
312        impl $crate::Resource for $target {
313            fn kind() -> $crate::value::Key {
314                let raw = extract_resource_kind!({ $($rest)* }).to_owned();
315                $crate::value::Key::from_raw(raw)
316            }
317
318            fn id(&$this) -> String {
319                use $crate::value::Stringify as JsonApiStringifyTrait;
320                extract_resource_id!({ $($rest)* }).stringify()
321            }
322
323            fn to_ident(
324                &$this,
325                _: &mut $crate::view::Context,
326            ) -> Result<$crate::doc::Identifier, $crate::Error> {
327                let mut ident = {
328                    let kind = <$target as $crate::Resource>::kind();
329                    let id = $crate::Resource::id($this);
330
331                    $crate::doc::Identifier::new(kind, id)
332                };
333
334                {
335                    let _meta = &mut ident.meta;
336                    expand_resource_impl!(@meta $this, _meta, {
337                        $($rest)*
338                    });
339                }
340
341                Ok(ident)
342            }
343
344            fn to_object(
345                &$this,
346                ctx: &mut $crate::view::Context,
347            ) -> Result<$crate::doc::Object, $crate::error::Error> {
348                #[allow(dead_code)]
349                fn item_kind<T: $crate::Resource>(_: &T) -> $crate::value::Key {
350                    T::kind()
351                }
352
353                #[allow(dead_code)]
354                fn iter_kind<'a, I, T>(_: &I) -> $crate::value::Key
355                where
356                    I: Iterator<Item = &'a T>,
357                    T: $crate::Resource + 'a,
358                {
359                    T::kind()
360                }
361
362                let mut obj = {
363                    let kind = <$target as $crate::Resource>::kind();
364                    let id = $crate::Resource::id($this);
365
366                    $crate::doc::Object::new(kind, id)
367                };
368
369                {
370                    let _attrs = &mut obj.attributes;
371                    expand_resource_impl!(@attrs $this, _attrs, ctx, {
372                        $($rest)*
373                    });
374                }
375
376                {
377                    let _links = &mut obj.links;
378                    expand_resource_impl!(@links $this, _links, {
379                        $($rest)*
380                    });
381                }
382
383                {
384                    let _meta = &mut obj.meta;
385                    expand_resource_impl!(@meta $this, _meta, {
386                        $($rest)*
387                    });
388                }
389
390                {
391                    let _related = &mut obj.relationships;
392                    expand_resource_impl!(@rel $this, _related, ctx, {
393                        $($rest)*
394                    });
395                }
396
397                Ok(obj)
398            }
399        }
400    };
401}
402
403#[doc(hidden)]
404#[macro_export]
405macro_rules! expand_resource_impl {
406    (@attrs $this:ident, $attrs:ident, $ctx:ident, {
407        attr $key:expr, $value:block
408        $($rest:tt)*
409    }) => {
410        if $ctx.field($key) {
411            let key = $key.parse::<$crate::value::Key>()?;
412            let value = $crate::to_value($value)?;
413
414            $attrs.insert(key, value);
415        }
416
417        expand_resource_impl!(@attrs $this, $attrs, $ctx, {
418            $($rest)*
419        });
420    };
421
422    (@attrs $this:ident, $($arg:ident),*, { attr $field:ident; $($rest:tt)* }) => {
423        expand_resource_impl!(@attrs $this, $($arg),*, {
424            attr stringify!($field), &$this.$field;
425            $($rest)*
426        });
427    };
428
429    (@attrs $($arg:ident),*, { attrs $($field:ident),+; $($rest:tt)* }) => {
430        expand_resource_impl!(@attrs $($arg),*, {
431            $(attr $field;)+
432            $($rest)*
433        });
434    };
435
436    (@rel $this:ident, $related:ident, $ctx:ident, {
437        has_many $key:expr, { $($body:tt)* }
438        $($rest:tt)*
439    }) => {
440        if $ctx.field($key) {
441            let key = $key.parse::<$crate::value::Key>()?;
442            expand_resource_impl!(@has_many $this, $related, key, $ctx, {
443                $($body)*
444            });
445        }
446
447        expand_resource_impl!(@rel $this, $related, $ctx, {
448            $($rest)*
449        });
450    };
451
452    (@rel $this:ident, $related:ident, $ctx:ident, {
453        has_one $key:expr, { $($body:tt)* }
454        $($rest:tt)*
455    }) => {
456        if $ctx.field($key) {
457            let key = $key.parse::<$crate::value::Key>()?;
458            expand_resource_impl!(@has_one $this, $related, key, $ctx, {
459                $($body)*
460            });
461        }
462
463        expand_resource_impl!(@rel $this, $related, $ctx, {
464            $($rest)*
465        });
466    };
467
468    (@rel $this:ident, $($arg:ident),*, {
469        has_many $($field:ident),*;
470        $($rest:tt)*
471    }) => {
472        expand_resource_impl!(@rel $this, $($arg),*, {
473            $(has_many stringify!($field), { data $this.$field.iter(); })*
474            $($rest)*
475        });
476    };
477
478    (@rel $this:ident, $($arg:ident),*, {
479        has_one $($field:ident),*;
480        $($rest:tt)*
481    }) => {
482        expand_resource_impl!(@rel $this, $($arg),*, {
483            $(has_one stringify!($field), { data $this.$field.as_ref(); })*
484            $($rest)*
485        });
486    };
487
488    (@has_many $this:ident, $related:ident, $key:ident, $ctx:ident, {
489        data $value:block
490        $($rest:tt)*
491    }) => {
492        let mut rel = $crate::doc::Relationship::new({
493            let mut ctx = $ctx.fork(iter_kind(&$value), &$key);
494            let mut data = match $value.size_hint() {
495                (_, Some(size)) => Vec::with_capacity(size),
496                _ => Vec::new(),
497            };
498
499            if ctx.included() {
500                for item in $value {
501                    let object = $crate::Resource::to_object(item, &mut ctx)?;
502                    let ident = $crate::doc::Identifier::from(&object);
503
504                    ctx.include(object);
505                    data.push(ident);
506                }
507            } else {
508                for item in $value {
509                    data.push($crate::Resource::to_ident(item, &mut ctx)?);
510                }
511            }
512
513            data.into()
514        });
515
516        {
517            let links = &mut rel.links;
518            expand_resource_impl!(@links $this, links, {
519                $($rest)*
520            });
521        }
522
523        {
524            let _meta = &mut rel.meta;
525            expand_resource_impl!(@meta $this, _meta, {
526                $($rest)*
527            });
528        }
529
530        $related.insert($key, rel);
531    };
532
533    (@has_one $this:ident, $related:ident, $key:ident, $ctx:ident, {
534        data $value:block
535        $($rest:tt)*
536    }) => {
537        let mut rel = $crate::doc::Relationship::new({
538            let mut data = None;
539
540            if let Some(item) = $value {
541                let mut ctx = $ctx.fork(item_kind(item), &$key);
542
543                data = Some($crate::Resource::to_ident(item, &mut ctx)?);
544
545                if ctx.included() {
546                    let object = $crate::Resource::to_object(item, &mut ctx)?;
547                    ctx.include(object);
548                }
549            }
550
551            data.into()
552        });
553
554        {
555            let _links = &mut rel.links;
556            expand_resource_impl!(@links $this, _links, {
557                $($rest)*
558            });
559        }
560
561        {
562            let _meta = &mut rel.meta;
563            expand_resource_impl!(@meta $this, _meta, {
564                $($rest)*
565            });
566        }
567
568        $related.insert($key, rel);
569    };
570
571    (@links $this:ident, $links:ident, {
572        link $key:expr, { $($body:tt)* }
573        $($rest:tt)*
574    }) => {
575        {
576            let key = $key.parse::<$crate::value::Key>()?;
577            let link = expand_resource_impl!(@link $this, {
578                $($body)*
579            });
580
581            $links.insert(key, link);
582        }
583
584        expand_resource_impl!(@links $this, $links, {
585            $($rest)*
586        });
587    };
588
589    (@links $($args:ident),+, {
590        link $key:expr, $value:expr;
591        $($rest:tt)*
592    }) => {
593        expand_resource_impl!(@links $($args),+, {
594            link $key, { href { $value } }
595            $($rest)*
596        });
597    };
598
599    (@link $this:ident, { href $value:block $($rest:tt)* }) => {{
600        let mut link = $value.parse::<$crate::doc::Link>()?;
601
602        {
603            let _meta = &link.meta;
604            expand_resource_impl!(@meta $this, _meta, {
605                $($rest)*
606            });
607        }
608
609        link
610    }};
611
612    (@meta $this:ident, $meta:ident, {
613        meta $key:expr, $value:block
614        $($rest:tt)*
615    }) => {
616        {
617            let key = $key.parse::<$crate::value::Key>()?;
618            let value = $crate::to_value($value)?;
619
620            $meta.insert(key, value);
621        }
622
623        expand_resource_impl!(@meta $this, $meta, {
624            $($rest)*
625        });
626    };
627
628    // Ignore has_many specific syntax in other scopes.
629    (@$scope:tt $($args:ident),+, {
630        has_many $key:expr, { $($body:tt)* }
631        $($rest:tt)*
632    }) => {
633        expand_resource_impl!(@$scope $($args),+, {
634            $($rest)*
635        });
636    };
637
638    // Ignore has_one specific syntax in other scopes.
639    (@$scope:tt $($args:ident),+, {
640        has_one $key:expr, { $($body:tt)* }
641        $($rest:tt)*
642    }) => {
643        expand_resource_impl!(@$scope $($args),+, {
644            $($rest)*
645        });
646    };
647
648    // Ignore link specific syntax in other scopes.
649    (@$scope:tt $($args:ident),+, {
650        link $key:expr, { $($body:tt)* }
651        $($rest:tt)*
652    }) => {
653        expand_resource_impl!(@$scope $($args),+, {
654            $($rest)*
655        });
656    };
657
658    (@$scope:tt $($args:ident),+, {
659        $kwd:ident $value:expr;
660        $($rest:tt)*
661    }) => {
662        expand_resource_impl!(@$scope $($args),+, {
663            $kwd { $value }
664            $($rest)*
665        });
666    };
667
668    (@$scope:tt $($args:ident),+, {
669        has_many $key:expr, $value:block
670        $($rest:tt)*
671    }) => {
672        expand_resource_impl!(@$scope $($args),+, {
673            $($rest)*
674        });
675    };
676
677    (@$scope:tt $($args:ident),+, {
678        has_one $key:expr, $value:block
679        $($rest:tt)*
680    }) => {
681        expand_resource_impl!(@$scope $($args),+, {
682            $($rest)*
683        });
684    };
685
686    (@$scope:tt $($args:ident),+, {
687        link $key:expr, $value:block
688        $($rest:tt)*
689    }) => {
690        expand_resource_impl!(@$scope $($args),+, {
691            $($rest)*
692        });
693    };
694
695    (@$scope:tt $($args:ident),+, {
696        $kwd:ident $key:expr, $value:expr;
697        $($rest:tt)*
698    }) => {
699        expand_resource_impl!(@$scope $($args),+, {
700            $kwd $key, { $value }
701            $($rest)*
702        });
703    };
704
705    (@$scope:tt $($args:ident),+, {
706        $skip:tt
707        $($rest:tt)*
708    }) => {
709        expand_resource_impl!(@$scope $($args),+, {
710            $($rest)*
711        });
712    };
713
714    ($($rest:tt)*) => ();
715}
716
717#[doc(hidden)]
718#[macro_export]
719macro_rules! extract_resource_id {
720    ({ id $value:block $($rest:tt)* }) => { $value };
721    ({ id $value:expr; $($rest:tt)* }) => { $value };
722    ({ $skip:tt $($rest:tt)* }) => { extract_resource_id!({ $($rest)* }) };
723    ({ $($rest:tt)* }) => ();
724}
725
726#[doc(hidden)]
727#[macro_export]
728macro_rules! extract_resource_kind {
729    ({ kind $value:block $($rest:tt)* }) => { $value };
730    ({ kind $value:expr; $($rest:tt)* }) => { $value };
731    ({ $skip:tt $($rest:tt)* }) => { extract_resource_kind!({ $($rest)* }) };
732    ({ $($rest:tt)* }) => ();
733}