Skip to content

Commit

Permalink
Updated usage example
Browse files Browse the repository at this point in the history
  • Loading branch information
cowwoc committed May 23, 2024
1 parent 01b1842 commit f5fbb35
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 67 deletions.
147 changes: 89 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,65 +36,100 @@ import static com.github.cowwoc.requirements.java.DefaultJavaValidators.assumeTh
import static com.github.cowwoc.requirements.java.DefaultJavaValidators.checkIf;
import static com.github.cowwoc.requirements.java.DefaultJavaValidators.requireThat;

public final class MissionControl
public final class Cake
{
public static void main(String[] args)
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()
{
// Method preconditions
requireThat(args, "args").length().isPositive();
String message = args[0];
requireThat(message, "message").startsWith("Houston, we've got a ").endsWith(".");

String[] words = message.replaceAll("[.,]", "").split("\\s+");
requireThat(words, "words").length().isEqualTo(5);
String subject = words[4];

// Class invariants or method postconditions
String reply = "What sort of " + subject + "?"; // <-- good reply
// String reply = "What sort of " + subject + " do you see?"; // <-- bad reply
assert assumeThat(reply, "reply").length().
withContext(message, "message").
isLessThan(message.length(), "message.length()").
elseThrow();
System.out.println("Message: " + message);
System.out.println("Reply : " + reply);
System.out.println();

// Return multiple validation failures at once
List<String> messages = checkIf(message, "message").isEmpty().
and(checkIf(subject, "subject").isEqualTo("cupcake")).
return checkIf(bitesTaken, "bitesTaken").isNotNegative().
and(checkIf(piecesLeft, "piecesLeft").isGreaterThan(3)).
elseGetMessages();
StringJoiner joiner = new StringJoiner("\n\n");
for (String failureMessage : messages)
joiner.add(failureMessage);
System.out.println("Multiple failures\n-----------------\n" + joiner);
}
}
```

Potential error messages look like this:
If you violate a **precondition**:

```java
Cake cake = new Cake(-1000);
```

You'll get:

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

If you violate a **class invariant**:

```java
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**:

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

You'll get:

```
java.lang.NullPointerException: "args" may not be null
java.lang.IllegalArgumentException: words.length must contain 5 elements.
words.length: 6
words : ["Houston", "we've", "got", "a", "bunny", "rabbit"]
java.lang.AssertionError: reply.length() must contain less than message.length() characters.
message.length(): 25
reply.length() : 28
reply : "What sort of dog do you see?"
message : "Houston, we've got a dog."
Multiple failures
-----------------
"message" must be empty.
Actual : "Houston, we've got a problem."
message.length: 25
"subject" must be equal to "cupcake"
Actual: "problem"
java.lang.AssertionError: "piecesLeft" may not be negative.
Actual: -4
```

If you violate **multiple** conditions at once:

```java
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
Expand Down Expand Up @@ -125,15 +160,11 @@ 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,
while `checkIf()` collects multiple validation failures before throwing an exception at the end.
`checkIf()` is more flexible than the others, but its syntax is more verbose.
* `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.

Exceptions that are thrown in response to invalid method arguments (e.g. `isGreaterThan(null, value)`) are
thrown by all validators and cannot be configured. Exceptions that are thrown in response to the value
failing a validation check, e.g. `isGreaterThan(5)` on a value of 0, are thrown by `requireThat()` and
`assumeThat()` but are recorded by `checkIf()` without being thrown. The type of thrown exceptions is
configurable using [ConfigurationUpdater#exceptionTransformer(Function)](https://cowwoc.github.io/requirements.java/9.0.0/docs/api/com.github.cowwoc.requirements/com/github/cowwoc/requirements/java/ConfigurationUpdater.html#exceptionTransformer(java.util.function.Function)).
Thrown exceptions may be configured using [ConfigurationUpdater.exceptionTransformer(Function)](https://cowwoc.github.io/requirements.java/9.0.0/docs/api/com.github.cowwoc.requirements.java/com/github/cowwoc/requirements/java/ConfigurationUpdater.html#exceptionTransformer(java.util.function.Function)).

See the [API documentation](https://cowwoc.github.io/requirements.java/9.0.0/docs/api/) for more details.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package com.github.cowwoc.requirements.java;

import com.github.cowwoc.pouch.core.WrappedCheckedException;
import com.github.cowwoc.requirements.java.type.part.Validator;

import java.util.function.Function;
Expand Down Expand Up @@ -153,7 +154,7 @@ public boolean throwOnFailure()
/**
* Returns a function that transforms the validation exception into a suitable runtime exception or error.
* The input and output of the function must be subclasses of {@code RuntimeException} or {@code Error}. If
* the output is not, it is wrapped in a {@code WrappedCheckedException}. If the function returns
* the output is not, it is wrapped in a {@link WrappedCheckedException}. If the function returns
* {@code null} the input exception will be thrown.
*
* @return a function that transforms the validation exception
Expand Down Expand Up @@ -184,17 +185,17 @@ public boolean equals(Object o)
if (!(o instanceof Configuration other))
return false;
return other.cleanStackTrace == cleanStackTrace && other.includeDiff == includeDiff &&
other.equalityMethod == equalityMethod && other.stringMappers.equals(stringMappers) &&
other.lazyExceptions == lazyExceptions() && other.throwOnFailure == throwOnFailure &&
other.exceptionTransformer == exceptionTransformer;
other.equalityMethod == equalityMethod && other.stringMappers.equals(stringMappers) &&
other.lazyExceptions == lazyExceptions() && other.throwOnFailure == throwOnFailure &&
other.exceptionTransformer == exceptionTransformer;
}

@Override
public String toString()
{
return "cleanStackTrace: " + cleanStackTrace + ", diffEnabled: " + includeDiff +
", equalityMethod: " + equalityMethod + ", stringMappers: " + stringMappers +
", lazyExceptions: " + lazyExceptions + ", throwOnFailure:" + throwOnFailure +
", exceptionTransformer: " + exceptionTransformer;
", equalityMethod: " + equalityMethod + ", stringMappers: " + stringMappers +
", lazyExceptions: " + lazyExceptions + ", throwOnFailure:" + throwOnFailure +
", exceptionTransformer: " + exceptionTransformer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package com.github.cowwoc.requirements.java;

import com.github.cowwoc.pouch.core.WrappedCheckedException;
import com.github.cowwoc.requirements.annotation.CheckReturnValue;
import com.github.cowwoc.requirements.java.type.part.Validator;

Expand Down Expand Up @@ -100,7 +101,7 @@ public interface ConfigurationUpdater extends AutoCloseable
/**
* Returns a function that transforms the validation exception into a suitable runtime exception or error.
* The input and output of the function must be subclasses of {@code RuntimeException} or {@code Error}. If
* the output is not, it is wrapped in a {@code WrappedCheckedException}. If the function returns
* the output is not, it is wrapped in a {@link WrappedCheckedException}. If the function returns
* {@code null} the input exception will be thrown.
*
* @return a function that transforms the validation exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package com.github.cowwoc.requirements.java.internal.implementation;

import com.github.cowwoc.pouch.core.WrappedCheckedException;
import com.github.cowwoc.requirements.annotation.CheckReturnValue;
import com.github.cowwoc.requirements.java.Configuration;
import com.github.cowwoc.requirements.java.EqualityMethod;
Expand Down Expand Up @@ -223,7 +224,7 @@ public MutableConfiguration throwOnFailure(boolean throwOnFailure)
/**
* Returns a function that transforms the validation exception into a suitable runtime exception or error.
* The input and output of the function must be subclasses of {@code RuntimeException} or {@code Error}. If
* the output is not, it is wrapped in a {@code WrappedCheckedException}. If the function returns
* the output is not, it is wrapped in a {@link WrappedCheckedException}. If the function returns
* {@code null} the input exception will be thrown.
*
* @return a function that transforms the validation exception
Expand Down

0 comments on commit f5fbb35

Please sign in to comment.