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")]
22 ty: String,
23}
24
25impl Subject {
26 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 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 pub fn ty(&self) -> &str {
49 &self.ty
50 }
51
52 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 serde_json::to_string(&s).unwrap();
128 }
129 }
130}