Overview
This project provides a Maven plug-in that can generate Swagger documentation for JAX-RS web services. The goal is to produce documentation for REST web services at build-time rather than run-time so that applicaiton runtime dependencies are kept to a minimum.
What it does
- Generates Swagger documentation as .json files at build-time
- Handles a specific style of JAX-RS service authoring; for example it expects annotations to be fairly complete
Motivation
This project was created because documentation for web services is important and Swagger is a great way to do it. To date Swagger documentation is either written completely by hand or is generated using Swagger annotations at runtime. Authoring Swagger docs from scratch means that the documentation is separate from the source code and it’s easy for the documentation to become inaccurate. Runtime generation of Swagger documentation adds a huge number of dependencies to your project’s runtime (about 30 the last time I counted). Dependencies add complexity and maintenance overhead, and also add overhead to maintaining an IP-clean project.
The goal of this project is to enable annotation-based documentation of JAX-RS web services without adding numerous project runtime dependencies.
Project Requirements
In order to generate Swagger documentation for your project the following requirements must be met:
- Your project has at least one JAX-RS for REST-based services
- Your JAX-RS service is annotated with Swagger API annotations
- Your project has a Maven build
This has been tested with Jersey JAX-RS services, but does not depend on Jersey directly so should work with JAX-RS services targeting other frameworks as well.
Maven Project Setup
Your project’s Maven pom should look something like this:
Add a Swagger annotations dependency so that Swagger annotations can be used in your project:
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.3</version>
</dependency>
Add this plug-in to your pom.xml’s build:
<build>
<finalName>web</finalName>
<plugins>
<plugin>
<groupId>com.greensopinion.swagger</groupId>
<artifactId>jaxrs-gen</artifactId>
<version>1.3.5</version>
<configuration>
<apiVersion>1.0</apiVersion>
<apiBasePath>/api/latest</apiBasePath>
<packageNames>
<param>my.project.package.name</param> <!-- the package name of your project's JAX-RS web services. -->
</packageNames>
<outputFolder>${project.build.directory}/web/api/docs</outputFolder>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Building Documentation
Documentation is created as part of the compile step as follows:
mvn compile
Your Maven output should look something like this:
[INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building web Maven Webapp 1.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ web --- [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ web --- [INFO] Compiling 13 source files to /myproject/web/target/classes [INFO] [INFO] --- jaxrs-gen:1.0-SNAPSHOT:generate (default) @ web --- [INFO] API class: myproject.web.MyService [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.796s [INFO] Finished at: Fri Mar 14 07:35:36 PDT 2014 [INFO] Final Memory: 19M/310M [INFO] ------------------------------------------------------------------------
Generated file layout
Generated documentation is created in files in a nested folder structure that mimics the layout of your services. For example, if you have two REST services as follows:
@Path("/pet")
public class PetService { ... }
@Path("/owner")
public class OwnerService { ... }
The generated documentation would be created as follows:
<outputFolder> +- index.json <-- the Swagger resource listing +- pet | +- index.json <-- the pet Swagger API declaration +- owner +- index.json <-- the owner Swagger API declaration
This layout combined with the Web Application configuration below will enable Swagger UI to interact with the services.
Web Application
I recommend having your web application serve up the Swagger documentation; that way your documentation is always up to date and available for those using its REST services.
The following addition to your web.xml will cause the generated documentation to be served as expected:
<welcome-file-list> <!-- other welcome files go here --> <welcome-file>index.json</welcome-file> </welcome-file-list>
Example JAX-RS Service
This Maven plug-in expects your JAX-RS service to look something like this:
@Path("/pets") @Api(value = "/pets", description = "Operations about pets") @Produces(MediaType.APPLICATION_JSON) public class PetService {
@GET @ApiOperation(value = "List all pets", notes = "List all pets. Results are paginated.", response = PetListing.class) public PetListing list(@QueryParam("start") @DefaultValue("0") int start, @QueryParam("count") @DefaultValue("50") int count) { return null; }
@GET @Path("/{id}") @ApiOperation(value = "Retrieve pet by id", notes = "Retrieves a pet by it's id.", response = Pet.class) @ApiResponses(value = { @ApiResponse(code = 404, message = "Pet not found", response = ServerError.class) }) public Pet retrievePet(@PathParam("id") long id) { return null; }
@DELETE @Path("/{id}") @ApiOperation(value = "Delete pet by id", notes = "Deletes a pet by it's id.") @ApiResponses(value = { @ApiResponse(code = 404, message = "Pet not found", response = ServerError.class) }) public void deletePet(@PathParam("id") long id) { }
@PUT @ApiOperation(value = "Creates a new pet", notes = "Creates a new pet with the given name.", response = PetHandle.class) public PetHandle createPet(PetValues petValues) { return null; }
@POST @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Updates a pet", notes = "Updates an existing pet with the provided details.") @ApiResponses(value = { @ApiResponse(code = 404, message = "Pet not found", response = ServerError.class) }) public void updatePet(Pet pet, @PathParam("id") long id) { } }
The following Swagger description was generated from the above API:
api-docs.json
{
"apiVersion": "1.0",
"swaggerVersion": "1.2",
"apis": [
{
"path": "/pets"
}
]
}
pet.json
{
"apiVersion": "1.0.1",
"swaggerVersion": "1.2",
"basePath": "/api/latest",
"resourcePath": "/pets/{petId}",
"description": "Operations about pets",
"apis": [
{
"path": "/pet",
"description": "Operations about pets",
"operations": [
{
"method": "GET",
"summary": "List all pets",
"notes": "List all pets. Results are paginated.",
"type": "PetListing",
"nickname": "list",
"produces": [
"application/json"
],
"parameters": [
{
"name": "start",
"defaultValue": "0",
"required": false,
"allowMultiple": false,
"type": "integer",
"format": "int32",
"paramType": "query"
},
{
"name": "count",
"defaultValue": "50",
"required": false,
"allowMultiple": false,
"type": "integer",
"format": "int32",
"paramType": "query"
}
]
},
{
"method": "PUT",
"summary": "Creates a new pet",
"notes": "Creates a new pet with the given name.",
"type": "PetHandle",
"nickname": "createPet",
"produces": [
"application/json"
],
"parameters": [
{
"name": "body",
"required": true,
"allowMultiple": false,
"type": "PetValues",
"paramType": "body"
}
]
}
]
},
{
"path": "/pet/{id}",
"description": "Operations about pets",
"operations": [
{
"method": "DELETE",
"summary": "Delete pet by id",
"notes": "Deletes a pet by it's id.",
"type": "void",
"nickname": "deletePet",
"produces": [
"application/json"
],
"parameters": [
{
"name": "id",
"required": true,
"allowMultiple": false,
"type": "integer",
"format": "int64",
"paramType": "path"
}
],
"responseMessages": [
{
"code": 404,
"message": "Pet not found",
"responseModel": "ServerError"
}
]
},
{
"method": "GET",
"summary": "Retrieve pet by id",
"notes": "Retrieves a pet by it's id.",
"type": "Pet",
"nickname": "retrievePet",
"produces": [
"application/json"
],
"parameters": [
{
"name": "id",
"required": true,
"allowMultiple": false,
"type": "integer",
"format": "int64",
"paramType": "path"
}
],
"responseMessages": [
{
"code": 404,
"message": "Pet not found",
"responseModel": "ServerError"
}
]
},
{
"method": "POST",
"summary": "Updates a pet",
"notes": "Updates an existing pet with the provided details.",
"type": "void",
"nickname": "updatePet",
"produces": [
"application/json"
],
"parameters": [
{
"name": "body",
"required": true,
"allowMultiple": false,
"type": "Pet",
"paramType": "body"
},
{
"name": "id",
"required": true,
"allowMultiple": false,
"type": "integer",
"format": "int64",
"paramType": "path"
}
],
"responseMessages": [
{
"code": 404,
"message": "Pet not found",
"responseModel": "ServerError"
}
]
}
]
}
],
"models": {
"Pet": {
"id": "Pet",
"required": [
"id",
"name",
"kind"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string",
"description": "The name of the pet"
},
"kind": {
"type": "PetKind",
"description": "The kind of pet",
"enum": [
"DOG",
"CAT",
"REPTILE",
"FISH",
"HORSE",
"OTHER"
]
},
"notes": {
"type": "string"
}
}
},
"PetHandle": {
"id": "PetHandle",
"required": [
"id"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string",
"description": "The name of the pet"
}
}
},
"PetListing": {
"id": "PetListing",
"description": "A listing of pets",
"required": [],
"properties": {
"start": {
"type": "integer",
"format": "int32",
"description": "The 0-based start index offset."
},
"count": {
"type": "integer",
"format": "int32",
"description": "The number of pets requested. May differ from the actual number of pets in the listing."
},
"total": {
"type": "integer",
"format": "int32",
"description": "The total number of pets in the system that correspond to the listing."
},
"pets": {
"type": "List",
"items": {
"$ref": "PetHandle"
},
"description": "The list of pet handles."
}
}
},
"PetValues": {
"id": "PetValues",
"required": [
"name",
"kind"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the pet"
},
"kind": {
"type": "PetKind",
"description": "The kind of pet",
"enum": [
"DOG",
"CAT",
"REPTILE",
"FISH",
"HORSE",
"OTHER"
]
},
"notes": {
"type": "string"
}
}
},
"ServerError": {
"id": "ServerError",
"description": "A representation of an error, providing an error code and message.",
"required": [
"code"
],
"properties": {
"code": {
"type": "string",
"description": "The error code, which can be used to identify the type of error."
},
"message": {
"type": "string",
"description": "The message describing the error."
},
"detail": {
"type": "string",
"description": "Detail of the error which can be used for diagnostic purposes."
}
}
}
}
}
Building
Build using Maven as follows:
`mvn clean verify`
Build status on Travis CI:
Building for Release
Build using Maven as follows:
mvn -Possrh -Darguments=-Dgpg.passphrase=... release:prepare mvn -Possrh -Darguments=-Dgpg.passphrase=... release:perform
License
Licensed under the Apache License, Version 2.0 (the “License”);
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.