ssi_vc/syntax/
mod.rs

1mod context;
2mod credential;
3mod non_empty_object;
4mod non_empty_vec;
5mod presentation;
6mod types;
7
8use std::collections::BTreeMap;
9
10pub use context::*;
11pub use credential::*;
12use iref::{Uri, UriBuf};
13pub use non_empty_object::*;
14pub use non_empty_vec::*;
15pub use presentation::*;
16use serde::{Deserialize, Serialize};
17pub use types::*;
18
19use crate::Identified;
20
21#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
22#[serde(untagged)]
23pub enum IdOr<T> {
24    Id(UriBuf),
25    NotId(T),
26}
27
28impl<T: Identified> IdOr<T> {
29    pub fn id(&self) -> &Uri {
30        match self {
31            Self::Id(id) => id,
32            Self::NotId(t) => t.id(),
33        }
34    }
35}
36
37impl<T: Identified> Identified for IdOr<T> {
38    fn id(&self) -> &Uri {
39        self.id()
40    }
41}
42
43impl<T> From<UriBuf> for IdOr<T> {
44    fn from(value: UriBuf) -> Self {
45        Self::Id(value)
46    }
47}
48
49#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
50#[serde(rename_all = "camelCase")]
51pub struct IdentifiedObject {
52    pub id: UriBuf,
53
54    #[serde(flatten)]
55    pub extra_properties: BTreeMap<String, json_syntax::Value>,
56}
57
58#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
59#[serde(rename_all = "camelCase")]
60pub struct MaybeIdentifiedObject {
61    #[serde(
62        default,
63        deserialize_with = "not_null",
64        skip_serializing_if = "Option::is_none"
65    )]
66    pub id: Option<UriBuf>,
67
68    #[serde(flatten)]
69    pub extra_properties: BTreeMap<String, json_syntax::Value>,
70}
71
72#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
73#[serde(rename_all = "camelCase")]
74pub struct IdentifiedTypedObject {
75    pub id: UriBuf,
76
77    #[serde(rename = "type", with = "value_or_array")]
78    pub types: Vec<String>,
79
80    #[serde(flatten)]
81    pub extra_properties: BTreeMap<String, json_syntax::Value>,
82}
83
84#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
85#[serde(rename_all = "camelCase")]
86pub struct MaybeIdentifiedTypedObject {
87    #[serde(
88        default,
89        deserialize_with = "not_null",
90        skip_serializing_if = "Option::is_none"
91    )]
92    pub id: Option<UriBuf>,
93
94    #[serde(rename = "type", with = "value_or_array")]
95    pub types: Vec<String>,
96
97    #[serde(flatten)]
98    pub extra_properties: BTreeMap<String, json_syntax::Value>,
99}
100
101#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
102#[serde(rename_all = "camelCase")]
103pub struct TypedObject {
104    #[serde(rename = "type", with = "value_or_array")]
105    pub types: Vec<String>,
106
107    #[serde(flatten)]
108    pub extra_properties: BTreeMap<String, json_syntax::Value>,
109}
110
111pub(crate) mod value_or_array {
112    use serde::{Deserialize, Serialize};
113    use ssi_core::OneOrMany;
114
115    pub fn serialize<T: Serialize, S>(value: &[T], serializer: S) -> Result<S::Ok, S::Error>
116    where
117        S: serde::Serializer,
118    {
119        match value.split_first() {
120            Some((first, [])) => first.serialize(serializer),
121            _ => value.serialize(serializer),
122        }
123    }
124
125    pub fn deserialize<'de, T: Deserialize<'de>, D>(deserializer: D) -> Result<Vec<T>, D::Error>
126    where
127        D: serde::Deserializer<'de>,
128    {
129        Ok(OneOrMany::deserialize(deserializer)?.into_vec())
130    }
131}
132
133pub(crate) mod non_empty_value_or_array {
134    use serde::{Deserialize, Serialize};
135    use ssi_core::OneOrMany;
136
137    use super::NonEmptyVec;
138
139    pub fn serialize<T: Serialize, S>(value: &[T], serializer: S) -> Result<S::Ok, S::Error>
140    where
141        S: serde::Serializer,
142    {
143        match value.split_first() {
144            Some((first, [])) => first.serialize(serializer),
145            _ => value.serialize(serializer),
146        }
147    }
148
149    pub fn deserialize<'de, T: Deserialize<'de>, D>(
150        deserializer: D,
151    ) -> Result<NonEmptyVec<T>, D::Error>
152    where
153        D: serde::Deserializer<'de>,
154    {
155        OneOrMany::deserialize(deserializer)?
156            .into_vec()
157            .try_into()
158            .map_err(serde::de::Error::custom)
159    }
160}
161
162/// Deserialize an `Option::Some`, without accepting `None` (null) as a value.
163///
164/// Combined with `#[serde(default)]` this allows a field to be either present
165/// and not `null`, or absent. But this will raise an error if the field is
166/// present but `null`.
167pub(crate) fn not_null<'de, T: Deserialize<'de>, D>(deserializer: D) -> Result<Option<T>, D::Error>
168where
169    D: serde::Deserializer<'de>,
170{
171    T::deserialize(deserializer).map(Some)
172}
173
174#[cfg(test)]
175mod tests {
176    use super::MaybeIdentifiedObject;
177    use serde_json::json;
178
179    #[test]
180    fn deserialize_id_not_null_1() {
181        assert!(serde_json::from_value::<MaybeIdentifiedObject>(json!({
182            "id": null
183        }))
184        .is_err())
185    }
186
187    #[test]
188    fn deserialize_id_not_null_2() {
189        assert!(serde_json::from_value::<MaybeIdentifiedObject>(json!({})).is_ok())
190    }
191
192    #[test]
193    fn deserialize_id_not_null_3() {
194        assert!(serde_json::from_value::<MaybeIdentifiedObject>(json!({
195            "id": "http://example.org/#id"
196        }))
197        .is_ok())
198    }
199}