Skip to content

[Phase 1] Improve Error Handling for Server Validation Responses #43

@democratize-technology-code-reviewer

Description

Overview

Following the architectural review of PR #42, we've identified that client-side validation in an API wrapper is an anti-pattern. The Grocy server is the single source of truth for validation rules. Our client library should focus on:

  1. Properly handling and presenting server validation errors
  2. Protecting the HTTP client from malformed inputs
  3. Providing good developer experience through TypeScript types (Phase 2)

Current State

  • Generic error handling that doesn't parse Grocy's validation responses
  • No distinction between different types of errors (validation, auth, network, etc.)
  • Poor developer experience when server rejects requests

Requirements

1. Parse Grocy API Error Responses

// Current: Generic error
Error: Request failed with status 400

// Desired: Parsed validation error
GrocyValidationError: Invalid product data
  - name: Field is required
  - location_id: Must be a valid location ID
  - qu_id_stock: Must be a valid quantity unit ID

2. Error Classification

Create specific error classes for different scenarios:

class GrocyError extends Error {
  constructor(message, statusCode, details) {
    super(message);
    this.statusCode = statusCode;
    this.details = details;
    Object.freeze(this);
  }
}

class GrocyValidationError extends GrocyError {
  constructor(message, validationErrors) {
    super(message, 400, validationErrors);
    this.validationErrors = Object.freeze(validationErrors);
  }
}

class GrocyAuthenticationError extends GrocyError {
  constructor(message = 'Invalid or missing API key') {
    super(message, 401);
  }
}

class GrocyNotFoundError extends GrocyError {
  constructor(resource, id) {
    super(`${resource} with ID ${id} not found`, 404);
  }
}

3. Minimal Client Protection

Only validate what could break our HTTP client:

function validateClientConfig(apiUrl, apiKey) {
  // Protect against malformed URLs that would crash fetch()
  try {
    new URL(apiUrl);
  } catch (e) {
    throw new Error('Invalid API URL format');
  }
  
  // Basic API key format check
  if (!apiKey || typeof apiKey !== 'string') {
    throw new Error('API key must be a non-empty string');
  }
}

4. Enhanced Error Messages

Transform Grocy's error responses into helpful messages:

async function handleApiError(response) {
  const contentType = response.headers.get('content-type');
  
  if (contentType?.includes('application/json')) {
    const error = await response.json();
    
    // Parse Grocy's specific error format
    if (response.status === 400 && error.error_details) {
      throw new GrocyValidationError(
        error.error_message || 'Validation failed',
        error.error_details
      );
    }
  }
  
  // Generic error fallback
  throw new GrocyError(
    `Request failed with status ${response.status}`,
    response.status
  );
}

5. Preserve Immutability

All error objects must be immutable following our v1.0.0 principles:

  • Use Object.freeze() on all error objects
  • No mutation of error properties after creation
  • Error details should be read-only

Implementation Notes

  1. Research Grocy's actual error response format by testing various failure scenarios
  2. Document the error format for future reference
  3. Ensure backward compatibility - existing error handling should continue to work
  4. Follow immutable patterns established in PR Phase 1: Input Validation and Immutable Responses #25

Success Criteria

  • All Grocy error types properly classified and parsed
  • Helpful error messages that guide developers to solutions
  • Immutable error objects throughout
  • No client-side business logic validation
  • Minimal performance impact (<1ms overhead)
  • Comprehensive tests for error scenarios
  • Documentation of Grocy's error response formats

Not in Scope

Related Issues


"Let the server validate. Let the client present errors beautifully."

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions