Awesome
openapi-spring-webflux-validator
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<!-- ALL-CONTRIBUTORS-BADGE:END -->A friendly kotlin library to validate API endpoints using an OpenApi 3 or Swagger 2 specification. Great with webflux functional. It works happily with Spring Webflux 6's baseline of Jakarta JVM runtime >=17.
<p align="center"> <img width="300" src="https://raw.githubusercontent.com/cdimascio/openapi-spring-webflux-validator/master/assets/openapi-webflux-validator-logo2.png" width="600"/> </p>Supports specifications in YAML and JSON
Prequisites
and Spring Webflux 6 + Java >=17
For use with Spring Boot 2 and Webflux 5, use openapi-spring-webflux-validator
version 3.5.0
. Java 8 or greater is required.
Install
Maven
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>openapi-spring-webflux-validator</artifactId>
<version>4.2.0</version>
</dependency>
Gradle
compile 'io.github.cdimascio:openapi-spring-webflux-validator:4.2.0'
For sbt, grape, ivy and more, see here
Usage (Kotlin)
This section and the next describe usage with Kotlin and Java respectively.
See this complete Spring Webflux example that uses openapi-spring-webflux-validator.
Configure (Kotlin)
This one-time configuration requires you to provide the location of the openapi/swagger specification and an optional custom error handler.
Supports JSON
and YAML
import io.github.cdimascio.openapi.Validate
val validate = Validate.configure("static/api.yaml")
with custom error handler
import org.springframework.web.reactive.function.server.ServerRequest
data class MyError(val request: ServerRequest, val code: String, val messages: List<String>)
val validate = Validate.configure("static/api.json") { request, status, messages ->
MyError(request, status.name, messages)
}
with custom ObjectMapper factory:
val validate = Validate.configure(
openApiSwaggerPath = "api.yaml",
errorHandler = { request, status, message -> ValidationError(request, status.value(), message[0]) },
objectMapperFactory = { ObjectMapper()
.registerKotlinModule()
.registerModule(JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) }
)
Validate a request (Kotlin + Reactor)
You can now validate a request in a coroutine style,
using the validate
instance created above:
without a body
validate.request(req) {
// Do stuff e.g. return a list of names
ok().body(Mono.just(listOf("carmine", "alex", "eliana")))
}
with body
validate.request(req).withBody(User::class.java) { body ->
// Note that body is deserialized as User!
// Now you can do stuff.
// For example, lets echo the request as the response
ok().body(Mono.just(body))
}
with body you want to process as string (e.g. for computing a request signature), or that you want to deserialize somehow specifically
val identity: (String) -> String = { it }
validate.request(req).withBody(String::class.java, readValue = identity) { body ->
ok().body(Mono.just("content length is ${body.length}"))
}
Validate a request (Kotlin + coroutines)
Or you can validate a request in a coroutine style,
using the validate
instance created above:
without a body
validate.requestAndAwait(req) {
// Do stuff e.g. return a list of names
ok().bodyValueAndAwait(listOf("carmine", "alex", "eliana"))
}
with body
validate.request(req).awaitBody(User::class.java) { body: User ->
// Note that body is deserialized as User!
// Now you can do stuff.
// For example, lets echo the request as the response
ok().bodyValueAndAwait(body)
}
with body you want to process as string (e.g. for computing a request signature), or that you want to deserialize somehow specifically
val identity: (String) -> String = { it }
validate.request(req).awaitBody(String::class.java, identity) { body: String ->
ok().bodyValueAndAwait("content length is ${body.length}")
}
Usage
Configure (Java)
This one-time configuration requires you to provide the location of the openapi/swagger specification and an optional custom error handler.
import io.github.cdimascio.openapi.Validate;
Validate<ValidationError> validate = Validate.configure("static/api.json")
with custom error handler
import org.springframework.web.reactive.function.server.ServerRequest;
class MyError {
private ServerRequest request;
private String id;
private String messages;
public MyError(ServerRequest request, String id, List<String> messages) {
this.request = request;
this.id = id;
this.messages = messages;
}
public ServerRequest getRequest() {
return request;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<String> getMessages() {
return messages;
}
public void setMessages(List<String> messages) {
this.messages = messages;
}
}
Validate<ValidationError> validate = Validate.configure("static/api.json", (request, status, messages) ->
new MyError(request, status.getName(), messages)
);
Validate a request (Java)
Using the validate
instance created above, you can now validate a request:
without a body
ArrayList<String> users = new ArrayList<String>() {{
add("carmine");
add("alex");
add("eliana");
}};
validate.request(req, () ->
// Do stuff e.g. return a list of user names
ServerResponse.ok().bodyValue(users)
);
with body
validate
.request(req)
.withBody(User.class, user ->
// Note that body is deserialized as User!
// Now you can do stuff.
// For example, lets echo the request as the response
ServerResponse.ok().bodyValue(user)
);
with body you want to process as string (e.g. for computing a request signature)
validate
.request(req)
.withBody(String.class, s -> s, body ->
ServerResponse.ok().bodyValue("content length is " + body.length())
);
Example Validation Output
Let's assume a POST
request to create a user requires the following request body:
{
"firstname": "carmine",
"lastname": "dimasico"
}
Let's now assume an API user misspells lastname
as lastnam
curl -X POST http://localhost:8080/api/users -H "Content-Type: application/json" -d'{
"firstname": "c",
"lastnam": "d"
}'
openapi-spring-webflux-validator
automatically validates the request against a Swagger spect and returns:
{
"code": 400,
"messages":[
"Object instance has properties which are not allowed by the schema: [\"lastnam\"]",
"Object has missing required properties ([\"lastname\"])"
]
}
Woah! Cool!! :-D
Example
Let's say you have an endpoint /users
that supports both GET
and POST
operations.
You can create those routes and validate them like so:
Create the routes in a reactive or coroutine style:
package myproject.controllers
import org.springframework.core.io.ClassPathResource
import org.springframework.http.MediaType.*
import org.springframework.web.reactive.function.server.ServerResponse.permanentRedirect
import org.springframework.web.reactive.function.server.coRouter
import org.springframework.web.reactive.function.server.plus
import org.springframework.web.reactive.function.server.router
import java.net.URI
class Routes(private val userHandler: UserHandler) {
fun router() = router {
"/api".nest {
accept(APPLICATION_JSON).nest {
POST("/users", userHandler::create)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::findAll)
}
}
} + coRouter {
"/coApi".nest {
accept(APPLICATION_JSON).nest {
POST("/users", userHandler::coCreate)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::coFindAll)
}
}
}
}
package myproject
import io.github.cdimascio.openapi.Validate
val validate = Validate.configure("static/api.yaml")
Validate with openapi-spring-webflux-validator
package myproject.controllers
import myproject.models.User
import myproject.validate
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.ServerResponse.ok
import org.springframework.web.reactive.function.server.bodyValueAndAwait
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
class UserHandler {
fun findAll(req: ServerRequest): Mono<ServerResponse> {
return validate.request(req) {
ok().bodyValue(listOf("carmine", "alex", "eliana"))
}
}
fun create(req: ServerRequest): Mono<ServerResponse> {
return validate.request(req).withBody(User::class.java) {
// it is the request body deserialized as User
ok().bodyValue(it)
}
}
suspend fun coFindAll(req: ServerRequest): ServerResponse {
return validate.requestAndAwait(req) {
ok().bodyValueAndAwait(listOf("carmine", "alex", "eliana"))
}
}
suspend fun coCreate(req: ServerRequest): ServerResponse {
return validate.request(req).awaitBody(User::class.java) {
// it is the request body deserialized as User
ok().bodyValueAndAwait(it)
}
}
}
License
<a href="https://www.buymeacoffee.com/m97tA5c" target="_blank"><img src="https://bmc-cdn.nyc3.digitaloceanspaces.com/BMC-button-images/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
Contributors ✨
Thanks goes to these wonderful people (emoji key):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- prettier-ignore-start --> <!-- markdownlint-disable --> <table> <tr> <td align="center"><a href="https://github.com/cdimascio"><img src="https://avatars1.githubusercontent.com/u/4706618?v=4" width="100px;" alt=""/><br /><sub><b>Carmine DiMascio</b></sub></a><br /><a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=cdimascio" title="Code">💻</a> <a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=cdimascio" title="Tests">⚠️</a> <a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=cdimascio" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/krzykrucz"><img src="https://avatars1.githubusercontent.com/u/18364177?v=4" width="100px;" alt=""/><br /><sub><b>Krzysiek Kruczyński</b></sub></a><br /><a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=krzykrucz" title="Code">💻</a> <a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=krzykrucz" title="Tests">⚠️</a> <a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=krzykrucz" title="Documentation">📖</a></td> <td align="center"><a href="https://github.com/chejerlakarthik"><img src="https://avatars0.githubusercontent.com/u/12871079?v=4" width="100px;" alt=""/><br /><sub><b>Chejerla Karthik</b></sub></a><br /><a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=chejerlakarthik" title="Code">💻</a></td> <td align="center"><a href="http://www.katielevy.com"><img src="https://avatars0.githubusercontent.com/u/8975181?v=4" width="100px;" alt=""/><br /><sub><b>Katie Levy</b></sub></a><br /><a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=katielevy1" title="Code">💻</a></td> <td align="center"><a href="https://github.com/reinterpretcat"><img src="https://avatars1.githubusercontent.com/u/1611077?v=4" width="100px;" alt=""/><br /><sub><b>Ilya Builuk</b></sub></a><br /><a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=reinterpretcat" title="Code">💻</a></td> <td align="center"><a href="http://simon.zambrovski.org/"><img src="https://avatars0.githubusercontent.com/u/673128?v=4" width="100px;" alt=""/><br /><sub><b>Simon Zambrovski</b></sub></a><br /><a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=zambrovski" title="Code">💻</a> <a href="https://github.com/cdimascio/openapi-spring-webflux-validator/commits?author=zambrovski" title="Tests">⚠️</a></td> </tr> </table> <!-- markdownlint-enable --> <!-- prettier-ignore-end --> <!-- ALL-CONTRIBUTORS-LIST:END -->This project follows the all-contributors specification. Contributions of any kind welcome!