swagger-spring-functional

A friendly kotlin library used to validate API endpoints against a Swagger 2.0 specification

A friendly kotlin library to validate API endpoints using an OpenApi 3.0.0 or Swagger 2.0 specification. Great with webflux functional. It works happily with any JVM language including Java >=8.

Supports specifications in YAML and JSON

See this complete Spring 5 Webflux example that uses openapi-spring-webflux-validator.

Prequisites

Java 8 or greater

Install

Maven

<dependency>
    <groupId>io.github.cdimascio</groupId>
    <artifactId>openapi-spring-webflux-validator</artifactId>
    <version>2.0.0</version>
</dependency>

Gradle

compile 'io.github.cdimascio:openapi-spring-webflux-validator:2.0.0'

For sbt, grape, ivy and more, see here

Usage (Kotlin)

This section and the next describe usage with Kotlin and Java respectively.

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.swagger.Validate
val validate = Validate.configure("static/api.yaml")

with custom error handler

data class MyError(val id: String, val messages: List<String>)
val validate = Validate.configure("static/api.json") { status, messages ->
   Error(status.name, messages)
}

with custom ObjectMapper factory:

val validate = Validate.configure(
   openApiSwaggerPath = "api.yaml",
   errorHandler = { status, message -> ValidationError(status.value(), message[0]) },
   objectMapperFactory = { ObjectMapper()
       .registerKotlinModule()
       .registerModule(JavaTimeModule())
       .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) }
)

Validate a request (Kotlin)

Using the validate instance created above, you can now validate a request:

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))
}

Usage (Java 8 or greater)

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.swagger.Validate;
Validate<ValidationError> validate = Validate.configure("static/api.json")

with custom error handler

class MyError {
    private String id;
    private  String messages;
    public MyError(String id, List<String> messages) {
        this.id = id;
        this.messages = messages;
    }
    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", (status, messages) ->
    new MyError(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(null, () -> {
    // Do stuff e.g. return a list of user names
    ServerResponse.ok().body(fromObject(users));
});

with body

validate
    .request(null)
    .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
        return ServerResponse.ok().body(fromObject(user))
    );

Example Valiation 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:

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.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)
            }
        }
    }
}
package myproject

import io.github.cdimascio.swagger.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.body
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

class UserHandler {

    fun findAll(req: ServerRequest): Mono<ServerResponse> {
        return validate.request(req) {
            ok().body(Mono.just(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().body(Mono.just(it))
       }
    }
}

License

Apache 2.0

Built with Compositor.io