Javalin fixes and validation (#20981)

* Fix for problems when a nullable parameter has a default value

When a default value is present the parameter is moved to non-nullable. This works because we could replace the null with a default value. This will actually set the default value.

* Adds (optional) validation to the beans.

Option to set: `useBeanValidation` to `true`. This will insert validation rules from the `jakarta.validation` package much like it does with the spring generator.

Aso sneaked in `JsonProperty` annotations in order to more formally adhere to the name in the spec as opposed to configuring ObjectMappers in order to translate names to match the spec.

* Update samples

* Fix for required headerParams
This commit is contained in:
koenlavooij 2025-03-29 17:58:48 +01:00 committed by GitHub
parent 045a9c16b1
commit 5e5832d982
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 233 additions and 5 deletions

View File

@ -0,0 +1,4 @@
{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
@field:jakarta.validation.Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{!
}}{{^isContainer}}{{^isPrimitiveType}}{{^isNumber}}{{^isUuid}}{{^isDateTime}}
@field:jakarta.validation.Valid{{/isDateTime}}{{/isUuid}}{{/isNumber}}{{/isPrimitiveType}}{{/isContainer}}

View File

@ -0,0 +1,38 @@
{{!
format: email
}}{{#isEmail}}
@field:jakarta.validation.constraints.Email{{/isEmail}}{{!
pattern set
}}{{#pattern}}
@field:jakarta.validation.constraints.Pattern(regexp="{{{pattern}}}"{{#vendorExtensions.x-pattern-message}}, message="{{vendorExtensions.x-pattern-message}}"{{/vendorExtensions.x-pattern-message}}){{/pattern}}{{!
minLength && maxLength set
}}{{#minLength}}{{#maxLength}}
@field:jakarta.validation.constraints.Size(min={{minLength}},max={{maxLength}}){{/maxLength}}{{/minLength}}{{!
minLength set, maxLength not
}}{{#minLength}}{{^maxLength}}
@field:jakarta.validation.constraints.Size(min={{minLength}}){{/maxLength}}{{/minLength}}{{!
minLength not set, maxLength set
}}{{^minLength}}{{#maxLength}}
@field:jakarta.validation.constraints.Size(max={{.}}){{/maxLength}}{{/minLength}}{{!
@Size: minItems && maxItems set
}}{{#minItems}}{{#maxItems}}
@field:jakarta.validation.constraints.Size(min={{minItems}},max={{maxItems}}) {{/maxItems}}{{/minItems}}{{!
@Size: minItems set, maxItems not
}}{{#minItems}}{{^maxItems}}
@field:jakarta.validation.constraints.Size(min={{minItems}}){{/maxItems}}{{/minItems}}{{!
@Size: minItems not set && maxItems set
}}{{^minItems}}{{#maxItems}}
@field:jakarta.validation.constraints.Size(max={{.}}){{/maxItems}}{{/minItems}}{{!
check for integer or long / all others=decimal type with @Decimal*
isInteger set
}}{{#isInteger}}{{#minimum}}
@field:jakarta.validation.constraints.Min({{.}}){{/minimum}}{{#maximum}}
@field:jakarta.validation.constraints.Max({{.}}){{/maximum}}{{/isInteger}}{{!
isLong set
}}{{#isLong}}{{#minimum}}
@field:jakarta.validation.constraints.Min({{.}}L){{/minimum}}{{#maximum}}
@field:jakarta.validation.constraints.Max({{.}}L){{/maximum}}{{/isLong}}{{!
Not Integer, not Long => we have a decimal value!
}}{{^isInteger}}{{^isLong}}{{#minimum}}
@field:jakarta.validation.constraints.DecimalMin("{{.}}"){{/minimum}}{{#maximum}}
@field:jakarta.validation.constraints.DecimalMax("{{.}}"){{/maximum}}{{/isLong}}{{/isInteger}}

View File

@ -0,0 +1,6 @@
{{#description}}
/* {{{.}}} */
{{/description}}
{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}}
@field:com.fasterxml.jackson.annotation.JsonProperty("{{{baseName}}}")
{{>modelMutable}} {{{name}}}: {{#isEnum}}{{{classname}}}.{{{nameInPascalCase}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{{defaultValue}}}{{^defaultValue}}null{{/defaultValue}}

View File

@ -0,0 +1,6 @@
{{#description}}
/* {{{.}}} */
{{/description}}
{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}}
@field:com.fasterxml.jackson.annotation.JsonProperty("{{{baseName}}}")
{{>modelMutable}} {{{name}}}: {{#isEnum}}{{{classname}}}.{{{nameInPascalCase}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}}?{{/isNullable}}{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}

View File

@ -0,0 +1,2 @@
{{#defaultValue}} ?:{{#isInteger}}{{{defaultValue}}}{{/isInteger}}{{#isLong}}{{{defaultValue}}}L{{/isLong}}{{#isShort}}{{{defaultValue}}}.toShort(){{/isShort}}{{#isDouble}}{{{defaultValue}}}.toDouble(){{/isDouble}}{{#isFloat}}{{{defaultValue}}}f{{/isFloat}}{{#isEnum}}{{{dataType}}}.valueOf("{{{defaultValue}}}"){{/isEnum}}{{#isBoolean}}{{{defaultValue}}}{{/isBoolean}}{{#isString}}"{{{defaultValue}}}"{{/isString}}{{#isUuid}}"UUID.fromString({{{defaultValue}}})"{{/isUuid}}{{#isUuid}}"UUID.fromString({{{defaultValue}}})"{{/isUuid}}
{{/defaultValue}}

View File

@ -1 +1 @@
{{#isPathParam}}ctx.pathParamAsClass<{{{dataType}}}>("{{baseName}}").get(){{/isPathParam}}
{{#isPathParam}}ctx.pathParamAsClass<{{{dataType}}}>("{{baseName}}").get(){{>paramDefault}}{{/isPathParam}}

View File

@ -1 +1 @@
{{#isQueryParam}}{{#isArray}}ctx.queryParams("{{baseName}}"){{/isArray}}{{^isArray}}ctx.queryParamAsClass<String>("{{baseName}}").get(){{/isArray}}{{/isQueryParam}}
{{#isQueryParam}}{{#isArray}}ctx.queryParams("{{baseName}}"){{/isArray}}{{^isArray}}ctx.queryParamAsClass<String>("{{baseName}}").get(){{>paramDefault}}{{/isArray}}{{/isQueryParam}}

View File

@ -0,0 +1,4 @@
{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}
@field:jakarta.validation.Valid{{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{!
}}{{^isContainer}}{{^isPrimitiveType}}{{^isNumber}}{{^isUuid}}{{^isDateTime}}
@field:jakarta.validation.Valid{{/isDateTime}}{{/isUuid}}{{/isNumber}}{{/isPrimitiveType}}{{/isContainer}}

View File

@ -0,0 +1,38 @@
{{!
format: email
}}{{#isEmail}}
@field:jakarta.validation.constraints.Email{{/isEmail}}{{!
pattern set
}}{{#pattern}}
@field:jakarta.validation.constraints.Pattern(regexp="{{{pattern}}}"{{#vendorExtensions.x-pattern-message}}, message="{{vendorExtensions.x-pattern-message}}"{{/vendorExtensions.x-pattern-message}}){{/pattern}}{{!
minLength && maxLength set
}}{{#minLength}}{{#maxLength}}
@field:jakarta.validation.constraints.Size(min={{minLength}},max={{maxLength}}){{/maxLength}}{{/minLength}}{{!
minLength set, maxLength not
}}{{#minLength}}{{^maxLength}}
@field:jakarta.validation.constraints.Size(min={{minLength}}){{/maxLength}}{{/minLength}}{{!
minLength not set, maxLength set
}}{{^minLength}}{{#maxLength}}
@field:jakarta.validation.constraints.Size(max={{.}}){{/maxLength}}{{/minLength}}{{!
@Size: minItems && maxItems set
}}{{#minItems}}{{#maxItems}}
@field:jakarta.validation.constraints.Size(min={{minItems}},max={{maxItems}}) {{/maxItems}}{{/minItems}}{{!
@Size: minItems set, maxItems not
}}{{#minItems}}{{^maxItems}}
@field:jakarta.validation.constraints.Size(min={{minItems}}){{/maxItems}}{{/minItems}}{{!
@Size: minItems not set && maxItems set
}}{{^minItems}}{{#maxItems}}
@field:jakarta.validation.constraints.Size(max={{.}}){{/maxItems}}{{/minItems}}{{!
check for integer or long / all others=decimal type with @Decimal*
isInteger set
}}{{#isInteger}}{{#minimum}}
@field:jakarta.validation.constraints.Min({{.}}){{/minimum}}{{#maximum}}
@field:jakarta.validation.constraints.Max({{.}}){{/maximum}}{{/isInteger}}{{!
isLong set
}}{{#isLong}}{{#minimum}}
@field:jakarta.validation.constraints.Min({{.}}L){{/minimum}}{{#maximum}}
@field:jakarta.validation.constraints.Max({{.}}L){{/maximum}}{{/isLong}}{{!
Not Integer, not Long => we have a decimal value!
}}{{^isInteger}}{{^isLong}}{{#minimum}}
@field:jakarta.validation.constraints.DecimalMin("{{.}}"){{/minimum}}{{#maximum}}
@field:jakarta.validation.constraints.DecimalMax("{{.}}"){{/maximum}}{{/isLong}}{{/isInteger}}

View File

@ -0,0 +1,6 @@
{{#description}}
/* {{{.}}} */
{{/description}}
{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}}
@field:com.fasterxml.jackson.annotation.JsonProperty("{{{baseName}}}")
{{>modelMutable}} {{{name}}}: {{#isEnum}}{{{classname}}}.{{{nameInPascalCase}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{{defaultValue}}}{{^defaultValue}}null{{/defaultValue}}

View File

@ -0,0 +1,6 @@
{{#description}}
/* {{{.}}} */
{{/description}}
{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}}
@field:com.fasterxml.jackson.annotation.JsonProperty("{{{baseName}}}")
{{>modelMutable}} {{{name}}}: {{#isEnum}}{{{classname}}}.{{{nameInPascalCase}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}}?{{/isNullable}}{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}

View File

@ -1 +1 @@
{{#isHeaderParam}}ctx.header("{{baseName}}"){{/isHeaderParam}}
{{#isHeaderParam}}ctx.header("{{baseName}}"){{#required}}{{^defaultValue}} ?: throw io.javalin.http.BadRequestResponse("Required header {{baseName}} not present") {{/defaultValue}}{{#defaultValue}}{{>paramDefault}}{{/defaultValue}}{{/required}}{{/isHeaderParam}}

View File

@ -0,0 +1,2 @@
{{#defaultValue}} ?:{{#isInteger}}{{{defaultValue}}}{{/isInteger}}{{#isLong}}{{{defaultValue}}}L{{/isLong}}{{#isShort}}{{{defaultValue}}}.toShort(){{/isShort}}{{#isDouble}}{{{defaultValue}}}.toDouble(){{/isDouble}}{{#isFloat}}{{{defaultValue}}}f{{/isFloat}}{{#isEnum}}{{{dataType}}}.valueOf("{{{defaultValue}}}"){{/isEnum}}{{#isBoolean}}{{{defaultValue}}}{{/isBoolean}}{{#isString}}"{{{defaultValue}}}"{{/isString}}{{#isUuid}}"UUID.fromString({{{defaultValue}}})"{{/isUuid}}{{#isUuid}}"UUID.fromString({{{defaultValue}}})"{{/isUuid}}
{{/defaultValue}}

View File

@ -1 +1 @@
{{#isPathParam}}ctx.pathParamAsClass<{{{dataType}}}>("{{baseName}}").get(){{/isPathParam}}
{{#isPathParam}}ctx.pathParamAsClass<{{{dataType}}}>("{{baseName}}").get(){{>paramDefault}}{{/isPathParam}}

View File

@ -1 +1 @@
{{#isQueryParam}}{{#isArray}}ctx.queryParams("{{baseName}}"){{/isArray}}{{^isArray}}ctx.queryParamAsClass<{{{dataType}}}>("{{baseName}}"){{^required}}.allowNullable(){{/required}}.get(){{/isArray}}{{/isQueryParam}}
{{#isQueryParam}}{{#isArray}}ctx.queryParams("{{baseName}}"){{/isArray}}{{^isArray}}ctx.queryParamAsClass<{{{dataType}}}>("{{baseName}}"){{^required}}.allowNullable(){{/required}}.get(){{>paramDefault}}{{/isArray}}{{/isQueryParam}}

View File

@ -20,9 +20,17 @@ package org.openapitools.server.models
* @param notNullableNotRequired
*/
data class Pet(
@field:com.fasterxml.jackson.annotation.JsonProperty("notNullable_required")
val notNullableRequired: kotlin.String,
@field:com.fasterxml.jackson.annotation.JsonProperty("nullable_required")
val nullableRequired: kotlin.String?,
@field:com.fasterxml.jackson.annotation.JsonProperty("nullable_notRequired")
val nullableNotRequired: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("notNullable_notRequired")
val notNullableNotRequired: kotlin.String? = null
)

View File

@ -18,7 +18,11 @@ package org.openapitools.server.models
* @param name
*/
data class Category(
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("name")
val name: kotlin.String? = null
)

View File

@ -19,8 +19,14 @@ package org.openapitools.server.models
* @param message
*/
data class ModelApiResponse(
@field:com.fasterxml.jackson.annotation.JsonProperty("code")
val code: kotlin.Int? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("type")
val type: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("message")
val message: kotlin.String? = null
)

View File

@ -22,12 +22,24 @@ package org.openapitools.server.models
* @param complete
*/
data class Order(
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("petId")
val petId: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("quantity")
val quantity: kotlin.Int? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("shipDate")
val shipDate: java.time.OffsetDateTime? = null,
/* Order Status */
@field:com.fasterxml.jackson.annotation.JsonProperty("status")
val status: Order.Status? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("complete")
val complete: kotlin.Boolean? = false
)
{

View File

@ -24,12 +24,24 @@ import org.openapitools.server.models.Tag
* @param status pet status in the store
*/
data class Pet(
@field:com.fasterxml.jackson.annotation.JsonProperty("name")
val name: kotlin.String,
@field:com.fasterxml.jackson.annotation.JsonProperty("photoUrls")
val photoUrls: kotlin.collections.List<kotlin.String>,
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("category")
val category: Category? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("tags")
val tags: kotlin.collections.List<Tag>? = null,
/* pet status in the store */
@field:com.fasterxml.jackson.annotation.JsonProperty("status")
val status: Pet.Status? = null
)
{

View File

@ -18,7 +18,11 @@ package org.openapitools.server.models
* @param name
*/
data class Tag(
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("name")
val name: kotlin.String? = null
)

View File

@ -24,14 +24,30 @@ package org.openapitools.server.models
* @param userStatus User Status
*/
data class User(
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("username")
val username: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("firstName")
val firstName: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("lastName")
val lastName: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("email")
val email: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("password")
val password: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("phone")
val phone: kotlin.String? = null,
/* User Status */
@field:com.fasterxml.jackson.annotation.JsonProperty("userStatus")
val userStatus: kotlin.Int? = null
)

View File

@ -18,7 +18,11 @@ package org.openapitools.server.models
* @param name
*/
data class Category(
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("name")
val name: kotlin.String? = null
)

View File

@ -19,8 +19,14 @@ package org.openapitools.server.models
* @param message
*/
data class ModelApiResponse(
@field:com.fasterxml.jackson.annotation.JsonProperty("code")
val code: kotlin.Int? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("type")
val type: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("message")
val message: kotlin.String? = null
)

View File

@ -22,12 +22,24 @@ package org.openapitools.server.models
* @param complete
*/
data class Order(
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("petId")
val petId: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("quantity")
val quantity: kotlin.Int? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("shipDate")
val shipDate: java.time.OffsetDateTime? = null,
/* Order Status */
@field:com.fasterxml.jackson.annotation.JsonProperty("status")
val status: Order.Status? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("complete")
val complete: kotlin.Boolean? = false
)
{

View File

@ -24,12 +24,24 @@ import org.openapitools.server.models.Tag
* @param status pet status in the store
*/
data class Pet(
@field:com.fasterxml.jackson.annotation.JsonProperty("name")
val name: kotlin.String,
@field:com.fasterxml.jackson.annotation.JsonProperty("photoUrls")
val photoUrls: kotlin.collections.List<kotlin.String>,
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("category")
val category: Category? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("tags")
val tags: kotlin.collections.List<Tag>? = null,
/* pet status in the store */
@field:com.fasterxml.jackson.annotation.JsonProperty("status")
val status: Pet.Status? = null
)
{

View File

@ -18,7 +18,11 @@ package org.openapitools.server.models
* @param name
*/
data class Tag(
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("name")
val name: kotlin.String? = null
)

View File

@ -24,14 +24,30 @@ package org.openapitools.server.models
* @param userStatus User Status
*/
data class User(
@field:com.fasterxml.jackson.annotation.JsonProperty("id")
val id: kotlin.Long? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("username")
val username: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("firstName")
val firstName: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("lastName")
val lastName: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("email")
val email: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("password")
val password: kotlin.String? = null,
@field:com.fasterxml.jackson.annotation.JsonProperty("phone")
val phone: kotlin.String? = null,
/* User Status */
@field:com.fasterxml.jackson.annotation.JsonProperty("userStatus")
val userStatus: kotlin.Int? = null
)