cdevents_sdk/
subject.rs

1use std::str::FromStr;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{Content, Id, UriReference};
6
7/// see <https://github.com/cdevents/spec/blob/main/spec.md#cdevent-subject>
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9#[serde(deny_unknown_fields)]
10pub struct Subject {
11    #[serde(rename = "content")]
12    content: Content,
13    #[serde(rename = "id")]
14    id: Id,
15    #[serde(
16        rename = "source",
17        default,
18        skip_serializing_if = "Option::is_none",
19    )]
20    source: Option<UriReference>,
21    #[serde(rename = "type")]
22    ty: String,
23}
24
25impl Subject {
26    /// see <https://github.com/cdevents/spec/blob/main/spec.md#id-subject>
27    pub fn id(&self) -> &Id {
28        &self.id
29    }
30
31    pub fn with_id(mut self, id: Id) -> Self {
32        self.id = id;
33        self
34    }
35
36    /// see <https://github.com/cdevents/spec/blob/main/spec.md#source-subject>
37    pub fn source(&self) -> &Option<UriReference> {
38        &self.source
39    }
40
41    pub fn with_source(mut self, source: UriReference) -> Self {
42        self.source = Some(source);
43        self
44    }
45
46    /// see <https://github.com/cdevents/spec/blob/main/spec.md#type-context>
47    /// derived from content
48    pub fn ty(&self) -> &str {
49        &self.ty
50    }
51
52    /// see <https://github.com/cdevents/spec/blob/main/spec.md#content>
53    pub fn content(&self) -> &Content {
54        &self.content
55    }
56
57    pub fn from_json(ty: &str, json: serde_json::Value) -> Result<Self, serde_json::Error> {
58        Ok(Subject {
59            id: json["id"]
60                .as_str()
61                .ok_or_else(|| serde::de::Error::missing_field("id"))?
62                .try_into()
63                .map_err(serde::de::Error::custom)?,
64            ty: json["type"]
65                .as_str()
66                .ok_or_else(|| serde::de::Error::missing_field("type"))?
67                .to_string(),
68            source: match json["source"].as_str() {
69                None => None,
70                Some(s) => Some(
71                    UriReference::from_str(s).map_err(serde::de::Error::custom)?,
72                ),
73            },
74            content: Content::from_json(ty, json["content"].clone())?,
75        })
76    }
77}
78
79impl<T> From<T> for Subject where T: Into<Content>{
80    fn from(content: T) -> Self {
81        let content = content.into();
82        let ty = crate::extract_subject_predicate(content.ty())
83            .map(|(s, _)| s)
84            .unwrap_or("unknown")
85            .to_owned();
86        Self {
87            content,
88            id: Id::default(),
89            source: None,
90            ty,
91        }
92    }
93}
94
95#[cfg(feature = "testkit")]
96impl<> proptest::arbitrary::Arbitrary for Subject {
97    type Parameters = ();
98    type Strategy = proptest::strategy::BoxedStrategy<Self>;
99    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
100        use proptest::prelude::*;
101        (
102            any::<Content>(),
103            any::<Id>(),
104            any::<Option<UriReference>>(),
105        ).prop_map(|(content, id, source)| {
106            let mut subject = Subject::from(content).with_id(id);
107            if let Some(source) = source {
108                subject = subject.with_source(source);
109            }
110            subject
111        }).boxed()
112    }
113}
114
115
116#[cfg(test)]
117mod tests {
118    use proptest::prelude::*;
119    use super::*;
120
121    proptest! {
122        #[test]
123        #[cfg(feature = "testkit")]
124        fn jsonify_arbitraries(s in any::<Subject>()) {
125            // Not enough information into json of subject to deserialize into the same sut content
126            // so only check that it could be serialized
127            serde_json::to_string(&s).unwrap();
128        }
129    }
130}