#!/usr/bin/env bash # The article's first table: the same thirteen failures under each exception-handling setup. # Writes docs/output/matrix-.txt (full responses) and docs/output/matrix-summary.txt. set -uo pipefail source "$(dirname "$0")/env.sh" SCENARIOS=( "domain exception (OrderNotFoundException)|GET|/orders/999||" "type mismatch (/orders/abc)|GET|/orders/abc||" "invalid body (@Valid)|POST|/orders|{\"sku\":\"\",\"quantity\":0,\"customerEmail\":\"nope\"}|" "invalid @RequestParam (@Max)|GET|/orders?limit=500||" "ErrorResponseException (OutOfStock)|POST|/orders|{\"sku\":\"SKU-2\",\"quantity\":3,\"customerEmail\":\"a@b.co\"}|" "ResponseStatusException|GET|/orders/legacy/7||" "unknown path|GET|/no-such-thing||" "wrong HTTP method|DELETE|/orders/1||" "malformed JSON|POST|/orders|{\"sku\":|" "unexpected exception|GET|/orders/boom||" "exception in a servlet filter|GET|/orders/1||X-Tenant: BAD!" "401 no credentials|GET|/admin/orders||" "403 wrong role|GET|/admin/orders||AUTH:user:user" ) classify() { # content-type, body -> shape label local ct="$1" body="$2" if [[ "$ct" == *problem+json* ]]; then echo "problem+json" elif [[ -z "$body" ]]; then echo "(empty body)" elif [[ "$body" == *'"timestamp"'* && "$body" == *'"error"'* ]]; then echo "Boot /error JSON" else echo "other: ${ct:-none}"; fi } SUMMARY="$OUT/matrix-summary.txt" PROFILES=("" "boot-flag" "advice" "advice,errors" "catchall-first") declare -A CELL for profile in "${PROFILES[@]}"; do name="${profile:-defaults}" "$MODULE_DIR/scripts/run.sh" "$profile" || exit 1 file="$OUT/matrix-$name.txt" { echo "# Profile: $name (spring-boot 4.1.1, spring-framework 7.0.9)" echo } > "$file" i=0 for s in "${SCENARIOS[@]}"; do IFS='|' read -r label method path body extra <<< "$s" args=(-s -o /tmp/pd-body -D /tmp/pd-headers -X "$method" "$BASE$path") [ -n "$body" ] && args+=(-H 'Content-Type: application/json' --data "$body") if [[ "$extra" == AUTH:* ]]; then args+=(-u "${extra#AUTH:}"); elif [ -n "$extra" ]; then args+=(-H "$extra"); fi curl "${args[@]}" status=$(head -1 /tmp/pd-headers | awk '{print $2}') ct=$(grep -i '^content-type:' /tmp/pd-headers | head -1 | cut -d' ' -f2- | tr -d '\r') resp=$(cat /tmp/pd-body) { echo "## $label" echo "\$ curl -X $method $path${body:+ -d '$body'}${extra:+ [$extra]}" echo "HTTP $status Content-Type: ${ct:-}" [ -n "$resp" ] && echo "$resp" echo } >> "$file" CELL["$i|$name"]="$status $(classify "$ct" "$resp")" i=$((i+1)) done done "$MODULE_DIR/scripts/stop.sh" { echo "# Which error shape does each failure produce? (status + body shape)" echo "# Spring Boot 4.1.1 / Spring Framework 7.0.9. Full bodies in matrix-.txt" echo printf '%-42s | %-22s | %-22s | %-22s | %-22s | %-22s\n' "failure" "defaults" "boot-flag" "advice" "advice,errors" "catchall-first" printf '%s\n' "$(printf -- '-%.0s' {1..165})" i=0 for s in "${SCENARIOS[@]}"; do label="${s%%|*}" printf '%-42s | %-22s | %-22s | %-22s | %-22s | %-22s\n' "$label" "${CELL["$i|defaults"]}" "${CELL["$i|boot-flag"]}" "${CELL["$i|advice"]}" "${CELL["$i|advice,errors"]}" "${CELL["$i|catchall-first"]}" i=$((i+1)) done } > "$SUMMARY" cat "$SUMMARY"