use std::marker::PhantomData;
use serde::de;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{Links, Meta, ResourceIdentifier};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum RelationshipData {
ToOne(Option<ResourceIdentifier>),
ToMany(Vec<ResourceIdentifier>),
}
impl Serialize for RelationshipData {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
RelationshipData::ToOne(None) => serializer.serialize_none(),
RelationshipData::ToOne(Some(rid)) => rid.serialize(serializer),
RelationshipData::ToMany(rids) => rids.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for RelationshipData {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::Null => Ok(RelationshipData::ToOne(None)),
serde_json::Value::Array(arr) => {
let rids: Vec<ResourceIdentifier> = arr
.into_iter()
.map(serde_json::from_value)
.collect::<Result<_, _>>()
.map_err(de::Error::custom)?;
Ok(RelationshipData::ToMany(rids))
}
serde_json::Value::Object(_) => {
let rid: ResourceIdentifier =
serde_json::from_value(value).map_err(de::Error::custom)?;
Ok(RelationshipData::ToOne(Some(rid)))
}
_ => Err(de::Error::custom(
"relationship data must be null, object, or array",
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Relationship<T> {
pub data: RelationshipData,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<Links>,
#[serde(skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
#[serde(skip)]
_phantom: PhantomData<T>,
}
impl<T> Relationship<T> {
pub fn new(data: RelationshipData) -> Self {
Self {
data,
links: None,
meta: None,
_phantom: PhantomData,
}
}
#[must_use]
pub fn identifiers(&self) -> &[ResourceIdentifier] {
match &self.data {
RelationshipData::ToOne(None) => &[],
RelationshipData::ToOne(Some(rid)) => std::slice::from_ref(rid),
RelationshipData::ToMany(rids) => rids.as_slice(),
}
}
pub fn ids(&self) -> impl Iterator<Item = &str> + '_ {
self.identifiers()
.iter()
.filter_map(|rid| rid.identity.as_id())
}
#[must_use]
pub fn first_id(&self) -> Option<&str> {
self.ids().next()
}
#[must_use]
pub fn first_id_or_lid(&self) -> Option<&str> {
self.identifiers()
.first()
.and_then(|rid| rid.identity.as_id().or_else(|| rid.identity.as_lid()))
}
pub fn single_id(&self) -> crate::Result<&str> {
match &self.data {
RelationshipData::ToOne(Some(rid)) => {
rid.identity.as_id().ok_or(crate::Error::LidNotIndexed)
}
RelationshipData::ToOne(None) => Err(crate::Error::NullRelationship),
RelationshipData::ToMany(_) => {
Err(crate::Error::RelationshipCardinalityMismatch { expected: "to-one" })
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::Identity;
#[test]
fn test_relationship_data_to_one() {
let json = r#"{"type":"people","id":"9"}"#;
let data: RelationshipData = serde_json::from_str(json).unwrap();
match &data {
RelationshipData::ToOne(Some(rid)) => {
assert_eq!(rid.type_, "people");
assert_eq!(rid.identity, Identity::Id("9".into()));
}
_ => panic!("expected ToOne(Some(...))"),
}
assert_eq!(serde_json::to_string(&data).unwrap(), json);
}
#[test]
fn test_relationship_data_to_one_null() {
let json = "null";
let data: RelationshipData = serde_json::from_str(json).unwrap();
assert!(matches!(data, RelationshipData::ToOne(None)));
assert_eq!(serde_json::to_string(&data).unwrap(), json);
}
#[test]
fn test_relationship_data_to_many() {
let json = r#"[{"type":"tags","id":"1"},{"type":"tags","id":"2"}]"#;
let data: RelationshipData = serde_json::from_str(json).unwrap();
match &data {
RelationshipData::ToMany(rids) => assert_eq!(rids.len(), 2),
_ => panic!("expected ToMany"),
}
assert_eq!(serde_json::to_string(&data).unwrap(), json);
}
#[test]
fn test_relationship_data_to_many_empty() {
let json = "[]";
let data: RelationshipData = serde_json::from_str(json).unwrap();
assert!(matches!(data, RelationshipData::ToMany(ref v) if v.is_empty()));
}
fn rid(type_: &str, id: &str) -> ResourceIdentifier {
ResourceIdentifier {
type_: type_.into(),
identity: Identity::Id(id.into()),
meta: None,
}
}
fn lid_rid(type_: &str, lid: &str) -> ResourceIdentifier {
ResourceIdentifier {
type_: type_.into(),
identity: Identity::Lid(lid.into()),
meta: None,
}
}
struct Target;
#[test]
fn relationship_ids_skips_null_to_one() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToOne(None));
let collected: Vec<&str> = rel.ids().collect();
assert!(collected.is_empty());
}
#[test]
fn relationship_ids_returns_single_id_for_to_one() {
let rel: Relationship<Target> =
Relationship::new(RelationshipData::ToOne(Some(rid("people", "9"))));
let collected: Vec<&str> = rel.ids().collect();
assert_eq!(collected, vec!["9"]);
}
#[test]
fn relationship_ids_returns_all_ids_for_to_many() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
rid("tags", "1"),
rid("tags", "2"),
rid("tags", "3"),
]));
let collected: Vec<&str> = rel.ids().collect();
assert_eq!(collected, vec!["1", "2", "3"]);
}
#[test]
fn relationship_ids_skips_lid_entries() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
rid("tags", "1"),
lid_rid("tags", "local-a"),
rid("tags", "3"),
]));
let collected: Vec<&str> = rel.ids().collect();
assert_eq!(collected, vec!["1", "3"]);
}
#[test]
fn relationship_first_id_for_to_one() {
let rel: Relationship<Target> =
Relationship::new(RelationshipData::ToOne(Some(rid("people", "9"))));
assert_eq!(rel.first_id(), Some("9"));
}
#[test]
fn relationship_first_id_for_null_to_one_is_none() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToOne(None));
assert_eq!(rel.first_id(), None);
}
#[test]
fn relationship_first_id_for_to_many_returns_first() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
rid("tags", "first"),
rid("tags", "second"),
]));
assert_eq!(rel.first_id(), Some("first"));
}
#[test]
fn relationship_first_id_for_empty_to_many_is_none() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![]));
assert_eq!(rel.first_id(), None);
}
#[test]
fn relationship_first_id_skips_lid_only_to_one() {
let rel: Relationship<Target> =
Relationship::new(RelationshipData::ToOne(Some(lid_rid("tags", "local"))));
assert_eq!(rel.first_id(), None);
}
#[test]
fn relationship_identifiers_for_null_to_one_is_empty() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToOne(None));
assert!(rel.identifiers().is_empty());
}
#[test]
fn relationship_identifiers_for_to_one_has_one_element() {
let rel: Relationship<Target> =
Relationship::new(RelationshipData::ToOne(Some(rid("people", "9"))));
let slice = rel.identifiers();
assert_eq!(slice.len(), 1);
assert_eq!(slice[0].type_, "people");
}
#[test]
fn relationship_identifiers_for_to_many_returns_all() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
rid("tags", "1"),
lid_rid("tags", "local"),
]));
assert_eq!(rel.identifiers().len(), 2);
}
#[test]
fn relationship_first_id_or_lid_returns_id() {
let rel: Relationship<Target> =
Relationship::new(RelationshipData::ToOne(Some(rid("tags", "42"))));
assert_eq!(rel.first_id_or_lid(), Some("42"));
}
#[test]
fn relationship_first_id_or_lid_returns_lid_when_that_is_all_there_is() {
let rel: Relationship<Target> =
Relationship::new(RelationshipData::ToOne(Some(lid_rid("tags", "local-a"))));
assert_eq!(rel.first_id_or_lid(), Some("local-a"));
}
#[test]
fn relationship_first_id_or_lid_returns_first_of_to_many() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
lid_rid("tags", "local"),
rid("tags", "server-id"),
]));
assert_eq!(rel.first_id_or_lid(), Some("local"));
}
#[test]
fn single_id_returns_to_one_id() {
let rel: Relationship<Target> =
Relationship::new(RelationshipData::ToOne(Some(rid("people", "9"))));
assert_eq!(rel.single_id().unwrap(), "9");
}
#[test]
fn single_id_errors_for_null_to_one() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToOne(None));
let err = rel.single_id().unwrap_err();
assert!(matches!(err, crate::Error::NullRelationship));
}
#[test]
fn single_id_errors_for_lid_only_to_one() {
let rel: Relationship<Target> =
Relationship::new(RelationshipData::ToOne(Some(lid_rid("tags", "local-a"))));
let err = rel.single_id().unwrap_err();
assert!(matches!(err, crate::Error::LidNotIndexed));
}
#[test]
fn single_id_errors_for_to_many() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![
rid("tags", "1"),
rid("tags", "2"),
]));
let err = rel.single_id().unwrap_err();
assert!(matches!(
err,
crate::Error::RelationshipCardinalityMismatch { expected: "to-one" }
));
}
#[test]
fn single_id_errors_for_empty_to_many() {
let rel: Relationship<Target> = Relationship::new(RelationshipData::ToMany(vec![]));
let err = rel.single_id().unwrap_err();
assert!(matches!(
err,
crate::Error::RelationshipCardinalityMismatch { expected: "to-one" }
));
}
}