nostralink 0.2.1

Linked data library for nostr
Documentation
//! Functions to transform nostr Events to RDF (using sophia)

use nostr_sdk::prelude::*;
use sophia::api::prelude::*;
use sophia::api::serializer::{Stringifier, TripleSerializer};
use sophia::inmem::graph::FastGraph;
use sophia::turtle::serializer::{nt::NtSerializer, turtle::TurtleSerializer};
use sophia_jsonld::loader::{ChainLoader, HttpLoader};
use sophia_jsonld::loader_factory::ClosureLoaderFactory;
use sophia_jsonld::parser::{JsonLdParser, JsonLdQuadSource};
use sophia_jsonld::JsonLdOptions;
use sophia_jsonld::JsonLdQuadSource::Quads;

use crate::err::LDError;
use crate::ldbuilder::NostraObject;
use crate::loaders::static_contexts_loader;
use crate::parser::{hybrid_parser, HybridLoader};
use crate::rdfify::event_to_jsonld;

/// Transform a JSON-LD [`Event`] to RDF, in the Turtle (ttl) format
/// (creates a new hybrid LD parser automatically)
pub async fn ttlify_event(event: &Event) -> Result<String, LDError> {
    ttlify_event_with_parser(event, &hybrid_parser()).await
}

/// Transform a JSON-LD [`Event`] to RDF, in the Turtle (ttl) format
pub async fn ttlify_event_with_parser(
    event: &Event,
    parser: &JsonLdParser<
        ClosureLoaderFactory<HybridLoader, impl Fn() -> HybridLoader>,
    >,
) -> Result<String, LDError> {
    let mut g = FastGraph::new();
    let mut turtlizer = TurtleSerializer::new_stringifier();

    let json_ld_str = event_to_jsonld(&event)?;

    match parser.async_parse_str(json_ld_str.as_str()).await {
        Quads(quads) => {
            for quad in quads {
                let triple = quad.into_triple();

                g.insert(triple.s(), triple.p(), triple.o())
                    .map_err(|_| LDError::TripleInsertError)?;
            }

            turtlizer
                .serialize_graph(&g)
                .map_err(|_| LDError::TTLSerializationError)?;

            return Ok(turtlizer.to_string());
        }
        JsonLdQuadSource::Err(err) => {
            eprintln!("{err:?}");

            Err(LDError::JsonLDParseError)
        }
    }
}

/// Serialize the event as ntriples with the default (sophia) hybrid parser
pub async fn ntify_event(event: &Event) -> Result<String, LDError> {
    ntify_event_with_parser(event, &hybrid_parser()).await
}

/// Serialize the event as ntriples
pub async fn ntify_event_with_parser(
    event: &Event,
    parser: &JsonLdParser<
        ClosureLoaderFactory<HybridLoader, impl Fn() -> HybridLoader>,
    >,
) -> Result<String, LDError> {
    let mut nt_stringifier = NtSerializer::new_stringifier();

    let quads = parser
        .async_parse_str(event_to_jsonld(&event)?.as_str())
        .await;

    Ok(nt_stringifier
        .serialize_triples(quads.to_triples())
        .map_err(|_| LDError::NTSerializationError)?
        .to_string())
}

/// Transform a JSON-LD [`Event`] to TTL (synchronous version)
pub fn ttlify_event_sync(event: &Event) -> Result<String, LDError> {
    let Ok(rt) = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
    else {
        return Err(LDError::TokioRuntimeError);
    };

    rt.block_on(ttlify_event(event))
}

/// Transform a JSON-LD [`Event`] to TTL (synchronous version) with an hybrid parser
pub fn ttlify_event_sync_with_parser(
    event: &Event,
    parser: &JsonLdParser<
        ClosureLoaderFactory<HybridLoader, impl Fn() -> HybridLoader>,
    >,
) -> Result<String, LDError> {
    let Ok(rt) = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
    else {
        return Err(LDError::TokioRuntimeError);
    };

    rt.block_on(ttlify_event_with_parser(event, &parser))
}

/// Transform a JSON-LD [`Event`] to n-triples (synchronous version) with an hybrid parser
pub fn ntify_event_sync_with_parser(
    event: &Event,
    parser: &JsonLdParser<
        ClosureLoaderFactory<HybridLoader, impl Fn() -> HybridLoader>,
    >,
) -> Result<String, LDError> {
    let Ok(rt) = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
    else {
        return Err(LDError::TokioRuntimeError);
    };

    rt.block_on(ntify_event_with_parser(event, &parser))
}

/// TTL-ify a [`trait@NostraObject`]
pub fn ttlify_nobject(
    mut obj: Box<dyn NostraObject>,
) -> Result<String, LDError> {
    let options = JsonLdOptions::new().with_document_loader_closure(|| {
        ChainLoader::new(static_contexts_loader(), HttpLoader::default())
    });

    let parser = JsonLdParser::new_with_options(options);
    let mut g = FastGraph::new();
    let mut turtlizer = TurtleSerializer::new_stringifier();

    let Ok(rt) = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
    else {
        return Err(LDError::TokioRuntimeError);
    };

    let value = obj.value().map_err(|_| LDError::JsonLDParseError)?;

    match rt.block_on(parser.async_parse_str(&value.to_string())) {
        Quads(quads) => {
            for quad in quads {
                let triple = quad.into_triple();

                g.insert(triple.s(), triple.p(), triple.o())
                    .map_err(|_| LDError::TripleInsertError)?;
            }

            turtlizer
                .serialize_graph(&g)
                .map_err(|_| LDError::TTLSerializationError)?;

            return Ok(turtlizer.to_string());
        }
        JsonLdQuadSource::Err(_err) => {
            return Err(LDError::JsonLDParseError);
        }
    }
}

#[tokio::test]
async fn test_rdfify_nostr_note() -> Result<(), Box<dyn std::error::Error>> {
    use crate::prelude::*;

    let keys = Keys::generate();
    let event = EventBuilder::text_note("Hello Nostr").sign(&keys).await?;
    let ttl = ttlify_event(&event).await.unwrap();

    let object: Value = serde_json::from_str(event.as_json().as_str()).unwrap();

    println!("{}", serde_json::to_string_pretty(&object).unwrap());
    println!("{ttl}");

    Ok(())
}