This behavior occured to me whilst working with an Swagger OpenAPI Endpoint ...
The OpenAPI definition looked alike:
paths:
/endpoint:
get:
summary: short summary
operationId: endpointFunction
parameters:
- name: timestamp
in: query
description: 'Given timestamp'
required: false
schema:
type: string
format: date-time
example: "2023-01-05T13:11:40.020747+01:00"
responses:
200:
description: 'Ok'
404:
description: 'Not Ok'
content: {}
500:
description: 'Failure'
content: { }
After compiling the definition using Maven, it looks like this:
public interface EndpointApiDelegate {
default Optional<NativeWebRequest> getRequest() {
return Optional.empty();
}
/**
* GET /endpoint : short summary
*
* @param timestamp Given timestamp (optional)
* @return Ok (status code 200)
* or Not ok (status code 404)
* or Interner Fehler (status code 500)
* @see EndpointApi#endpointFunction
*/
default ResponseEntity<Void> endpointFunction(OffsetDateTime timestamp) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
Now, trying to send data directly via ..
- a browser's URL
- a http-file (.http)
- postman and others
### Sample FAILING call in .http-file
GET http://localhost:{{port}}/endpoint?timestamp=22023-01-05T13:11:40.020747+01:00
.. using the given example (2023-01-05T13:11:40.020747+01:00), it fails.
"Failed to convert value of type [java.lang.String] to required type [java.time.LocalDateTime]; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam @org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime] for value ... .. .
Reasoning
Query parameters are parsed with a different charset (not sure these are the right words, but it fits the behaviour)
Therefore the parsing fails reading the '+'-character.
Resolution
### Sample WORKING call in .http-file
GET http://localhost:{{port}}/endpoint?timestamp=2023-01-03T11%3A29%3A47.526612%2B01%3A00
Attention
In case the solution hopefully feels impractical ... .. .
. .. ... if it does: YOU ARE RIGHT !!!
Passing Date-Time (in this case) is a 'bad endpoint design'-decision
Swagger OpenApi offers checks for int (minimum and maximum) and string (regex-pattern) directly in the OpenApi definition.
Still Date-Time offers special pitfalls as min/max values may be dependent on relative 'everyday passing time'.
I imagine automated min/max values are therefore not yet implemented.
Is there a better solution? Sure! :)
Use a POST request instead and define APPLICATION JSON in the RequestBody instead of an URL-RequestParameter.
components:
schemas:
TimestampEntity:
type: object
properties:
timestamp:
description: 'timestamp'
type: string
format: date-time
example: "2023-01-05T13:11:40.020747+01:00"
paths:
/endpoint:
post:
summary: short summary
operationId: endpointFunction
# parameters:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TimestampEntity'
responses:
200:
description: 'Ok'
404:
description: 'Not Ok'
content: {}
500:
description: 'Failure'
content: { }
That compiles to:
default Optional<NativeWebRequest> getRequest() {
return Optional.empty();
}
/**
* POST /endpoint : short summary
*
* @param timestampEntity
(optional)
* @return Ok (status code 200)
* or Not Ok (status code 404)
* or Failure (status code 500)
* @see EndpointApi#endpointFunction
*/
default ResponseEntity<Void> endpointFunction(TimestampEntity timestampEntity) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
Using a generated Timestamp.class:
public class TimestampEntity {
@JsonProperty("timestamp")
private OffsetDateTime timestamp;
public TimestampEntity timestamp(OffsetDateTime timestamp) {
this.timestamp = timestamp;
return this;
}
... .. .
Now the value (2023-01-05T13:11:40.020747+01:00) will be parsed appropriately.
This is the end of being entangled in an URL charset-scuffle ;-)