japi 0.3.2

An implementation of JSON:API
Documentation
use japi::{
    Document, 
    Attributes, 
    ResourceObject, 
    OptionalVec, 
    Relationships, 
    Links, 
    Link, 
    Relationship,
    GenericObject,
};
use serde_derive::{Serialize, Deserialize};
use serde_json::{self, json};
use std::convert::TryInto;

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct Article {
    title: String,
}
impl Attributes for Article { 
    fn kind() -> String { "articles".into() } 
    fn links(id: &str) -> Option<Links> {
        let mut links = Links::new();
        links.insert("self".into(), Link::Url(format!("/articles/{}", id)));
        Some(links)
    }
}
impl Article {
    fn add_author(article: &mut ResourceObject<Article>, author: &ResourceObject<People>) {
        let mut links = Links::new();
        links.insert("self".into(), 
            Link::Url(format!("/articles/{}/relationships/author", article.id.as_ref().unwrap())));
        links.insert("related".into(), 
            Link::Url(format!("/articles/{}/author", article.id.as_ref().unwrap())));
        let r = Relationship {
            links: Some(links),
            data: OptionalVec::One(Some(author.try_into().unwrap())),
            ..Default::default()
        };
        article.add_relationship("author".into(), r);
    }

    fn add_comments(article: &mut ResourceObject<Article>, comments: &[ResourceObject<Comment>]) {
        let mut links = Links::new();
        links.insert("self".into(),
            Link::Url(format!("/articles/{}/relationships/comments", article.id.as_ref().unwrap())));
        links.insert("related".into(), 
            Link::Url(format!("/articles/{}/comments", article.id.as_ref().unwrap())));
        let r = Relationship {
            links: Some(links),
            data: OptionalVec::Many(comments.iter().map(|c| c.try_into().unwrap()).collect()),
            ..Default::default()
        };
        article.add_relationship("comments".into(), r);
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct People {
    first_name: String,
    last_name: String,
    contact: String,
}
impl Attributes for People { 
    fn kind() -> String { "people".into() } 
    fn links(id: &str) -> Option<Links> {
        let mut links = Links::new();
        links.insert("self".into(), Link::Url(format!("/people/{}", id)));
        Some(links)
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct Comment {
    body: String,
}
impl Attributes for Comment { 
    fn kind() -> String { "comments".into() } 
    fn links(id: &str) -> Option<Links> {
        let mut links = Links::new();
        links.insert("self".into(), Link::Url(format!("/comments/{}", id)));
        Some(links)
    }
}
impl Comment {
    fn add_author(comment: &mut ResourceObject<Comment>, author: &ResourceObject<People>) {
        let relationship = Relationship {
            data: OptionalVec::One(Some(author.try_into().unwrap())),
            ..Default::default()
        };
        comment.add_relationship("author".into(), relationship);
    }
}

#[test]
fn test() {
    let author = ResourceObject::new("9".into(), Some(People { 
        first_name: "kitty".into(),
        last_name: "cat".into(),
        contact: "kitty@cat.space".into(), 
    }));
    let person_2 = ResourceObject::<People>::new("2".into(), None);

    let mut comment_5 = ResourceObject::new("5".into(), Some(Comment {
        body: "First!".into(),
    }));
    Comment::add_author(&mut comment_5, &person_2);
    let mut comment_12 = ResourceObject::new("12".into(), Some(Comment {
        body: "I like XML better".into(),
    }));
    Comment::add_author(&mut comment_12, &author);
    
    let comments = [comment_5, comment_12];

    let mut article = ResourceObject::new("1".into(), Some(Article {
        title: "JSON:API is kind of strange in rust".into(),
    }));
    Article::add_author(&mut article, &author);
    Article::add_comments(&mut article, &comments);

    let mut included = Vec::new();
    included.extend(comments.into_iter().map(|v| GenericObject::from(v)));
    included.push(author.into());

    let document = Document {
        data: OptionalVec::One(Some(article.into())),
        included: Some(included),
        ..Default::default()
    };

    let v1 = serde_json::to_value(&document).unwrap();
    let v2 : serde_json::Value = serde_json::from_str(include_str!("article.json")).unwrap();
    assert_eq!(v1, v2);
}