nostralink 0.1.9

Linked data library for nostr
Documentation
use super::vars as Vars;
use crate::err::LDError;
use language::Language;
use nostr::{
    event::EventId, types::url::Error as RelayUrlError,
    types::url::ParseError as RelayUrlParseError, PublicKey, RelayUrl,
    Timestamp,
};
use oxigraph::model::{IriParseError, NamedNode};
use oxiri::Iri;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt;
use std::marker::PhantomData;
use std::time::SystemTime;
use url::Url;

// Extracts/converts values from a RdfResultRow
pub trait RowExtractor {
    fn event_id(&self) -> Result<EventId, nostr::event::Error> {
        Err(nostr::event::Error::InvalidId)
    }

    fn public_key(&self) -> Result<PublicKey, nostr::key::Error> {
        Err(nostr::key::Error::InvalidPublicKey)
    }

    fn seen_at(&self) -> Option<Timestamp> {
        None
    }

    fn content_type(&self) -> Option<String> {
        None
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum RdfCellValue {
    Int(i32),
    Float(f64),
    Text(String),
    Node(String),
    Blank(),
}

impl TryInto<i32> for RdfCellValue {
    type Error = ();

    fn try_into(self) -> Result<i32, Self::Error> {
        match self {
            RdfCellValue::Int(value) => Ok(value),
            _ => todo!(),
        }
    }
}

impl TryInto<u64> for RdfCellValue {
    type Error = &'static str;

    fn try_into(self) -> Result<u64, Self::Error> {
        match self {
            RdfCellValue::Int(value) => {
                let v: u64 = value as u64;
                Ok(v)
            }
            _ => todo!(),
        }
    }
}

impl TryInto<PublicKey> for RdfCellValue {
    type Error = nostr::key::Error;

    fn try_into(self) -> Result<PublicKey, Self::Error> {
        match self {
            RdfCellValue::Text(value) => Ok(PublicKey::parse(&value)?),
            _ => Err(Self::Error::InvalidPublicKey),
        }
    }
}

impl fmt::Display for RdfCellValue {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            RdfCellValue::Int(value) => {
                write!(f, "{}", value)
            }
            RdfCellValue::Float(value) => {
                write!(f, "{}", value)
            }
            RdfCellValue::Text(value) => {
                write!(f, "{}", value)
            }
            RdfCellValue::Node(value) => {
                write!(f, "{}", value)
            }
            _ => {
                write!(f, "NA")
            }
        }
    }
}

impl RdfCellValue {
    pub fn to_named_node(&self) -> Result<NamedNode, IriParseError> {
        match self {
            RdfCellValue::Node(value) => {
                Ok(NamedNode::from(Iri::parse(value.to_string())?))
            }
            RdfCellValue::Text(value) => {
                Ok(NamedNode::from(Iri::parse(value.to_string())?))
            }
            _ => todo!(),
        }
    }
    pub fn to_event_id(&self) -> Result<EventId, nostr::event::Error> {
        match self {
            RdfCellValue::Text(value) => Ok(EventId::parse(value)?),
            _ => Err(nostr::event::Error::InvalidId),
        }
    }
    pub fn to_public_key(&self) -> Result<PublicKey, nostr::key::Error> {
        match self {
            RdfCellValue::Text(value) => Ok(PublicKey::parse(value)?),
            _ => Err(nostr::key::Error::InvalidPublicKey),
        }
    }

    pub fn to_language(&self) -> Result<Language, Box<dyn std::error::Error>> {
        match self {
            RdfCellValue::Text(value) => {
                match Language::from_tag(value.as_str()) {
                    Some(lang) => Ok(lang),
                    None => Err(Box::from("No such language")),
                }
            }
            _ => Err(Box::from("Invalid cell")),
        }
    }
}

impl TryInto<i32> for RdfCell {
    type Error = ();

    fn try_into(self) -> Result<i32, Self::Error> {
        Ok(self.value.try_into()?)
    }
}

impl TryInto<u64> for RdfCell {
    type Error = &'static str;

    fn try_into(self) -> Result<u64, Self::Error> {
        Ok(self.value.try_into()?)
    }
}

impl TryInto<String> for &RdfCell {
    type Error = &'static str;

    fn try_into(self) -> Result<String, Self::Error> {
        Ok(format!("{}", self.value))
    }
}

impl TryInto<Timestamp> for &RdfCell {
    type Error = LDError;

    fn try_into(self) -> Result<Timestamp, Self::Error> {
        match self.value {
            RdfCellValue::Int(value) => Ok(Timestamp::from_secs(
                value
                    .try_into()
                    .map_err(|_| LDError::InvalidTimestampError)?,
            )),
            _ => Err(LDError::CellValueError),
        }
    }
}

impl TryInto<PublicKey> for &RdfCell {
    type Error = nostr::key::Error;

    fn try_into(self) -> Result<PublicKey, Self::Error> {
        match &self.value {
            RdfCellValue::Text(value) => Ok(PublicKey::parse(&value)?),
            _ => Err(Self::Error::InvalidPublicKey),
        }
    }
}

impl TryInto<RelayUrl> for &RdfCell {
    type Error = RelayUrlError;

    fn try_into(self) -> Result<RelayUrl, Self::Error> {
        match &self.value {
            RdfCellValue::Text(value) => {
                let vals = value.to_string();
                Ok(RelayUrl::parse(vals.as_str())?)
            }
            _ => Err(RelayUrlError::Url(RelayUrlParseError::EmptyHost)),
        }
    }
}

impl RdfCell {
    pub fn new(name: String, value: RdfCellValue) -> Self {
        Self { name, value }
    }

    pub fn new_text(name: &str, s: String) -> Self {
        Self {
            name: name.to_string(),
            value: RdfCellValue::Text(s),
        }
    }

    pub fn to_string(&self) -> String {
        format!("{}", self.value)
    }

    pub fn to_int(&self) -> Option<i32> {
        match self.value {
            RdfCellValue::Int(value) => Some(value),
            _ => None,
        }
    }

    pub fn to_ts(&self) -> Result<Timestamp, LDError> {
        match self.value {
            RdfCellValue::Int(value) => Ok(Timestamp::from_secs(
                value
                    .try_into()
                    .map_err(|_| LDError::InvalidTimestampError)?,
            )),
            _ => Err(LDError::CellValueError),
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RdfCell {
    pub name: String,
    pub value: RdfCellValue,
}

pub type RdfResultRow = HashMap<String, RdfCell>;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TRdfResultRow<T>(pub HashMap<String, RdfCell>, pub PhantomData<T>)
where
    T: std::clone::Clone;

impl<T: std::clone::Clone> From<TRdfResultRow<T>> for RdfResultRow {
    fn from(row: TRdfResultRow<T>) -> Self {
        row.0
    }
}

pub const CONCAT_SEP: &str = ",";

impl<T: std::clone::Clone> TRdfResultRow<T> {
    // Commonly used SparQL variable names

    pub fn get(&self, key: &str) -> Option<&RdfCell> {
        self.0.get(key)
    }

    pub fn get_string(&self, key: &str) -> Option<String> {
        self.0.get(key).and_then(|cell| Some(cell.to_string()))
    }

    pub fn get_url(&self, key: &str) -> Option<Url> {
        self.0.get(key).and_then(|cell| {
            if let Ok(url) = Url::parse(&cell.to_string()) {
                return Some(url);
            }
            None
        })
    }

    // Get the cell for a key which was built with GROUP_CONCAT
    pub fn get_deconcat(&self, key: &str) -> Option<RdfCell> {
        if let Some(cell) = self.0.get(key) {
            let values: Vec<String> = cell
                .to_string()
                .split(CONCAT_SEP)
                .map(str::to_string)
                .collect();

            if let Some(val) = values.last() {
                return Some(RdfCell {
                    name: cell.name.clone(),
                    value: RdfCellValue::Text(val.to_string()),
                });
            }
        }

        None
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RdfResultSet {
    pub when: SystemTime,
    pub column_headings: Vec<String>,
    pub rows: Vec<RdfResultRow>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TRdfResultSet<T: std::clone::Clone> {
    pub when: SystemTime,
    pub column_headings: Vec<String>,
    pub rows: Vec<TRdfResultRow<T>>,
}

impl<T: std::clone::Clone> Into<TRdfResultRow<T>> for RdfResultRow {
    fn into(self) -> TRdfResultRow<T> {
        TRdfResultRow::<T>(self, PhantomData)
    }
}

impl<T: std::clone::Clone> Into<TRdfResultSet<T>> for RdfResultSet {
    fn into(self) -> TRdfResultSet<T> {
        TRdfResultSet::<T> {
            when: self.when,
            column_headings: self.column_headings,
            rows: self.rows.iter().map(|r| r.clone().into()).collect(),
        }
    }
}

impl<T: std::clone::Clone> TRdfResultSet<T> {
    pub fn count(&self) -> usize {
        self.rows.len()
    }

    /// Returns the first row
    pub fn first(&self) -> Option<TRdfResultRow<T>> {
        if self.count() > 0 {
            return Some(self.rows[0].clone());
        }

        None
    }
}

impl RdfResultSet {
    pub fn count(&self) -> usize {
        self.rows.len()
    }

    /// Returns the first row
    pub fn first(&self) -> Option<RdfResultRow> {
        if self.count() > 0 {
            return Some(self.rows[0].clone());
        }

        None
    }
}

impl<T: std::clone::Clone> RowExtractor for TRdfResultRow<T> {
    fn event_id(&self) -> Result<EventId, nostr::event::Error> {
        match self.get(Vars::EVENT_ID) {
            Some(ev_id) => Ok(ev_id.value.to_event_id()?),
            None => Err(nostr::event::Error::InvalidId),
        }
    }

    fn public_key(&self) -> Result<PublicKey, nostr::key::Error> {
        match self.get(Vars::PUBK) {
            Some(pubk) => Ok(pubk.value.to_public_key()?),
            None => Err(nostr::key::Error::InvalidPublicKey),
        }
    }

    /// Returns the timestamp of the time this event was seen
    fn seen_at(&self) -> Option<Timestamp> {
        match self.get(Vars::EVENT_SEEN_AT) {
            Some(cell) => match cell.try_into() {
                Ok(ts) => Some(ts),
                Err(_e) => None,
            },
            None => None,
        }
    }

    fn content_type(&self) -> Option<String> {
        match self.get(Vars::CONTENT_TYPE) {
            Some(cell) => Some(cell.to_string()),
            None => None,
        }
    }
}