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
use std::collections::HashSet;
use std::ops::{
	Deref,
	DerefMut
};
use futures::future::{BoxFuture, FutureExt};
use iref::{
	Iri,
	IriBuf
};
use json::JsonValue;
use crate::{
	Error,
	Id,
	Indexed,
	Object,
	ContextMut,
	context::{
		self,
		Loader
	},
	expansion
};

/// Result of the document expansion algorithm.
///
/// It is just an alias for a set of (indexed) objects.
pub type ExpandedDocument<T> = HashSet<Indexed<Object<T>>>;

/// JSON-LD document.
///
/// This trait represent a JSON-LD document that can be expanded into an [`ExpandedDocument`].
/// It is notabily implemented for the [`JsonValue`] type.
pub trait Document<T: Id> {
	/// The type of local contexts that may appear in the document.
	///
	/// This will most likely be [`JsonValue`].
	type LocalContext: context::Local<T>;

	/// Document location, if any.
	fn base_url(&self) -> Option<Iri>;

	/// Expand the document with a custom base URL, initial context, document loader and
	/// expansion options.
	///
	/// If you do not wish to set the base URL and expansion options yourself, the
	/// [`expand`](`Document::expand`) method is more appropriate.
	///
	/// This is an asynchronous method since expanding the context may require loading remote
	/// ressources. It returns a boxed [`Future`](`std::future::Future`) to the result.
	fn expand_with<'a, C: Send + Sync + ContextMut<T>, L: Send + Sync + Loader>(&'a self, base_url: Option<Iri>, context: &'a C, loader: &'a mut L, options: expansion::Options) -> BoxFuture<'a, Result<ExpandedDocument<T>, Error>> where
		C::LocalContext: Send + Sync + From<L::Output> + From<Self::LocalContext>,
		L::Output: Into<Self::LocalContext>,
		T: 'a + Send + Sync;

	/// Expand the document.
	///
	/// Uses the given initial context and the given document loader.
	/// The default implementation is equivalent to [`expand_with`](`Document::expand_with`), but
	/// uses the document [`base_url`](`Document::base_url`), with the default
	/// options.
	///
	/// This is an asynchronous method since expanding the context may require loading remote
	/// ressources. It returns a boxed [`Future`](`std::future::Future`) to the result.
	///
	/// # Example
	/// ```
	/// use std_async::task;
	/// use json_ld::{Document, JsonContext, NoLoader};
	///
	/// // Prepare the initial context.
	/// let context = JsonContext::new();
	///
	/// let doc = json::parse("{
	/// 	\"@context\": {
	/// 		\"name\": \"http://xmlns.com/foaf/0.1/name\",
	/// 		\"knows\": \"http://xmlns.com/foaf/0.1/knows\"
	/// 	},
	/// 	\"@id\": \"http://timothee.haudebourg.net/\",
	/// 	\"name\": \"Timothée Haudebourg\",
	/// 	\"knows\": [
	/// 		{
	/// 			\"name\": \"Amélie Barbe\"
	/// 		}
	/// 	]
	/// }")?;
	/// let expanded_doc = task::block_on(doc.expand(&context, &mut NoLoader))?;
	/// ```
	fn expand<'a, C: Send + Sync + ContextMut<T>, L: Send + Sync + Loader>(&'a self, context: &'a C, loader: &'a mut L) -> BoxFuture<'a, Result<ExpandedDocument<T>, Error>> where
		C::LocalContext: Send + Sync + From<L::Output> + From<Self::LocalContext>,
		L::Output: Into<Self::LocalContext>,
		T: 'a + Send + Sync
	{
		self.expand_with(self.base_url(), context, loader, expansion::Options::default())
	}
}

/// Default JSON document implementation.
impl<T: Id> Document<T> for JsonValue {
	type LocalContext = JsonValue;

	/// Returns `None`.
	///
	/// Use [`RemoteDocument`] to attach a base URL to a `JsonValue` document.
	fn base_url(&self) -> Option<Iri> {
		None
	}

	fn expand_with<'a, C: Send + Sync + ContextMut<T>, L: Send + Sync + Loader>(&'a self, base_url: Option<Iri>, context: &'a C, loader: &'a mut L, options: expansion::Options) -> BoxFuture<'a, Result<ExpandedDocument<T>, Error>> where
		C::LocalContext: Send + Sync + From<L::Output> + From<JsonValue>,
		L::Output: Into<JsonValue>,
		T: 'a + Send + Sync
	{
		expansion::expand(context, self, base_url, loader, options).boxed()
	}
}

/// Remote JSON-LD document.
///
/// Represent a document located at a given base URL.
/// This is the result of loading a document with [`Loader::load`](`crate::Loader::load`).
/// It is a simple wrapper that [`Deref`] to the underlying document while remembering its
/// base URL.
///
/// # Example
/// ```
/// #[macro_use]
/// extern crate static_iref;
///
/// use std_async::task;
/// use json_ld::FsLoader;
///
/// # Prepare the loader.
/// let mut loader = FsLoader::new();
/// loader.mount(iri!("https://w3c.github.io/json-ld-api"), "json-ld-api");
///
/// # Load the remote document.
/// let url = iri!("https://w3c.github.io/json-ld-api/tests/expand-manifest.jsonld");
/// let doc: RemoteDocument<JsonValue> = task::block_on(loader.load(url)).unwrap();
/// ```
#[derive(Clone)]
pub struct RemoteDocument<D = JsonValue> {
	/// The base URL of the document.
	base_url: IriBuf,

	/// The document contents.
	doc: D,
}

impl<D> RemoteDocument<D> {
	/// Create a new remote document from the document contents and base URL.
	pub fn new(doc: D, base_url: Iri) -> RemoteDocument<D> {
		RemoteDocument {
			base_url: base_url.into(),
			doc: doc
		}
	}

	/// Consume the remote document and return the inner document.
	pub fn into_document(self) -> D {
		self.doc
	}

	/// Consume the remote document and return the inner document along with its base URL.
	pub fn into_parts(self) -> (D, IriBuf) {
		(self.doc, self.base_url)
	}
}

/// A Remote document is a document.
impl<T: Id, D: Document<T>> Document<T> for RemoteDocument<D> {
	type LocalContext = D::LocalContext;

	fn base_url(&self) -> Option<Iri> {
		Some(self.base_url.as_iri())
	}

	fn expand_with<'a, C: Send + Sync + ContextMut<T>, L: Send + Sync + Loader>(&'a self, base_url: Option<Iri>, context: &'a C, loader: &'a mut L, options: expansion::Options) -> BoxFuture<'a, Result<ExpandedDocument<T>, Error>> where
		C::LocalContext: Send + Sync + From<L::Output> + From<Self::LocalContext>,
		L::Output: Into<Self::LocalContext>,
		T: 'a + Send + Sync
	{
		self.doc.expand_with(base_url, context, loader, options)
	}
}

impl<D> Deref for RemoteDocument<D> {
	type Target = D;

	fn deref(&self) -> &D {
		&self.doc
	}
}

impl<D> DerefMut for RemoteDocument<D> {
	fn deref_mut(&mut self) -> &mut D {
		&mut self.doc
	}
}