Skip to content

Add Error Handling & Debugging Guide #2

@rz1989s

Description

@rz1989s

Problem Statement

Context7 Benchmark Impact: Q1 scored 82/100, Q6 scored 87/100

Q1 Feedback (82/100):

"Absence of explicit error handling patterns during deserialization, no discussion of version compatibility, minimal coverage of performance implications for deeply nested structures"

Q6 Feedback (87/100):

"Absence of explicit error handling in most examples, minimal coverage of debugging techniques, lack of concrete examples showing how to handle deserialization failures"

Critical Gaps:

  • No error handling patterns documentation
  • No deserialization failure examples
  • No debugging techniques guide
  • Limited troubleshooting content

Proposed Solution

Add comprehensive error handling and debugging documentation.

1. New Page: docs/guide/error-handling.md

---
title: Error Handling
description: Patterns for handling errors in LUMOS-based Solana programs
---

# Error Handling

## Rust Error Handling

### Deserialization Errors

Handle Borsh deserialization failures:

```rust
use anchor_lang::prelude::*;
use borsh::BorshDeserialize;

pub fn process_account(data: &[u8]) -> Result<PlayerAccount> {
    PlayerAccount::try_from_slice(data)
        .map_err(|e| {
            msg!("Failed to deserialize PlayerAccount: {}", e);
            error!(ErrorCode::DeserializationFailed)
        })
}

#[error_code]
pub enum ErrorCode {
    #[msg("Failed to deserialize account data")]
    DeserializationFailed,
}

Account Discriminator Validation

Validate account type before deserializing:

use anchor_lang::Discriminator;

pub fn validate_and_deserialize(data: &[u8]) -> Result<PlayerAccount> {
    // Check discriminator (first 8 bytes)
    let disc = &data[..8];
    
    if disc != PlayerAccount::DISCRIMINATOR {
        return Err(error!(ErrorCode::InvalidAccountType));
    }
    
    PlayerAccount::try_from_slice(&data[8..])
        .map_err(|_| error!(ErrorCode::DeserializationFailed))
}

Field Validation

Validate field values after deserialization:

pub fn update_score(ctx: Context<UpdateScore>, amount: u64) -> Result<()> {
    let player = &mut ctx.accounts.player;
    
    // Validate before operation
    require!(
        amount > 0,
        ErrorCode::InvalidAmount
    );
    
    // Check for overflow
    let new_score = player.score
        .checked_add(amount)
        .ok_or(ErrorCode::ScoreOverflow)?;
    
    require!(
        new_score <= 1_000_000,
        ErrorCode::ScoreExceedsMaximum
    );
    
    player.score = new_score;
    Ok(())
}

#[error_code]
pub enum ErrorCode {
    #[msg("Amount must be greater than zero")]
    InvalidAmount,
    #[msg("Score overflow detected")]
    ScoreOverflow,
    #[msg("Score exceeds maximum allowed value")]
    ScoreExceedsMaximum,
}

Type Validation

Ensure correct types during deserialization:

impl PlayerAccount {
    pub fn validate(&self) -> Result<()> {
        // Validate PublicKey is not default
        require!(
            self.wallet != Pubkey::default(),
            ErrorCode::InvalidPublicKey
        );
        
        // Validate String is valid UTF-8
        require!(
            self.name.is_ascii(),
            ErrorCode::InvalidName
        );
        
        // Validate Vec is within size limits
        require!(
            self.inventory.len() <= 100,
            ErrorCode::InventoryTooLarge
        );
        
        Ok(())
    }
}

TypeScript Error Handling

Safe Deserialization

Wrap deserialization in try-catch:

import * as borsh from '@coral-xyz/borsh';
import { PlayerAccount, PlayerAccountSchema } from './generated';

function deserializePlayer(data: Buffer): PlayerAccount | null {
  try {
    // Skip 8-byte discriminator for Anchor accounts
    const accountData = data.slice(8);
    
    const player = borsh.deserialize(
      PlayerAccountSchema,
      accountData
    ) as PlayerAccount;
    
    // Validate after deserialization
    if (!validatePlayer(player)) {
      console.error('Player validation failed');
      return null;
    }
    
    return player;
  } catch (error) {
    console.error('Deserialization failed:', error);
    return null;
  }
}

Validation Before Serialization

Validate data before sending to program:

function validatePlayer(player: PlayerAccount): boolean {
  // Check required fields
  if (!player.wallet || player.wallet.length !== 32) {
    console.error('Invalid wallet address');
    return false;
  }
  
  // Check number ranges
  if (player.score < 0 || player.score > Number.MAX_SAFE_INTEGER) {
    console.error('Score out of valid range');
    return false;
  }
  
  // Check string constraints
  if (!player.name || player.name.length > 32) {
    console.error('Invalid name length');
    return false;
  }
  
  return true;
}

Type Guards for Enums

Safe enum handling with type guards:

type GameState = 
  | { kind: 'Active'; data: ActiveData }
  | { kind: 'Paused'; data: PausedData }
  | { kind: 'Finished'; score: number };

function isActiveState(
  state: GameState
): state is Extract<GameState, { kind: 'Active' }> {
  return state.kind === 'Active';
}

function handleGameState(state: GameState) {
  if (isActiveState(state)) {
    // TypeScript knows state.data is ActiveData
    console.log('Game is active:', state.data);
  } else if (state.kind === 'Paused') {
    console.log('Game is paused:', state.data);
  } else {
    console.log('Game finished with score:', state.score);
  }
}

Handling RPC Errors

Graceful error handling for RPC calls:

async function fetchPlayerSafe(
  connection: Connection,
  address: PublicKey
): Promise<PlayerAccount | null> {
  try {
    const accountInfo = await connection.getAccountInfo(address);
    
    if (!accountInfo) {
      console.log('Account not found');
      return null;
    }
    
    if (accountInfo.data.length < 8) {
      console.error('Account data too short');
      return null;
    }
    
    return deserializePlayer(accountInfo.data);
  } catch (error) {
    if (error instanceof Error) {
      console.error('RPC error:', error.message);
    }
    return null;
  }
}

Common Errors

Error: "Failed to deserialize account data"

Cause: Data format doesn't match schema
Solutions:

  • Verify account discriminator matches
  • Ensure schema matches on-chain data
  • Check if account was migrated

Error: "Number precision lost"

Cause: u64/u128 exceeds JavaScript Number range
Solution: Use BigInt for large numbers

const balance = BigInt(accountData.balance);

Error: "Invalid PublicKey"

Cause: Invalid base58 string or wrong length
Solution: Validate before constructing

function safePublicKey(address: string): PublicKey | null {
  try {
    return new PublicKey(address);
  } catch {
    return null;
  }
}

Error: "Circular dependency detected"

Cause: Type A references B, B references A
Solution: Restructure types or use references

// ❌ Circular
struct A { b: B }
struct B { a: A }

// ✅ Use Pubkey reference
struct A { b_address: Pubkey }
struct B { a_address: Pubkey }

### 2. New Page: `docs/guide/debugging.md`

```markdown
---
title: Debugging
description: Techniques for debugging LUMOS schemas and generated code
---

# Debugging LUMOS

## Schema Validation

### Check Schema Syntax

```bash
# Validate schema without generating code
lumos validate schemas/player.lumos

Common syntax errors:

  • Missing colons after field names
  • Undefined type references
  • Invalid attribute syntax
  • Circular dependencies

View Generated Code

# Generate and inspect
lumos generate schemas/player.lumos

# View Rust output
cat generated.rs

# View TypeScript output
cat generated.ts

Deserialization Debugging

Rust: Print Binary Data

use hex;

pub fn debug_account_data(data: &[u8]) {
    msg!("Account data (hex): {}", hex::encode(data));
    msg!("Account data length: {}", data.len());
    msg!("Discriminator: {}", hex::encode(&data[..8]));
}

TypeScript: Inspect Binary Data

function debugAccountData(data: Buffer) {
  console.log('Length:', data.length);
  console.log('Hex:', data.toString('hex'));
  console.log('Discriminator:', data.slice(0, 8).toString('hex'));
}

Compare Expected vs Actual

#[cfg(test)]
mod tests {
    #[test]
    fn test_account_size() {
        let expected_size = 8 + 32 + 2 + 8; // discriminator + wallet + level + score
        let actual = PlayerAccount {
            wallet: Pubkey::default(),
            level: 1,
            score: 0,
        };
        
        let serialized = actual.try_to_vec().unwrap();
        assert_eq!(serialized.len(), expected_size - 8); // Exclude discriminator
    }
}

Performance Debugging

Measure Serialization Time

use std::time::Instant;

let start = Instant::now();
let bytes = account.try_to_vec()?;
msg!("Serialization took: {:?}", start.elapsed());

Account Size Analysis

# Calculate account size
lumos size schemas/player.lumos --type PlayerAccount

Deep Nesting Analysis

// Check nesting depth
fn calculate_nesting_depth<T>() -> usize {
    // Implement recursive depth calculation
}

msg!("Nesting depth: {}", calculate_nesting_depth::<MyStruct>());

Common Issues

Issue: Generated code doesn't compile

Debug steps:

  1. Check LUMOS CLI version: lumos --version
  2. Validate schema: lumos validate schema.lumos
  3. Check Rust/TS dependencies
  4. Review error messages carefully

Issue: Deserialization fails in tests

Debug steps:

  1. Print hex data before deserialization
  2. Compare with expected format
  3. Check discriminator matches
  4. Verify Borsh version consistency

Issue: Type mismatch between Rust and TypeScript

Debug steps:

  1. Regenerate both files from same schema
  2. Check type mappings are correct
  3. Verify Borsh schema definitions match
  4. Test serialization round-trip

### 3. New Page: `docs/reference/common-errors.md`

```markdown
---
title: Common Errors
description: Reference guide for common LUMOS errors and solutions
---

# Common Errors Reference

## CLI Errors

| Error | Cause | Solution |
|-------|-------|----------|
| `Failed to parse schema at line X` | Syntax error | Check .lumos file syntax |
| `Undefined type: TypeName` | Type not defined | Define type or import |
| `Circular dependency detected` | Type A → B → A | Restructure types |
| `Invalid attribute: #[unknown]` | Unsupported attribute | Check documentation |

## Runtime Errors

| Error | Cause | Solution |
|-------|-------|----------|
| `Deserialization failed` | Data format mismatch | Verify schema matches data |
| `Invalid discriminator` | Wrong account type | Check account address |
| `Number precision lost` | u64 > MAX_SAFE_INTEGER | Use BigInt |
| `Invalid PublicKey` | Bad base58 string | Validate format |

## Migration Errors

| Error | Cause | Solution |
|-------|-------|----------|
| `Account size mismatch` | Schema size changed | Realloc before migration |
| `Version mismatch` | Old client, new data | Update client code |
| `Field not found` | Breaking change | Implement migration |

4. Update Existing Pages

Add "Error Handling" sections to:

  • docs/guide/getting-started.md
  • docs/guide/rust-integration.md
  • docs/guide/typescript-integration.md

5. Update Navigation

sidebar: {
  '/guide/': [
    // ... existing
    {
      text: 'Advanced',
      items: [
        { text: 'Error Handling', link: '/guide/error-handling' },
        { text: 'Debugging', link: '/guide/debugging' },
      ]
    }
  ],
  '/reference/': [
    // ... existing
    { text: 'Common Errors', link: '/reference/common-errors' },
  ]
}

Acceptance Criteria

  • docs/guide/error-handling.md created with comprehensive patterns
  • docs/guide/debugging.md created with debugging techniques
  • docs/reference/common-errors.md created with error reference table
  • Error handling sections added to 3 existing guide pages
  • Navigation updated in .vitepress/config.ts
  • Code examples include error handling
  • Target: Q1: 82→92 (+10), Q6: 87→95 (+8)

Impact

Context7 Benchmark:

  • Q1: 82 → 92 (+10 points)
  • Q6: 87 → 95 (+8 points)

Overall Score: 88.0 → 89.8 (+1.8 points)

User Value:

  • Reduced production errors
  • Better debugging workflows
  • Comprehensive error reference
  • Clearer troubleshooting paths

Related

  • Context7 Benchmark Questions 1, 6
  • Rust error handling best practices
  • TypeScript type safety patterns

Priority Justification

🟡 HIGH - Affects 2 questions, critical for production usage, improves developer experience

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions