All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jooby.spec.RouteSpec Maven / Gradle / Ivy

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */
package org.jooby.spec;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;

/**
 * 

spec

* *

* The spec module allows you to export your API/microservices outside an application. *

* *

* The goal of this module is to define a common way to write APIs and provide you API tools like * live doc and testing for FREE. By FREE we mean: *

* *
* You aren't force to learn any other tool or annotated your code special annotations. All you * have to do is: **write your application** following a few/minor suggestions. *
* *

* This module process, collect and compile routes from your application. It * extracts HTTP method/pattern, parameter, responses, types and doc. *

* *

* You will find here the basis and the necessary documentation to build and expose rich APIs for * free, but keep in mind this module isn't intended for direct usage. It is the basis for tools * like Swagger or RAML. *

* *

api def

* *

* The goal of this module is to define a common way to write APIs and provide you API tools like * live doc and testing for FREE. By FREE we mean: *

* *
* You aren't force to learn any other tool or annotated your code special annotations. All you * have to do is: **write your application** following a few/minor suggestions. *
* * Let's review how to build rich APIs using the spec module via script or * mvc programming model: * *

script API

*
 * {@code
 * {
 *   /{@literal *}{@literal *}
 *    {@literal *} Everything about your Pets.
 *    {@literal *}/
 *   use("/api/pets")
 *      /{@literal *}{@literal *}
 *       * List pets ordered by name.
 *       *
 *       * @param start Start offset, useful for paging. Default is 0.
 *       * @param max Max page size, useful for paging. Default is 200.
 *       * @return Pets ordered by name.
 *       {@literal *}/
 *       .get(req {@literal ->} {
 *         int start = req.param("start").intValue(0);
 *         int max = req.param("max").intValue(200);
 *         DB db = req.require(DB.class);
 *         List<Pet> pets = db.findAll(Pet.class, start, max);
 *         return pets;
 *       })
 *      /{@literal *}{@literal *}
 *       * Find pet by ID
 *       *
 *       * @param id Pet ID.
 *       * @return Returns 200 with a single pet or 404
 *       {@literal *}/
 *       .get("/:id",req {@literal ->} {
 *         int id=req.param("id").intValue();
 *         DB db=req.require(DB.class);
 *         Pet pet = db.find(Pet.class,id);
 *         return pet;
 *       })
 *       /{@literal *}{@literal *}
 *        * Add a new pet to the store.
 *        *
 *        * @param body Pet object that needs to be added to the store.
 *        * @return Returns a saved pet.
 *        {@literal *}/
 *       .post(req {@literal ->} {
 *         Pet pet=req.body().to(Pet.class);
 *         DB db=req.require(DB.class);
 *         db.save(pet);
 *         return pet;
 *       })
 *       /{@literal *}{@literal *}
 *        * Update an existing pet.
 *        *
 *        * @param body Pet object that needs to be updated.
 *        * @return Returns a saved pet.
 *        {@literal *}/
 *       .put(req {@literal ->} {
 *         Pet pet=req.body().to(Pet.class);
 *         DB db=req.require(DB.class);db.save(pet);
 *         return pet;
 *       })
 *       /{@literal *}{@literal *}
 *        * Deletes a pet by ID.
 *        *
 *        * @param id Pet ID.
 *        * @return A 204
 *       {@literal *}/
 *       .delete("/:id",req {@literal ->} {
 *         int id=req.param("id").intValue();
 *         DB db=req.require(DB.class);
 *         db.delete(Pet.class,id);
 *         return Results.noContent();
 *       })
 *       .produces("json")
 *       .consumes("json");}
 * }
* *

MVC API

* *
 * {@code
 * /{@literal *}{@literal *}
 *  * Everything about your Pets.
 *  {@literal *}/
 *  @Path("/api/pets")
 *  @Consumes("json")
 *  @Produces("json")
 *  public class Pets {
 *
 *    private DB db;
 *
 *    @Inject
 *    public Pets(final DB db) {
 *      this.db = db;
 *    }
 *
 *    /{@literal *}{@literal *}
 *     * List pets ordered by name.
 *     *
 *     * @param start Start offset, useful for paging. Default is 0.
 *     * @param max Max page size, useful for paging. Default is 200.
 *     * @return Pets ordered by name.
 *     {@literal *}/
 *     @GET
 *     public List<Pet> list(final Optional start, final Optional max) {
 *       List<Pet> pets = db.findAll(Pet.class, start.orElse(0), max.orElse(200));
 *       return pets;
 *     }
 *
 *     /{@literal *}{@literal *}
 *      * Find pet by ID.
 *      *
 *      * @param id Pet ID.
 *      * @return Returns a single pet
 *      {@literal *}/
 *      @Path("/:id")
 *      @GET
 *      public Pet get(final int id) {
 *        Pet pet = db.find(Pet.class, id);
 *        return pet;
 *       }
 *
 *      /{@literal *}{@literal *}
 *       * Add a new pet to the store.
 *       *
 *       * @param pet Pet object that needs to be added to the store.
 *       * @return Returns a saved pet.
 *       {@literal *}/
 *       @POST
 *       public Pet post(@Body final Pet pet) {
 *         db.save(pet);
 *         return pet;
 *       }
 *
 *      /{@literal *}{@literal *}
 *       * Update an existing pet.
 *       *
 *       * @param body Pet object that needs to be updated.
 *       * @return Returns a saved pet.
 *       {@literal *}/
 *       @PUT
 *       public Pet put(@Body final Pet pet) {
 *        db.save(pet);
 *        return pet;
 *       }
 *
 *      /{@literal *}{@literal *}
 *       * Deletes a pet by ID.
 *       *
 *       * @param id Pet ID.
 *       {@literal *}/
 *       @DELETE
 *       public void delete(final int id) {
 *         db.delete(Pet.class, id);
 *       }
 *     }
 *   }
 * 
* *

* Previous examples are feature identical, but they were written in very different way. Still * they produces an output likes: *

* *
 * {@code
 * GET /api/pets
 *  summary: Everything about your Pets.
 *  doc: List pets ordered by name.
 *  consumes: [application/json]
 *  produces: [application/json]
 *  params:
 *    start:
 *      paramType: QUERY
 *      type: int
 *      value: 0
 *      doc: Start offset, useful for paging. Default is 0.
 *    max:
 *      paramType: QUERY
 *      type: int
 *      value: 200
 *      doc: Max page size, useful for paging. Default is 200.
 *  response:
 *    type: java.util.List
 *    doc: Pets ordered by name.
 * GET /api/pets/:id
 *  summary: Everything about your Pets.
 *  doc: Find pet by ID
 *  consumes: [application/json]
 *  produces: [application/json]
 *  params:
 *    id:
 *      paramType: PATH
 *      type: int
 *      doc: Pet ID.
 *  response:
 *    type: apps.model.Pet
 *    doc: Returns 200 with a single pet or 404
 * POST /api/pets
 *  summary: Everything about your Pets.
 *  doc: Add a new pet to the store.
 *  consumes: [application/json]
 *  produces: [application/json]
 *  params:
 *    body:
 *      paramType: BODY
 *      type: apps.model.Pet
 *      doc: Pet object that needs to be added to the store.
 *  response:
 *    type: apps.model.Pet
 *    doc: Returns a saved pet.
 * PUT /api/pets
 *  summary: Everything about your Pets.
 *  doc: Update an existing pet.
 *  consumes: [application/json]
 *  produces: [application/json]
 *  params:
 *    body:
 *      paramType: BODY
 *      type: apps.model.Pet
 *      doc: Pet object that needs to be updated.
 *  response:
 *    type: apps.model.Pet
 *    doc: Returns a saved pet.
 * DELETE /api/pets/:id
 *  summary: Everything about your Pets.
 *  doc: Deletes a pet by ID.
 *  consumes: [application/json]
 *  produces: [application/json]
 *  params:
 *    id:
 *      paramType: PATH
 *      type: int
 *      doc: Pet ID.
 *  response:
 *    type: void
 *    doc: A 204
 * }
* *

* NOTE: We use text for simplicity and easy read, but keep in mind the output * is compiled in binary format. *

* * *

how it works?

* *

* The spec module scan and parse the source code: *.java and produces a list of * {@link RouteSpec}. *

*

* There is a jooby:spec maven plugin for that collects and compiles {@link RouteSpec} * at build time, useful for production environments where the source code isn't available. *

* *

Why do we parse the source code?

* *

* It is required for getting information from script routes. We don't need that for * mvc routes because all the information is available via Reflection and * {@link Method}. *

* *

* But also, you can write clean and useful JavaDoc in your source code that later are added to the * API information. *

* *

Why don't parse byte-code with ASM?

*

* Good question, the main reason is that we lost generic type information and we aren't able to * tell if the route response is for example a list of pets. *

* *

script rules

*

* Have a look at the previous examples again? Do you see anything special? No, right? *

*

* Well there are some minor things you need to keep in mind for getting or collecting route * metadata from script routes: *

* *

params

*

* Params need to be in one sentence/statement, like: * *

* *
 * req {@literal ->} {
 *   int id = req.param("id").intValue();
 * }
 * 
* * not like: * *
 * req {@literal ->} {
 *   Mutant p = req.param("id");
 *   int id = p.intValue();
 * }
 * 
* *

response type (a.k.a return type)

* *

* There should be ONLY one return statement and return type needs to be declared * as variable, like: *

* *
 * req {@literal ->} {
 *   ...
 *   Pet pet = db.find(id); // variable pet
 *   ...
 *   return pet;
 * }
 * 
* * not like: * *
 * req {@literal ->} {
 *   ...
 *   return db.find(id); // we aren't able to tell what type returns db.find
 * }
 * 
* * or * *
 * req {@literal ->} {
 *   ...
 *   if (...) {
 *     return ...;
 *   } else {
 *     return ...;
 *   }
 * }
 * 
* *

* There is a workaround if these rules doesn't make sense to you and/or the algorithm fails to * resolve the correct type. Please checkout next section. *

* *

API doc

*

* If you take a few minutes and write good quality doc the prize will be huge! *

*

* The tool takes the doc and export it as part of your API!! *

*

* Here is an example on how to document script routes: *

* *
 *   /{@literal *}{@literal *}
 *    * Everything about your Pets.
 *    {@literal *}/
 *   use("/api/pets")
 *      /{@literal *}{@literal *}
 *       * List pets ordered by name.
 *       *
 *       * @param start Start offset, useful for paging. Default is 0.
 *       * @param max Max page size, useful for paging. Default is 200.
 *       * @return Pets ordered by name.
 *       {@literal *}/
 *     .get(req {@literal ->} {
 *       int start = req.param("start").intValue(0);
 *       int max = req.param("max").intValue(200);
 *       DB db = req.require(DB.class);
 *       List<Pet> pets = db.findAll(Pet.class, start, max);
 *       return pets;
 *     });
 * 
* * The spec for /api/pets will have the following doc: * *
 *   params:
 *     start:
 *       paramType: QUERY
 *       type: int
 *       value: 0
 *       doc: Start offset, useful for paging. Default is 0.
 *     max:
 *       paramType: QUERY
 *       type: int
 *       value: 200
 *       doc: Max page size, useful for paging. Default is 200.
 *   response:
 *     type: java.util.List<apps.model.Pet>
 *     doc: Pets ordered by name.
 * 
* *

response

* *

* With JavaDoc, you can control the default type returned by the route and/or the status codes. * For example: *

* *
 *
 *   /{@literal *}{@literal *}
 *    * Find pet by ID.
 *    *
 *    * @param id Pet ID.
 *    * @return Returns a {@link Pet} with 200 status or 404
 *    {@literal *}/
 *    get(req {@literal ->} {
 *      DB db = req.require(DB.class);
 *      return db.find(Pet.class, id);
 *    });
 * 
* *

* Here you tell the tool that this route produces a Pet, response looks like: *

* *
 * response:
 *   type: apps.model.Pet
 *    statusCodes:
 *      200: Success
 *      404: Not Found
 *    doc: Returns 200 with a single pet or 404
 * 
* *

* You can override the default message of the status code with: *

* *
 * @return Returns a {@link Pet} with 200 = Success status or 404 = Missing
 * 
* *

* Finally, you can specify the response type via JavaDoc type references: * {@link Pet}. This is useful when the tool isn't able to detect the type for * you and/or you aren't able to follow the return rules described before. *

* * @author edgar * @see RouteProcessor * @since 0.15.0 */ public interface RouteSpec extends Serializable { /** * @return Top level doc (a.k.a summary). */ Optional summary(); /** * @return Route name. */ Optional name(); /** * @return Route method. */ String method(); /** * @return Route pattern. */ String pattern(); /** * @return Route doc. */ Optional doc(); /** * @return List all the types this route can consumes, defaults is: {@code * / *}. */ List consumes(); /** * @return List all the types this route can produces, defaults is: {@code * / *}. */ List produces(); /** * @return List of params or empty list. */ List params(); /** * @return Route response. */ RouteResponse response(); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy