/**
* 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.Map;
import java.util.Optional;
/**
* <h1>spec</h1>
*
* <p>
* The spec module allows you to export your API/microservices outside an application.
* </p>
*
* <p>
* 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 <strong>FREE</strong>. By <strong>FREE</strong> we mean:
* </p>
*
* <blockquote>
* 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.
* </blockquote>
*
* <p>
* This module process, collect and compile <strong>routes</strong> from your application. It
* extracts HTTP method/pattern, parameter, responses, types and doc.
* </p>
*
* <p>
* 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 <a href="http://swagger.io/">Swagger</a> or <a href="http://raml.org">RAML</a>.
* </p>
*
* <h1>api def</h1>
*
* <p>
* 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 <strong>FREE</strong>. By <strong>FREE</strong> we mean:
* </p>
*
* <blockquote>
* 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.
* </blockquote>
*
* Let's review how to build rich APIs using the <code>spec</code> module via <code>script</code> or
* <code>mvc</code> programming model:
*
* <h2>script API</h2>
* <pre>
* {@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 <code>0</code>.
* * @param max Max page size, useful for paging. Default is <code>200</code>.
* * @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 <code>200</code> with a single pet or <code>404</code>
* {@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 <code>204</code>
* {@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");}
* }</pre>
*
* <h2>MVC API</h2>
*
* <pre>
* {@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 <code>0</code>.
* * @param max Max page size, useful for paging. Default is <code>200</code>.
* * @return Pets ordered by name.
* {@literal *}/
* @GET
* public List<Pet> list(final Optional<Integer> start, final Optional<Integer> 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);
* }
* }
* }
* </pre>
*
* <p>
* Previous examples are feature identical, but they were written in very different way. Still
* they produces an output likes:
* </p>
*
* <pre>
* {@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 <code>0</code>.
* max:
* paramType: QUERY
* type: int
* value: 200
* doc: Max page size, useful for paging. Default is <code>200</code>.
* response:
* type: java.util.List<apps.model.Pet>
* 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 <code>200</code> with a single pet or <code>404</code>
* 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 <code>204</code>
* }</pre>
*
* <p>
* <strong>NOTE</strong>: We use <code>text</code> for simplicity and easy read, but keep in mind
* the output
* is compiled in binary format.
* </p>
*
*
* <h2>how it works?</h2>
*
* <p>
* The spec module scan and parse the source code: <code>*.java</code> and produces a list of
* {@link RouteSpec}.
* </p>
* <p>
* There is a <code>jooby:spec</code> maven plugin for that collects and compiles {@link RouteSpec}
* at build time, useful for production environments where the source code isn't available.
* </p>
*
* <h3>Why do we parse the source code?</h3>
*
* <p>
* It is required for getting information from <code>script routes</code>. We don't need that for
* <code>mvc routes</code> because all the information is available via <code>Reflection</code> and
* {@link Method}.
* </p>
*
* <p>
* But also, you can write clean and useful JavaDoc in your source code that later are added to the
* API information.
* </p>
*
* <h3>Why don't parse byte-code with ASM?</h3>
* <p>
* 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.
* </p>
*
* <h2>script rules</h2>
* <p>
* Have a look at the previous examples again? Do you see anything special? No, right?
* </p>
* <p>
* Well there are some minor things you need to keep in mind for getting or collecting route
* metadata from <code>script</code> routes:
* </p>
*
* <h3>params</h3>
* <p>
* Params need to be in one sentence/statement, like:
*
* </p>
*
* <pre>
* req {@literal ->} {
* int id = req.param("id").intValue();
* }
* </pre>
*
* not like:
*
* <pre>
* req {@literal ->} {
* Mutant p = req.param("id");
* int id = p.intValue();
* }
* </pre>
*
* <h3>response type (a.k.a return type)</h3>
*
* <p>
* There should be <strong>ONLY one</strong> return statement and return type needs to be declared
* as variable, like:
* </p>
*
* <pre>
* req {@literal ->} {
* ...
* Pet pet = db.find(id); // variable pet
* ...
* return pet;
* }
* </pre>
*
* not like:
*
* <pre>
* req {@literal ->} {
* ...
* return db.find(id); // we aren't able to tell what type returns db.find
* }
* </pre>
*
* or
*
* <pre>
* req {@literal ->} {
* ...
* if (...) {
* return ...;
* } else {
* return ...;
* }
* }
* </pre>
*
* <p>
* 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.
* </p>
*
* <h2>API doc</h2>
* <p>
* If you take a few minutes and write good quality doc the prize will be huge!
* </p>
* <p>
* The tool takes the doc and export it as part of your API!!
* </p>
* <p>
* Here is an example on how to document script routes:
* </p>
*
* <pre>
* /{@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 <code>0</code>.
* * @param max Max page size, useful for paging. Default is <code>200</code>.
* * @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;
* });
* </pre>
*
* The spec for <code>/api/pets</code> will have the following doc:
*
* <pre>
* params:
* start:
* paramType: QUERY
* type: int
* value: 0
* doc: Start offset, useful for paging. Default is <code>0</code>.
* max:
* paramType: QUERY
* type: int
* value: 200
* doc: Max page size, useful for paging. Default is <code>200</code>.
* response:
* type: java.util.List<apps.model.Pet>
* doc: Pets ordered by name.
* </pre>
*
* <h3>response</h3>
*
* <p>
* With JavaDoc, you can control the default type returned by the route and/or the status codes.
* For example:
* </p>
*
* <pre>
*
* /{@literal *}{@literal *}
* * Find pet by ID.
* *
* * @param id Pet ID.
* * @return Returns a {@link Pet} with <code>200</code> status or <code>404</code>
* {@literal *}/
* get(req {@literal ->} {
* DB db = req.require(DB.class);
* return db.find(Pet.class, id);
* });
* </pre>
*
* <p>
* Here you tell the tool that this route produces a <code>Pet</code>, response looks like:
* </p>
*
* <pre>
* response:
* type: apps.model.Pet
* statusCodes:
* 200: Success
* 404: Not Found
* doc: Returns <code>200</code> with a single pet or <code>404</code>
* </pre>
*
* <p>
* You can override the default message of the status code with:
* </p>
*
* <pre>
* @return Returns a <code>{@link Pet}</code> with <code>200 = Success</code> status or
* <code>404 = Missing</code>
* </pre>
*
* <p>
* Finally, you can specify the response type via JavaDoc type references:
* <code>{@link Pet}</code>. 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.
* </p>
*
* @author edgar
* @see RouteProcessor
* @since 0.15.0
*/
public interface RouteSpec extends Serializable {
/**
* @return Top level doc (a.k.a summary).
*/
Optional<String> summary();
/**
* @return Route name.
*/
Optional<String> name();
/**
* @return Route method.
*/
String method();
/**
* @return Route pattern.
*/
String pattern();
/**
* @return Route doc.
*/
Optional<String> doc();
/**
* @return List all the types this route can consumes, defaults is: {@code * / *}.
*/
List<String> consumes();
/**
* @return List all the types this route can produces, defaults is: {@code * / *}.
*/
List<String> produces();
/**
* @return List of params or empty list.
*/
List<RouteParam> params();
/**
* @return Route response.
*/
RouteResponse response();
/**
* Additional route attributes.
*
* @return Route attributes.
*/
Map<String, Object> attributes();
}