Add custom-validation, etag-caching, restclient-basic-auth: Boot 4.1 API pass
Three companion modules verifying and rewriting the Boot 4.1.1 / Framework 7.0.9 story for three older articles: the javax->jakarta.validation namespace fix plus Jakarta Validation 3.1 record-validation clarification, ETag/ conditional-request APIs re-verified unchanged plus the starter rename, and RestTemplate Basic Auth rebuilt on RestClient with the exchange() trap called out. 19 real passing tests generate every transcript quoted from the three companion articles. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EQNA6DJ9VgCtW6zhCE8Xud
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
Class: jakarta.validation.Validation
|
||||
Jar file: jakarta.validation-api-3.1.1.jar
|
||||
Bundle-SymbolicName: jakarta.validation.jakarta.validation-api
|
||||
Bundle-Version: 3.1.1
|
||||
Implementation-Version: null
|
||||
@@ -0,0 +1,4 @@
|
||||
Class: org.hibernate.validator.internal.engine.ValidatorFactoryImpl
|
||||
Jar file: hibernate-validator-9.1.3.Final.jar
|
||||
Implementation-Title: hibernate-validator
|
||||
Implementation-Version: 9.1.3.Final
|
||||
@@ -0,0 +1,6 @@
|
||||
$ curl -s -X POST localhost:8080/contact \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"email":"[email protected]","message":"This is spam."}'
|
||||
|
||||
HTTP status: 400
|
||||
Body: {"message":"Message contains 'spam' and must be at least 50 characters long."}
|
||||
@@ -0,0 +1,6 @@
|
||||
$ curl -s -X POST localhost:8080/contact \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"email":"[email protected]","message":"This is a legitimate message about an issue I'm facing."}'
|
||||
|
||||
HTTP status: 200
|
||||
Body: Contact form submitted successfully!
|
||||
@@ -0,0 +1,12 @@
|
||||
$ curl -s -i -X POST localhost:8080/contact-record \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"email":"[email protected]","message":"This is spam."}'
|
||||
|
||||
HTTP status: 400
|
||||
Body: '' (empty!)
|
||||
|
||||
# The record parameter has no BindingResult to collect field errors into, unlike
|
||||
# ContactController#submitContactForm's class-based, BindingResult-carrying signature.
|
||||
# A failing constraint throws MethodArgumentNotValidException instead, and Boot 4.1's
|
||||
# default handling for it returns an EMPTY body when the request has no Accept header
|
||||
# asking for a structured error. See the next transcript for what changes with one.
|
||||
@@ -0,0 +1,12 @@
|
||||
$ curl -s -X POST localhost:8080/contact-record \
|
||||
-H 'Content-Type: application/json' -H 'Accept: application/json' \
|
||||
-d '{"email":"[email protected]","message":"This is spam."}'
|
||||
|
||||
HTTP status: 400
|
||||
Content-Type: null
|
||||
Body: '' (still empty!)
|
||||
|
||||
# An Accept header alone changes nothing -- the body is still empty. What actually turns
|
||||
# on a structured error body is the spring.mvc.problemdetails.enabled property, which is
|
||||
# off by default and unrelated to content negotiation. See
|
||||
# docs/output/07-problemdetails-enabled-record-rejected.txt for the same request with it on.
|
||||
@@ -0,0 +1,6 @@
|
||||
$ curl -s -X POST localhost:8080/booking \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"startDate":"2026-05-10","endDate":"2026-05-01"}'
|
||||
|
||||
HTTP status: 400
|
||||
Body:
|
||||
@@ -0,0 +1,10 @@
|
||||
$ curl -s -X POST localhost:8080/booking-record \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"startDate":"2026-05-10","endDate":"2026-05-01"}'
|
||||
|
||||
HTTP status: 400
|
||||
Body:
|
||||
|
||||
# Class-level @DateRangeValid, placed on the record's type declaration exactly as it would
|
||||
# be on a class, is honoured the same way. The validator reads booking.startDate() /
|
||||
# booking.endDate() (accessor methods) instead of getStartDate()/getEndDate().
|
||||
@@ -0,0 +1,6 @@
|
||||
$ curl -s -X POST localhost:8080/booking-record \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"startDate":"2026-05-01","endDate":"2026-05-10"}'
|
||||
|
||||
HTTP status: 200
|
||||
Body: Booking accepted: 2026-05-01 -> 2026-05-10
|
||||
@@ -0,0 +1,18 @@
|
||||
# application.yaml: spring.mvc.problemdetails.enabled: true (opt-in; unrelated to Bean
|
||||
# Validation 3.1 itself -- this property already existed in Boot 3)
|
||||
|
||||
$ curl -s -X POST localhost:8080/contact-record \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"email":"[email protected]","message":"This is spam."}'
|
||||
|
||||
HTTP status: 400
|
||||
Content-Type: application/problem+json
|
||||
Body: {"detail":"Invalid request content.","instance":"/contact-record","status":400,"title":"Bad Request"}
|
||||
|
||||
# Progress over the empty body, but notice what is MISSING: no mention of "spam", no field
|
||||
# name. Boot's default MethodArgumentNotValidException -> ProblemDetail mapping fills in
|
||||
# only the generic RFC 9457 fields (title, status, detail="Invalid request content.").
|
||||
# Per-field messages -- what the class-based /contact endpoint hand-rolls from
|
||||
# bindingResult.getFieldErrors() -- need a custom @ExceptionHandler that does the same
|
||||
# thing into the ProblemDetail's own "properties" map. Turning the property on is not,
|
||||
# by itself, a drop-in replacement for BindingResult-based error reporting.
|
||||
Reference in New Issue
Block a user