bloomBUILD•SHIP•BLOOM
.md
Validation & Diagnostics

Validation & Errors

bloom_validate and bloom_errors provide zero-codegen declarative DTO schema validation and a standardized HTTP exception hierarchy with environment-aware error masking.

Request Schema Validation with bloom_validate

Subclass BloomRequestSchema to define request body contracts. Validation runs without code generators and accumulates all field errors in a single pass:

lib/apps/notes/serializers.dart
dart
// lib/apps/notes/serializers.dart - Request DTO Schemas
import 'package:bloom_validate/bloom_validate.dart';

class CreateNoteSchema extends BloomRequestSchema {
  CreateNoteSchema(super.data);
  CreateNoteSchema.fromRequest(super.request) : super.fromRequest();

  // Declarative, zero-codegen validation rules
  late final String title = requireStringLength('title', min: 2, max: 200, description: 'Note title');
  late final String body = optionalString('body', defaultValue: '') ?? '';
  late final int priority = optionalIntRange('priority', min: 1, max: 5, defaultValue: 3)!;
  late final List<String> tags = optionalList<String>('tags', (item) => item.toString(), defaultValue: const [])!;

  @override
  void validate() {
    title;
    body;
    priority;
    tags;
  }
}

Inside route handlers, parse schemas using BloomRequestSchema.validateSchema():

lib/apps/notes/views.dart
dart
// Route handler with BloomRequestSchema and typed errors
import 'package:bloom_framework/bloom_server.dart';
import 'package:bloom_errors/bloom_errors.dart';
import 'package:bloom_validate/bloom_validate.dart';
import 'serializers.dart';

Future<BloomResponse> handleCreateNote(BloomRequest req) async {
  // 1. Validate incoming JSON body
  final CreateNoteSchema schema;
  try {
    schema = BloomRequestSchema.validateSchema(CreateNoteSchema.fromRequest(req));
  } on BloomValidationException catch (e) {
    // Converts to BloomValidationFailedException (HTTP 422)
    throw BloomValidationFailedException(message: e.message, errors: e.errors);
  }

  // 2. Strongly-typed, clean access
  final title = schema.title;
  final body = schema.body;
  final priority = schema.priority;

  return BloomResponse.json({
    'title': title,
    'body': body,
    'priority': priority,
  }, statusCode: 201);
}

Typed HTTP Exceptions with bloom_errors

bloom_errors provides a complete set of strongly-typed exceptions carrying status codes, machine-readable error codes, and structured detail maps:

Exception ClassStatusError CodeDescription
BloomBadRequestException400bad_requestMalformed request or invalid parameters.
BloomUnauthorizedException401unauthorizedMissing or invalid session token.
BloomForbiddenException403forbiddenCaller lacks permissions or resource is locked.
BloomNotFoundException404not_foundRequested route, record, or file does not exist.
BloomConflictException409conflictResource state conflict (e.g. duplicate key).
BloomValidationFailedException422validation_failedPayload validation failed (carries field error list).
BloomTooManyRequestsException429too_many_requestsRate limit exceeded (supports retry-after).
BloomInternalException500internal_server_errorUnexpected server or infrastructure failure.

Unified Error Middleware & Environment Masking

BloomErrorMiddleware serves as the outermost middleware on BloomApiRouter, formatting all exceptions into standardized JSON envelopes. In production (APP_ENV=production), unhandled exception traces are masked to protect server internals:

bin/server.dart
dart
// bin/server.dart - Registering BloomErrorMiddleware
import 'package:bloom_framework/bloom_server.dart';
import 'package:bloom_errors/bloom_errors.dart';

void main() async {
  final router = BloomApiRouter();

  // Outermost middleware intercepts all downstream errors
  router.use(const BloomErrorMiddleware());

  // Custom application error mapping
  BloomErrorMapper.register<QuotaExceededException>((e) {
    return BloomForbiddenException(
      'Account monthly storage quota exceeded',
      {'quota_bytes': e.quotaBytes, 'used_bytes': e.usedBytes},
    );
  });

  await router.serve(port: 8080);
}

Sibling Exception Mapping

BloomErrorMapper automatically converts errors from bloom_db, bloom_auth_server, bloom_storage, and bloom_validate into their matching BloomApiException equivalents without tight compile-time coupling.