Skip to content

Commit fb66803

Browse files
committed
Implement FromStr for JsonApiDocument and Resource
1 parent d38093e commit fb66803

File tree

2 files changed

+71
-45
lines changed

2 files changed

+71
-45
lines changed

src/api.rs

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use serde_json;
22
use std::collections::HashMap;
3+
use errors::*;
4+
use std::str::FromStr;
5+
use std;
36

47
/// Permitted JSON-API values (all JSON Values)
58
pub type JsonApiValue = serde_json::Value;
@@ -247,11 +250,16 @@ impl JsonApiDocument {
247250
}
248251

249252
}
253+
}
254+
255+
impl FromStr for JsonApiDocument {
256+
type Err = Error;
250257

251258
/// Instantiate from string
252259
///
253260
/// ```
254261
/// use jsonapi::api::JsonApiDocument;
262+
/// use std::str::FromStr;
255263
///
256264
/// let serialized = r#"{
257265
/// "data" : [
@@ -263,35 +271,12 @@ impl JsonApiDocument {
263271
/// let doc = JsonApiDocument::from_str(&serialized);
264272
/// assert_eq!(doc.is_ok(), true);
265273
/// ```
266-
pub fn from_str(s: &str) -> Result<Self, serde_json::Error> {
267-
serde_json::from_str(s)
274+
fn from_str(s: &str) -> Result<Self> {
275+
serde_json::from_str(s).chain_err(|| "Error parsing Document")
268276
}
269277
}
270278

271279
impl Resource {
272-
/// Instantiate from string
273-
///
274-
/// ```
275-
/// use jsonapi::api::Resource;
276-
///
277-
/// let serialized = r#"{
278-
/// "id":"1",
279-
/// "type":"post",
280-
/// "attributes":{
281-
/// "title": "Rails is Omakase",
282-
/// "likes": 250
283-
/// },
284-
/// "relationships":{},
285-
/// "links" :{}
286-
/// }"#;
287-
///
288-
/// let data = Resource::from_str(&serialized);
289-
/// assert_eq!(data.is_ok(), true);
290-
/// ```
291-
pub fn from_str(s: &str) -> Result<Self, serde_json::Error> {
292-
serde_json::from_str(s)
293-
}
294-
295280
pub fn get_relationship(&self, name: &str) -> Option<&Relationship> {
296281
match self.relationships {
297282
None => None,
@@ -308,6 +293,7 @@ impl Resource {
308293
///
309294
/// ```
310295
/// use jsonapi::api::Resource;
296+
/// use std::str::FromStr;
311297
///
312298
/// let serialized = r#"{
313299
/// "id":"1",
@@ -343,7 +329,7 @@ impl Resource {
343329
}
344330
}
345331

346-
pub fn diff(&self, other: Resource) -> Result<PatchSet, DiffPatchError> {
332+
pub fn diff(&self, other: Resource) -> std::result::Result<PatchSet, DiffPatchError> {
347333
if self._type != other._type {
348334
Err(DiffPatchError::IncompatibleTypes(self._type.clone(), other._type.clone()))
349335
} else {
@@ -391,7 +377,7 @@ impl Resource {
391377
}
392378
}
393379

394-
pub fn patch(&mut self, patchset: PatchSet) -> Result<Resource, DiffPatchError> {
380+
pub fn patch(&mut self, patchset: PatchSet) -> Result<Resource> {
395381
let mut res = self.clone();
396382
for patch in &patchset.patches {
397383
res.attributes.insert(patch.subject.clone(), patch.next.clone());
@@ -400,16 +386,45 @@ impl Resource {
400386
}
401387
}
402388

389+
impl FromStr for Resource {
390+
type Err = Error;
391+
392+
/// Instantiate from string
393+
///
394+
/// ```
395+
/// use jsonapi::api::Resource;
396+
/// use std::str::FromStr;
397+
///
398+
/// let serialized = r#"{
399+
/// "id":"1",
400+
/// "type":"post",
401+
/// "attributes":{
402+
/// "title": "Rails is Omakase",
403+
/// "likes": 250
404+
/// },
405+
/// "relationships":{},
406+
/// "links" :{}
407+
/// }"#;
408+
///
409+
/// let data = Resource::from_str(&serialized);
410+
/// assert_eq!(data.is_ok(), true);
411+
/// ```
412+
fn from_str(s: &str) -> Result<Self> {
413+
serde_json::from_str(s).chain_err(|| "Error parsing resource" )
414+
}
415+
}
416+
417+
403418
impl Relationship {
404-
pub fn as_id(&self) -> Result<Option<&JsonApiId>, RelationshipAssumptionError> {
419+
pub fn as_id(&self) -> std::result::Result<Option<&JsonApiId>, RelationshipAssumptionError> {
405420
match self.data {
406421
IdentifierData::None => Ok(None),
407422
IdentifierData::Multiple(_) => Err(RelationshipAssumptionError::RelationshipIsAList),
408423
IdentifierData::Single(ref data) => Ok(Some(&data.id)),
409424
}
410425
}
411426

412-
pub fn as_ids(&self) -> Result<Option<JsonApiIds>, RelationshipAssumptionError> {
427+
pub fn as_ids(&self) -> std::result::Result<Option<JsonApiIds>, RelationshipAssumptionError> {
413428
match self.data {
414429
IdentifierData::None => Ok(None),
415430
IdentifierData::Single(_) => Err(RelationshipAssumptionError::RelationshipIsNotAList),

src/model.rs

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,28 @@ use errors::*;
44
use serde::{Deserialize, Serialize};
55
use serde_json::{from_value, to_value, Value, Map};
66

7-
/* A trait for any struct that can be converted from/into a Resource.
8-
* The only requirement is that your struct has a field that implements
9-
* `ToString` and can be used as an ID. (not necesarilly called ID though).
10-
* You shouldn't be implementing JsonApiModel manually, look at the
11-
* `jsonapi_model` macro instead.
12-
*/
7+
/// A trait for any struct that can be converted from/into a Resource.
8+
/// The only requirement is that your struct has an 'id: String' field.
9+
/// You shouldn't be implementing JsonApiModel manually, look at the
10+
/// `jsonapi_model!` macro instead.
1311
pub trait JsonApiModel: Serialize
1412
where for<'de> Self: Deserialize<'de>
1513
{
14+
#[doc(hidden)]
1615
fn jsonapi_type(&self) -> String;
16+
#[doc(hidden)]
1717
fn jsonapi_id(&self) -> String;
18+
#[doc(hidden)]
1819
fn relationship_fields() -> Option<&'static [&'static str]>;
20+
#[doc(hidden)]
1921
fn build_relationships(&self) -> Option<Relationships>;
22+
#[doc(hidden)]
2023
fn build_included(&self) -> Option<Resources>;
2124

2225
fn from_jsonapi_resource(resource: &Resource, included: &Option<Resources>)
2326
-> Result<Self>
2427
{
25-
Self::from_serializable(Self::resource_to_attrs(&resource, &included))
28+
Self::from_serializable(Self::resource_to_attrs(resource, included))
2629
}
2730

2831
fn from_jsonapi_document(doc: &JsonApiDocument) -> Result<Self> {
@@ -31,11 +34,11 @@ pub trait JsonApiModel: Serialize
3134
match *primary_data {
3235
PrimaryData::None => bail!("Document had no data"),
3336
PrimaryData::Single(ref resource) =>
34-
Self::from_jsonapi_resource(&resource, &doc.included),
37+
Self::from_jsonapi_resource(resource, &doc.included),
3538
PrimaryData::Multiple(ref resources) => {
3639
let all: Vec<ResourceAttributes> = resources
3740
.iter()
38-
.map(|r| Self::resource_to_attrs(&r, &doc.included))
41+
.map(|r| Self::resource_to_attrs(r, &doc.included))
3942
.collect();
4043
Self::from_serializable(all)
4144
}
@@ -76,14 +79,16 @@ pub trait JsonApiModel: Serialize
7679
}
7780

7881

82+
#[doc(hidden)]
7983
fn build_has_one<M: JsonApiModel>(model: &M) -> Relationship {
8084
Relationship{
8185
data: IdentifierData::Single(model.as_resource_identifier()),
8286
links: None
8387
}
8488
}
8589

86-
fn build_has_many<M: JsonApiModel>(models: &Vec<M>) -> Relationship {
90+
#[doc(hidden)]
91+
fn build_has_many<M: JsonApiModel>(models: &[M]) -> Relationship {
8792
Relationship{
8893
data: IdentifierData::Multiple(
8994
models.iter().map(|m| m.as_resource_identifier()).collect()
@@ -92,6 +97,7 @@ pub trait JsonApiModel: Serialize
9297
}
9398
}
9499

100+
#[doc(hidden)]
95101
fn as_resource_identifier(&self) -> ResourceIdentifier {
96102
ResourceIdentifier {
97103
_type: self.jsonapi_type(),
@@ -103,17 +109,19 @@ pub trait JsonApiModel: Serialize
103109
* before calling this, so there's no need to ignore it like we do
104110
* with the attributes that correspond with relationships.
105111
* */
112+
#[doc(hidden)]
106113
fn extract_attributes(attrs: &Map<String, Value>) -> ResourceAttributes {
107114
attrs.iter().filter(|&(key, _)|{
108-
if let Some(ref fields) = Self::relationship_fields(){
115+
if let Some(fields) = Self::relationship_fields(){
109116
if fields.contains(&key.as_str()) {
110117
return false;
111118
}
112119
}
113-
return true;
120+
true
114121
}).map(|(k,v)|{ (k.clone(), v.clone()) }).collect()
115122
}
116123

124+
#[doc(hidden)]
117125
fn to_resources(&self) -> Resources {
118126
let (me, maybe_others) = self.to_jsonapi_resource();
119127
let mut flattened = vec![me];
@@ -123,17 +131,19 @@ pub trait JsonApiModel: Serialize
123131
flattened
124132
}
125133

126-
fn lookup<'a>(needle: &ResourceIdentifier, haystack: &'a Resources)
134+
#[doc(hidden)]
135+
fn lookup<'a>(needle: &ResourceIdentifier, haystack: &'a [Resource])
127136
-> Option<&'a Resource>
128137
{
129138
for resource in haystack {
130139
if resource._type == needle._type && resource.id == needle.id {
131-
return Some(&resource)
140+
return Some(resource)
132141
}
133142
}
134143
None
135144
}
136145

146+
#[doc(hidden)]
137147
fn resource_to_attrs(resource: &Resource, included: &Option<Resources>)
138148
-> ResourceAttributes
139149
{
@@ -148,15 +158,15 @@ pub trait JsonApiModel: Serialize
148158
IdentifierData::None => Value::Null,
149159
IdentifierData::Single(ref identifier) => {
150160
let found = Self::lookup(identifier, inc)
151-
.map(|r| Self::resource_to_attrs(r, &included) );
161+
.map(|r| Self::resource_to_attrs(r, included) );
152162
to_value(found)
153163
.expect("Casting Single relation to value")
154164
},
155165
IdentifierData::Multiple(ref identifiers) => {
156166
let found: Vec<Option<ResourceAttributes>> =
157167
identifiers.iter().map(|id|{
158168
Self::lookup(id, inc).map(|r|{
159-
Self::resource_to_attrs(r, &included)
169+
Self::resource_to_attrs(r, included)
160170
})
161171
}).collect();
162172
to_value(found)
@@ -171,6 +181,7 @@ pub trait JsonApiModel: Serialize
171181
new_attrs
172182
}
173183

184+
#[doc(hidden)]
174185
fn from_serializable<S: Serialize>(s: S) -> Result<Self> {
175186
from_value(to_value(s).unwrap())
176187
.chain_err(|| "Error casting via serde_json")

0 commit comments

Comments
 (0)