use serde_json;
use std::collections::HashMap;
use crate::errors::*;
use std::str::FromStr;
use std;
pub type JsonApiValue = serde_json::Value;
pub type Resources = Vec<Resource>;
pub type ResourceIdentifiers = Vec<ResourceIdentifier>;
pub type Links = HashMap<String, JsonApiValue>;
pub type Meta = HashMap<String, JsonApiValue>;
pub type ResourceAttributes = HashMap<String, JsonApiValue>;
pub type Relationships = HashMap<String, Relationship>;
pub type Included = Vec<Resource>;
pub type JsonApiErrors = Vec<JsonApiError>;
pub type JsonApiId = String;
pub type JsonApiIds<'a> = Vec<&'a JsonApiId>;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ResourceIdentifier {
#[serde(rename = "type")]
pub _type: String,
pub id: JsonApiId,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub struct Resource {
#[serde(rename = "type")]
pub _type: String,
pub id: JsonApiId,
#[serde(default)]
pub attributes: ResourceAttributes,
#[serde(skip_serializing_if = "Option::is_none")]
pub relationships: Option<Relationships>,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<Links>,
#[serde(skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Relationship {
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<IdentifierData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<Links>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum PrimaryData {
None,
Single(Box<Resource>),
Multiple(Resources),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum IdentifierData {
None,
Single(ResourceIdentifier),
Multiple(ResourceIdentifiers),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub struct JsonApiDocument {
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<PrimaryData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub included: Option<Resources>,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<Links>,
#[serde(skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
#[serde(skip_serializing_if = "Option::is_none")]
pub errors: Option<JsonApiErrors>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jsonapi: Option<JsonApiInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub struct ErrorSource {
pub pointer: Option<String>,
pub parameter: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub struct JsonApiError {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<Links>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<ErrorSource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct JsonApiInfo {
pub version: Option<String>,
pub meta: Option<Meta>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Pagination {
pub first: Option<String>,
pub prev: Option<String>,
pub next: Option<String>,
pub last: Option<String>,
}
#[derive(Debug)]
pub struct Patch {
pub patch_type: PatchType,
pub subject: String,
pub previous: JsonApiValue,
pub next: JsonApiValue,
}
#[derive(Debug)]
pub struct PatchSet {
pub resource_type: String,
pub resource_id: String,
pub patches: Vec<Patch>,
}
impl PatchSet {
pub fn new_for(resource: &Resource) -> Self {
PatchSet {
resource_type: resource._type.clone(),
resource_id: resource.id.clone(),
patches: Vec::<Patch>::new(),
}
}
pub fn push(&mut self, patch: Patch) {
self.patches.push(patch);
}
}
impl JsonApiDocument {
fn has_errors(&self) -> bool {
self.errors.is_some()
}
fn has_meta(&self) -> bool {
self.errors.is_some()
}
fn has_included(&self) -> bool {
self.included.is_some()
}
fn has_data(&self) -> bool {
self.data.is_some()
}
pub fn is_valid(&self) -> bool {
self.validate().is_none()
}
pub fn validate(&self) -> Option<Vec<DocumentValidationError>> {
let mut errors = Vec::<DocumentValidationError>::new();
if self.has_data() && self.has_errors() {
errors.push(DocumentValidationError::DataWithErrors);
}
if self.has_included() && !self.has_data() {
errors.push(DocumentValidationError::IncludedWithoutData);
}
if !(self.has_data() || self.has_meta() || self.has_errors()) {
errors.push(DocumentValidationError::MissingContent);
}
match errors.len() {
0 => None,
_ => Some(errors),
}
}
}
impl FromStr for JsonApiDocument {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
serde_json::from_str(s).chain_err(|| "Error parsing Document")
}
}
impl Resource {
pub fn get_relationship(&self, name: &str) -> Option<&Relationship> {
match self.relationships {
None => None,
Some(ref relationships) => {
match relationships.get(name) {
None => None,
Some(rel) => Some(rel),
}
}
}
}
pub fn get_attribute(&self, name: &str) -> Option<&JsonApiValue> {
match self.attributes.get(name) {
None => None,
Some(val) => Some(val),
}
}
pub fn diff(&self, other: Resource) -> std::result::Result<PatchSet, DiffPatchError> {
if self._type != other._type {
Err(DiffPatchError::IncompatibleTypes(
self._type.clone(),
other._type.clone(),
))
} else {
let mut self_keys: Vec<String> =
self.attributes.iter().map(|(key, _)| key.clone()).collect();
self_keys.sort();
let mut other_keys: Vec<String> = other
.attributes
.iter()
.map(|(key, _)| key.clone())
.collect();
other_keys.sort();
let matching = self_keys
.iter()
.zip(other_keys.iter())
.filter(|&(a, b)| a == b)
.count();
if matching != self_keys.len() {
Err(DiffPatchError::DifferentAttributeKeys)
} else {
let mut patchset = PatchSet::new_for(self);
for (attr, self_value) in &self.attributes {
match other.attributes.get(attr) {
None => {
error!(
"Resource::diff unable to find attribute {:?} in {:?}",
attr,
other
);
}
Some(other_value) => {
if self_value != other_value {
patchset.push(Patch {
patch_type: PatchType::Attribute,
subject: attr.clone(),
previous: self_value.clone(),
next: other_value.clone(),
});
}
}
}
}
Ok(patchset)
}
}
}
pub fn patch(&mut self, patchset: PatchSet) -> Result<Resource> {
let mut res = self.clone();
for patch in &patchset.patches {
res.attributes.insert(
patch.subject.clone(),
patch.next.clone(),
);
}
Ok(res)
}
}
impl FromStr for Resource {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
serde_json::from_str(s).chain_err(|| "Error parsing resource")
}
}
impl Relationship {
pub fn as_id(&self) -> std::result::Result<Option<&JsonApiId>, RelationshipAssumptionError> {
match self.data {
Some(IdentifierData::None) => Ok(None),
Some(IdentifierData::Multiple(_)) => Err(RelationshipAssumptionError::RelationshipIsAList),
Some(IdentifierData::Single(ref data)) => Ok(Some(&data.id)),
None => Ok(None),
}
}
pub fn as_ids(&self) -> std::result::Result<Option<JsonApiIds>, RelationshipAssumptionError> {
match self.data {
Some(IdentifierData::None) => Ok(None),
Some(IdentifierData::Single(_)) => Err(RelationshipAssumptionError::RelationshipIsNotAList),
Some(IdentifierData::Multiple(ref data)) => Ok(Some(data.iter().map(|x| &x.id).collect())),
None => Ok(None),
}
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum DocumentValidationError {
IncludedWithoutData,
DataWithErrors,
MissingContent,
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum JsonApiDataError {
AttributeNotFound,
IncompatibleAttributeType,
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum RelationshipAssumptionError {
RelationshipIsAList,
RelationshipIsNotAList,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DiffPatchError {
IncompatibleTypes(String, String),
DifferentAttributeKeys,
NonExistentProperty(String),
IncorrectPropertyValue(String),
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum PatchType {
Relationship,
Attribute,
}