use std::sync::Arc;
use ikigai_core::{
builtins, ArgSpec, Description, EndpointSpace, Error, Exact, FnEndpoint, Invocation, Kernel,
MetaRenderer, ReprType, Representation, Result, UriTemplate, Verb,
};
use ikigai_vocab::TurtleRenderer;
struct CliRenderer;
impl MetaRenderer for CliRenderer {
fn render(&self, description: &Description, target: &ReprType) -> Result<Representation> {
if target.media_type == "application/json" {
let json = serde_json::to_vec(description)
.map_err(|e| Error::Endpoint(format!("describe as json: {e}")))?;
return Ok(Representation::new(ReprType::new("application/json"), json));
}
TurtleRenderer.render(description, target)
}
}
fn wrap_impl(inv: &Invocation<'_>) -> Result<Representation> {
let text = inv.inline_str("text")?;
Ok(Representation::new(
ReprType::new("text/plain").with_param("charset", "utf-8"),
format!("[{text}]").into_bytes(),
)
.cacheable())
}
fn wrap() -> FnEndpoint {
FnEndpoint::new("wrap", wrap_impl).with_description(
Description::new("wrap")
.title("Wrap")
.summary("Surrounds the `text` argument with square brackets.")
.verb(Verb::Source)
.verb(Verb::Meta)
.input(ArgSpec::new("text").summary("the text to wrap"))
.output("text/plain;charset=utf-8"),
)
}
fn split_impl(inv: &Invocation<'_>) -> Result<Representation> {
let input = inv.inline_str("in")?;
let items = input
.split(',')
.map(str::trim)
.collect::<Vec<_>>()
.join("\n");
Ok(Representation::new(
ReprType::new("text/plain").with_param("charset", "utf-8"),
items.into_bytes(),
)
.cacheable())
}
fn split() -> FnEndpoint {
FnEndpoint::new("split", split_impl).with_description(
Description::new("split")
.title("Split")
.summary("Splits the `in` argument on commas into newline-separated items.")
.verb(Verb::Source)
.verb(Verb::Meta)
.input(ArgSpec::new("in").summary("comma-separated items"))
.output("text/plain;charset=utf-8"),
)
}
fn greet_impl(inv: &Invocation<'_>) -> Result<Representation> {
let greeting = inv.inline_str("greeting")?;
let name = inv.inline_str("name")?;
Ok(Representation::new(
ReprType::new("text/plain").with_param("charset", "utf-8"),
format!("{greeting}, {name}").into_bytes(),
)
.cacheable())
}
fn greet() -> FnEndpoint {
FnEndpoint::new("greet", greet_impl).with_description(
Description::new("greet")
.title("Greet")
.summary("Combines `greeting` and `name` into a greeting.")
.verb(Verb::Source)
.verb(Verb::Meta)
.input(ArgSpec::new("greeting").summary("the salutation, e.g. Hello"))
.input(ArgSpec::new("name").summary("who to greet"))
.output("text/plain;charset=utf-8"),
)
}
fn page_impl(_inv: &Invocation<'_>) -> Result<Representation> {
let body = "ikigai compose demo — one pull, recursively assembled\n\n \
toUpper : $a{urn:fn:toUpper?in=\"resource oriented computing\"}\n \
wrap : $a{urn:demo:wrap?text=hello}\n \
greet : $a{urn:demo:greet?greeting=Hi&name=World}\n\n\
literal marker (escaped, not expanded): $$a{urn:fn:toUpper?in=x}\n";
Ok(Representation::new(
ReprType::new("text/plain").with_param("charset", "utf-8"),
body.as_bytes().to_vec(),
)
.cacheable())
}
fn page() -> FnEndpoint {
FnEndpoint::new("page", page_impl).with_description(
Description::new("page")
.title("Demo page")
.summary("A compose shape: a text template with `$a{<iri>}` transclusion markers.")
.verb(Verb::Source)
.verb(Verb::Meta)
.output("text/plain;charset=utf-8"),
)
}
pub fn kernel() -> Kernel {
let echo = UriTemplate::parse("urn:demo:echo/{message}").expect("valid template");
let space = EndpointSpace::new()
.bind(Exact::new("urn:fn:toUpper"), builtins::to_upper())
.bind(Exact::new("urn:fn:reverseList"), builtins::reverse_list())
.bind(Exact::new("urn:demo:wrap"), wrap())
.bind(Exact::new("urn:demo:split"), split())
.bind(Exact::new("urn:demo:greet"), greet())
.bind(Exact::new("urn:fn:compose"), builtins::compose())
.bind(Exact::new("urn:data:page"), page())
.bind(echo, builtins::echo());
Kernel::with_meta_renderer(Arc::new(space), Arc::new(CliRenderer))
}
#[cfg(test)]
mod tests {
use super::*;
use futures::executor::block_on;
use ikigai_core::{ArgRef, Capability, Iri, Request};
#[test]
fn wrap_routes_the_text_argument() {
let kernel = kernel();
let request = Request::new(Verb::Source, Iri::parse("urn:demo:wrap").unwrap())
.with_arg("text", ArgRef::Inline(b"hi".to_vec()));
let representation = block_on(kernel.issue(request, &Capability::root())).unwrap();
assert_eq!(representation.bytes, b"[hi]");
}
#[test]
fn split_makes_a_newline_list_for_map() {
let kernel = kernel();
let request = Request::new(Verb::Source, Iri::parse("urn:demo:split").unwrap())
.with_arg("in", ArgRef::Inline(b"a, b ,c".to_vec()));
let representation = block_on(kernel.issue(request, &Capability::root())).unwrap();
assert_eq!(representation.bytes, b"a\nb\nc");
}
#[test]
fn greet_combines_two_arguments() {
let kernel = kernel();
let request = Request::new(Verb::Source, Iri::parse("urn:demo:greet").unwrap())
.with_arg("greeting", ArgRef::Inline(b"Hello".to_vec()))
.with_arg("name", ArgRef::Inline(b"World".to_vec()));
let representation = block_on(kernel.issue(request, &Capability::root())).unwrap();
assert_eq!(representation.bytes, b"Hello, World");
}
}