Openapi Spring Webflux Validator Save

🌱 A friendly kotlin library to validate API endpoints using an OpenApi 3.0 and Swagger 2.0 specification

Project README

openapi-spring-webflux-validator

Maven Central Codacy Badge All Contributors

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.

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

Apache 2.0

Buy Me A Coffee

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Carmine DiMascio

💻 ⚠️ 📖

Krzysiek Kruczyński

💻 ⚠️ 📖

Chejerla Karthik

💻

Katie Levy

💻

Ilya Builuk

💻

Simon Zambrovski

💻 ⚠️

This project follows the all-contributors specification. Contributions of any kind welcome!

Open Source Agenda is not affiliated with "Openapi Spring Webflux Validator" Project. README Source: cdimascio/openapi-spring-webflux-validator

Open Source Agenda Badge

Open Source Agenda Rating