#![allow(dead_code)]
pub mod plan;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use async_trait::async_trait;
use futures::executor::block_on;
use ikigai_core::{
ArgRef, AsyncFnEndpoint, Capability, Clock, Description, Endpoint, EndpointSpace, Error, Exact,
Fallback, FnEndpoint, Invocation, Iri, Kernel, ReprType, Representation, Request, Result,
Space, Time, Verb,
};
use ikigai_script::authority::{Ceiling, CeilingPolicy};
use ikigai_script::{Backend, MemoryBackend, ScriptSpace, SpaceConfig};
pub struct TickingClock(AtomicU64);
impl Default for TickingClock {
fn default() -> Self {
TickingClock(AtomicU64::new(1_789_430_400_000))
}
}
impl Clock for TickingClock {
fn now(&self) -> Time {
Time::from_millis(self.0.fetch_add(1_000, Ordering::SeqCst))
}
}
pub struct Counted<E> {
inner: E,
pub calls: Arc<AtomicUsize>,
}
#[async_trait]
impl<E: Endpoint> Endpoint for Counted<E> {
async fn invoke(&self, inv: &Invocation<'_>) -> Result<Representation> {
self.calls.fetch_add(1, Ordering::SeqCst);
self.inner.invoke(inv).await
}
fn name(&self) -> &str {
self.inner.name()
}
fn describe(&self) -> Description {
self.inner.describe()
}
}
pub const CAP_VAULT: &str = "urn:cap:test:vault";
fn probes() -> EndpointSpace {
let whoami = FnEndpoint::new("whoami", |inv: &Invocation<'_>| {
let text = match inv.capability.scopes() {
None => "root".to_string(),
Some(scopes) => scopes.iter().cloned().collect::<Vec<_>>().join("\n"),
};
Ok(Representation::new(ReprType::new("text/plain"), text.into_bytes()).cacheable())
})
.with_description(
Description::new("whoami")
.verb(Verb::Source)
.verb(Verb::Meta)
.output("text/plain"),
);
let vault = FnEndpoint::new("vault", |_inv: &Invocation<'_>| {
Ok(Representation::new(
ReprType::new("text/plain"),
b"stored".to_vec(),
))
})
.with_description(
Description::new("vault")
.verb(Verb::Sink)
.verb(Verb::Meta)
.requires(CAP_VAULT)
.output("text/plain"),
);
let fallback = AsyncFnEndpoint::new("fallback", |inv| {
Box::pin(async move {
let of = Iri::parse(inv.inline_str("of")?)
.map_err(|e| Error::Endpoint(format!("`of` is not an IRI: {e}")))?;
match inv.source(&of).await {
Ok(found) => Ok(found.cacheable()),
Err(Error::NotFound(_)) => Ok(Representation::new(
ReprType::new("text/plain"),
b"fallback".to_vec(),
)
.cacheable()),
Err(other) => Err(other),
}
})
})
.with_description(
Description::new("fallback")
.verb(Verb::Source)
.verb(Verb::Meta)
.output("text/plain"),
);
EndpointSpace::new()
.bind(Exact::new("urn:test:whoami"), whoami)
.bind(Exact::new("urn:test:vault"), vault)
.bind(Exact::new("urn:test:fallback"), fallback)
}
pub struct Host {
pub kernel: Kernel,
pub backend: Arc<dyn Backend>,
pub space: Arc<ScriptSpace>,
pub evals: Arc<AtomicUsize>,
}
impl Host {
pub fn evals(&self) -> usize {
self.evals.load(Ordering::SeqCst)
}
}
pub fn host_with(backend: Arc<dyn Backend>, ceiling: CeilingPolicy) -> Host {
let evals = Arc::new(AtomicUsize::new(0));
let config = SpaceConfig::new(Arc::clone(&backend), ceiling);
let lisp = EndpointSpace::new().bind(
Exact::new("urn:lisp:eval"),
Counted {
inner: ikigai_lisp::eval(),
calls: Arc::clone(&evals),
},
);
let space = Arc::new(ikigai_script::space(config));
let root = Fallback::new(vec![
Arc::clone(&space) as Arc<dyn Space>,
Arc::new(lisp) as Arc<dyn Space>,
Arc::new(probes()) as Arc<dyn Space>,
]);
let kernel = Kernel::with_meta_renderer(Arc::new(root), Arc::new(ikigai_vocab::TurtleRenderer))
.with_clock(Arc::new(TickingClock::default()));
Host {
kernel,
backend,
space,
evals,
}
}
pub fn host() -> Host {
host_with(
Arc::new(MemoryBackend::new()),
ikigai_script::authority::same_for_all(Ceiling::unbounded()),
)
}
pub fn request(verb: Verb, iri: &str, args: &[(&str, &str)]) -> Request {
args.iter().fold(
Request::new(verb, Iri::parse(iri).expect("a test IRI")),
|request, (name, value)| request.with_arg(*name, ArgRef::Inline(value.as_bytes().to_vec())),
)
}
pub fn call(
kernel: &Kernel,
capability: &Capability,
verb: Verb,
iri: &str,
args: &[(&str, &str)],
) -> std::result::Result<String, Error> {
block_on(kernel.issue(request(verb, iri, args), capability))
.map(|repr| String::from_utf8_lossy(&repr.bytes).into_owned())
}
pub fn ok(kernel: &Kernel, verb: Verb, iri: &str, args: &[(&str, &str)]) -> String {
call(kernel, &Capability::root(), verb, iri, args)
.unwrap_or_else(|e| panic!("{verb:?} {iri} {args:?} failed: {e}"))
}
pub fn publish(kernel: &Kernel, name: &str, source: &str, args: &[(&str, &str)]) -> String {
let mut all = vec![("content", source)];
all.extend_from_slice(args);
ok(kernel, Verb::Sink, &format!("urn:script:{name}"), &all)
.trim()
.to_string()
}
pub fn cap(scopes: &[&str]) -> Capability {
Capability::scoped(scopes.iter().copied())
}
pub fn denied(result: std::result::Result<String, Error>) -> String {
match result {
Err(Error::Denied(message)) => message,
other => panic!("expected a typed Denied, got {other:?}"),
}
}
pub struct CountedDyn {
inner: Arc<dyn Endpoint>,
calls: Arc<AtomicUsize>,
}
#[async_trait]
impl Endpoint for CountedDyn {
async fn invoke(&self, inv: &Invocation<'_>) -> Result<Representation> {
self.calls.fetch_add(1, Ordering::SeqCst);
self.inner.invoke(inv).await
}
fn name(&self) -> &str {
self.inner.name()
}
fn describe(&self) -> Description {
self.inner.describe()
}
}
pub struct CountingStore {
inner: EndpointSpace,
queries: Arc<AtomicUsize>,
}
impl Space for CountingStore {
fn resolve(&self, request: &Request, scope: &ikigai_core::Scope) -> ikigai_core::Resolution {
let resolution = self.inner.resolve(request, scope);
let target = request.target.as_str();
if target.starts_with("urn:iki:store:graph-") && target != "urn:iki:store:graph-update" {
let calls = Arc::clone(&self.queries);
resolution.map_endpoint(|inner| Arc::new(CountedDyn { inner, calls }))
} else {
resolution
}
}
fn entries(&self) -> Option<Vec<ikigai_core::SpaceEntry>> {
self.inner.entries()
}
}
pub struct SparqlHost {
pub kernel: Arc<Kernel>,
pub backend: Arc<dyn Backend>,
pub space: Arc<ScriptSpace>,
queries: Arc<AtomicUsize>,
pub changed: Arc<std::sync::Mutex<Vec<String>>>,
}
impl SparqlHost {
pub fn queries(&self) -> usize {
self.queries.load(Ordering::SeqCst)
}
}
pub fn sparql_host_with(
door: ikigai_script::sparql::SparqlDoor,
ceiling: CeilingPolicy,
) -> SparqlHost {
let backend: Arc<dyn Backend> = Arc::new(MemoryBackend::new());
let kernel_cell: Arc<std::sync::OnceLock<std::sync::Weak<Kernel>>> =
Arc::new(std::sync::OnceLock::new());
let changed = Arc::new(std::sync::Mutex::new(Vec::new()));
let hook: ikigai_script::endpoints::ChangeHook = {
let cell = Arc::clone(&kernel_cell);
let changed = Arc::clone(&changed);
Arc::new(move |name: &str| {
changed.lock().unwrap().push(name.to_string());
if let Some(kernel) = cell.get().and_then(std::sync::Weak::upgrade) {
kernel.cut(ikigai_core::BINDINGS_THREAD);
}
})
};
let config = SpaceConfig::new(Arc::clone(&backend), ceiling)
.sparql(door)
.on_change(hook);
let queries = Arc::new(AtomicUsize::new(0));
let store = CountingStore {
inner: ikigai_store::space(ikigai_store::DurableStore::in_memory().expect("a store")),
queries: Arc::clone(&queries),
};
let lisp = EndpointSpace::new().bind(Exact::new("urn:lisp:eval"), ikigai_lisp::eval());
let space = Arc::new(ikigai_script::space(config));
let root = Fallback::new(vec![
Arc::clone(&space) as Arc<dyn Space>,
Arc::new(store) as Arc<dyn Space>,
Arc::new(lisp) as Arc<dyn Space>,
Arc::new(probes()) as Arc<dyn Space>,
]);
let kernel = Arc::new(
Kernel::with_meta_renderer(Arc::new(root), Arc::new(ikigai_vocab::TurtleRenderer))
.with_clock(Arc::new(TickingClock::default())),
);
kernel_cell
.set(Arc::downgrade(&kernel))
.unwrap_or_else(|_| unreachable!("set once"));
SparqlHost {
kernel,
backend,
space,
queries,
changed,
}
}
pub fn sparql_host() -> SparqlHost {
sparql_host_with(
ikigai_script::sparql::SparqlDoor::store(),
ikigai_script::authority::same_for_all(Ceiling::unbounded()),
)
}
pub fn seed(kernel: &Kernel, update: &str) {
ok(
kernel,
Verb::Sink,
"urn:iki:store:update",
&[("content", update)],
);
}
pub const CAP_GREET: &str = "urn:cap:test:greet";
pub const CAP_NET: &str = "urn:cap:test:net:*";
fn plan_probes() -> EndpointSpace {
let greet = FnEndpoint::new("greet", |inv: &Invocation<'_>| {
let who = inv.inline_str("who").unwrap_or("nobody");
Ok(Representation::new(
ReprType::new("text/plain"),
format!("hello, {who}").into_bytes(),
)
.cacheable())
})
.with_description(
Description::new("greet")
.verb(Verb::Source)
.verb(Verb::Meta)
.requires(CAP_GREET)
.output("text/plain"),
);
let host = FnEndpoint::new("host", |inv: &Invocation<'_>| {
let host = inv.inline_str("host")?;
let exact = format!("urn:cap:test:net:{host}");
if !inv.capability.allows(&exact) {
return Err(Error::Denied(format!("reaching {host} needs `{exact}`")));
}
Ok(Representation::new(
ReprType::new("text/plain"),
format!("reached {host}").into_bytes(),
))
})
.with_description(
Description::new("host")
.verb(Verb::Source)
.verb(Verb::Meta)
.requires(CAP_NET)
.output("text/plain"),
);
EndpointSpace::new()
.bind(Exact::new("urn:test:greet"), greet)
.bind(Exact::new("urn:test:host"), host)
}
pub struct PlanHost {
pub kernel: Arc<Kernel>,
pub backend: Arc<dyn Backend>,
pub space: Arc<ScriptSpace>,
evals: Arc<AtomicUsize>,
}
impl PlanHost {
pub fn evals(&self) -> usize {
self.evals.load(Ordering::SeqCst)
}
}
pub fn plan_host_with(ceiling: CeilingPolicy) -> PlanHost {
let backend: Arc<dyn Backend> = Arc::new(MemoryBackend::new());
let kernel_cell: Arc<std::sync::OnceLock<std::sync::Weak<Kernel>>> =
Arc::new(std::sync::OnceLock::new());
let hook: ikigai_script::endpoints::ChangeHook = {
let cell = Arc::clone(&kernel_cell);
Arc::new(move |_name: &str| {
if let Some(kernel) = cell.get().and_then(std::sync::Weak::upgrade) {
kernel.cut(ikigai_core::BINDINGS_THREAD);
}
})
};
let config = SpaceConfig::new(Arc::clone(&backend), ceiling).on_change(hook);
let evals = Arc::new(AtomicUsize::new(0));
let lisp = EndpointSpace::new().bind(Exact::new("urn:lisp:eval"), ikigai_lisp::eval());
let space = Arc::new(ikigai_script::space(config));
let root = Fallback::new(vec![
Arc::clone(&space) as Arc<dyn Space>,
Arc::new(plan::doors(Arc::clone(&evals))) as Arc<dyn Space>,
Arc::new(lisp) as Arc<dyn Space>,
Arc::new(probes()) as Arc<dyn Space>,
Arc::new(plan_probes()) as Arc<dyn Space>,
]);
let kernel = Arc::new(
Kernel::with_meta_renderer(Arc::new(root), Arc::new(ikigai_vocab::TurtleRenderer))
.with_clock(Arc::new(TickingClock::default())),
);
kernel_cell
.set(Arc::downgrade(&kernel))
.unwrap_or_else(|_| unreachable!("set once"));
PlanHost {
kernel,
backend,
space,
evals,
}
}
pub fn plan_host() -> PlanHost {
plan_host_with(ikigai_script::authority::same_for_all(Ceiling::unbounded()))
}