Skip to main content

Error Handling

Error types and handling patterns across all three language implementations.

Error Hierarchy

All library operations use a unified error system with specific error types for different failure modes.

SigError

Base error type for all library operations.

pub enum SigError {
Config(String),
Provider(String),
Model(String),
Io(#[from] std::io::Error),
Serialization(#[from] serde_json::Error),
InvalidInput(String),
}

pub type Result<T> = std::result::Result<T, SigError>;

Usage:

use odin_prompt_toolkit::{sign_text, SigError};

match sign_text(text, &provider, version, None).await {
Ok(result) => println!("Success: {}", result.signature_string),
Err(SigError::Provider(msg)) => eprintln!("Provider error: {}", msg),
Err(SigError::InvalidInput(msg)) => eprintln!("Invalid input: {}", msg),
Err(e) => eprintln!("Other error: {}", e),
}

Error Types

ConfigError

Raised when LSH configuration parameters are invalid or incompatible.

Common Causes:

  • Invalid families, bits, or bands values
  • Configuration mismatch (e.g., bands don't divide bits evenly)
  • Negative or zero values

Examples:

// Invalid configuration
let config = LshConfig {
families: 0, // Must be > 0
bits: 256,
bands: 16,
};
// Returns: Err(SigError::Config("families must be > 0"))

ProviderError

Raised when embedding provider fails to generate embeddings.

Common Causes:

  • OpenAI API errors (authentication, rate limits, network)
  • ONNX runtime errors
  • Model file not found
  • Network timeouts

Examples:

// Missing API key
let provider = OpenAIProvider::new("".to_string(), None, None, None);
// API call fails: Err(SigError::Provider("Authentication failed"))

// Network error
// Returns: Err(SigError::Provider("Network timeout after 30s"))

ModelError

Raised when ONNX model loading or inference fails.

Common Causes:

  • Model file corrupted or missing
  • ONNX runtime initialization failure
  • Incompatible model format
  • Out of memory during inference

Examples:

// Model file not found
let cache = ModelCache::new()?;
let provider = OnnxProvider::new(&cache, Some("nonexistent/model".to_string()), None, 0, 0).await;
// Returns: Err(SigError::Provider("Failed to download model: ..."))

InvalidInputError

Raised when input data doesn't meet requirements.

Common Causes:

  • Empty text input
  • Malformed signature string
  • Invalid hex characters in signature
  • Wrong embedding dimensions
  • Zero-length or NaN vectors

Examples:

use odin_prompt_toolkit::{parse_signature_string, SigError};

// Invalid signature format
match parse_signature_string("invalid") {
Err(SigError::InvalidInput(msg)) => {
println!("Invalid: {}", msg);
// "Invalid signature prefix: invalid"
},
_ => {}
}

// Non-hex characters
match parse_signature_string("0din-v1:xyz123") {
Err(SigError::InvalidInput(msg)) => {
println!("Invalid: {}", msg);
// "Invalid hex signature: xyz123"
},
_ => {}
}

Error Handling Patterns

Retry with Exponential Backoff

For transient provider errors (rate limits, network issues):

use tokio::time::{sleep, Duration};

async fn sign_with_retry(
text: &str,
provider: &dyn EmbeddingProvider,
max_retries: u32,
) -> Result<SignatureResult, SigError> {
let mut delay_ms = 1000;

for attempt in 0..max_retries {
match sign_text(text, provider, SignatureVersion::Latest, None).await {
Ok(result) => return Ok(result),
Err(SigError::Provider(msg)) if attempt < max_retries - 1 => {
eprintln!("Retry {}/{}: {}", attempt + 1, max_retries, msg);
sleep(Duration::from_millis(delay_ms)).await;
delay_ms *= 2; // Exponential backoff
},
Err(e) => return Err(e),
}
}

Err(SigError::Provider("Max retries exceeded".to_string()))
}

Graceful Degradation

Fall back to alternative behavior on errors:

from odin_prompt_toolkit import sign_text, ProviderError, ModelError
from odin_prompt_toolkit.providers import OpenAIProvider, OnnxProvider, ModelCache

async def sign_with_fallback(text: str):
"""Try OpenAI, fall back to ONNX on error."""
try:
# Try OpenAI first (higher quality)
provider = OpenAIProvider(api_key=os.getenv("OPENAI_API_KEY"))
return await sign_text(text, provider=provider, version=SignatureVersion.V0)
except (ProviderError, ModelError) as e:
print(f"OpenAI failed ({e}), falling back to ONNX...")

# Fall back to ONNX (local, no API key needed)
cache = ModelCache()
provider = await OnnxProvider.new(cache)
return await sign_text(text, provider=provider, version=SignatureVersion.V1)

Input Validation

Validate inputs before expensive operations:

import { parseSignatureString, InvalidInputError } from '@0din/prompt-toolkit';

function validateAndCompare(sig1: string, sig2: string): number | null {
try {
const parsed1 = parseSignatureString(sig1);
const parsed2 = parseSignatureString(sig2);

// Check version compatibility
if (parsed1.version !== parsed2.version) {
console.error('Cannot compare signatures from different versions');
return null;
}

// Proceed with comparison
return hammingDistanceHex(parsed1.signature, parsed2.signature);

} catch (error) {
if (error instanceof InvalidInputError) {
console.error(`Validation failed: ${error.message}`);
return null;
}
throw error;
}
}

Error Context

Adding Context

Wrap errors with additional context:

use anyhow::{Context, Result};

async fn process_batch(texts: Vec<String>) -> Result<Vec<SignatureResult>> {
let mut results = Vec::new();

for (i, text) in texts.iter().enumerate() {
let result = sign_text(text, &provider, version, None)
.await
.with_context(|| format!("Failed to sign text at index {}", i))?;
results.push(result);
}

Ok(results)
}

Best Practices

1. Catch Specific Errors First

Always catch specific error types before catching the base SigError:

try:
result = await sign_text(text, provider=provider)
except InvalidInputError:
# Handle input validation errors
pass
except ProviderError:
# Handle provider errors (maybe retry)
pass
except SigError:
# Handle all other errors
pass

2. Use Type Guards (TypeScript)

if (error instanceof ProviderError) {
// TypeScript knows error.message is available
console.error(`Provider error: ${error.message}`);
}

3. Don't Swallow Errors

Always log or handle errors appropriately:

# ❌ BAD: Silently ignoring errors
try:
result = await sign_text(text, provider=provider)
except:
pass # Error is lost!

# ✅ GOOD: Log errors
try:
result = await sign_text(text, provider=provider)
except SigError as e:
logger.error(f"Signature generation failed: {e}")
raise # Re-raise if caller should handle it

4. Clean Up Resources

Always close providers in finally blocks or use context managers:

provider = await OnnxProvider.new(cache)
try:
result = await sign_text(text, provider=provider)
finally:
await provider.close() # Always clean up

# Or use async context manager (if implemented)
async with OnnxProvider.new(cache) as provider:
result = await sign_text(text, provider=provider)

See Also