use crate::{Activity, ActivityId, DataError, Fingerprint, Validate, ValidationError, emit_error};
use core::fmt;
use serde::{Deserialize, Serialize};
use serde_with::{OneOrMany, serde_as, skip_serializing_none};
use std::hash::Hasher;
#[serde_as]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
struct Activities(
#[serde_as(deserialize_as = "OneOrMany<_>", serialize_as = "Vec<_>")] Vec<Activity>,
);
#[serde_as]
#[derive(Debug, Serialize)]
struct ActivitiesId(#[serde_as(serialize_as = "Vec<_>")] Vec<ActivityId>);
impl From<Activities> for ActivitiesId {
fn from(value: Activities) -> Self {
ActivitiesId(value.0.into_iter().map(ActivityId::from).collect())
}
}
impl From<ActivitiesId> for Activities {
fn from(value: ActivitiesId) -> Self {
Activities(value.0.into_iter().map(Activity::from).collect())
}
}
#[serde_as]
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ContextActivities {
parent: Option<Activities>,
grouping: Option<Activities>,
category: Option<Activities>,
other: Option<Activities>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize)]
pub(crate) struct ContextActivitiesId {
parent: Option<ActivitiesId>,
grouping: Option<ActivitiesId>,
category: Option<ActivitiesId>,
other: Option<ActivitiesId>,
}
impl From<ContextActivities> for ContextActivitiesId {
fn from(value: ContextActivities) -> Self {
ContextActivitiesId {
parent: value.parent.map(|x| x.into()),
grouping: value.grouping.map(|x| x.into()),
category: value.category.map(|x| x.into()),
other: value.other.map(|x| x.into()),
}
}
}
impl From<ContextActivitiesId> for ContextActivities {
fn from(value: ContextActivitiesId) -> Self {
ContextActivities {
parent: value.parent.map(Activities::from),
grouping: value.grouping.map(Activities::from),
category: value.category.map(Activities::from),
other: value.other.map(Activities::from),
}
}
}
impl ContextActivities {
pub fn builder() -> ContextActivitiesBuilder {
ContextActivitiesBuilder::default()
}
pub fn parent(&self) -> &[Activity] {
if let Some(z_parent) = self.parent.as_ref() {
z_parent.0.as_slice()
} else {
&[]
}
}
pub fn grouping(&self) -> &[Activity] {
if let Some(z_grouping) = self.grouping.as_ref() {
z_grouping.0.as_slice()
} else {
&[]
}
}
pub fn category(&self) -> &[Activity] {
if let Some(z_category) = self.category.as_ref() {
z_category.0.as_slice()
} else {
&[]
}
}
pub fn other(&self) -> &[Activity] {
if let Some(z_other) = self.other.as_ref() {
z_other.0.as_slice()
} else {
&[]
}
}
}
impl Fingerprint for ContextActivities {
fn fingerprint<H: Hasher>(&self, state: &mut H) {
if self.parent.is_some() {
Fingerprint::fingerprint_slice(self.parent(), state)
}
if self.grouping.is_some() {
Fingerprint::fingerprint_slice(self.grouping(), state)
}
if self.category.is_some() {
Fingerprint::fingerprint_slice(self.category(), state)
}
if self.other.is_some() {
Fingerprint::fingerprint_slice(self.other(), state)
}
}
}
impl fmt::Display for ContextActivities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut vec = vec![];
if self.parent.is_some() {
vec.push(format!(
"parent: [{}]",
self.parent()
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
))
}
if self.grouping.is_some() {
vec.push(format!(
"grouping: [{}]",
self.grouping()
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
))
}
if self.category.is_some() {
vec.push(format!(
"category: [{}]",
self.category()
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
))
}
if self.other.is_some() {
vec.push(format!(
"other: [{}]",
self.other()
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
))
}
let res = vec
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ");
write!(f, "{{ {res} }}")
}
}
impl Validate for ContextActivities {
fn validate(&self) -> Vec<ValidationError> {
let mut vec = vec![];
if self.parent.is_some() {
self.parent().iter().for_each(|x| vec.extend(x.validate()));
}
if self.grouping.is_some() {
self.grouping()
.iter()
.for_each(|x| vec.extend(x.validate()));
}
if self.category.is_some() {
self.category()
.iter()
.for_each(|x| vec.extend(x.validate()));
}
if self.other.is_some() {
self.other().iter().for_each(|x| vec.extend(x.validate()));
}
vec
}
}
#[derive(Debug, Default)]
pub struct ContextActivitiesBuilder {
_parent: Vec<Activity>,
_grouping: Vec<Activity>,
_category: Vec<Activity>,
_other: Vec<Activity>,
}
impl ContextActivitiesBuilder {
pub fn parent(mut self, val: Activity) -> Result<Self, DataError> {
val.check_validity()?;
self._parent.push(val);
Ok(self)
}
pub fn grouping(mut self, val: Activity) -> Result<Self, DataError> {
val.check_validity()?;
self._grouping.push(val);
Ok(self)
}
pub fn category(mut self, val: Activity) -> Result<Self, DataError> {
val.check_validity()?;
self._category.push(val);
Ok(self)
}
pub fn other(mut self, val: Activity) -> Result<Self, DataError> {
val.check_validity()?;
self._other.push(val);
Ok(self)
}
pub fn build(self) -> Result<ContextActivities, DataError> {
if self._parent.is_empty()
&& self._grouping.is_empty()
&& self._category.is_empty()
&& self._other.is_empty()
{
emit_error!(DataError::Validation(ValidationError::ConstraintViolation(
"At least one of the keys must be set".into()
)))
} else {
Ok(ContextActivities {
parent: if self._parent.is_empty() {
None
} else {
Some(Activities(self._parent))
},
grouping: if self._grouping.is_empty() {
None
} else {
Some(Activities(self._grouping))
},
category: if self._category.is_empty() {
None
} else {
Some(Activities(self._category))
},
other: if self._other.is_empty() {
None
} else {
Some(Activities(self._other))
},
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_missing_keys() -> Result<(), DataError> {
const CA: &str = r#"{}"#;
let ca = serde_json::from_str::<ContextActivities>(CA).map_err(|x| DataError::JSON(x))?;
assert_eq!(ca.parent, None);
assert!(ca.parent().is_empty());
assert_eq!(ca.grouping, None);
assert!(ca.grouping().is_empty());
assert_eq!(ca.category, None);
assert!(ca.category().is_empty());
assert_eq!(ca.other, None);
assert!(ca.other().is_empty());
Ok(())
}
#[test]
fn test_one_or_many() -> Result<(), DataError> {
const CA1: &str = r#"{"parent":{"id":"http://xapi.acticity/1"}}"#;
const CA2: &str =
r#"{"other":[{"id":"http://xapi.activity/1"},{"id":"http://xapi.activity/2"}]}"#;
let one = serde_json::from_str::<ContextActivities>(CA1).map_err(|x| DataError::JSON(x))?;
assert!(one.parent.is_some());
assert_eq!(one.parent().len(), 1);
let many =
serde_json::from_str::<ContextActivities>(CA2).map_err(|x| DataError::JSON(x))?;
assert!(many.other.is_some());
assert_eq!(many.other().len(), 2);
Ok(())
}
#[test]
fn test_serialize_as_array() {
const CA: &str = r#"{"parent":{"id":"http://xapi.acticity/1"}}"#;
const EXPECTED: &str = r#"{"parent":[{"id":"http://xapi.acticity/1"}]}"#;
let ca = serde_json::from_str::<ContextActivities>(CA).unwrap();
let actual = serde_json::to_string(&ca).unwrap();
assert_eq!(EXPECTED, actual);
}
}