use std::{pin::pin, sync::Arc};
use futures::Stream;
use taganak_core::prelude::{Term, Triple};
use async_stream::stream;
use taganak_framework::prelude::StreamExt;
use taganak_orm::re::TripleError;
use tracing::error;
#[derive(Debug, Clone, thiserror::Error)]
pub enum ExecError {
#[error("Invalid arguments")]
ArgError,
#[error("Operation failed")]
OpError,
}
impl ExecError {
pub fn exit_code(&self) -> i32 {
match self {
Self::ArgError => 1,
Self::OpError => 2,
}
}
}
pub trait Provider {
fn name() -> &'static str;
fn version() -> &'static str;
fn modules() -> &'static [&'static str] {
&[]
}
fn subject() -> Term {
format!(
"<http://tinipon.taganak.net/reg/providers/{}>",
Self::name()
)
.try_into()
.expect("default impl should be ok")
}
async fn describe(
recv_subject: Arc<Term>,
) -> impl Stream<Item = Result<Arc<Triple>, TripleError>> {
let subject = Arc::new(Self::subject());
let a: Arc<Term> = Arc::new(
"<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"
.try_into()
.expect("static IRI"),
);
let by: Arc<Term> = Arc::new(
"<http://tinipon.taganak.net/vocab#provider>"
.try_into()
.expect("static IRI"),
);
let module_pred: Arc<Term> = Arc::new(
"<http://tinipon.taganak.net/vocab#module>"
.try_into()
.expect("static IRI"),
);
let provider: Arc<Term> = Arc::new(
"<http://tinipon.taganak.net/vocab#Provider>"
.try_into()
.expect("static IRI"),
);
let result: Arc<Term> = Arc::new(
"<http://tinipon.taganak.net/vocab#ProviderResult>"
.try_into()
.expect("static IRI"),
);
stream! {
yield Triple::new(recv_subject.clone(), a.clone(), result.clone());
yield Triple::new(recv_subject.clone(), by.clone(), subject.clone());
yield Triple::new(subject.clone(), a.clone(), provider.clone());
for module in Self::modules() {
yield Triple::new(subject.clone(), module_pred.clone(), Arc::new(format!("<http://tinipon.taganak.net/reg/modules/{}#>", module).try_into().expect("should be valid")));
}
}
}
async fn run(recv_subject: Arc<Term>) -> impl Stream<Item = Result<Arc<Triple>, TripleError>>;
async fn exec() -> Result<(), ExecError> {
let mut args = std::env::args();
if args.len() != 3 {
return Err(ExecError::ArgError);
}
let recv_subject = args.next_back().expect("we just checked");
let op = args.next_back().expect("we jsut checked");
let recv_subject: Arc<Term> = match recv_subject.try_into() {
Ok(term) => Arc::new(term),
Err(e) => {
return Err(ExecError::ArgError);
}
};
match op.as_str() {
"describe" => {
let mut describe = pin!(Self::describe(recv_subject.clone()).await);
while let Some(triple) = describe.next().await {
if let Ok(triple) = triple {
println!("{}", *triple);
} else {
error!("Failed to describe: {:?}", triple);
return Err(ExecError::OpError);
}
}
}
"run" => {
let mut run = pin!(Self::run(recv_subject.clone()).await);
while let Some(triple) = run.next().await {
if let Ok(triple) = triple {
println!("{}", *triple);
} else {
error!("Failed to run: {:?}", triple);
return Err(ExecError::OpError);
}
}
}
other => {
error!("Invalid operation: {other}");
return Err(ExecError::ArgError);
}
}
Ok(())
}
}
#[macro_export]
macro_rules! tinipon_main {
($Provider:ty) => {
fn main() {
let res = tinipon::prelude::tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(<$Provider>::exec());
if let Err(e) = res {
std::process::exit(e.exit_code());
}
}
};
}
#[macro_export]
macro_rules! tinipon_defaults {
() => {
fn name() -> &'static str {
env!("CARGO_BIN_NAME")
}
fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
};
}