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

4. Build Clients

Client crates depend on the same API crate with features = ["client"]. Enable the API crate’s fs feature too when using generated file-upload helpers that read parts from disk.

[dependencies]
workspace-api = { path = "../workspace-api", default-features = false, features = ["client"] }

Rust REST Client

The generated REST client turns paths, query values, and request bodies into typed method arguments.

use workspace_api::{CreateTaskRequest, TaskServiceClient};

let mut client = TaskServiceClient::builder("https://workspace.example.com")
    .with_timeout(std::time::Duration::from_secs(10))
    .build()?;

client.set_bearer_token(Some(token));

let tasks = client
    .get_projects_by_project_id_tasks("project-123".to_string())
    .await?;

let created = client
    .post_projects_by_project_id_tasks(
        "project-123".to_string(),
        CreateTaskRequest {
            title: "Write release notes".to_string(),
            assignee_id: None,
        },
    )
    .await?;

The client method names mirror the generated handler names, so compiler errors surface contract changes immediately.

Rust File Client

The generated file client builds multipart requests and download requests.

use workspace_api::{AttachmentServiceClient, AttachmentServiceTasksByTaskIdUploadMultipart};

let mut client = AttachmentServiceClient::builder("https://workspace.example.com")
    .with_timeout(std::time::Duration::from_secs(30))
    .build()?;

client.set_bearer_token(Some(token));

let form = AttachmentServiceTasksByTaskIdUploadMultipart::new()
    .file("notes.pdf", Some("notes.pdf"), Some("application/pdf"))
    .await?;

let uploaded = client
    .tasks_by_task_id_upload("task-123".to_string(), form)
    .await?;

let response = client
    .download_by_attachment_id(uploaded.attachment_id)
    .await?;
let bytes = response.bytes().await?;

For tests or browser-like buffered content, generated multipart builders also provide *_bytes helpers where file parts are declared.

If you use the disk-streaming file(...) helper above, depend on the API crate with both client and fs enabled:

[dependencies]
workspace-api = { path = "../workspace-api", default-features = false, features = ["client", "fs"] }

TypeScript Clients From OpenAPI

If your browser app is TypeScript, generate a fetch client from the OpenAPI files emitted by the server build. Generated clients usually accept one config object per call:

import {
  getProjectsProjectIdTasks,
  postProjectsProjectIdTasks,
} from './generated/task-client';

const baseUrl = 'https://workspace.example.com/api/v1';

const tasks = await getProjectsProjectIdTasks({
  baseUrl,
  headers: { Authorization: `Bearer ${token}` },
  path: { project_id: 'project-123' },
});

const created = await postProjectsProjectIdTasks({
  baseUrl,
  headers: { Authorization: `Bearer ${token}` },
  path: { project_id: 'project-123' },
  body: {
    title: 'Write release notes',
    assignee_id: null,
  },
});

File uploads use FormData or the generator’s multipart object shape:

await postTasksTaskIdUpload({
  baseUrl: 'https://workspace.example.com/api/v1/attachments',
  headers: { Authorization: `Bearer ${token}` },
  path: { task_id: 'task-123' },
  body: { file },
});

WebSocket Notifications

The bidirectional client registers typed notification handlers before connecting:

let mut activity = ActivityServiceClientBuilder::new("wss://workspace.example.com/ws")
    .with_jwt_token(token)
    .build()
    .await?;

activity.on_task_changed(|event| {
    println!("task changed: {}", event.task_id);
});

activity.connect().await?;
activity.subscribe_project("project-123".to_string()).await?;

Use generated clients directly at application edges, then wrap them in small domain-specific adapters if the UI needs a simpler interface.