ftl-sdk-macros

Crates.ioftl-sdk-macros
lib.rsftl-sdk-macros
version0.14.0
created_at2025-07-13 21:53:18.197493+00
updated_at2025-08-25 06:23:06.745861+00
descriptionProcedural macros for the FTL SDK
homepage
repositoryhttps://github.com/fastertools/ftl
max_upload_size
id1750852
size17,571
Ian McDonald (bowlofarugula)

documentation

README

FTL SDK Rust Macros

Procedural macros for reducing boilerplate in FTL tool components written in Rust.

Overview

This crate provides the tools! macro for defining multiple tool handler functions with minimal boilerplate. The macro:

  • Supports multiple tools in a single component
  • Automatically derives JSON schemas from your input types (requires JsonSchema derive)
  • Supports both synchronous and asynchronous functions
  • Generates the complete HTTP handler with routing
  • Handles all the boilerplate for you

Usage

Basic Tool Handler

The tools! macro simplifies creating tool handlers:

use ftl_sdk::{tools, text, ToolResponse};
use serde::Deserialize;
use schemars::JsonSchema;

#[derive(Deserialize, JsonSchema)]
struct EchoRequest {
    message: String,
}

#[derive(Deserialize, JsonSchema)]
struct ReverseRequest {
    text: String,
}

tools! {
    /// Echoes back the input message
    fn echo(req: EchoRequest) -> ToolResponse {
        text!("Echo: {}", req.message)
    }
    
    /// Reverses the input text
    fn reverse(req: ReverseRequest) -> ToolResponse {
        let reversed: String = req.text.chars().rev().collect();
        text!("{}", reversed)
    }
}

Complete Example

Here's a more complete example showing how the macro works with multiple tools:

use ftl_sdk::{tools, text, error};
use serde::Deserialize;
use schemars::JsonSchema;

#[derive(Deserialize, JsonSchema)]
struct CalculatorRequest {
    a: f64,
    b: f64,
}

#[derive(Deserialize, JsonSchema)]
struct ConvertRequest {
    value: f64,
    from_unit: String,
    to_unit: String,
}

tools! {
    /// Add two numbers
    fn add(req: CalculatorRequest) -> ToolResponse {
        text!("{} + {} = {}", req.a, req.b, req.a + req.b)
    }
    
    /// Subtract two numbers
    fn subtract(req: CalculatorRequest) -> ToolResponse {
        text!("{} - {} = {}", req.a, req.b, req.a - req.b)
    }
    
    /// Divide two numbers
    fn divide(req: CalculatorRequest) -> ToolResponse {
        if req.b == 0.0 {
            return error!("Cannot divide by zero");
        }
        text!("{} / {} = {}", req.a, req.b, req.a / req.b)
    }
    
    /// Convert between units
    fn convert(req: ConvertRequest) -> ToolResponse {
        // Conversion logic here
        text!("Converted {} {} to {}", req.value, req.from_unit, req.to_unit)
    }
}

// The macro generates the HTTP handler with routing automatically!

Generated Code

The tools! macro generates:

  • A handle_tool_component async function that returns metadata for all tools on GET
  • Path-based routing (e.g., /add, /subtract) for POST requests
  • Automatic JSON deserialization of request bodies
  • Error handling with proper HTTP status codes
  • Correct Content-Type headers
  • Full Spin HTTP component integration

Important: Input Validation

Just like with the TypeScript SDK, tools should NOT validate inputs themselves. The FTL gateway handles all input validation against your tool's JSON Schema before invoking your handler. This means:

  • Your handler can assume all inputs match the schema
  • Focus on business logic, not validation
  • The gateway enforces all JSON Schema constraints

Response Macros

The SDK provides convenient macros for creating responses:

tools! {
    fn demo_responses(input: DemoInput) -> ToolResponse {
        // Simple text response
        text!("Hello, {}!", input.name)
        
        // Error response
        error!("Something went wrong: {}", reason)
        
        // Structured response with data
        let data = json!({ "result": 42, "status": "complete" });
        structured!(data, "Calculation finished")
    }
}

Async Support

The tools! macro fully supports async functions:

tools! {
    /// Async tool example
    async fn fetch_data(req: FetchRequest) -> ToolResponse {
        // Async operations work seamlessly
        let result = some_async_operation().await?;
        text!("Fetched: {}", result)
    }
}

License

Apache-2.0

Release Process

See RELEASE_ORDER.md for release instructions.

Selective CI

This package now uses selective CI - only relevant tests run for each package.

Commit count: 496

cargo fmt