1use std::{borrow::Borrow, marker::PhantomData};
2
3use educe::Educe;
4use iref::{Iri, IriBuf, IriRef};
5use serde::{Deserialize, Serialize};
6use ssi_json_ld::syntax::ContextEntry;
7
8#[derive(Educe, Serialize)] #[educe(Debug, Clone)]
18#[serde(transparent, bound = "")]
19pub struct Context<V, T = ()>(ssi_json_ld::syntax::Context, PhantomData<(V, T)>);
20
21impl<V: RequiredContext, T: RequiredContextList> Default for Context<V, T> {
22 fn default() -> Self {
23 Self(
24 ssi_json_ld::syntax::Context::Many(
25 std::iter::once(ContextEntry::IriRef(V::CONTEXT_IRI.as_iri_ref().to_owned()))
26 .chain(
27 T::CONTEXT_IRIS
28 .iter()
29 .map(|&i| ContextEntry::IriRef(i.as_iri_ref().to_owned())),
30 )
31 .collect(),
32 ),
33 PhantomData,
34 )
35 }
36}
37
38impl<V, T> Context<V, T> {
39 pub fn contains(&self, entry: &ContextEntry) -> bool {
41 match &self.0 {
42 ssi_json_ld::syntax::Context::One(e) => e == entry,
43 ssi_json_ld::syntax::Context::Many(entries) => entries.iter().any(|e| e == entry),
44 }
45 }
46
47 pub fn contains_iri(&self, iri: &Iri) -> bool {
49 self.contains_iri_ref(iri.as_iri_ref())
50 }
51
52 pub fn contains_iri_ref(&self, iri_ref: &IriRef) -> bool {
54 match &self.0 {
55 ssi_json_ld::syntax::Context::One(ContextEntry::IriRef(i)) => i == iri_ref,
56 ssi_json_ld::syntax::Context::One(_) => false,
57 ssi_json_ld::syntax::Context::Many(entries) => entries
58 .iter()
59 .any(|e| matches!(e, ContextEntry::IriRef(i) if i == iri_ref)),
60 }
61 }
62
63 pub fn iter(&self) -> std::slice::Iter<ContextEntry> {
65 self.0.iter()
66 }
67
68 pub fn insert(&mut self, entry: ContextEntry) -> bool {
76 if self.contains(&entry) {
77 false
78 } else {
79 let mut entries = match std::mem::take(&mut self.0) {
80 ssi_json_ld::syntax::Context::One(e) => vec![e],
81 ssi_json_ld::syntax::Context::Many(entries) => entries,
82 };
83
84 entries.push(entry);
85 self.0 = ssi_json_ld::syntax::Context::Many(entries);
86 true
87 }
88 }
89}
90
91impl<'a, V, T> IntoIterator for &'a Context<V, T> {
92 type Item = &'a ContextEntry;
93 type IntoIter = std::slice::Iter<'a, ContextEntry>;
94
95 fn into_iter(self) -> Self::IntoIter {
96 self.iter()
97 }
98}
99
100impl<V, T> IntoIterator for Context<V, T> {
101 type Item = ContextEntry;
102 type IntoIter = ssi_json_ld::syntax::context::IntoIter;
103
104 fn into_iter(self) -> Self::IntoIter {
105 self.0.into_iter()
106 }
107}
108
109impl<V, T> Extend<ContextEntry> for Context<V, T> {
110 fn extend<E: IntoIterator<Item = ContextEntry>>(&mut self, iter: E) {
111 for entry in iter {
112 self.insert(entry);
113 }
114 }
115}
116
117impl<V, T> AsRef<ssi_json_ld::syntax::Context> for Context<V, T> {
118 fn as_ref(&self) -> &ssi_json_ld::syntax::Context {
119 &self.0
120 }
121}
122
123impl<V, T> Borrow<ssi_json_ld::syntax::Context> for Context<V, T> {
124 fn borrow(&self) -> &ssi_json_ld::syntax::Context {
125 &self.0
126 }
127}
128
129impl<V, T> From<Context<V, T>> for ssi_json_ld::syntax::Context {
130 fn from(value: Context<V, T>) -> Self {
131 value.0
132 }
133}
134
135#[derive(Debug, thiserror::Error)]
138pub enum InvalidContext {
139 #[error("unexpected context entry (expected `{0}`)")]
140 UnexpectedContext(IriBuf, ContextEntry),
141
142 #[error("missing required context entry `{0}`")]
143 MissingRequiredContext(IriBuf),
144}
145
146impl<V: RequiredContext, T: RequiredContextList> TryFrom<ssi_json_ld::syntax::Context>
147 for Context<V, T>
148{
149 type Error = InvalidContext;
150
151 fn try_from(value: ssi_json_ld::syntax::Context) -> Result<Self, Self::Error> {
152 let entries = match value {
153 ssi_json_ld::syntax::Context::One(entry) => vec![entry],
154 ssi_json_ld::syntax::Context::Many(entries) => entries,
155 };
156
157 match entries.split_first() {
158 Some((ContextEntry::IriRef(iri), rest)) if iri == V::CONTEXT_IRI => {
159 let mut expected = T::CONTEXT_IRIS.iter();
160 let mut rest = rest.iter();
161 loop {
162 match (expected.next(), rest.next()) {
163 (Some(e), Some(ContextEntry::IriRef(f))) if *e == f => (),
164 (Some(e), Some(f)) => {
165 break Err(InvalidContext::UnexpectedContext(
166 (*e).to_owned(),
167 f.clone(),
168 ))
169 }
170 (Some(e), None) => {
171 break Err(InvalidContext::MissingRequiredContext((*e).to_owned()))
172 }
173 _ => {
174 break Ok(Self(
175 ssi_json_ld::syntax::Context::Many(entries),
176 PhantomData,
177 ))
178 }
179 }
180 }
181 }
182 Some((other, _)) => Err(InvalidContext::UnexpectedContext(
183 V::CONTEXT_IRI.to_owned(),
184 other.clone(),
185 )),
186 None => Err(InvalidContext::MissingRequiredContext(
187 V::CONTEXT_IRI.to_owned(),
188 )),
189 }
190 }
191}
192
193impl<'de, V: RequiredContext, T: RequiredContextList> Deserialize<'de> for Context<V, T> {
194 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
195 where
196 D: serde::Deserializer<'de>,
197 {
198 let context = ssi_json_ld::syntax::Context::deserialize(deserializer)?;
199 context.try_into().map_err(serde::de::Error::custom)
200 }
201}
202
203pub trait RequiredContext {
204 const CONTEXT_IRI: &'static Iri;
205}
206
207pub trait RequiredContextList {
209 const CONTEXT_IRIS: &'static [&'static Iri];
210}
211
212impl RequiredContextList for () {
213 const CONTEXT_IRIS: &'static [&'static Iri] = &[];
214}
215
216impl<T: RequiredContext> RequiredContextList for T {
217 const CONTEXT_IRIS: &'static [&'static Iri] = &[T::CONTEXT_IRI];
218}
219
220macro_rules! required_context_tuple {
221 ($($t:ident: $n:tt),*) => {
222 impl<$($t : RequiredContext),*> RequiredContextList for ($($t),*,) {
223 const CONTEXT_IRIS: &'static [&'static Iri] = &[
224 $($t::CONTEXT_IRI),*
225 ];
226 }
227 };
228}
229
230required_context_tuple!(T0: 0);
231required_context_tuple!(T0: 0, T1: 1);
232required_context_tuple!(T0: 0, T1: 1, T2: 2);
233required_context_tuple!(T0: 0, T1: 1, T2: 2, T3: 3);