1use std::str::FromStr;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{Content, Id, UriReference};
6
7#[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", skip_serializing_if = "Option::is_none", default)]
22 #[deprecated = "this file was removed since cdevents spec 0.5"]
23 #[doc(hidden)]
24 ty: Option<String>,
25}
26
27impl Subject {
28 pub fn id(&self) -> &Id {
30 &self.id
31 }
32
33 pub fn with_id(mut self, id: Id) -> Self {
34 self.id = id;
35 self
36 }
37
38 pub fn source(&self) -> &Option<UriReference> {
40 &self.source
41 }
42
43 pub fn with_source(mut self, source: UriReference) -> Self {
44 self.source = Some(source);
45 self
46 }
47
48 pub fn content(&self) -> &Content {
56 &self.content
57 }
58
59 #[allow(deprecated)]
60 pub fn from_json(ty: &str, json: serde_json::Value) -> Result<Self, serde_json::Error> {
61 Ok(Subject {
62 id: json["id"]
63 .as_str()
64 .ok_or_else(|| serde::de::Error::missing_field("id"))?
65 .try_into()
66 .map_err(serde::de::Error::custom)?,
67 ty: json["type"]
68 .as_str().map(|s| s.to_owned()),
69 source: match json["source"].as_str() {
70 None => None,
71 Some(s) => Some(
72 UriReference::from_str(s).map_err(serde::de::Error::custom)?,
73 ),
74 },
75 content: Content::from_json(ty, json["content"].clone())?,
76 })
77 }
78}
79
80impl<T> From<T> for Subject where T: Into<Content>{
81 #[allow(deprecated)]
82 fn from(content: T) -> Self {
83 let content = content.into();
84 let ty = crate::extract_subject_predicate(content.ty())
86 .map(|(s, _)| s.to_string());
87 Self {
88 content,
89 id: Id::default(),
90 source: None,
91 ty,
92 }
93 }
94}
95
96#[cfg(feature = "testkit")]
97impl<> proptest::arbitrary::Arbitrary for Subject {
98 type Parameters = ();
99 type Strategy = proptest::strategy::BoxedStrategy<Self>;
100 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
101 use proptest::prelude::*;
102 (
103 any::<Content>(),
104 any::<Id>(),
105 any::<Option<UriReference>>(),
106 ).prop_map(|(content, id, source)| {
107 let mut subject = Subject::from(content).with_id(id);
108 if let Some(source) = source {
109 subject = subject.with_source(source);
110 }
111 subject
112 }).boxed()
113 }
114}
115
116
117#[cfg(test)]
118mod tests {
119 use proptest::prelude::*;
120 use super::*;
121
122 proptest! {
123 #[test]
124 #[cfg(feature = "testkit")]
125 fn jsonify_arbitraries(s in any::<Subject>()) {
126 serde_json::to_string(&s).unwrap();
129 }
130 }
131}