use crate::{
Context,
ExpandedDocument,
Loader,
ProcessingMode,
compaction::{self, Compact},
context_processing::{self, Process},
expansion,
flattening::ConflictingIndexes,
syntax::ErrorCode,
};
use iri_rs::IriBuf;
use jsonld_core::{ContextLoadError, Document, LoadError, RdfQuads, RemoteContextReference, rdf::RdfDirection};
use rdfx::{
BlankIdBuf,
LocalGenerator,
vocabulary::{self, Vocabulary, VocabularyMut},
};
use std::hash::Hash;
mod remote_document;
#[derive(Clone)]
pub struct Options<I = IriBuf> {
pub base: Option<I>,
pub compact_arrays: bool,
pub compact_to_relative: bool,
pub expand_context: Option<RemoteContextReference<I>>,
pub ordered: bool,
pub processing_mode: ProcessingMode,
pub rdf_direction: Option<RdfDirection>,
pub produce_generalized_rdf: bool,
pub expansion_policy: expansion::Policy,
}
impl<I> Options<I> {
#[must_use]
pub fn unordered(self) -> Self {
Self { ordered: false, ..self }
}
#[must_use]
pub fn with_expand_context(self, context: RemoteContextReference<I>) -> Self {
Self {
expand_context: Some(context),
..self
}
}
pub fn context_processing_options(&self) -> context_processing::Options {
context_processing::Options {
processing_mode: self.processing_mode,
..Default::default()
}
}
pub fn expansion_options(&self) -> expansion::Options {
expansion::Options {
processing_mode: self.processing_mode,
ordered: self.ordered,
policy: self.expansion_policy,
}
}
pub fn compaction_options(&self) -> compaction::Options {
compaction::Options {
processing_mode: self.processing_mode,
compact_to_relative: self.compact_to_relative,
compact_arrays: self.compact_arrays,
ordered: self.ordered,
}
}
}
impl<I> Default for Options<I> {
fn default() -> Self {
Self {
base: None,
compact_arrays: true,
compact_to_relative: true,
expand_context: None,
ordered: false,
processing_mode: ProcessingMode::JsonLd1_1,
rdf_direction: None,
produce_generalized_rdf: false,
expansion_policy: expansion::Policy::default(),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum ExpandError<E = std::convert::Infallible> {
#[error("Expansion failed: {0}")]
Expansion(expansion::Error<E>),
#[error("Context processing failed: {0}")]
ContextProcessing(context_processing::Error<E>),
#[error(transparent)]
Loading(#[from] LoadError<E>),
#[error(transparent)]
ContextLoading(ContextLoadError<E>),
}
impl<E> ExpandError<E> {
pub fn code(&self) -> ErrorCode {
match self {
Self::Expansion(e) => e.code(),
Self::ContextProcessing(e) => e.code(),
Self::Loading(_) => ErrorCode::LoadingDocumentFailed,
Self::ContextLoading(_) => ErrorCode::LoadingRemoteContextFailed,
}
}
}
impl<E> From<expansion::Error<E>> for ExpandError<E> {
fn from(e: expansion::Error<E>) -> Self {
Self::Expansion(e)
}
}
impl<E> From<context_processing::Error<E>> for ExpandError<E> {
fn from(e: context_processing::Error<E>) -> Self {
Self::ContextProcessing(e)
}
}
impl<E> From<ContextLoadError<E>> for ExpandError<E> {
fn from(e: ContextLoadError<E>) -> Self {
Self::ContextLoading(e)
}
}
pub type ExpandResult<I, B, E> = Result<ExpandedDocument<I, B>, ExpandError<E>>;
pub type IntoDocumentResult<I, B, E> = Result<Document<I, B>, ExpandError<E>>;
#[derive(Debug, thiserror::Error)]
pub enum CompactError<E = std::convert::Infallible> {
#[error("Expansion failed: {0}")]
Expand(ExpandError<E>),
#[error("Context processing failed: {0}")]
ContextProcessing(context_processing::Error<E>),
#[error("Compaction failed: {0}")]
Compaction(compaction::Error<E>),
#[error(transparent)]
Loading(#[from] LoadError<E>),
#[error(transparent)]
ContextLoading(ContextLoadError<E>),
}
impl<E> CompactError<E> {
pub fn code(&self) -> ErrorCode {
match self {
Self::Expand(e) => e.code(),
Self::ContextProcessing(e) => e.code(),
Self::Compaction(e) => e.code(),
Self::Loading(_) => ErrorCode::LoadingDocumentFailed,
Self::ContextLoading(_) => ErrorCode::LoadingRemoteContextFailed,
}
}
}
impl<E> From<ExpandError<E>> for CompactError<E> {
fn from(e: ExpandError<E>) -> Self {
Self::Expand(e)
}
}
impl<E> From<context_processing::Error<E>> for CompactError<E> {
fn from(e: context_processing::Error<E>) -> Self {
Self::ContextProcessing(e)
}
}
impl<E> From<compaction::Error<E>> for CompactError<E> {
fn from(e: compaction::Error<E>) -> Self {
Self::Compaction(e)
}
}
impl<E> From<ContextLoadError<E>> for CompactError<E> {
fn from(e: ContextLoadError<E>) -> Self {
Self::ContextLoading(e)
}
}
pub type CompactResult<E> = Result<jstrict::Value, CompactError<E>>;
#[derive(Debug, thiserror::Error)]
pub enum FlattenError<I, B, E = std::convert::Infallible> {
#[error("Expansion failed: {0}")]
Expand(ExpandError<E>),
#[error("Compaction failed: {0}")]
Compact(CompactError<E>),
#[error("Conflicting indexes: {0}")]
ConflictingIndexes(ConflictingIndexes<I, B>),
#[error("Generated id failure: {0}")]
GeneratedId(jsonld_core::id::GeneratedIdError),
#[error(transparent)]
Loading(#[from] LoadError<E>),
#[error(transparent)]
ContextLoading(ContextLoadError<E>),
}
impl<I, B, E> FlattenError<I, B, E> {
pub fn code(&self) -> ErrorCode {
match self {
Self::Expand(e) => e.code(),
Self::Compact(e) => e.code(),
Self::ConflictingIndexes(_) => ErrorCode::ConflictingIndexes,
Self::GeneratedId(_) => ErrorCode::ConflictingIndexes,
Self::Loading(_) => ErrorCode::LoadingDocumentFailed,
Self::ContextLoading(_) => ErrorCode::LoadingRemoteContextFailed,
}
}
}
impl<I, B, E> From<ExpandError<E>> for FlattenError<I, B, E> {
fn from(e: ExpandError<E>) -> Self {
Self::Expand(e)
}
}
impl<I, B, E> From<CompactError<E>> for FlattenError<I, B, E> {
fn from(e: CompactError<E>) -> Self {
Self::Compact(e)
}
}
impl<I, B, E> From<jsonld_core::flattening::NodeMapError<I, B>> for FlattenError<I, B, E> {
fn from(e: jsonld_core::flattening::NodeMapError<I, B>) -> Self {
match e {
jsonld_core::flattening::NodeMapError::Conflicting(c) => Self::ConflictingIndexes(c),
jsonld_core::flattening::NodeMapError::GeneratedId(g) => Self::GeneratedId(g),
}
}
}
impl<I, B, E> From<ContextLoadError<E>> for FlattenError<I, B, E> {
fn from(e: ContextLoadError<E>) -> Self {
Self::ContextLoading(e)
}
}
pub type FlattenResult<I, B, E> = Result<jstrict::Value, FlattenError<I, B, E>>;
#[derive(Debug, thiserror::Error)]
pub enum ToRdfError<E = std::convert::Infallible> {
#[error("Expansion failed: {0}")]
Expand(ExpandError<E>),
}
impl<E> ToRdfError<E> {
pub fn code(&self) -> ErrorCode {
match self {
Self::Expand(e) => e.code(),
}
}
}
impl<E> From<ExpandError<E>> for ToRdfError<E> {
fn from(e: ExpandError<E>) -> Self {
Self::Expand(e)
}
}
pub type ToRdfResult<V, G, E> = Result<ToRdf<V, G>, ToRdfError<E>>;
pub type CompareResult<E> = Result<bool, ExpandError<E>>;
#[derive(Debug, thiserror::Error)]
pub enum JsonLdError<E = std::convert::Infallible> {
#[error(transparent)]
Expand(#[from] ExpandError<E>),
#[error(transparent)]
Compact(#[from] CompactError<E>),
#[error(transparent)]
Flatten(#[from] FlattenError<iri_rs::IriBuf, rdfx::BlankIdBuf, E>),
#[error(transparent)]
ToRdf(#[from] ToRdfError<E>),
}
pub trait JsonLdProcessor<Iri>: Sized {
async fn compare_full<N, L>(
&self,
other: &Self,
vocabulary: &mut N,
loader: &L,
options: Options<Iri>,
warnings: impl context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> CompareResult<L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: Clone + Eq + Hash;
async fn compare_with_using<'a, N, L>(&'a self, other: &'a Self, vocabulary: &'a mut N, loader: &'a L, options: Options<Iri>) -> CompareResult<L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.compare_full(other, vocabulary, loader, options, ()).await
}
async fn compare_with<'a, N, L>(&'a self, other: &'a Self, vocabulary: &'a mut N, loader: &'a L) -> CompareResult<L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.compare_with_using(other, vocabulary, loader, Options::default()).await
}
async fn compare_using<'a, L>(&'a self, other: &'a Self, loader: &'a L, options: Options<Iri>) -> CompareResult<L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
{
self.compare_with_using(other, rdfx::vocabulary::no_vocabulary_mut(), loader, options).await
}
async fn compare<'a, L>(&'a self, other: &'a Self, loader: &'a L) -> CompareResult<L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
{
self.compare_with(other, rdfx::vocabulary::no_vocabulary_mut(), loader).await
}
async fn expand_full<N, L>(
&self,
vocabulary: &mut N,
loader: &L,
options: Options<Iri>,
warnings: impl context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> ExpandResult<Iri, N::BlankId, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: Clone + Eq + Hash;
async fn expand_with_using<'a, N, L>(&'a self, vocabulary: &'a mut N, loader: &'a L, options: Options<Iri>) -> ExpandResult<Iri, N::BlankId, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.expand_full(vocabulary, loader, options, ()).await
}
async fn expand_with<'a, N, L>(&'a self, vocabulary: &'a mut N, loader: &'a L) -> ExpandResult<Iri, N::BlankId, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.expand_with_using(vocabulary, loader, Options::default()).await
}
async fn expand_using<'a, L>(&'a self, loader: &'a L, options: Options<Iri>) -> ExpandResult<Iri, BlankIdBuf, L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
{
self.expand_with_using(vocabulary::no_vocabulary_mut(), loader, options).await
}
async fn expand<'a, L>(&'a self, loader: &'a L) -> ExpandResult<Iri, BlankIdBuf, L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
{
self.expand_with(vocabulary::no_vocabulary_mut(), loader).await
}
async fn into_document_full<'a, N, L>(
self,
vocabulary: &'a mut N,
loader: &'a L,
options: Options<Iri>,
warnings: impl 'a + context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> IntoDocumentResult<Iri, N::BlankId, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: 'a + Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash;
async fn into_document_with_using<'a, N, L>(
self,
vocabulary: &'a mut N,
loader: &'a L,
options: Options<Iri>,
) -> IntoDocumentResult<Iri, N::BlankId, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: 'a + Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.into_document_full(vocabulary, loader, options, ()).await
}
async fn into_document_with<'a, N, L>(self, vocabulary: &'a mut N, loader: &'a L) -> IntoDocumentResult<Iri, N::BlankId, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: 'a + Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.into_document_with_using(vocabulary, loader, Options::default()).await
}
async fn into_document<'a, L>(self, loader: &'a L) -> IntoDocumentResult<Iri, BlankIdBuf, L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: 'a + Clone + Eq + Hash,
L: Loader,
{
self.into_document_with(vocabulary::no_vocabulary_mut(), loader).await
}
async fn compact_full<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
context: RemoteContextReference<Iri>,
loader: &'a L,
options: Options<Iri>,
warnings: impl 'a + context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> CompactResult<L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash;
async fn compact_with_using<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
context: RemoteContextReference<Iri>,
loader: &'a L,
options: Options<Iri>,
) -> CompactResult<L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.compact_full(vocabulary, context, loader, options, ()).await
}
async fn compact_with<'a, N, L>(&'a self, vocabulary: &'a mut N, context: RemoteContextReference<Iri>, loader: &'a L) -> CompactResult<L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.compact_with_using(vocabulary, context, loader, Options::default()).await
}
async fn compact_using<'a, L>(&'a self, context: RemoteContextReference<Iri>, loader: &'a L, options: Options<Iri>) -> CompactResult<L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
{
self.compact_with_using(vocabulary::no_vocabulary_mut(), context, loader, options).await
}
async fn compact<'a, L>(&'a self, context: RemoteContextReference<Iri>, loader: &'a L) -> CompactResult<L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
{
self.compact_with(vocabulary::no_vocabulary_mut(), context, loader).await
}
async fn flatten_full<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
generator: &'a mut impl LocalGenerator,
context: Option<RemoteContextReference<Iri>>,
loader: &'a L,
options: Options<Iri>,
warnings: impl 'a + context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> FlattenResult<Iri, N::BlankId, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash;
async fn flatten_with_using<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
generator: &'a mut impl LocalGenerator,
loader: &'a L,
options: Options<Iri>,
) -> FlattenResult<Iri, N::BlankId, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.flatten_full(vocabulary, generator, None, loader, options, ()).await
}
async fn flatten_with<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
generator: &'a mut impl LocalGenerator,
loader: &'a L,
) -> FlattenResult<Iri, N::BlankId, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.flatten_with_using(vocabulary, generator, loader, Options::default()).await
}
async fn flatten_using<'a, L>(
&'a self,
generator: &'a mut impl LocalGenerator,
loader: &'a L,
options: Options<Iri>,
) -> FlattenResult<Iri, BlankIdBuf, L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
{
self.flatten_with_using(vocabulary::no_vocabulary_mut(), generator, loader, options).await
}
async fn flatten<'a, L>(&'a self, generator: &'a mut impl LocalGenerator, loader: &'a L) -> FlattenResult<Iri, BlankIdBuf, L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
{
self.flatten_with(vocabulary::no_vocabulary_mut(), generator, loader).await
}
async fn to_rdf_full<N, G, L>(
&self,
mut vocabulary: N,
generator: G,
loader: &L,
options: Options<Iri>,
warnings: impl context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> ToRdfResult<N, G, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: Clone + Eq + Hash,
G: LocalGenerator,
{
let rdf_direction = options.rdf_direction;
let produce_generalized_rdf = options.produce_generalized_rdf;
let expanded_input = self
.expand_full(&mut vocabulary, loader, options.unordered(), warnings)
.await
.map_err(ToRdfError::Expand)?;
Ok(ToRdf::new(vocabulary, generator, expanded_input, rdf_direction, produce_generalized_rdf))
}
async fn to_rdf_with_using<N, G, L>(&self, vocabulary: N, generator: G, loader: &L, options: Options<Iri>) -> ToRdfResult<N, G, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: Clone + Eq + Hash,
G: LocalGenerator,
{
self.to_rdf_full(vocabulary, generator, loader, options, ()).await
}
async fn to_rdf_with<N, G, L>(&self, vocabulary: N, generator: G, loader: &L) -> ToRdfResult<N, G, L::Error>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
N::BlankId: Clone + Eq + Hash,
G: LocalGenerator,
{
self.to_rdf_full(vocabulary, generator, loader, Options::default(), ()).await
}
async fn to_rdf_using<G, L>(&self, generator: G, loader: &L, options: Options<Iri>) -> ToRdfResult<(), G, L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
G: LocalGenerator,
{
self.to_rdf_with_using((), generator, loader, options).await
}
async fn to_rdf<G, L>(&self, generator: G, loader: &L) -> ToRdfResult<(), G, L::Error>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
L: Loader,
G: LocalGenerator,
{
self.to_rdf_using(generator, loader, Options::default()).await
}
}
pub struct ToRdf<V: Vocabulary, G> {
vocabulary: V,
generator: G,
doc: ExpandedDocument<V::Iri, V::BlankId>,
rdf_direction: Option<RdfDirection>,
produce_generalized_rdf: bool,
}
impl<V: Vocabulary, G: rdfx::LocalGenerator> ToRdf<V, G> {
fn new(
mut vocabulary: V,
mut generator: G,
mut doc: ExpandedDocument<V::Iri, V::BlankId>,
rdf_direction: Option<RdfDirection>,
produce_generalized_rdf: bool,
) -> Self
where
V: VocabularyMut,
V::Iri: Clone + Eq + Hash,
V::BlankId: Clone + Eq + Hash,
{
let _ = doc.relabel_and_canonicalize_with(&mut vocabulary, &mut generator);
Self {
vocabulary,
generator,
doc,
rdf_direction,
produce_generalized_rdf,
}
}
pub fn quads(&mut self) -> jsonld_core::rdf::Quads<'_, V, G> {
self.doc
.rdf_quads_full(&mut self.vocabulary, &mut self.generator, self.rdf_direction, self.produce_generalized_rdf)
}
#[inline(always)]
pub fn cloned_quads(&mut self) -> jsonld_core::rdf::ClonedQuads<'_, V, G> {
self.quads().cloned()
}
pub fn vocabulary(&self) -> &V {
&self.vocabulary
}
pub fn vocabulary_mut(&mut self) -> &mut V {
&mut self.vocabulary
}
pub fn into_vocabulary(self) -> V {
self.vocabulary
}
pub fn generator(&self) -> &G {
&self.generator
}
pub fn generator_mut(&mut self) -> &mut G {
&mut self.generator
}
pub fn into_generator(self) -> G {
self.generator
}
pub fn document(&self) -> &ExpandedDocument<V::Iri, V::BlankId> {
&self.doc
}
pub fn document_mut(&mut self) -> &mut ExpandedDocument<V::Iri, V::BlankId> {
&mut self.doc
}
pub fn into_document(self) -> ExpandedDocument<V::Iri, V::BlankId> {
self.doc
}
}
async fn compact_expanded_full<'a, T, N, L>(
expanded_input: &'a T,
url: Option<&'a N::Iri>,
vocabulary: &'a mut N,
context: RemoteContextReference<N::Iri>,
loader: &'a L,
options: Options<N::Iri>,
mut warnings: impl context_processing::WarningHandler<N>,
) -> Result<jstrict::Value, CompactError<L::Error>>
where
N: VocabularyMut,
N::Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
T: Compact<N::Iri, N::BlankId>,
L: Loader,
{
let context_base = url.or(options.base.as_ref());
let context = context
.load_context_with(vocabulary, loader)
.await
.map_err(CompactError::ContextLoading)?
.into_document();
let mut active_context = context
.process_full(
vocabulary,
&Context::new(None),
loader,
context_base.cloned(),
options.context_processing_options(),
&mut warnings,
)
.await
.map_err(CompactError::ContextProcessing)?;
match options.base.as_ref() {
Some(base) => active_context.set_base_iri(Some(base.clone())),
None => {
if options.compact_to_relative && active_context.base_iri().is_none() {
active_context.set_base_iri(url.cloned());
}
}
}
expanded_input
.compact_full(vocabulary, active_context.as_ref(), loader, options.compaction_options(), warnings)
.await
.map_err(CompactError::Compaction)
}
#[cfg(test)]
mod tests {
use jsonld_core::{NoLoader, RemoteDocument};
use jstrict::Value;
use rdfx::generator;
use std::future::Future;
use crate::JsonLdProcessor;
async fn assert_send<F: Future + Send>(f: F) -> F::Output {
f.await
}
#[tokio::test]
async fn to_rdf_is_send() {
let generator = generator::Blank::new();
let document = RemoteDocument::new(None, None, Value::Null);
let f = document.to_rdf(generator, &NoLoader);
let _ = assert_send(f).await;
}
}