Skip to content

cowwoc/requirements.java

Repository files navigation

Maven Central build-status

checklist Requirements API

API Changelog javascript, typescript

A fluent API for enforcing design contracts with automatic message generation:

✔️ Easy to use
✔️ Fast
✔️ Production-ready

To get started, add this Maven dependency:

<dependency>
  <groupId>com.github.cowwoc.requirements</groupId>
  <artifactId>java</artifactId>
  <version>9.0.0</version>
</dependency>

Usage Example

import java.util.List;
import java.util.StringJoiner;

import static com.github.cowwoc.requirements.java.DefaultJavaValidators.assumeThat;
import static com.github.cowwoc.requirements.java.DefaultJavaValidators.checkIf;
import static com.github.cowwoc.requirements.java.DefaultJavaValidators.requireThat;

public final class Cake
{
  private byte bitesTaken = 0;
  private int piecesLeft;

  public Cake(int piecesLeft)
  {
    requireThat(piecesLeft, "piecesLeft").isPositive();
    this.piecesLeft = piecesLeft;
  }

  public int eat()
  {
    ++bitesTaken;
    assert assumeThat(bitesTaken, "bitesTaken").isNotNegative().elseThrow();

    piecesLeft -= ThreadLocalRandom.current().nextInt(5);

    assert assumeThat(piecesLeft, "piecesLeft").isNotNegative().elseThrow();
    return piecesLeft;
  }

  public List<String> getFailures()
  {
    return checkIf(bitesTaken, "bitesTaken").isNotNegative().
      and(checkIf(piecesLeft, "piecesLeft").isGreaterThan(3)).
      elseGetMessages();
  }
}

If you violate a precondition:

Cake cake = new Cake(-1000);

You'll get:

java.lang.IllegalArgumentException: "piecesLeft" must be positive.
Actual: -1000

If you violate a class invariant:

Cake cake = new Cake(1_000_000);
while (true)
  cake.eat();

You'll get:

java.lang.AssertionError: "bitesTaken" may not be negative.
Actual: -128

If you violate a postcondition:

Cake cake = new Cake(100);
while (true)
  cake.eat();

You'll get:

java.lang.AssertionError: "piecesLeft" may not be negative.
Actual: -4

If you violate multiple conditions at once:

Cake cake = new Cake(1);
cake.bitesTaken = -1;
cake.piecesLeft = 2;
StringJoiner failures = new StringJoiner("\n\n");
for (String failure : cake.getFailures())
    failures.add(failure);
System.out.println(failures);

You'll get:

"bitesTaken" may not be negative.
Actual: -1

"piecesLeft" must be greater than 3.
Actual: 2

Features

This library offers the following features:

Entry Points

Designed for discovery using your favorite IDE's auto-complete feature. The main entry points are:

The first three methods use a shared configuration, while JavaValidators allows you to create an independent configuration.

  • requireThat() and assumeThat() throw an exception on the first validation failure.
  • checkIf() returns multiple validation failures at once. It is more flexible than the others, but its syntax is more verbose.

Thrown exceptions may be configured using ConfigurationUpdater.exceptionTransformer(Function).

See the API documentation for more details.

Tips

  • Use assert with assumeThat().elseThrow() for sanity checks. When assertions are disabled, the checks will get removed.
  • Use checkIf().elseGetMessages() to return failure messages without throwing an exception. This is the fastest validation approach, ideal for web services.
  • To enhance the clarity of failure messages, you should provide parameter names, even when they are optional. In other words, favor assumeThat(value, name) to assumeThat(value).

Third-party libraries and tools

This library supports the following third-party libraries and tools:

Licenses