Decide Which Requests Belong

Define a small HTTP contract, teach Boolean predicates and Choice, and reject invalid quantities before side effects.

The arithmetic accepts an empty array and returns zero. A shop may quite reasonably refuse to accept an order with no items. We need a request contract that makes that decision before doing useful work on the order.

Start with a small contract you can read without a specification language:

Input or outcomeDecision for this lesson
Method and pathPOST /validate
Order identityorderId is a string
ItemsA nonempty array
QuantityEvery item has a positive whole-number quantity
Valid commandStatus 200 with order identity and valid: true
Invalid commandStatus 400 with error: "INVALID_ORDER"

This is a validation exercise, not yet an order-creation endpoint. It checks a selected set of rules. Product identifiers, maximum sizes and the complete command shape will be formalized with RAML in chapter 13.

Command: Identifier + items: Numeric quantity. Choice: Positive whole qty?: Nonempty items?. Response: Valid: 200: Invalid: 400. The status and body express the same request decision.

Open this checkpoint in ACB

Stop the previous application. Open book/checkpoints/07-request-contract from the companion as the project folder, then open src/main/mule/app.xml. Use Flow List to select the named example. Run Run Mule Application from Run and Debug; wait for deployment before sending requests. After a canvas edit, use Save and Hot-deploy to Local Runtime.

The HTTP verification command, run from the companion root while this checkpoint is running, is python3 book/run.py verify 07. It checks the supplied baseline; restore exercise changes before using it.

Make a Boolean decision

Every rule in that table has to reach the flow as one condition that produces true or false. payload.orderId is String tests a type. item.qty > 0 compares two numbers. and requires both conditions to hold. Parentheses make the scope of a negation explicit — that matters as soon as not appears beside other Boolean operators.

isEmpty asks whether a collection has no elements. (not isEmpty(payload.items)) therefore requires at least one item. We first check that items is an array — otherwise the later item rules do not have the collection they expect.

To check every quantity, identify the invalid items. filter resembles map in taking an array and a callback, but its callback answers whether to keep an input element — it selects elements rather than constructing a new result for each one. Here we keep items whose quantity rule fails. The order passes when that rejected-item array is empty.

floor(item.qty) == item.qty checks that a numeric quantity has no fractional part. For 4 the comparison succeeds; for 1.5 it fails. This rule permits counts of pens, not measured quantities such as 1.5 kilograms. A different domain needs a different contract.

Put the decision in a Choice

Example 016 — Validate an order request

The complete source is in checkpoints/07-request-contract/src/main/mule/app.xml.

Choose Flow List → validate-request. Select Choice and inspect its two routes. The first route’s condition is the following expression; enter it in expression mode without another wrapper:

payload.orderId is String and
payload.items is Array and
(not isEmpty(payload.items)) and
isEmpty(payload.items filter (item) ->
  not (item.qty is Number and item.qty > 0
       and floor(item.qty) == item.qty))

Select the Transform Message inside that route. Its payload script constructs {orderId: payload.orderId, valid: true} with JSON output. In the Otherwise route, Set Variable assigns httpStatus the numeric expression 400; the following Transform Message constructs {error: "INVALID_ORDER"} with JSON output.

Return to Listener → Advanced and inspect the response settings. The normal response status expression is vars.httpStatus default 200; the error response status is vars.httpStatus default 500, with payload as its body. Keep the distinction between a JSON body’s fields and the Listener’s status setting.

Choice evaluates its when expressions in order and runs the first one that matches; with a single when, the otherwise branch takes everything else. Order starts to matter the moment two conditions can both match the same request — put the more specific one first, and test an input that satisfies both. The two branches build different responses, but they belong to the same request contract.

Whatever a branch leaves behind also becomes the next processor’s input. Here each branch ends the flow with its own response, so the shapes may differ. A Choice in the middle of a flow needs a common result shape, or explicit targets, so the component after it reads one documented thing rather than inferring which route ran from whichever fields happen to exist.

The listener now has an explicit response status expression — vars.httpStatus default 200 means use the variable when it has a value, otherwise use 200. The invalid branch sets the variable to 400 before constructing the error body. Returning an object with an error field alone would not set the HTTP status.

For the ordinary calculation fixture, the response is:

{"orderId":"A-1001","valid":true}

For {"orderId":"A-1001","items":[{"qty":-1}]}, the response is status 400 with:

{"error":"INVALID_ORDER"}

These outcomes need separate checks. A suite that sends only the valid order could miss a broken predicate that always chooses the success branch — a missing pair of parentheses around the negation changes which part of the condition it controls, and the success path can still look correct. Keep explicit failing fixtures so the intended rule is tested on both sides.

Keep validation close to the boundary

Validation before a side effect has a useful consequence: the service can reject an invalid command without asking another system to undo work. Later chapters assert that rejected input never reaches the catalogue or the database operation.

Avoid adding every conceivable rule to this first predicate. Separate structural questions, such as whether items is an array, from business questions, such as whether a particular retailer may buy a product. The request boundary should make both responsibilities visible, but the mechanisms and sources of authority can differ — and a narrow structural check ahead of the routing decision keeps the Choice itself readable.

For now, the inline predicate fits beside the two branches it selects. When several operations require the same rule, give that rule a named helper or validation subflow and retain tests for its boundary cases. Do not extract a helper whose only explanation is a reference to a chapter that has not yet taught its expression.

Choice condition and routes in the ACB component panel

The When route’s condition is the four-line predicate; Otherwise builds the rejection. The red marker on each line is a design-time metadata diagnostic, not a runtime error; Appendix A explains why these schema-free projects show them.

Try it

1. Test both sides of the quantity rule. Try 1, 0, -1, 1.5 and the string "4".

Show answer

Only numeric 1 satisfies this lesson’s positive whole-number rule. Zero and negative quantities fail the positivity check, 1.5 fails the whole-number check, and "4" is a string. Accepting numeric text would require an explicit normalization decision and its own tests.

2. Explain filter here. What does the callback return, and what does filter return?

Show answer

The callback returns a Boolean for each item. filter returns an array containing the input items for which that Boolean is true. This predicate keeps invalid items, so isEmpty succeeds only when none were found.

3. Inspect the HTTP response. Remove the Set Variable that supplies 400 in the invalid branch. Why is the error body no longer enough?

Show answer

The listener falls back to status 200. A client now receives a successful HTTP status with an error-shaped body. Status and body must both express the contract.

Choice handles a decision we expected to make. A processor can also fail while executing. The next chapter gives those failures an explicit boundary and response.

Next: Where a Failure Goes

Comments