identity_kit/vc/model.rs
1//! Building blocks shared by credentials and presentations: a [`Party`] (the issuer or the
2//! holder), a [`Subject`] and the [`TypedObject`] of every extension point.
3
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::did::{Context, Did, DidUrl, OneOrMany, Uri};
9
10use super::CredentialError;
11
12/// The base context of the Verifiable Credentials Data Model 1.1.
13pub const CONTEXT_V1: &str = "https://www.w3.org/2018/credentials/v1";
14
15/// The base context of the Verifiable Credentials Data Model 2.0.
16pub const CONTEXT_V2: &str = "https://www.w3.org/ns/credentials/v2";
17
18/// The issuer of a credential or the holder of a presentation: a URI, or an object whose `id`
19/// is that URI and that carries further properties, a name say.
20///
21/// # Examples
22///
23/// ```
24/// use identity_kit::vc::Party;
25/// use serde_json::json;
26///
27/// let issuer: Party = serde_json::from_value(json!("https://example.edu/issuers/565049"))?;
28/// assert_eq!(issuer.id().as_str(), "https://example.edu/issuers/565049");
29///
30/// let issuer: Party = serde_json::from_value(json!({
31/// "id": "https://example.edu/issuers/565049",
32/// "name": "Example University"
33/// }))?;
34/// assert_eq!(issuer.id().as_str(), "https://example.edu/issuers/565049");
35/// assert_eq!(issuer.properties().and_then(|p| p.get("name")), Some(&json!("Example University")));
36/// # Ok::<(), serde_json::Error>(())
37/// ```
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(untagged)]
40#[non_exhaustive]
41pub enum Party {
42 /// Just the identifier.
43 Uri(Uri),
44 /// The identifier with further properties.
45 Object {
46 /// The identifier.
47 id: Uri,
48 /// Every other property, in insertion order.
49 #[serde(flatten)]
50 properties: IndexMap<String, Value>,
51 },
52}
53
54impl Party {
55 /// A party with `properties` besides its `id`.
56 pub fn object(id: impl Into<Uri>, properties: IndexMap<String, Value>) -> Self {
57 Self::Object {
58 id: id.into(),
59 properties,
60 }
61 }
62
63 /// The identifier.
64 pub fn id(&self) -> &Uri {
65 match self {
66 Self::Uri(id) | Self::Object { id, .. } => id,
67 }
68 }
69
70 /// The properties besides `id`, when the party is an object.
71 pub fn properties(&self) -> Option<&IndexMap<String, Value>> {
72 match self {
73 Self::Uri(_) => None,
74 Self::Object { properties, .. } => Some(properties),
75 }
76 }
77}
78
79impl From<Uri> for Party {
80 fn from(id: Uri) -> Self {
81 Self::Uri(id)
82 }
83}
84
85impl From<Did> for Party {
86 fn from(did: Did) -> Self {
87 Self::Uri(did.into())
88 }
89}
90
91impl From<&Did> for Party {
92 fn from(did: &Did) -> Self {
93 Self::Uri(did.into())
94 }
95}
96
97impl From<DidUrl> for Party {
98 fn from(url: DidUrl) -> Self {
99 Self::Uri(url.into())
100 }
101}
102
103/// A `credentialSubject`: the optional `id` of the subject and the claims about it.
104///
105/// A subject without `id` makes a bearer credential.
106///
107/// # Examples
108///
109/// ```
110/// use identity_kit::did::Uri;
111/// use identity_kit::vc::Subject;
112/// use serde_json::json;
113///
114/// let subject = Subject::new(Uri::parse("did:example:ebfeb1f712ebc6f1c276e12ec21")?)
115/// .with_property("alumniOf", json!({ "name": "Example University" }));
116/// assert_eq!(
117/// serde_json::to_string(&subject)?,
118/// r#"{"id":"did:example:ebfeb1f712ebc6f1c276e12ec21","alumniOf":{"name":"Example University"}}"#
119/// );
120/// # Ok::<(), Box<dyn std::error::Error>>(())
121/// ```
122#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
123#[non_exhaustive]
124pub struct Subject {
125 /// The identifier of the subject, absent in a bearer credential.
126 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub id: Option<Uri>,
128 /// The claims about the subject, in insertion order.
129 #[serde(flatten)]
130 pub properties: IndexMap<String, Value>,
131}
132
133impl Subject {
134 /// A subject identified by `id`, without claims yet.
135 pub fn new(id: impl Into<Uri>) -> Self {
136 Self {
137 id: Some(id.into()),
138 properties: IndexMap::new(),
139 }
140 }
141
142 /// This subject with the claim `name` set to `value`.
143 #[must_use]
144 pub fn with_property(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
145 self.properties.insert(name.into(), value.into());
146 self
147 }
148}
149
150/// An object with a `type` and usually an `id`: a `credentialStatus`, `credentialSchema`,
151/// `refreshService`, `termsOfUse` or `evidence` entry, whose meaning the type defines.
152///
153/// # Examples
154///
155/// ```
156/// use identity_kit::did::Uri;
157/// use identity_kit::vc::TypedObject;
158/// use serde_json::json;
159///
160/// let status = TypedObject::new("StatusList2021Entry")
161/// .with_id(Uri::parse("https://example.edu/status/24#94567")?)
162/// .with_property("statusPurpose", "revocation")
163/// .with_property("statusListIndex", "94567");
164/// assert!(status.has_type("StatusList2021Entry"));
165/// assert_eq!(
166/// serde_json::to_string(&status)?,
167/// r#"{"id":"https://example.edu/status/24#94567","type":"StatusList2021Entry","statusPurpose":"revocation","statusListIndex":"94567"}"#
168/// );
169/// # Ok::<(), Box<dyn std::error::Error>>(())
170/// ```
171#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
172#[non_exhaustive]
173pub struct TypedObject {
174 /// The identifier, which most extension points require.
175 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub id: Option<Uri>,
177 /// The `type`, which selects the meaning of the other properties.
178 #[serde(rename = "type")]
179 pub types: OneOrMany<String>,
180 /// Every other property, in insertion order.
181 #[serde(flatten)]
182 pub properties: IndexMap<String, Value>,
183}
184
185impl TypedObject {
186 /// An object of type `object_type`, without `id`.
187 pub fn new(object_type: impl Into<String>) -> Self {
188 Self {
189 id: None,
190 types: OneOrMany::One(object_type.into()),
191 properties: IndexMap::new(),
192 }
193 }
194
195 /// This object with its `id` set.
196 #[must_use]
197 pub fn with_id(mut self, id: impl Into<Uri>) -> Self {
198 self.id = Some(id.into());
199 self
200 }
201
202 /// This object with the property `name` set to `value`.
203 #[must_use]
204 pub fn with_property(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
205 self.properties.insert(name.into(), value.into());
206 self
207 }
208
209 /// Whether `object_type` is among the types.
210 pub fn has_type(&self, object_type: &str) -> bool {
211 self.types.iter().any(|t| t == object_type)
212 }
213
214 /// The first type.
215 pub fn object_type(&self) -> &str {
216 self.types.first().map_or("", String::as_str)
217 }
218}
219
220/// The first URI of `context`, which the data model requires to be its base context.
221pub(super) fn first_context(context: &Context) -> Option<&str> {
222 match context.as_value() {
223 Value::String(uri) => Some(uri),
224 Value::Array(items) => items.first().and_then(Value::as_str),
225 _ => None,
226 }
227}
228
229/// Checks that `context` starts with `base` and `types` include `required`.
230pub(super) fn check_context_and_type(
231 context: &Context,
232 base: &'static str,
233 types: &OneOrMany<String>,
234 required: &'static str,
235) -> Result<(), CredentialError> {
236 if first_context(context) != Some(base) {
237 return Err(CredentialError::InvalidContext { expected: base });
238 }
239 if !types.iter().any(|t| t == required) {
240 return Err(CredentialError::MissingType(required));
241 }
242 Ok(())
243}
244
245/// `context` with `uri` appended.
246pub(super) fn append_context(context: Context, uri: String) -> Context {
247 let mut uris = match context.into_value() {
248 Value::Array(items) => items,
249 single => vec![single],
250 };
251 uris.push(Value::String(uri));
252 Context::new(Value::Array(uris)).expect("a non-empty array")
253}
254
255/// `types` with `added` appended, as a list.
256pub(super) fn push_type(types: &mut OneOrMany<String>, added: String) {
257 let mut list = core::mem::replace(types, OneOrMany::Many(Vec::new())).into_vec();
258 list.push(added);
259 *types = OneOrMany::Many(list);
260}