vld-tauri 0.1.1

Tauri IPC command validation for the vld validation library
Documentation

Crates.io docs.rs License Platform GitHub issues GitHub stars

vld-tauri

Tauri validation for the vld validation library.

Zero dependency on tauri itself — only vld + serde + serde_json. Add tauri separately in your app.

What can be validated

Area Function / Type Description
IPC commands validate(), VldPayload<T> Validate #[tauri::command] arguments
Events validate_event(), VldEvent<T> Validate emit()/listen() payloads
App state validate_state() Validate config/state before app.manage()
Plugin config validate_plugin_config() Validate plugin JSON configuration
Channels validate_channel_message() Validate outgoing Channel::send() data
Raw JSON validate_args() Validate a raw JSON string
Error type VldTauriError Serializable error for command results

Installation

[dependencies]
vld-tauri = "0.1"
vld = "0.1"
tauri = "2"
serde_json = "1"

IPC Commands

Pattern 1 — Explicit Validation (recommended)

use vld_tauri::prelude::*;

vld::schema! {
    #[derive(Debug, Clone, serde::Serialize)]
    pub struct CreateUser {
        pub name: String  => vld::string().min(2).max(50),
        pub email: String => vld::string().email(),
    }
}

#[tauri::command]
fn create_user(payload: serde_json::Value) -> Result<String, VldTauriError> {
    let user = validate::<CreateUser>(payload)?;
    Ok(format!("Created {}", user.name))
}

Pattern 2 — Auto-Validated Payload

VldPayload<T> validates during deserialization. Fields accessible via Deref.

#[tauri::command]
fn create_user(payload: VldPayload<CreateUser>) -> Result<String, VldTauriError> {
    Ok(format!("Created {}", payload.name))
}

Event Payloads

use tauri::Listener;

// Explicit validation
app.listen("user:update", |event| {
    let payload: serde_json::Value = serde_json::from_str(event.payload()).unwrap();
    match validate_event::<UserUpdate>(payload) {
        Ok(update) => println!("Valid: {:?}", update),
        Err(e) => eprintln!("Invalid event: {e}"),
    }
});

// Auto-validation via VldEvent<T>
app.listen("user:update", |event| {
    match serde_json::from_str::<VldEvent<UserUpdate>>(event.payload()) {
        Ok(update) => println!("id={}", update.id),
        Err(e) => eprintln!("Bad payload: {e}"),
    }
});

State Validation at Init

let config_json = std::fs::read_to_string("config.json")?;
let config = validate_state::<AppConfig>(
    serde_json::from_str(&config_json)?
).expect("Invalid config");
app.manage(config);

Plugin Config Validation

let plugin_cfg: serde_json::Value = /* from tauri.conf.json */;
let cfg = validate_plugin_config::<MyPluginConfig>(plugin_cfg)
    .expect("Invalid plugin config");

Channel Messages

#[tauri::command]
fn stream(channel: tauri::ipc::Channel<serde_json::Value>) -> Result<(), VldTauriError> {
    let msg = serde_json::json!({"percent": 50, "status": "working"});
    let validated = validate_channel_message::<Progress>(msg)?;
    channel.send(serde_json::to_value(&validated).unwrap()).unwrap();
    Ok(())
}

Error Format

VldTauriError implements Serialize, so Tauri returns it directly:

{
  "error": "Validation failed",
  "issues": [
    { "path": ".name", "message": "String must be at least 2 characters" },
    { "path": ".email", "message": "Invalid email address" }
  ]
}

Frontend Usage (TypeScript)

import { invoke } from '@tauri-apps/api/core';

interface VldError {
  error: string;
  issues: Array<{ path: string; message: string }>;
}

try {
  const result = await invoke('create_user', {
    payload: { name: 'Alice', email: 'alice@example.com' }
  });
} catch (err) {
  const vldErr = err as VldError;
  for (const issue of vldErr.issues) {
    console.error(`${issue.path}: ${issue.message}`);
  }
}

License

MIT