apiplant-function 0.1.0

Ergonomic helpers and the `function!` macro for writing apiplant functions
Documentation

apiplant-function

Write an apiplant function without the ABI boilerplate.

Instead of hand-implementing the [apiplant_abi] traits, exporting a root module, and shuttling JSON in and out by hand, you write one ordinary typed function and call [function!]:

use apiplant_function::prelude::*;

#[derive(serde::Deserialize, Default)]
struct Config { #[serde(default)] greeting: String }

#[derive(serde::Deserialize, JsonSchema)]
struct Input { name: String }

#[derive(serde::Serialize, JsonSchema)]
struct Output { message: String }

fn greet(ctx: &Context<Config>, input: Input) -> Result<Output, String> {
    Ok(Output { message: format!("{}, {}!", ctx.config().greeting, input.name) })
}

apiplant_function::function! {
    name: "greet",
    description: "Greets a person",
    method: Post,
    visibility: Public,
    handler: greet,
}
# fn main() {}

The macro generates the root module, reads/writes JSON, resolves typed config and input, and turns your Err(_) into a 400. Types are inferred from the handler's signature — you never name them twice. With the default schema feature the input and output types must also derive JsonSchema so the endpoint shows up typed in the OpenAPI docs.

Use [functions!] to export several from one library — each with its own name, manifest and handler.

Functions as lifecycle hooks

A function can also be attached to a resource's lifecycle from models/<name>.toml, in which case [Context::hook] carries the operation's context — the row created or fetched, the rows a list returned, the request URL, the caller's auth status — and the [reply] helpers say what should happen next. One function per event, so a handler never has to work out why it was called:

# use apiplant_function::prelude::*;
fn post_after_create(ctx: &Context<()>, row: serde_json::Value) -> Result<serde_json::Value, String> {
    let actor = ctx.hook().and_then(|hook| hook.principal_id.clone());
    ctx.info(&format!("post {} created by {actor:?}", row["id"]));
    Ok(reply::proceed())
}

apiplant_function::functions! {
    {
        name: "post_after_create",
        description: "Records a newly created post",
        method: Post,
        visibility: Private,
        handler: post_after_create,
    },
}
# fn main() {}