1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
use super::{DefinedTerms, Environment, Merged};
use crate::{Error, Options, ProcessingStack, Warning, WarningHandler};
use contextual::WithContext;
use iref::{Iri, IriRef};
use json_ld_core::{warning, Context, Id, Loader, Term};
use json_ld_syntax::{self as syntax, context::definition::Key, ExpandableRef, Nullable};
use rdf_types::{BlankId, Vocabulary, VocabularyMut};
use syntax::{context::definition::KeyOrKeywordRef, is_keyword_like, CompactIri};

pub struct MalformedIri(pub String);

impl From<MalformedIri> for Warning {
	fn from(MalformedIri(s): MalformedIri) -> Self {
		Self::MalformedIri(s)
	}
}

/// Result of the [`expand_iri_with`] function.
pub type ExpandIriResult<T, B, E> = Result<Term<T, B>, Error<E>>;

/// Default values for `document_relative` and `vocab` should be `false` and `true`.
#[allow(clippy::too_many_arguments)]
pub async fn expand_iri_with<'a, N, L, W>(
	mut env: Environment<'a, N, L, W>,
	active_context: &'a mut Context<N::Iri, N::BlankId>,
	value: Nullable<ExpandableRef<'a>>,
	document_relative: bool,
	vocab: bool,
	local_context: &'a Merged<'a>,
	defined: &'a mut DefinedTerms,
	remote_contexts: ProcessingStack<N::Iri>,
	options: Options,
) -> ExpandIriResult<N::Iri, N::BlankId, L::Error>
where
	N: VocabularyMut,
	N::Iri: Clone + PartialEq,
	N::BlankId: Clone + PartialEq,
	L: Loader<N::Iri>,
	W: WarningHandler<N>,
{
	match value {
		Nullable::Null => Ok(Term::Null),
		Nullable::Some(ExpandableRef::Keyword(k)) => Ok(Term::Keyword(k)),
		Nullable::Some(ExpandableRef::String(value)) => {
			if is_keyword_like(value) {
				return Ok(Term::Null);
			}

			// If `local_context` is not null, it contains an entry with a key that equals value, and the
			// value of the entry for value in defined is not true, invoke the Create Term Definition
			// algorithm, passing active context, local context, value as term, and defined. This will
			// ensure that a term definition is created for value in active context during Context
			// Processing.
			Box::pin(super::define(
				Environment {
					vocabulary: env.vocabulary,
					loader: env.loader,
					warnings: env.warnings,
				},
				active_context,
				local_context,
				value.into(),
				defined,
				remote_contexts.clone(),
				None,
				false,
				options.with_no_override(),
			))
			.await?;

			if let Some(term_definition) = active_context.get(value) {
				// If active context has a term definition for value, and the associated IRI mapping
				// is a keyword, return that keyword.
				if let Some(value) = term_definition.value() {
					if value.is_keyword() {
						return Ok(value.clone());
					}
				}

				// If vocab is true and the active context has a term definition for value, return the
				// associated IRI mapping.
				if vocab {
					return match term_definition.value() {
						Some(value) => Ok(value.clone()),
						None => Ok(Term::Null),
					};
				}
			}

			if value.find(':').map(|i| i > 0).unwrap_or(false) {
				if let Ok(blank_id) = BlankId::new(value) {
					return Ok(Term::Id(Id::blank(
						env.vocabulary.insert_blank_id(blank_id),
					)));
				}

				if value == "_:" {
					return Ok(Term::Id(Id::Invalid("_:".to_string())));
				}

				if let Ok(compact_iri) = CompactIri::new(value) {
					// If local context is not null, it contains a `prefix` entry, and the value of the
					// prefix entry in defined is not true, invoke the Create Term Definition
					// algorithm, passing active context, local context, prefix as term, and defined.
					// This will ensure that a term definition is created for prefix in active context
					// during Context Processing.
					Box::pin(super::define(
						Environment {
							vocabulary: env.vocabulary,
							loader: env.loader,
							warnings: env.warnings,
						},
						active_context,
						local_context,
						KeyOrKeywordRef::Key(compact_iri.prefix().into()),
						defined,
						remote_contexts,
						None,
						false,
						options.with_no_override(),
					))
					.await?;

					// If active context contains a term definition for prefix having a non-null IRI
					// mapping and the prefix flag of the term definition is true, return the result
					// of concatenating the IRI mapping associated with prefix and suffix.
					let prefix_key = Key::from(compact_iri.prefix().to_string());
					if let Some(term_definition) = active_context.get_normal(&prefix_key) {
						if term_definition.prefix {
							if let Some(mapping) = &term_definition.value {
								let mut result =
									mapping.with(&*env.vocabulary).as_str().to_string();
								result.push_str(compact_iri.suffix());

								return Ok(Term::Id(Id::from_string_in(env.vocabulary, result)));
							}
						}
					}
				}

				if let Ok(iri) = Iri::new(value) {
					return Ok(Term::Id(Id::iri(env.vocabulary.insert(iri))));
				}
			}

			// If vocab is true, and active context has a vocabulary mapping, return the result of
			// concatenating the vocabulary mapping with value.
			if vocab {
				match active_context.vocabulary() {
					Some(Term::Id(mapping)) => {
						let mut result = mapping.with(&*env.vocabulary).as_str().to_string();
						result.push_str(value);

						return Ok(Term::Id(Id::from_string_in(env.vocabulary, result)));
					}
					Some(_) => return Ok(invalid_iri(&mut env, value.to_string())),
					None => (),
				}
			}

			// Otherwise, if document relative is true set value to the result of resolving value
			// against the base IRI from active context. Only the basic algorithm in section 5.2 of
			// [RFC3986] is used; neither Syntax-Based Normalization nor Scheme-Based Normalization
			// are performed. Characters additionally allowed in IRI references are treated in the
			// same way that unreserved characters are treated in URI references, per section 6.5 of
			// [RFC3987].
			if document_relative {
				if let Ok(iri_ref) = IriRef::new(value) {
					if let Some(iri) =
						super::resolve_iri(env.vocabulary, iri_ref, active_context.base_iri())
					{
						return Ok(Term::from(iri));
					}
				}
			}

			// Return value as is.
			Ok(invalid_iri(&mut env, value.to_string()))
		}
	}
}

fn invalid_iri<N, L, W: json_ld_core::warning::Handler<N, Warning>>(
	env: &mut Environment<N, L, W>,
	value: String,
) -> Term<N::Iri, N::BlankId>
where
	N: Vocabulary,
{
	env.warnings
		.handle(env.vocabulary, MalformedIri(value.clone()).into());
	Term::Id(Id::Invalid(value))
}

/// Default values for `document_relative` and `vocab` should be `false` and `true`.
pub fn expand_iri_simple<W, N, L, H>(
	env: &mut Environment<N, L, H>,
	active_context: &Context<N::Iri, N::BlankId>,
	value: Nullable<ExpandableRef>,
	document_relative: bool,
	vocab: bool,
) -> Term<N::Iri, N::BlankId>
where
	N: VocabularyMut,
	N::Iri: Clone,
	N::BlankId: Clone,
	W: From<MalformedIri>,
	H: warning::Handler<N, W>,
{
	match value {
		Nullable::Null => Term::Null,
		Nullable::Some(ExpandableRef::Keyword(k)) => Term::Keyword(k),
		Nullable::Some(ExpandableRef::String(value)) => {
			if is_keyword_like(value) {
				return Term::Null;
			}

			if let Some(term_definition) = active_context.get(value) {
				// If active context has a term definition for value, and the associated IRI mapping
				// is a keyword, return that keyword.
				if let Some(value) = term_definition.value() {
					if value.is_keyword() {
						return value.clone();
					}
				}

				// If vocab is true and the active context has a term definition for value, return the
				// associated IRI mapping.
				if vocab {
					return match term_definition.value() {
						Some(value) => value.clone(),
						None => Term::Null,
					};
				}
			}

			if value.find(':').map(|i| i > 0).unwrap_or(false) {
				if let Ok(blank_id) = BlankId::new(value) {
					return Term::Id(Id::blank(env.vocabulary.insert_blank_id(blank_id)));
				}

				if value == "_:" {
					return Term::Id(Id::Invalid("_:".to_string()));
				}

				if let Ok(compact_iri) = CompactIri::new(value) {
					// If active context contains a term definition for prefix having a non-null IRI
					// mapping and the prefix flag of the term definition is true, return the result
					// of concatenating the IRI mapping associated with prefix and suffix.
					let prefix_key = Key::from(compact_iri.prefix().to_string());
					if let Some(term_definition) = active_context.get_normal(&prefix_key) {
						if term_definition.prefix {
							if let Some(mapping) = &term_definition.value {
								let mut result =
									mapping.with(&*env.vocabulary).as_str().to_string();
								result.push_str(compact_iri.suffix());

								return Term::Id(Id::from_string_in(env.vocabulary, result));
							}
						}
					}
				}

				if let Ok(iri) = Iri::new(value) {
					return Term::Id(Id::iri(env.vocabulary.insert(iri)));
				}
			}

			// If vocab is true, and active context has a vocabulary mapping, return the result of
			// concatenating the vocabulary mapping with value.
			if vocab {
				match active_context.vocabulary() {
					Some(Term::Id(mapping)) => {
						let mut result = mapping.with(&*env.vocabulary).as_str().to_string();
						result.push_str(value);

						return Term::Id(Id::from_string_in(env.vocabulary, result));
					}
					Some(_) => return invalid_iri_simple(env, value.to_string()),
					None => (),
				}
			}

			// Otherwise, if document relative is true set value to the result of resolving value
			// against the base IRI from active context. Only the basic algorithm in section 5.2 of
			// [RFC3986] is used; neither Syntax-Based Normalization nor Scheme-Based Normalization
			// are performed. Characters additionally allowed in IRI references are treated in the
			// same way that unreserved characters are treated in URI references, per section 6.5 of
			// [RFC3987].
			if document_relative {
				if let Ok(iri_ref) = IriRef::new(value) {
					if let Some(iri) =
						super::resolve_iri(env.vocabulary, iri_ref, active_context.base_iri())
					{
						return Term::from(iri);
					}
				}
			}

			// Return value as is.
			invalid_iri_simple(env, value.to_string())
		}
	}
}

fn invalid_iri_simple<W, N, L, H>(
	env: &mut Environment<N, L, H>,
	value: String,
) -> Term<N::Iri, N::BlankId>
where
	N: Vocabulary,
	W: From<MalformedIri>,
	H: warning::Handler<N, W>,
{
	env.warnings
		.handle(env.vocabulary, MalformedIri(value.clone()).into());
	Term::Id(Id::Invalid(value))
}