Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 resourceArgRef::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.