Error Handling

Ack provides detailed error information when validation fails. This guide explains how to interpret and use these errors.

The SchemaResult Object

All safeParse() methods in Ack return a SchemaResult object. This object encapsulates either the successfully validated data or a SchemaError.

  • result.isOk: Returns true if validation succeeded, false otherwise.
  • result.isFail: Returns true if validation failed, false otherwise.
  • result.getOrThrow(): Returns the validated data if isOk is true, otherwise throws an exception.
  • result.getOrNull(): Returns the validated data if isOk is true, otherwise returns null.
  • result.getError(): Returns the SchemaError if isFail is true, otherwise throws an exception.
  • result.getOrElse(defaultValue): Returns the validated data if isOk is true, otherwise returns the defaultValue.
import 'package:ack/ack.dart';

final schema = Ack.string().minLength(5);
final result = schema.safeParse('abc');

if (result.isFail) {
  final error = result.getError();
  print('Validation failed: $error');

  // Try to get data (will throw)
  try {
    result.getOrThrow();
  } catch (e) {
    print('Caught exception: $e');
  }

  // Get a default value
  final dataOrDefault = result.getOrElse(() => 'default_string');
  print('Data or default: $dataOrDefault'); // Output: default_string
}

Understanding SchemaError

The SchemaError object contains details about the validation failure.

  • error.name: A short identifier for the error type (e.g., 'string', 'object').
  • error.errorKey: The specific error key (e.g., 'schema_constraints_error', 'schema_nested_error').
  • error.value: The actual value that failed validation.
  • error.schema: The schema that was being validated against.
final userSchema = Ack.object({
  'name': Ack.string(),
  'age': Ack.integer().min(18),
  'address': Ack.object({
    'city': Ack.string()
  })
});

final invalidData = {
  'name': 'Test',
  'age': 15, // Fails min(18)
  'address': {
    'city': 123 // Fails Ack.string
  }
};

final result = userSchema.safeParse(invalidData);

if (result.isFail) {
  final error = result.getError();
  print('Error Name: ${error.name}');         // Example: 'object'
  print('Error Key: ${error.errorKey}');      // Example: 'schema_nested_error'
  print('Failed Value: ${error.value}');      // Example: the invalid data
  print('Full Error: $error');               // Complete error details

  // Note: The exact error structure (especially for nested errors) can vary.
  // Sometimes the top-level error might be SchemaNestedError,
  // and you might need to inspect its nested errors.
}

Error Types

Ack uses specific error types that inherit from SchemaError. Each error type provides detailed information about why validation failed.

TypeMismatchError

Occurs when the input value type doesn't match the expected schema type.

final schema = Ack.string();
final result = schema.safeParse(42); // Number instead of string

if (result.isFail) {
  final error = result.getError() as TypeMismatchError;
  print('Expected: ${error.expectedType}'); // "string"
  print('Actual: ${error.actualType}');     // "integer"
  print('Error: ${error.message}');          // "Expected string, got integer"
}

SchemaConstraintsError

Occurs when the value violates one or more validation constraints (e.g., minLength, min, email).

final schema = Ack.string().minLength(5).email();
final result = schema.safeParse('abc');

if (result.isFail) {
  final error = result.getError() as SchemaConstraintsError;
  print('Constraint violations: ${error.constraints.length}');

  // Access individual failed constraints
  for (final constraintError in error.constraints) {
    print('- Constraint: ${constraintError.constraint}');
    print('- Message: ${constraintError.message}');
  }
}

SchemaNestedError

Occurs when validation fails within nested objects or lists. Contains a list of child errors.

final schema = Ack.object({
  'name': Ack.string().minLength(2),
  'age': Ack.integer().min(0),
});

final result = schema.safeParse({
  'name': 'J',    // Too short
  'age': -5,      // Negative
});

if (result.isFail) {
  final error = result.getError() as SchemaNestedError;
  print('Nested errors: ${error.errors.length}');

  // Recursively inspect nested errors
  for (final nestedError in error.errors) {
    print('- Field: ${nestedError.name}');
    print('- Error: ${nestedError.message}');
  }
}

SchemaValidationError

Occurs when custom validation logic added via .refine() fails.

final schema = Ack.integer().refine(
  (value) => value % 2 == 0,
  message: 'Must be an even number',
);

final result = schema.safeParse(3);

if (result.isFail) {
  final error = result.getError() as SchemaValidationError;
  print('Validation error: ${error.message}'); // "Must be an even number"
}

SchemaTransformError

Occurs when a transformation function (from .transform()) throws an exception.

final schema = Ack.string().transform((value) {
  if (value == null) throw Exception('Cannot transform null');
  return value.toUpperCase();
});

final result = schema.safeParse(null);

if (result.isFail) {
  final error = result.getError() as SchemaTransformError;
  print('Transform error: ${error.message}');
  print('Cause: ${error.cause}'); // Original exception
}

Working with Error Types

Use pattern matching or type checks to handle different error types:

final result = schema.safeParse(data);

if (result.isFail) {
  final error = result.getError();

  switch (error) {
    case TypeMismatchError():
      print('Wrong type: expected ${error.expectedType}');
    case SchemaConstraintsError():
      print('Failed ${error.constraints.length} constraints');
    case SchemaNestedError():
      print('Nested validation failed at ${error.errors.length} locations');
    case SchemaValidationError():
      print('Custom validation failed: ${error.message}');
    case SchemaTransformError():
      print('Transformation failed: ${error.cause}');
    default:
      print('Validation failed: ${error.message}');
  }
}

Displaying Errors in UI (Flutter Example)

When using Ack with Flutter forms, you can extract the error message for display.

// Inside TextFormField validator
validator: (value) {
  final result = someSchema.safeParse(value);
  if (result.isFail) {
    // Return the error details for the UI
    return result.getError().toString();
  }
  return null; // Return null if valid
}

See the Form Validation Guide for more details.

Custom Error Messages

Built-in constraints provide default error messages. For custom error messages, create custom constraints using the .constrain() method. See Custom Validation Guide for details.

// Built-in constraints use default messages
final schema = Ack.string().minLength(5);

final result = schema.safeParse('abc');
if (result.isFail) {
  final error = result.getError();
  print(error.toString()); // Output: validation error details
}

Next Steps

Learn more about related topics: