Fork me on GitHub

RestApidDoc plugin for Grails



Description

The RestApiDoc plugin allows to document your Grails Rest API. Thanks to some Annotations (@), you will be ready to build a full API report (with a playground to perform test request). The plugin is based on jsondoc.

This plugin allows you to document methods (description, HTTP path/verb, parameters, response type...) and resources (description, all fields,...). It does a lot of stuff for you (computing path/verb for the method, retrieving field type,...)

A sample report is available here. The github repo for this sample is here.


Full demo doc
Full demo doc


The project is hosted on Github and is available for use under the Apache 2.0 License. You can report bugs and discuss features on the GitHub issues page.

Installation


Just add the following line in your BuildConfig (plugins):

compile ":rest-api-doc:0.5"

The resources plugin must be in your BuildConfig! (runtime ":resources:1.2.1")

Grails version Status Comment
2.4
2.3
2.2 or lesser UrlMapping rules extractor appears in 2.3. Two possibilities:
  • Rewrite a AnsiConsoleUrlMappingsRenderer.groovy (from Grails-Core) compatible with Grails 2.2 or lesser
  • Turn off the "auto-path" generation

Changelog


  • 0.5: Add PATCH support + ISSUE 18
  • 0.4: OutputFile config is now split in outputFileGeneration and outputFileReading. outputFileGeneration is the location of the json file after the generation. outputFileReading is the location of the json file in the server.
  • 0.3.1: Fix
  • 0.3: Retrieve method parameters type (issue #15), Support no-domain classes (issue #14), query parameters work in playground (issue #13), Support URL without extension.
  • 0.2: Bug fixes, multiple extensions support
  • 0.1.3: Support Grails 2.4 (thanks to github.com/uberall)
  • 0.1.2: Fix duplication in action
  • 0.1.1: Fix issue with @RestApiError(s)
  • 0.1: First release

How to run


Run the command:

grails rest-api-doc

A restapidoc.json file will be create on the root of your Grails project (in outputFileGeneration).
http://.../restApiDoc/api service output the JSON file.

You can call this to view the Web report:

http://.../restApiDoc/?doc_url=http://.../restApiDoc/api#

Example for the sample app:
http://localhost:8080/RestApiDoc-example/restApiDoc/?doc_url=http://localhost:8080/RestApiDoc-example/restApiDoc/api#

In production, you have to put the json file (restapidoc.json by default) on the root of your app (new File("restapidoc.json") from your app must work). You can change the location on the server with outputFileReading.


Getting started


To document methods

                    
    @RestApi(name = "book services", description = "Methods for managing books")
    class BookController {

        @RestApiMethod(description="Get a book")
        @RestApiParams(params=[
            @RestApiParam(name="id", type="long", paramType = RestApiParamType.PATH, description = "The book id")
        ])
        def show() {
            Book.read(params.id)
        }
    }
                    
              

The result will be like this. You can test your method in the playground area.




To document a resource/object

                    
    @RestApiObject(name = "book", description = "A book available on the store")
    class Book {

        @RestApiObjectField(description = "The title of the book")
        String title

        @RestApiObjectField(description = "Year published for this book")
        Integer year

        @RestApiObjectField(description = "The book category")
        String category

        @RestApiObjectField(description = "The author of the book")
        Author author

        [...]
    }
                    
              
The result will be like this.


User Guide

Document methods (controller)


@RestApi

    
        @RestApi(name = "book services", description = "Methods for managing books")
        class BookController {
        }
    
Parameter Description Comment
String name The name of the service
String description The service description

@RestApiMethod

    
        @RestApiMethod(description="Get an author")
        def show() {
        }

        @RestApiMethod(description="List all authors", listing = true)
        def list() {
        }

        @RestApiMethod(description="Get stats data for an author", path="/custom_path/stats.{json}", verb = RestApiVerb.PUT) //path and verb are stupid...just for the example...
        def stats() {
        }
    

Declare on a method. Unfortunately, you cannot declare a Java Annotation on a Grails action (def show = {...}). Anyway, Grails methods are better than action/closure.

Parameter Description Comment
String description The method description
String path The HTTP path for this method Optional. By now, we support 3 ways to define the path:
  1. First, the plugin looks into this value,
  2. If path is null, the plugin looks inside your Url Mappings rules to retrieve the good path for the method,
  3. If no Url Mapping are found for this method, it will return the default Grails path (controller/action/id?) where action are compute with the config value pathPerMethodPrefix (show() will be /show/$id, add() will be /add,...).
RestApiVerb verb The HTTP verb for this method Optional, similar to the "path" property.
  1. First, the plugin looks into this value,
  2. If null, looks inside Url Mappings,
  3. If no rule for this method, retrieve the verb thanks to the method name (add/save => POST, edit/udpate => PUT,...). You can change these rules with config verbPerMethodPrefix.
String[] extensions Extension for the path service Optional, by default the defaultFormat value ("json").

A service may have multiple extensions. The path (from UrlMappings, path attribute,...) must contain the string "{format}" (will be replaced by each extensions).
For example: if path = "/user/{id}/picture.{format}" and extensions = ["png","jpg"], you will get two entries in the documentation.
  • /user/{id}/picture.png
  • /user/{id}/picture.jpg

The config value defaultFormatString ("{format}" by default) may be changed by something else.
String[] produces The output format Optional, by default, the config value for defaultResponseType will be used (MediaType.APPLICATION_JSON_VALUE by default). You can change this with another MediaType.
String[] consumes The input format Same as produces.
boolean listing The method will response a list (instead of an object) Optional, by default false. If config property defaultParamsQueryMultiple is set, you can add default params for all listing methods. Usefull if you have, for example, max/offset for methods returning a list.

@RestApiParams

A simple array of @RestApiParam


@RestApiParam

    
        @RestApiMethod(description="Get an author")
        @RestApiParams(params=[
            @RestApiParam(name="id", type="long", paramType = RestApiParamType.PATH, description = "The author id")
        ])
        def show() {
        }

        @RestApiMethod(description="List books written by the author", listing = true) //author/$id/book.json?max=$max
        @RestApiParams(params=[
            @RestApiParam(name="id", type="long", paramType = RestApiParamType.PATH, description = "The author id")
            @RestApiParam(name="max", type="long", paramType = RestApiParamType.QUERY, description = "Limit for listing")
        ])
        def listByAuthor() {
            //params.id or params.max
        }

    
Declare on a method
Parameter Description Comment
String name The param name
String description The param description
String type The param type (String, Long, Book,...)
RestApiParamType paramType The HTTP param type (PATH or QUERY)

If you use Controller method with parameters, the type option may be ignored. The plugin will automatically retrieve the type. The @RestApiParam must be ordered like the method parameters.

    
        @RestApiMethod(description="...)
        @RestApiParams(params=[
            @RestApiParam(name="lastname",  paramType = RestApiParamType.PATH, description = "The lastname criteria"), // the plugin will get the type from the method parameter
        ])
        def search(String lastname) {
            //we don't use params.lastname,...
        }
    

@RestApiResponseObject

    
        //a method in BookController will return Book by default...
        def show() {
        }

        @RestApiResponseObject(objectIdentifier = "Long")
        def getNumberOfBooks() {
        }

        @RestApiResponseObject(objectIdentifier = "[stats]") //see "Define Custom Domain" section
        def stats() {
        }
    
Declare on a method
Parameter Description Comment
String objectIdentifier The method return type Optional, by default the plugin will compute the name by looking to the controller name (BookController => Book).
If you controller has a special name (LibraryController for Book domain), you can implements a “String currentDomainName()” method in your controller. It must return the domain name.

If the method return a simple type (String, Integer,...), you can just set objectIdentifier with the good type.

If the method return a complex type (which is not a domain), you can define a custom domain.

@RestApiBodyObject

Declare on a method

Parameter Description Comment
String name The method input type Same as objectIdentifier from @RestApiResponseObject

@RestApiErrors

An array of @RestApiError


@RestApiError

Declare on a method

    
        @RestApiErrors(apierrors=[
            @RestApiError(code="404",description="The book was not found!")
        ])
        def show() {
        }
    
Parameter Description Comment
String code The HTTP code
String description The error description
See config section for defaultErrorAll, defaultErrorGet,... properties. You will be able to add default errors for methods (a 404 for all GET methods, a 409 for all POST methods,...).

Document objects (domain)


@RestApiObject

    
        @RestApiObject(name = "author", description = "An author write books")
        class Author {
    

Declare on a domain

Parameter Description Comment
String name The name of the resource
String description The resource description

@RestApiObjectField

    
        @RestApiObjectField(description = "The firstname of the author")
        String firstname

        @RestApiObjectField(description = "The lastname of the author")
        String lastname
    
    
        //Shhh keep it secret, it will not be print in response (!presentInResponse)
        //Let say that if not set (!mandatory), we set birthday to 01/01/1970 (defaultValue)...
        @RestApiObjectField(
            description = "The birthday date of the author",
            presentInResponse = false,
            mandatory = false,
            defaultValue = "01/01/1970"
        )
        Date birthday
    
    

        @RestApiObjectField(
            apiFieldName= "books",
            description = "Books id written by this author",
            allowedType =  "List",
            useForCreation = false
        )
        static hasMany = [book:Book]

        @RestApiObjectFields(params=[
            //This field is in response but not in domain (concat firstname and lastname). We can doc it on transients...
            @RestApiObjectField(
                apiFieldName= "fullname",
                description = "The first and lastname",
                allowedType =  "String",
                useForCreation = false)
        ])
        static transients = []
    

Declare on a domain property

Parameter Description Comment
String description The field description
String allowedType The field type Optional, by default, the type of the field is retrieve by the plugin.
String apiFieldName The field name in response Optional, by default the name will be automatically retrieve from the class field name.

Use case: If the field has another name in the response (response.theAuthor instead of response.author), you have to set the value with "theAuthor".
boolean useForCreation Field is used for creation Optional, by default true (all fields are used for creation).

Use case: An auto-id or a createdDate field should be set with useForCreation=false if there are not used when creating the domain.
boolean mandatory Field is mandatory. Only usefull if useForCreation=true. Optional, by default all fields use for creation are mandatory.

Use case: If the category is not mandatory to create a book, set mandatory to false.
boolean defaultValue Field default value. Only usefull if mandatory = false Optional, by default an optional field (not mandatory) has the default value according its java type (number = 0, object = null, list = [],...)

Use case: If by default, the category of a book is "None", set defaultValue = "None".
boolean presentInResponse Field is used for creation but no present in response. Optional, by default all fields are present in response.

Use case: If you have a User domain. You can add a user with name and password properties. But you don't want to have the password in response. The password will be "useForCreation" but not "presentInResponse".

Define custom field

Sometimes, your service may response transient fields or fields from other domain. You can add annotations on other Grails properties like hasMany, transients,... The first example, an author may have a "fullname" (firstname + " " + lastname) in its JSON/XML response. Its a transient field (we only store firstname and lastname).

    
        @RestApiObjectFields(params=[
            @RestApiObjectField(
                apiFieldName= "fullname",
                description = "The first and last name",
                allowedType =  "String",
                useForCreation = false)
        ])
        static transients = []
    

The second example, an author may have a list of book. Let says that we only response the list of book's id (e.g. author.book=[1,5,6]).

    
        @RestApiObjectField(
            apiFieldName= "books",
            description = "Books id written by this author",
            allowedType =  "List",
            useForCreation = false)
        static hasMany = [book:Book]
    

Define custom domain

Sometimes, a service may response a special type (not a domain).

    
        @RestApiMethod(description="Get stats data for an author")
        @RestApiParams(params=[
            @RestApiParam(
                name="max",
                type="int",
                paramType = RestApiParamType.PATH,
                description = "Max number of author to retrieve")
        ])
        def stats() {
            def author = Author.read(params.id)
            render ([fullname:author + " " + author.lastname, numberOfBook: author.book.size()] as JSON).toString()
        }
    

The response is an object with fullname and numberOfBook properties. You can create a class. You can call it "CustomResponseDoc" (or something else...) and put it wherever you want in your src/ directory.

Change the config property customClassName with the full class path (customClassName="my.package.CustomResponseDoc").

To define a new resource type, add a new field and fill the RestApiObjectField and RestApiObjectField(s).

    
        package my.package

        class CustomResponseDoc {
            @RestApiObjectField(description = "An author stats")
            @RestApiObjectFields(params=[
                @RestApiObjectField(
                    apiFieldName = "fullname",
                    description = "The author fullname",
                    allowedType = "String",
                    useForCreation = false),
                @RestApiObjectField(
                    apiFieldName = "numberOfBook",
                    description = "The number of books created by this author",
                    allowedType = "Integer",
                    useForCreation = false)
            ])
            static def stats

        [...]
        }
    

Now you can add @RestApiResponseObject(objectIdentifier = "[stats]") on the method stats() from your controller.


Configuration

There are some default values defined for each configuration fields. You can simply override them. The config path is grails.plugins.restapidoc.

Name Description Default
String docVersion The documentation version "0.1"
String basePath The path for your app. If you don't override this, you can't play with doc playground. "Fill with basePath config"
String outputFileGeneration The file where to store the JSON after generation "restapidoc.json"
String outputFileReading The JSON file to read when looking for the report. Usefull if you upload the file in a specific location on the server. "restapidoc.json"
String customClassName The class path for all customs domains (Define custom domain). You must put the full package path and class name. null
String defaultFormat The default format in URL
.$format => .json
"json"
String defaultResponseType The default response type MediaType.APPLICATION_JSON_VALUE
String controllerPrefix Usefull if you have a prefix in your controller. With MyBookController, the domain will be MyBook. If controllerPrefix is "My", It will be "Book" ""
String controllerSuffix See controllerPrefix "Controller"
String defaultFormatString The string representing the format in each service path. This will be replaced by "defaultFormat" value (from config) or extensions value (from annotation doc) "{format}"
List defaultObjectFields You can add some default doc fields for all your domains.
Example:
grails.plugins.restapidoc.defaultObjectFields = [[name:"id", type:"Long", useFroCreation="false"]]
[]
List defaultErrorAll Default errors to define for all methods. For example:
["400": "Bad Request: missing parameters or bad message format",
"401": "Unauthorized: must be auth",
"403": "Forbidden: role error",
"404": "Object not found"]
[]
List defaultErrorGet Same as defaultErrorAll, but only for GET methods []
List defaultErrorPost Same as defaultErrorAll, but only for POST methods. For example: [
"409": "Object already exist"
]
[ ]
List defaultErrorPut Same as defaultErrorAll, but only for PUT methods [ ]
List defaultErrorDelete Same as defaultErrorAll, but only for DELETE methods []
Map verbPerMethodPrefix Map to link a method prefix name with a verb. Only used if no verb is defined in @RestApiMethod and no URL Mappings rules are found for the method.
You can override or add new rules (e.g. "modify": PUT)
[ "show" : "GET", "list" : "GET", "save" : "POST", "add" : "POST", "update" : "PUT", "edit" : "PUT", "delete" : "DELETE", "remove" : "DELETE" ]
Map pathPerMethodPrefix Same as verbPerMethodPrefix but for the path. Only used if no path is defined in @RestApiMethod and no URL Mappings rules are found for the method. [ "show" : "show/{id}", "list" : "list", "add" : "add", "save" : "save", "update" : "udpate/{id}", "edit" : "edit/{id}", "delete" : "delete/{id}", "remove" : "delete/{id}", ]
String grailsDomainDefaultType The default type for your Grails Domain.
You may set "Long" if you only set an id for a Book.author in a book response. By default, it will be "Author"
null, meaning that it will be the domain Class name
List defaultParamsQueryAll Default params for all your queries []
List defaultParamsQuerySingle Default params for all your methods where listing = false []
List defaultParamsQueryMultiple Default params for all your methods where listing = true

For example:
defaultParamsQueryMultiple = [
[name:"max",description:"Pagination: Number of record per page (default 0 = no pagination)",type:"int"],
[name:"offset",description:"Pagination: Offset of first record (default 0 = first record)",type:"int"]
]
[]

Extending

If you find that you need a feature that RestApiDoc does not currently support, either let me know via the Github issue tracker, or fork the project and and easily extend RestApidDoc plugin!


Copyright

Copyright © 2014 Rollus Loïc