Hello, resource
The smallest useful thing: an endpoint that camel-cases text. It is this book’s “hello world”, and it is a real endpoint — the listing below is included from the crate that compiles it, so it cannot drift from what actually runs.
The implementation
/// Camel-case the whitespace-separated words of the `in` argument.
///
/// The first word is left exactly as given and each subsequent word has its first
/// character upper-cased, so `"resource oriented computing"` becomes
/// `"resourceOrientedComputing"`.
///
/// ⚠ Note what it deliberately does *not* do: it never lower-cases anything. `"Hello
/// WORLD"` becomes `"HelloWORLD"`, because the input's own casing is treated as
/// meaningful rather than as noise to normalize away.
pub fn camel_case_impl(inv: &Invocation<'_>) -> Result<Representation> {
let input = inv.inline_str("in")?;
Ok(Representation::new(text_plain_utf8(), camel(input).into_bytes()).cacheable())
}
/// The pure function under the endpoint, so `camel-title` can reuse it without a
/// second resolution.
pub fn camel(input: &str) -> String {
let mut words = input.split_whitespace();
let mut output = words.next().unwrap_or_default().to_string();
for word in words {
let mut characters = word.chars();
if let Some(first) = characters.next() {
output.extend(first.to_uppercase());
output.push_str(characters.as_str());
}
}
output
}
The endpoint is the first function; the second is the pure string function under it, split out so a later chapter can reuse it. Four things in there are worth slowing down for.
inv.inline_str("in")
Arguments arrive on the Invocation, by name, and an argument may be inline bytes or a
reference to another resource — ArgRef::Inline and ArgRef::Reference. inline_str
says “give me this argument as a UTF-8 string, and fail cleanly if it is not there, is not
text, or is a reference rather than a value.”
That last clause is the one to notice. An endpoint written this way accepts values, and
taking a resource is an opt-in: you match ArgRef::Reference(iri) yourself and resolve it
with inv.source(&iri).await, which makes the endpoint async and threads your answer to
theirs. It is a real change to your code, and it is
exercise 3.
Pipes do not need it, which is worth separating out: | is not a shell feature bolted on,
but what it passes along is the previous resolution’s output, inline — one resolution’s
answer becoming the next one’s argument, by value.
Representation::new(text_plain_utf8(), …)
You return bytes and their type. Always. The type is not decoration: it is what lets a transreptor find a route from what you produced to what somebody asked for.
.cacheable()
You are asserting this is a pure function of its declared inputs. Same arguments, same answer, forever. The kernel may then cache it and hand out the cached representation under a golden thread.
Get this wrong in the optimistic direction and you have a bug that is very hard to see:
stale answers that look plausible. The rule of thumb is the honest one — when in doubt,
do not cache. camel-case is genuinely pure, so it says so.
char::to_uppercase returns an iterator
Not a char. Some characters upper-case to more than one — German ß becomes SS — so the
API cannot pretend otherwise. output.extend(first.to_uppercase()) is the Unicode-correct
form; push would not compile, which is the type system doing you a favour.
Try it
#![allow(unused)]
fn main() {
extern crate hello_camel;
extern crate ikigai_core;
use hello_camel::camel_case;
use ikigai_core::Endpoint;
// Every endpoint knows its own name.
assert_eq!(camel_case().describe().id, "camel-case");
}
And the endpoint itself, resolved by the kernel in this page:
HelloWORLD [computed]
That is an endpoint. What you get for having resolved a name rather than called a function is the next chapter, What resolution buys you.