jsonapi_rs/
model.rs

1//! Defines the `JsonApiModel` trait. This is primarily used in conjunction with
2//! the [`jsonapi_model!`](../macro.jsonapi_model.html) macro to allow arbitrary
3//! structs which implement `Deserialize` to be converted to/from a
4//! [`JsonApiDocument`](../api/struct.JsonApiDocument.html) or
5//! [`Resource`](../api/struct.Resource.html)
6pub use std::collections::HashMap;
7pub use crate::api::*;
8use crate::errors::*;
9use serde::{Deserialize, Serialize};
10use serde_json::{from_value, to_value, Value, Map};
11
12/// A trait for any struct that can be converted from/into a
13/// [`Resource`](api/struct.Resource.tml). The only requirement is that your
14/// struct has an `id: String` field.
15/// You shouldn't be implementing JsonApiModel manually, look at the
16/// `jsonapi_model!` macro instead.
17pub trait JsonApiModel: Serialize
18where
19    for<'de> Self: Deserialize<'de>,
20{
21    #[doc(hidden)]
22    fn jsonapi_type(&self) -> String;
23    #[doc(hidden)]
24    fn jsonapi_id(&self) -> String;
25    #[doc(hidden)]
26    fn relationship_fields() -> Option<&'static [&'static str]>;
27    #[doc(hidden)]
28    fn build_relationships(&self) -> Option<Relationships>;
29    #[doc(hidden)]
30    fn build_included(&self) -> Option<Resources>;
31
32    fn from_jsonapi_resource(resource: &Resource, included: &Option<Resources>)
33        -> Result<Self>
34    {
35
36        let visited_relationships: Vec<&str> = Vec::new();
37        Self::from_serializable(Self::resource_to_attrs(resource, included, &visited_relationships))
38    }
39
40    /// Create a single resource object or collection of resource
41    /// objects directly from 
42    /// [`DocumentData`](../api/struct.DocumentData.html). This method
43    /// will parse the document (the `data` and `included` resources) in an
44    /// attempt to instantiate the calling struct.
45    fn from_jsonapi_document(doc: &DocumentData) -> Result<Self> {
46        match doc.data.as_ref() {
47            Some(primary_data) => {
48                match *primary_data {
49                    PrimaryData::None => bail!("Document had no data"),
50                    PrimaryData::Single(ref resource) => {
51                        Self::from_jsonapi_resource(resource, &doc.included)
52                    }
53                    PrimaryData::Multiple(ref resources) => {
54                        let visited_relationships: Vec<&str> = Vec::new();
55                        let all: Vec<ResourceAttributes> = resources
56                            .iter()
57                            .map(|r| Self::resource_to_attrs(r, &doc.included, &visited_relationships))
58                            .collect();
59                        Self::from_serializable(all)
60                    }
61                }
62            }
63            None => bail!("Document had no data"),
64        }
65    }
66
67    /// Converts the instance of the struct into a
68    /// [`Resource`](../api/struct.Resource.html)
69    fn to_jsonapi_resource(&self) -> (Resource, Option<Resources>) {
70        if let Value::Object(mut attrs) = to_value(self).unwrap() {
71            let _ = attrs.remove("id");
72            let resource = Resource {
73                _type: self.jsonapi_type(),
74                id: self.jsonapi_id(),
75                relationships: self.build_relationships(),
76                attributes: Self::extract_attributes(&attrs),
77                ..Default::default()
78            };
79
80            (resource, self.build_included())
81        } else {
82            panic!("{} is not a Value::Object", self.jsonapi_type())
83        }
84    }
85
86
87    /// Converts the struct into a complete
88    /// [`JsonApiDocument`](../api/struct.JsonApiDocument.html)
89    fn to_jsonapi_document(&self) -> JsonApiDocument {
90        let (resource, included) = self.to_jsonapi_resource();
91        JsonApiDocument::Data (
92            DocumentData {
93                data: Some(PrimaryData::Single(Box::new(resource))),
94                included,
95                ..Default::default()
96            }
97        )
98    }
99
100
101    #[doc(hidden)]
102    fn build_has_one<M: JsonApiModel>(model: &M) -> Relationship {
103        Relationship {
104            data: Some(IdentifierData::Single(model.as_resource_identifier())),
105            links: None
106        }
107    }
108
109    #[doc(hidden)]
110    fn build_has_many<M: JsonApiModel>(models: &[M]) -> Relationship {
111        Relationship {
112            data: Some(IdentifierData::Multiple(
113                models.iter().map(|m| m.as_resource_identifier()).collect()
114            )),
115            links: None
116        }
117    }
118
119    #[doc(hidden)]
120    fn as_resource_identifier(&self) -> ResourceIdentifier {
121        ResourceIdentifier {
122            _type: self.jsonapi_type(),
123            id: self.jsonapi_id(),
124        }
125    }
126
127    /* Attribute corresponding to the model is removed from the Map
128     * before calling this, so there's no need to ignore it like we do
129     * with the attributes that correspond with relationships.
130     * */
131    #[doc(hidden)]
132    fn extract_attributes(attrs: &Map<String, Value>) -> ResourceAttributes {
133        attrs
134            .iter()
135            .filter(|&(key, _)| {
136                if let Some(fields) = Self::relationship_fields() {
137                    if fields.contains(&key.as_str()) {
138                        return false;
139                    }
140                }
141                true
142            })
143            .map(|(k, v)| (k.clone(), v.clone()))
144            .collect()
145    }
146
147    #[doc(hidden)]
148    fn to_resources(&self) -> Resources {
149        let (me, maybe_others) = self.to_jsonapi_resource();
150        let mut flattened = vec![me];
151        if let Some(mut others) = maybe_others {
152            flattened.append(&mut others);
153        }
154        flattened
155    }
156
157    /// When passed a `ResourceIdentifier` (which contains a `type` and `id`)
158    /// this will iterate through the collection provided `haystack` in an
159    /// attempt to find and return the `Resource` whose `type` and `id`
160    /// attributes match
161    #[doc(hidden)]
162    fn lookup<'a>(needle: &ResourceIdentifier, haystack: &'a [Resource])
163        -> Option<&'a Resource>
164    {
165        for resource in haystack {
166            if resource._type == needle._type && resource.id == needle.id {
167                return Some(resource);
168            }
169        }
170        None
171    }
172
173    /// Return a [`ResourceAttributes`](../api/struct.ResourceAttributes.html)
174    /// object that contains the attributes in this `resource`. This will be
175    /// called recursively for each `relationship` on the resource in an attempt
176    /// to satisfy the properties for the calling struct.
177    ///
178    /// The last parameter in this function call is `visited_relationships` which is used as this
179    /// function is called recursively. This `Vec` contains the JSON:API `relationships` that were
180    /// visited when this function was called last. When operating on the root node of the document
181    /// this is simply started with an empty `Vec`.
182    ///
183    /// Tracking these "visited" relationships is necessary to prevent infinite recursion and stack
184    /// overflows. This situation can arise when the "included" resource object includes the parent
185    /// resource object - it will simply ping pong back and forth unable to acheive a finite
186    /// resolution.
187    ///
188    /// The JSON:API specification doesn't communicate the direction of a relationship.
189    /// Furthermore the current implementation of this crate does not establish an object graph
190    /// that could be used to traverse these relationships effectively.
191    #[doc(hidden)]
192    fn resource_to_attrs(resource: &Resource, included: &Option<Resources>, visited_relationships: &Vec<&str>)
193        -> ResourceAttributes
194    {
195        let mut new_attrs = HashMap::new();
196        new_attrs.clone_from(&resource.attributes);
197        new_attrs.insert("id".into(), resource.id.clone().into());
198
199        // Copy the contents of `visited_relationships` so that we can mutate within the lexical
200        // scope of this function call. This is also important so each edge that we follow (the
201        // relationship) is not polluted by data from traversing sibling relationships
202        let mut this_visited: Vec<&str> = Vec::new();
203        for rel in visited_relationships.iter() {
204            this_visited.push(rel);
205        }
206
207        if let Some(relations) = resource.relationships.as_ref() {
208            if let Some(inc) = included.as_ref() {
209                for (name, relation) in relations {
210                    // If we have already visited this resource object, exit early and do not
211                    // recurse through the relations
212                    if this_visited.contains(&name.as_str()) {
213                        return new_attrs;
214                    }
215                    // Track that we have visited this relationship to avoid infinite recursion
216                    this_visited.push(name);
217
218                    let value = match relation.data {
219                        Some(IdentifierData::None) => Value::Null,
220                        Some(IdentifierData::Single(ref identifier)) => {
221                            let found = Self::lookup(identifier, inc)
222                                .map(|r| Self::resource_to_attrs(r, included, &this_visited) );
223                            to_value(found)
224                                .expect("Casting Single relation to value")
225                        },
226                        Some(IdentifierData::Multiple(ref identifiers)) => {
227                            let found: Vec<Option<ResourceAttributes>> =
228                                identifiers.iter().map(|identifier|{
229                                    Self::lookup(identifier, inc).map(|r|{
230                                        Self::resource_to_attrs(r, included, &this_visited)
231                                    })
232                                }).collect();
233                            to_value(found)
234                                .expect("Casting Multiple relation to value")
235                        },
236                        None => Value::Null,
237                    };
238                    new_attrs.insert(name.to_string(), value);
239                }
240            }
241        }
242        new_attrs
243    }
244
245    #[doc(hidden)]
246    fn from_serializable<S: Serialize>(s: S) -> Result<Self> {
247        from_value(to_value(s)?).map_err(Error::from)
248    }
249}
250
251/// Converts a `vec!` of structs into
252/// [`Resources`](../api/type.Resources.html)
253///
254pub fn vec_to_jsonapi_resources<T: JsonApiModel>(
255    objects: Vec<T>,
256) -> (Resources, Option<Resources>) {
257    let mut included = vec![];
258    let resources = objects
259        .iter()
260        .map(|obj| {
261            let (res, mut opt_incl) = obj.to_jsonapi_resource();
262            if let Some(ref mut incl) = opt_incl {
263                included.append(incl);
264            }
265            res
266        })
267        .collect::<Vec<_>>();
268    let opt_included = if included.is_empty() {
269        None
270    } else {
271        Some(included)
272    };
273    (resources, opt_included)
274}
275
276/// Converts a `vec!` of structs into a
277/// [`JsonApiDocument`](../api/struct.JsonApiDocument.html)
278///
279/// ```rust
280/// use serde::{Deserialize, Serialize};
281/// use jsonapi_rs::api::*;
282/// use jsonapi_rs::jsonapi_model;
283/// use jsonapi_rs::model::*;
284///
285/// #[derive(Debug, PartialEq, Serialize, Deserialize)]
286/// struct Flea {
287///     id: String,
288///     name: String,
289/// }
290///
291/// jsonapi_model!(Flea; "flea");
292///
293/// let fleas = vec![
294///     Flea {
295///         id: "2".into(),
296///         name: "rick".into(),
297///     },
298///     Flea {
299///         id: "3".into(),
300///         name: "morty".into(),
301///     },
302/// ];
303/// let doc = vec_to_jsonapi_document(fleas);
304/// assert!(doc.is_valid());
305/// ```
306pub fn vec_to_jsonapi_document<T: JsonApiModel>(objects: Vec<T>) -> JsonApiDocument {
307    let (resources, included) = vec_to_jsonapi_resources(objects);
308    JsonApiDocument::Data (
309        DocumentData {
310            data: Some(PrimaryData::Multiple(resources)),
311            included,
312            ..Default::default()
313        }
314    )
315}
316
317impl<M: JsonApiModel> JsonApiModel for Box<M> {
318    fn jsonapi_type(&self) -> String {
319        self.as_ref().jsonapi_type()
320    }
321
322    fn jsonapi_id(&self) -> String {
323        self.as_ref().jsonapi_id()
324    }
325
326    fn relationship_fields() -> Option<&'static [&'static str]> {
327        M::relationship_fields()
328    }
329
330    fn build_relationships(&self) -> Option<Relationships> {
331        self.as_ref().build_relationships()
332    }
333
334    fn build_included(&self) -> Option<Resources> {
335        self.as_ref().build_included()
336    }
337}
338
339/// When applied this macro implements the
340/// [`JsonApiModel`](model/trait.JsonApiModel.html) trait for the provided type
341///
342#[macro_export]
343macro_rules! jsonapi_model {
344    ($model:ty; $type:expr) => (
345        impl JsonApiModel for $model {
346            fn jsonapi_type(&self) -> String { $type.to_string() }
347            fn jsonapi_id(&self) -> String { self.id.to_string() }
348            fn relationship_fields() -> Option<&'static [&'static str]> { None }
349            fn build_relationships(&self) -> Option<Relationships> { None }
350            fn build_included(&self) -> Option<Resources> { None }
351        }
352    );
353    ($model:ty; $type:expr;
354        has one $( $has_one:ident ),*
355    ) => (
356        jsonapi_model!($model; $type; has one $( $has_one ),*; has many);
357    );
358    ($model:ty; $type:expr;
359        has many $( $has_many:ident ),*
360    ) => (
361        jsonapi_model!($model; $type; has one; has many $( $has_many ),*);
362    );
363    ($model:ty; $type:expr;
364        has one $( $has_one:ident ),*;
365        has many $( $has_many:ident ),*
366    ) => (
367        impl JsonApiModel for $model {
368            fn jsonapi_type(&self) -> String { $type.to_string() }
369            fn jsonapi_id(&self) -> String { self.id.to_string() }
370
371            fn relationship_fields() -> Option<&'static [&'static str]> {
372                static FIELDS: &'static [&'static str] = &[
373                     $( stringify!($has_one),)*
374                     $( stringify!($has_many),)*
375                ];
376
377                Some(FIELDS)
378            }
379
380            fn build_relationships(&self) -> Option<Relationships> {
381                let mut relationships = HashMap::new();
382                $(
383                    relationships.insert(stringify!($has_one).into(),
384                        Self::build_has_one(&self.$has_one)
385                    );
386                )*
387                $(
388                    relationships.insert(
389                        stringify!($has_many).into(),
390                        {
391                            let values = &self.$has_many.get_models();
392                            Self::build_has_many(values)
393                        }
394                    );
395                )*
396                Some(relationships)
397            }
398
399            fn build_included(&self) -> Option<Resources> {
400                let mut included:Resources = vec![];
401                $( included.append(&mut self.$has_one.to_resources()); )*
402                $(
403                    for model in self.$has_many.get_models() {
404                        included.append(&mut model.to_resources());
405                    }
406                )*
407                Some(included)
408            }
409        }
410    );
411}