Skip to content

tomitribe/checkmate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Checkmate

Checkmate is a fluent Java library for validating runtime conditions with styled, aligned output and built-in short-circuiting. The name is both a nod to the chess term and a play on "mate" as in friend or helper ("check mate").

It supports rich semantics like warnings, fallback values, and exception propagation, and is ideal for configuration validation, deployment assertions, CLI diagnostics or an alternative to some uses of java.util.Optional.

<dependency>
  <groupId>org.tomitribe</groupId>
  <artifactId>checkmate</artifactId>
  <version>1.0</version>
</dependency>

Styles of Checking

Checkmate supports multiple styles of checking depending on the use case. Each provides a fluent, readable way to validate logic and format output. Choose based on your context: imperative, fluent, test-like, or functional.

Style 1: Manual Check with Check Object

This style gives you full control over the logic and messaging. Instantiate a Check, run your validation, then call .pass(), .fail(), or .error() accordingly.

final File file = new File("/opt/java");
final Check check = checks.check(file + " exists");

if (file.exists()) {
    check.pass();
} else {
    check.fail();
}

You can also report exceptions:

try {
    someAction();
    check.pass();
} catch (IOException e) {
    check.error(e);
}

Style 2: Immediate Lambda Checks

Simple and concise. Best used when the condition is already evaluated:

checks.check("File exists", () -> file.exists());
checks.check("Optional config", () -> config.isPresent(), WARN);

Style 3: Fluent Checks on a Single Object

Use checks.object("label", object) for multiple validations on the same object. Checks short-circuit after the first failure.

checks.object("JAVA_HOME", new File("/opt/java"))
      .check("exists", File::exists)
      .check("is directory", File::isDirectory)
      .check("is readable", File::canRead)
      .check("is writable", File::canWrite);

The above may generate output like the following:

JAVA_HOME exists . . . . . . . . . . . . . . . . PASS
JAVA_HOME is directory . . . . . . . . . . . . . PASS
JAVA_HOME bin readable . . . . . . . . . . . . . SKIP
JAVA_HOME bin writable . . . . . . . . . . . . . SKIP

Style 4: Fluent + Inline Return

Declare the object inline and return it only if all checks pass.

final File dir = checks.object("JAVA_HOME", new File("/opt/java"))
                       .check("exists", File::exists)
                       .check("is directory", File::isDirectory)
                       .check("can read", File::canRead)
                       .getOrThrow(() -> new IllegalStateException("Invalid JAVA_HOME"));

Style 5: Navigating Sub-Objects with .map()

Use .map() to chain checks to derived objects. Skips downstream checks if prior ones fail.

final File bin = checks.object("JAVA_HOME", dir)
                       .check("exists", File::exists)
                       .check("is directory", file -> false)
                       .map("bin directory", file -> new File("bin"))
                       .check("exists", File::exists)
                       .check("is file", File::isFile)
                       .check("can read", File::canRead)
                       .getOrThrow(() -> new RuntimeException("Invalid JAVA_HOME/bin/ directory"));

Output:

JAVA_HOME exists. . . . . . . . . . . . . . . . . PASS
JAVA_HOME is directory . . . . . . . . . . . . . FAIL

Style 6: Fallbacks with .or()

Use .or(…​) to try alternative objects if validation fails.

final File javaHome = checks.object("JAVA_HOME", new File("/opt/java"))
    .check("exists", File::exists)
    .check("is directory", File::isDirectory)
.or("JAVA_HOME (fallback #1)", new File("/usr/lib/jvm/java-11-openjdk"))
    .check("exists", File::exists)
    .check("is directory", File::isDirectory)
.or("JAVA_HOME (fallback #2)", new File("/usr/java/latest"))
    .check("exists", File::exists)
    .check("is directory", File::isDirectory)
.or("JAVA_HOME (fallback #3)", new File(System.getProperty("java.home")))
    .check("exists", File::exists)
    .check("is directory", File::isDirectory)
.getOrThrow(() -> new IllegalStateException("No valid JAVA_HOME found"));

Output:

JAVA_HOME exists. . . . . . . . . . . . . . . . . FAIL
JAVA_HOME is directory. . . . . . . . . . . . . . SKIP
JAVA_HOME (fallback #1) exists. . . . . . . . . . FAIL
JAVA_HOME (fallback #1) is directory. . . . . . . SKIP
JAVA_HOME (fallback #2) exists. . . . . . . . . . PASS
JAVA_HOME (fallback #2) is directory . . . . . . PASS

Controlling Output

Printing to Console

To enable output:

final Checks checks = Checks.builder()
                            .print(System.out, 50)
                            .build();

Silent Mode

To disable output:

final Checks checks = Checks.builder().build();

This is useful when you’re using .getOrThrow() or .result() to control flow and don’t want logging.

Implementing Custom Output

You can implement your own output using CheckLogger.

public class Slf4jLogger implements CheckLogger {
    private final Logger logger = LoggerFactory.getLogger("checkmate");

    @Override
    public Check log(final String name) {
        return new Check() {
            public void pass() { logger.info("{} PASS", name); }
            public void fail() { logger.error("{} FAIL", name); }
            public void warn() { logger.warn("{} WARN", name); }
            public void skip() { logger.debug("{} SKIP", name); }
            public void fail(String reason) { logger.error("{} FAIL  {}", name, reason); }
            public void warn(String reason) { logger.warn("{} WARN  {}", name, reason); }
            public void error(String reason) { logger.error("{} ERROR {}", name, reason); }
        };
    }
}

Register the logger:

final Checks checks = Checks.builder()
                            .logger(new Slf4jLogger())
                            .build();

Multiple Loggers

Multiple loggers may be used:

Checks.builder()
      .logger(new Slf4jLogger())
      .print(System.out, 50)
      .build();

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages