eskema 0.1.0 copy "eskema: ^0.1.0" to clipboard
eskema: ^0.1.0 copied to clipboard

Small & composable runtime validation library

codecov build Pub Version

Eskema #

Eskema is a small, composable runtime validation library for Dart. It helps you validate dynamic values (JSON, Maps, Lists, primitives) with readable validators and clear error messages.

Use cases #

Here are some common usecases for Eskema:

  • Validate untyped API JSON before mapping to models (catch missing/invalid fields early).
  • Guard inbound request payloads (HTTP handlers, jobs) with clear, fail-fast errors.
  • Validate runtime config and feature flags from files or remote sources.

Install #

dart pub add eskema

Quick start #

Validate a map using a schema-like validator and read a detailed result or a boolean.

1. Create a simple eskema #

import 'package:eskema/eskema.dart';

final userValidator = eskema({
    'username': isString(),

    // Combine validators using `all` and `any`
    'age': all([isInt(), isGte(0)]),

    // or use operators for simplicity:
    'theme': (isString() & (isEq('light') | isEq('dark'))).nullable(),

    // Some zero-arg validators also have canonical aliases: e.g. `$isBool`, `$isString`
    'premium': nullable($isBool),

    // Make a validator nullable
    'email': stringMatchesPattern(
      RegExp(r"^[^@\s]+@[^@\s]+\.[^@\s]+$"),
      error: 'a valid email address',
    ).nullable(),
});

2. Validate the eskema #

The simplest way to check if a validator is valid, is by using the isValid method:

final ok = userValidator.isValid({ 'username': 'bob', 'age': 42 });
print("User is valid: $ok");  // true

You can use the .validate method to get a descriptive error message:

final res = userValidator.validate({
'username': 'alice',
'age': -1,
});
print(res); // false - "Expected age -> greater than or equal to 0, got -1"

You can also make the validation throw

try {
	userValidator.validateOrThrow({'username': 'bob'});
} catch (e) {
	print(e); // ValidatorFailedException with a helpful message
}

Table of contents #

API overview #

Check the docs for the technical documentation.

  • Core

    • IEskValidator / EskValidator — object-based validators that return EskResult
      • EskResult.isValid, .isNotValid, .expected, .value, nice toString()
  • Common validators (examples)

    • Types: isType<T>() e.g. isType<String>(); shorthands: isString(), isInt(), isDouble(), isBool()
      • Nullability: isNull(); make any validator nullable with nullable(v) or v.nullable()
      • Numbers: isGt(n), isGte(n), isLt(n), isLte(n)
      • Equality: isEq(value), deep equality isDeepEq(value)
      • Strings: stringIsOfLength(n), stringContains(sub), stringNotContains(sub), stringMatchesPattern(pattern)
      • Lists: listIsOfLength(n), listEach(itemValidator), eskemaList([...])
      • Maps: eskema({ 'key': validator, ... })
  • Composition

    • all([...]) — AND composition (stops on first failure)
      • any([...]) — OR composition (passes if any succeed)
      • not(v) — invert a validator
      • nullable(v) or v.nullable() — allow null
  • Results & helpers

    • .validate(value)EskResult
      • .isValid(value) / .isNotValid(value) → bool
      • .validateOrThrow(value) throws on invalid input

Tip: Some zero-arg validators also have canonical aliases (e.g. $isString, $isBool) for concise usage.

Examples #

Custom validators #

Zero-arg validator

final isHelloWorld = all([
  $isString,
  EskValidator((value) => EskResult(
    isValid: value == 'Hello world',
    expected: 'Hello world',
    value: value,
  )),
]);

print(isHelloWorld.isValid('Hello world'));  // true
print(isHelloWorld.validate('hey'));         // false - 'Expected Hello world, got "hey"'

Validator with args

IEskValidator isInRange(num min, num max) {
  return all([
    isType<num>(),
    EskValidator((value) => EskResult(
      isValid: value >= min && value <= max,
      expected: 'number to be between $min and $max',
      value: value,
    )),
  ]);
}

print(isInRange(0, 5).isValid(2)); // true
print(isInRange(0, 5).validate(6)); // false - "Expected number to be between 0 and 5, got 6"

Class-based validators #

Prefer a class for complex/structured validation? Use EskMap with EskField.

import 'package:eskema/eskema.dart';

enum Theme { light, dark }

class SettingsValidator extends EskMap {
	final theme = EskField(
	    id: 'theme', 
	    validators: [isOneOf(Theme.values)],
    );

	final notificationsEnabled = EskField(
		id: 'notificationsEnabled',
		nullable: true,
		validators: [isBool()],
	);

	SettingsValidator({required super.id, super.nullable});

	@override
	get fields => [theme, notificationsEnabled];
}

class UserValidator extends EskMap {
	final name = EskField(id: 'name', validators: [isString()]);
	final settings = SettingsValidator(id: 'settings', nullable: true);

	@override
	get fields => [name, settings];
}

final v = UserValidator();
final result = v.validate({ 'name': 'Test', 'settings': { 'theme': Theme.dark } });
print(result.isValid); // true

Validate untyped API JSON #

import 'package:eskema/eskema.dart';

final apiUser = eskema({
	'id': isInt(),
	'email': stringMatchesPattern(
		RegExp(r'^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$'),
		error: 'a valid email',
	),
	'name': isString(),
	'roles': listEach(isString()).nullable(),
});

final result = apiUser.validate(apiJson);
if (result.isNotValid) log('invalid user: $result');

Validate runtime config and feature flags #

final config = eskema({
	'featureX': isBool(),
	'theme': isOneOf(['light', 'dark']),
	'retry': all([isInt(), isGte(0)]),
	'allowedHosts': listEach(isString()).nullable(),
});

final isConfigValid = config.isValid(configMap);
assert(isConfigValid, 'Invalid config: $cfgRes');

More #

  • See example/ for runnable demos
  • Check test/ for behavior coverage
2
likes
150
points
15
downloads

Publisher

unverified uploader

Weekly Downloads

Small & composable runtime validation library

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

collection

More

Packages that depend on eskema