1use std::{borrow::Cow, hash::Hash};
2
3use iref::Uri;
4use rdf_types::VocabularyMut;
5use serde::{Deserialize, Serialize};
6use ssi_claims_core::{ClaimsValidity, DateTimeProvider, ValidateClaims};
7use ssi_json_ld::{JsonLdError, JsonLdNodeObject, JsonLdObject, JsonLdTypes, Loader};
8use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject, Vocabulary};
9
10use super::{RequiredContextList, RequiredTypeSet};
11use crate::{v1, v2, MaybeIdentified};
12
13pub type AnyJsonCredential<S = json_syntax::Object> = AnySpecializedJsonCredential<S>;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(
26 untagged,
27 bound(
28 serialize = "S: Serialize",
29 deserialize = "S: Deserialize<'de>, C: RequiredContextList, T: RequiredTypeSet"
30 )
31)]
32pub enum AnySpecializedJsonCredential<S = json_syntax::Object, C = (), T = ()> {
33 V1(v1::syntax::SpecializedJsonCredential<S, C, T>),
34 V2(v2::syntax::SpecializedJsonCredential<S, C, T>),
35}
36
37impl<S, C, T> JsonLdObject for AnySpecializedJsonCredential<S, C, T> {
38 fn json_ld_context(&self) -> Option<Cow<ssi_json_ld::syntax::Context>> {
39 match self {
40 Self::V1(c) => c.json_ld_context(),
41 Self::V2(c) => c.json_ld_context(),
42 }
43 }
44}
45
46impl<S, C, T> JsonLdNodeObject for AnySpecializedJsonCredential<S, C, T> {
47 fn json_ld_type(&self) -> JsonLdTypes {
48 match self {
49 Self::V1(c) => c.json_ld_type(),
50 Self::V2(c) => c.json_ld_type(),
51 }
52 }
53}
54
55impl<S, C, T, E, P> ValidateClaims<E, P> for AnySpecializedJsonCredential<S, C, T>
56where
57 E: DateTimeProvider,
58{
59 fn validate_claims(&self, env: &E, proof: &P) -> ClaimsValidity {
60 match self {
61 Self::V1(c) => c.validate_claims(env, proof),
62 Self::V2(c) => c.validate_claims(env, proof),
63 }
64 }
65}
66
67impl<S, C, T> MaybeIdentified for AnySpecializedJsonCredential<S, C, T> {
68 fn id(&self) -> Option<&Uri> {
69 match self {
70 Self::V1(c) => c.id(),
71 Self::V2(c) => c.id(),
72 }
73 }
74}
75
76impl<S, C, T> ssi_json_ld::Expandable for AnySpecializedJsonCredential<S, C, T>
77where
78 S: Serialize,
79{
80 type Error = JsonLdError;
81
82 type Expanded<I: Interpretation, V: Vocabulary>
83 = ssi_json_ld::ExpandedDocument<V::Iri, V::BlankId>
84 where
85 I: Interpretation,
86 V: VocabularyMut,
87 V::Iri: LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
88 V::BlankId: LinkedDataResource<I, V> + LinkedDataSubject<I, V>;
89
90 #[allow(async_fn_in_trait)]
91 async fn expand_with<I, V>(
92 &self,
93 ld: &mut LdEnvironment<V, I>,
94 loader: &impl Loader,
95 ) -> Result<Self::Expanded<I, V>, Self::Error>
96 where
97 I: Interpretation,
98 V: VocabularyMut,
99 V::Iri: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
100 V::BlankId: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
101 {
102 let json = ssi_json_ld::CompactJsonLd(json_syntax::to_value(self).unwrap());
103 json.expand_with(ld, loader).await
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use ssi_json_ld::{json_ld, ContextLoader, Expandable};
110
111 use super::*;
112
113 #[async_std::test]
114 async fn reject_undefined_type_v2() {
115 let input: AnyJsonCredential = serde_json::from_value(serde_json::json!({
116 "@context": [
117 "https://www.w3.org/ns/credentials/v2",
118 { "@vocab": null }
119 ],
120 "type": [
121 "VerifiableCredential",
122 "ExampleTestCredential"
123 ],
124 "issuer": "did:example:issuer",
125 "credentialSubject": {
126 "id": "did:example:subject"
127 }
128 }))
129 .unwrap();
130 match input.expand(&ContextLoader::default()).await.unwrap_err() {
131 JsonLdError::Expansion(json_ld::expansion::Error::InvalidTypeValue) => (),
132 e => panic!("{:?}", e),
133 }
134 }
135
136 #[async_std::test]
137 async fn reject_undefined_type_v1() {
138 let input: AnyJsonCredential = serde_json::from_value(serde_json::json!({
139 "@context": [
140 "https://www.w3.org/2018/credentials/v1",
141 { "@vocab": null }
142 ],
143 "type": [
144 "VerifiableCredential",
145 "ExampleTestCredential"
146 ],
147 "issuer": "did:example:issuer",
148 "credentialSubject": {
149 "id": "did:example:subject"
150 }
151 }))
152 .unwrap();
153 match input.expand(&ContextLoader::default()).await.unwrap_err() {
154 JsonLdError::Expansion(json_ld::expansion::Error::InvalidTypeValue) => (),
155 e => panic!("{:?}", e),
156 }
157 }
158}