From 855d1fd409cfa23a2f26ebe798f76cd637b68a6d Mon Sep 17 00:00:00 2001 From: Dru Sellers Date: Sun, 17 Jan 2021 08:40:29 -0600 Subject: [PATCH 1/3] doc: update doc link and tests --- src/lib.rs | 42 ++++++++++++++++++++++++++++++++++++++---- src/model.rs | 12 ++++++------ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 875d03b..b4fde54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ //! ### Basic Usage with Macro //! //! Using the [`jsonapi_model!`][jsonapi_model] macro a struct can be converted -//! into a [`JsonApiDocument`][JsonApiDocument] or [`Resource`][Resource]. It is +//! into a [`JsonApiDocument`][api::JsonApiDocument] or [`Resource`][Resource]. It is //! required that the struct have an `id` property whose type is `String`. The //! second argument in the [`jsonapi_model!`][jsonapi_model] marco defines the //! `type` member as required by the [JSON:API] specification @@ -63,6 +63,10 @@ //! variable type in `Result` //! //! ```rust +//! # #[macro_use] extern crate serde_json; +//! # #[macro_use] extern crate jsonapi; +//! # use jsonapi::api::JsonApiDocument; +//! # use serde_json; //! let serialized = r#" //! { //! "data": [{ @@ -88,7 +92,7 @@ //! } //! ] //! }"#; -//! let data: Result = serde_json::from_str(&serialized); +//! let data: Result = serde_json::from_str(&serialized); //! assert_eq!(data.is_ok(), true); //! ``` //! @@ -96,11 +100,41 @@ //! [Resource::from_str](api/struct.Resource.html) trait implementation //! //! ```rust -//! let data = Resource::from_str(&serialized); +//! # #[macro_use] extern crate serde_json; +//! # #[macro_use] extern crate jsonapi; +//! # use jsonapi::api::JsonApiDocument; +//! # use serde_json; +//! # use std::str::FromStr; +//! let serialized = r#" +//! { +//! "data": [{ +//! "type": "articles", +//! "id": "1", +//! "attributes": { +//! "title": "JSON:API paints my bikeshed!", +//! "body": "The shortest article. Ever." +//! }, +//! "relationships": { +//! "author": { +//! "data": {"id": "42", "type": "people"} +//! } +//! } +//! }], +//! "included": [ +//! { +//! "type": "people", +//! "id": "42", +//! "attributes": { +//! "name": "John" +//! } +//! } +//! ] +//! }"#; +//! let data = JsonApiDocument::from_str(&serialized); //! assert_eq!(data.is_ok(), true); //! ``` //! -//! [`JsonApiDocument`][JsonApiDocument] implements `PartialEq` which allows two +//! [`JsonApiDocument`][api::JsonApiDocument] implements `PartialEq` which allows two //! documents to be compared for equality. If two documents possess the **same //! contents** the ordering of the attributes and fields within the JSON:API //! document are irrelevant and their equality will be `true`. diff --git a/src/model.rs b/src/model.rs index 7870922..47eab07 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,8 +1,8 @@ //! Defines the `JsonApiModel` trait. This is primarily used in conjunction with //! the [`jsonapi_model!`](../macro.jsonapi_model.html) macro to allow arbitrary //! structs which implement `Deserialize` to be converted to/from a -//! [`JsonApiDocument`](../api/struct.JsonApiDocument.html) or -//! [`Resource`](../api/struct.Resource.html) +//! [`JsonApiDocument`](crate::api::JsonApiDocument) or +//! [`Resource`](crate::api::Resource) pub use std::collections::HashMap; pub use crate::api::*; use crate::errors::*; @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{from_value, to_value, Value, Map}; /// A trait for any struct that can be converted from/into a -/// [`Resource`](api/struct.Resource.tml). The only requirement is that your +/// [`Resource`](crate::api::Resource). The only requirement is that your /// struct has an `id: String` field. /// You shouldn't be implementing JsonApiModel manually, look at the /// `jsonapi_model!` macro instead. @@ -249,7 +249,7 @@ where } /// Converts a `vec!` of structs into -/// [`Resources`](../api/type.Resources.html) +/// [`Resources`](crate::api::Resources) /// pub fn vec_to_jsonapi_resources( objects: Vec, @@ -274,7 +274,7 @@ pub fn vec_to_jsonapi_resources( } /// Converts a `vec!` of structs into a -/// [`JsonApiDocument`](../api/struct.JsonApiDocument.html) +/// [`JsonApiDocument`](crate::api::JsonApiDocument) /// /// ```rust /// #[macro_use] extern crate serde_derive; @@ -337,7 +337,7 @@ impl JsonApiModel for Box { } /// When applied this macro implements the -/// [`JsonApiModel`](model/trait.JsonApiModel.html) trait for the provided type +/// [`JsonApiModel`](crate::api::JsonApiModel) trait for the provided type /// #[macro_export] macro_rules! jsonapi_model { From ac6c0b1646e348816374498f678b51200194b5cd Mon Sep 17 00:00:00 2001 From: Dru Sellers Date: Sun, 17 Jan 2021 12:21:10 -0600 Subject: [PATCH 2/3] fix: obsolete code --- tests/helper.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/helper.rs b/tests/helper.rs index d17cfe2..f97351b 100644 --- a/tests/helper.rs +++ b/tests/helper.rs @@ -1,4 +1,3 @@ -use std::error::Error; use std::fs::File; use std::io::prelude::*; use std::path::Path; @@ -8,14 +7,14 @@ pub fn read_json_file(filename: &str) -> String { let display = path.display(); let mut file = match File::open(&path) { - Err(why) => panic!("couldn't open {}: {}", display, Error::description(&why)), + Err(why) => panic!("couldn't open {}: {}", display, &why.to_string()), Ok(file) => file, }; let mut s = String::new(); if let Err(why) = file.read_to_string(&mut s) { - panic!("couldn't read {}: {}", display, Error::description(&why)); + panic!("couldn't read {}: {}", display, &why.to_string()); }; s From 8efda71050d7ba43c97247ca4a2dfb83b424bd60 Mon Sep 17 00:00:00 2001 From: Dru Sellers Date: Sun, 17 Jan 2021 13:14:56 -0600 Subject: [PATCH 3/3] feat: support optional relations --- src/model.rs | 20 +++++++++++++++++--- tests/model_test.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/model.rs b/src/model.rs index 47eab07..bd07675 100644 --- a/src/model.rs +++ b/src/model.rs @@ -353,16 +353,17 @@ macro_rules! jsonapi_model { ($model:ty; $type:expr; has one $( $has_one:ident ),* ) => ( - jsonapi_model!($model; $type; has one $( $has_one ),*; has many); + jsonapi_model!($model; $type; has one $( $has_one ),*; has many; has optional); ); ($model:ty; $type:expr; has many $( $has_many:ident ),* ) => ( - jsonapi_model!($model; $type; has one; has many $( $has_many ),*); + jsonapi_model!($model; $type; has one; has many $( $has_many ),*; has optional); ); ($model:ty; $type:expr; has one $( $has_one:ident ),*; - has many $( $has_many:ident ),* + has many $( $has_many:ident ),*; + has optional $( $has_opt:ident ),* ) => ( impl JsonApiModel for $model { fn jsonapi_type(&self) -> String { $type.to_string() } @@ -372,6 +373,7 @@ macro_rules! jsonapi_model { static FIELDS: &'static [&'static str] = &[ $( stringify!($has_one),)* $( stringify!($has_many),)* + $( stringify!($has_opt),)* ]; Some(FIELDS) @@ -393,12 +395,24 @@ macro_rules! jsonapi_model { } ); )* + $( + if let Some(model) = &self.$has_opt { + relationships.insert(stringify!($has_opt).into(), + Self::build_has_one(model) + ); + } + )* Some(relationships) } fn build_included(&self) -> Option { let mut included:Resources = vec![]; $( included.append(&mut self.$has_one.to_resources()); )* + $( + if let Some(resource) = &self.$has_opt { + included.append(&mut resource.to_resources()); + } + )* $( for model in self.$has_many.get_models() { included.append(&mut model.to_resources()); diff --git a/tests/model_test.rs b/tests/model_test.rs index 1f6518a..966418d 100644 --- a/tests/model_test.rs +++ b/tests/model_test.rs @@ -21,10 +21,11 @@ jsonapi_model!(Author; "authors"; has many books); struct Book { id: String, title: String, + forward: Option, first_chapter: Chapter, chapters: Vec } -jsonapi_model!(Book; "books"; has one first_chapter; has many chapters); +jsonapi_model!(Book; "books"; has one first_chapter; has many chapters; has optional forward); #[derive(Debug, PartialEq, Serialize, Deserialize)] struct Chapter { @@ -39,6 +40,7 @@ fn to_jsonapi_document_and_back() { let book = Book { id: "1".into(), title: "The Fellowship of the Ring".into(), + forward: None, first_chapter: Chapter { id: "1".into(), title: "A Long-expected Party".into(), ordering: 1 }, chapters: vec![ Chapter { id: "1".into(), title: "A Long-expected Party".into(), ordering: 1 }, @@ -57,6 +59,30 @@ fn to_jsonapi_document_and_back() { assert_eq!(book, book_again); } +#[test] +fn to_jsonapi_document_and_back_optional() { + let book = Book { + id: "1".into(), + title: "The Fellowship of the Ring".into(), + forward: Some(Chapter { id: "0".into(), title: "abc".into(), ordering: 0}), + first_chapter: Chapter { id: "1".into(), title: "A Long-expected Party".into(), ordering: 1 }, + chapters: vec![ + Chapter { id: "1".into(), title: "A Long-expected Party".into(), ordering: 1 }, + Chapter { id: "2".into(), title: "The Shadow of the Past".into(), ordering: 2 }, + Chapter { id: "3".into(), title: "Three is Company".into(), ordering: 3 } + ], + }; + + let doc = book.to_jsonapi_document(); + let json = serde_json::to_string(&doc).unwrap(); + let book_doc: DocumentData = serde_json::from_str(&json) + .expect("Book DocumentData should be created from the book json"); + let book_again = Book::from_jsonapi_document(&book_doc) + .expect("Book should be generated from the book_doc"); + + assert_eq!(book, book_again); +} + #[test] fn numeric_id() { #[derive(Debug, PartialEq, Serialize, Deserialize)]