1use ::linked_data::{LinkedDataResource, LinkedDataSubject};
2use iref::IriBuf;
3use linked_data::LinkedData;
4use rdf_types::{
5 dataset::{BTreeGraph, IndexedBTreeDataset, PatternMatchingDataset},
6 interpretation::{ReverseTermInterpretation, WithGenerator},
7 vocabulary::IriVocabulary,
8 Id, InterpretationMut, LexicalQuad, Quad, Term, Triple, Vocabulary, VocabularyMut,
9};
10use serde::Serialize;
11use ssi_json_ld::{
12 CompactJsonLd, Expandable, ExpandedDocument, JsonLdError, JsonLdLoaderProvider,
13 JsonLdNodeObject, JsonLdObject, JsonLdTypes, Loader,
14};
15use ssi_rdf::{urdna2015, Interpretation, IntoNQuads, LdEnvironment};
16use std::{borrow::Cow, hash::Hash};
17
18use crate::{suite::SerializeCryptographicSuite, CryptographicSuite, ProofConfigurationRef};
19
20impl<'a, S: CryptographicSuite> ProofConfigurationRef<'a, S> {
21 pub fn embed<'d>(
22 self,
23 document: &'d impl JsonLdNodeObject,
24 ) -> EmbeddedProofConfigurationRef<'d, 'a, S> {
25 EmbeddedProofConfigurationRef {
26 context: document.json_ld_context(),
27 type_: document.json_ld_type(),
28 proof: self,
29 }
30 }
31
32 pub async fn expand(
33 self,
34 environment: &impl JsonLdLoaderProvider,
35 document: &impl JsonLdNodeObject,
36 ) -> Result<ExpandedProofConfiguration, ConfigurationExpansionError>
37 where
38 S: SerializeCryptographicSuite,
39 {
40 let embedded = self.embed(document);
41 let expanded = embedded.expand(environment.loader()).await?;
42 let mut interpretation = WithGenerator::new((), ssi_rdf::generator::Blank::new());
43 expanded.extract(&mut (), &mut interpretation)
44 }
45}
46
47#[derive(Debug, thiserror::Error)]
48pub enum ConfigurationExpansionError {
49 #[error("JSON-LD expansion failed: {0}")]
50 Expansion(#[from] JsonLdError),
51
52 #[error(transparent)]
53 IntoQuads(#[from] ::linked_data::IntoQuadsError),
54
55 #[error("missing proof configuration")]
56 MissingProof,
57
58 #[error("missing proof configuration value")]
59 InvalidProofValue,
60
61 #[error("invalid proof type")]
62 InvalidProofType,
63
64 #[error("missing proof configuration graph")]
65 MissingProofGraph,
66
67 #[error("invalid JSON-LD context")]
68 InvalidContext,
69}
70
71#[derive(Serialize)]
72#[serde(bound = "S: SerializeCryptographicSuite")]
73pub struct EmbeddedProofConfigurationRef<'d, 'a, S: CryptographicSuite> {
74 #[serde(rename = "@context", default, skip_serializing_if = "Option::is_none")]
75 context: Option<Cow<'d, ssi_json_ld::syntax::Context>>,
76
77 #[serde(rename = "type", skip_serializing_if = "JsonLdTypes::is_empty")]
78 type_: JsonLdTypes<'d>,
79
80 proof: ProofConfigurationRef<'a, S>,
81}
82
83impl<S: CryptographicSuite> JsonLdObject for EmbeddedProofConfigurationRef<'_, '_, S> {
84 fn json_ld_context(&self) -> Option<Cow<ssi_json_ld::syntax::Context>> {
85 self.context.as_deref().map(Cow::Borrowed)
86 }
87}
88
89impl<S: CryptographicSuite> JsonLdNodeObject for EmbeddedProofConfigurationRef<'_, '_, S> {
90 fn json_ld_type(&self) -> JsonLdTypes {
91 self.type_.reborrow()
92 }
93}
94
95impl<S: SerializeCryptographicSuite> Expandable for EmbeddedProofConfigurationRef<'_, '_, S> {
96 type Error = ConfigurationExpansionError;
97 type Expanded<I, V>
98 = ExpandedEmbeddedProofConfiguration<V::Iri, V::BlankId>
99 where
100 I: Interpretation,
101 V: VocabularyMut,
102 V::Iri: LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
103 V::BlankId: LinkedDataResource<I, V> + LinkedDataSubject<I, V>;
104
105 async fn expand_with<I, V>(
106 &self,
107 ld: &mut LdEnvironment<V, I>,
108 loader: &impl Loader,
109 ) -> Result<Self::Expanded<I, V>, Self::Error>
110 where
111 I: Interpretation,
112 V: VocabularyMut,
113 V::Iri: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
114 V::BlankId: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
115 {
116 let json = json_syntax::to_value(self).unwrap();
117 let mut expanded = CompactJsonLd(json).expand_with(ld, loader).await?;
118 expanded.canonicalize();
119 Ok(ExpandedEmbeddedProofConfiguration(expanded))
120 }
121}
122
123pub struct ExpandedEmbeddedProofConfiguration<I, B>(ssi_json_ld::ExpandedDocument<I, B>);
124
125impl<I, B> ExpandedEmbeddedProofConfiguration<I, B>
126where
127 I: Clone + Eq + Hash,
128 B: Clone + Eq + Hash,
129{
130 pub fn extract<V, R>(
131 self,
132 vocabulary: &mut V,
133 interpretation: &mut R,
134 ) -> Result<ExpandedProofConfiguration, ConfigurationExpansionError>
135 where
136 R: InterpretationMut<V>
137 + ReverseTermInterpretation<Iri = I, BlankId = B, Literal = V::Literal>,
138 R::Resource: Clone,
139 V: VocabularyMut<Iri = I, BlankId = B>,
140 I: LinkedDataResource<R, V> + LinkedDataSubject<R, V>,
141 B: LinkedDataResource<R, V> + LinkedDataSubject<R, V>,
142 {
143 match self.0.into_main_node() {
144 Some(node) => {
145 let proof_prop = ssi_json_ld::Id::iri(
147 vocabulary
148 .get(ssi_security::PROOF)
149 .ok_or(ConfigurationExpansionError::MissingProof)?,
150 );
151 let proof_type = match node.get_any(&proof_prop) {
152 Some(proof) => match proof.as_node() {
153 Some(proof) => match &proof.graph {
154 Some(proof_graph) => match proof_graph.first() {
155 Some(proof) => match proof.as_node() {
156 Some(proof) => match proof.types() {
157 [ssi_json_ld::Id::Valid(Id::Iri(iri))] => {
158 Ok(vocabulary.iri(iri).unwrap().to_owned())
159 }
160 _ => Err(ConfigurationExpansionError::InvalidProofType),
161 },
162 None => Err(ConfigurationExpansionError::InvalidProofValue),
163 },
164 None => Err(ConfigurationExpansionError::MissingProof),
165 },
166 None => Err(ConfigurationExpansionError::InvalidProofValue),
167 },
168 None => Err(ConfigurationExpansionError::InvalidProofValue),
169 },
170 None => Err(ConfigurationExpansionError::MissingProof),
171 }?;
172
173 let (subject, quads) = ::linked_data::to_lexical_subject_quads_with(
174 vocabulary,
175 interpretation,
176 None,
177 &node,
178 )?;
179
180 let subject = Term::Id(subject);
181
182 let mut dataset: IndexedBTreeDataset = quads
183 .into_iter()
184 .map(|q| Quad(Term::Id(q.0), Term::iri(q.1), q.2, q.3.map(Term::Id)))
185 .collect();
186
187 let proof_prop = Term::iri(ssi_security::PROOF.to_owned());
188 match dataset.quad_objects(None, &subject, &proof_prop).next() {
189 Some(Term::Id(proof_id)) => {
190 let proof_id = Term::Id(proof_id.clone());
191 match dataset.remove_graph(Some(&proof_id)) {
192 Some(graph) => Ok(ExpandedProofConfiguration {
193 type_iri: proof_type,
194 graph,
195 }),
196 None => Err(ConfigurationExpansionError::MissingProofGraph),
197 }
198 }
199 Some(Term::Literal(_)) => Err(ConfigurationExpansionError::InvalidProofValue),
200 None => Err(ConfigurationExpansionError::MissingProof),
201 }
202 }
203 None => Err(ConfigurationExpansionError::InvalidContext),
204 }
205 }
206}
207
208impl<I: Interpretation, V: Vocabulary> LinkedData<I, V>
209 for ExpandedEmbeddedProofConfiguration<V::Iri, V::BlankId>
210where
211 ExpandedDocument<V::Iri, V::BlankId>: LinkedData<I, V>,
212{
213 fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
214 where
215 S: linked_data::Visitor<I, V>,
216 {
217 self.0.visit(visitor)
218 }
219}
220
221pub struct ExpandedProofConfiguration {
223 pub type_iri: IriBuf,
224 pub graph: BTreeGraph,
225}
226
227impl ExpandedProofConfiguration {
228 pub fn quads(&self) -> impl '_ + Iterator<Item = LexicalQuad> {
230 let quads = self.graph.iter().map(|Triple(s, p, o)| {
231 Quad(
232 s.as_lexical_term_ref().into_id().unwrap(),
233 p.as_lexical_term_ref().into_iri().unwrap(),
234 o.as_lexical_term_ref(),
235 None,
236 )
237 });
238
239 urdna2015::normalize(quads)
240 }
241
242 pub fn nquads_lines(&self) -> Vec<String> {
244 self.quads().into_nquads_lines()
245 }
246
247 pub fn nquads(&self) -> String {
249 self.quads().into_nquads()
250 }
251}